fix gradients

This commit is contained in:
2026-02-14 00:11:59 +01:00
parent 1e126734b0
commit 05c916e9a9
9 changed files with 729 additions and 442 deletions

View File

@@ -12,62 +12,136 @@ templ Layout(title string, username string) {
<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... }
<link rel="preconnect" href="https://fonts.googleapis.com"/>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"/>
<script>
// Tailwind config for custom styles
// Tailwind config - must be set before DOM loads
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
colors: {
primary: {
DEFAULT: '#3b82f6',
dark: '#2563eb',
},
accent: {
DEFAULT: '#8b5cf6',
dark: '#7c3aed',
light: '#a78bfa',
},
surface: {
DEFAULT: '#1f2937',
dark: '#111827',
lighter: '#374151',
}
}
accent: {
DEFAULT: '#c084fc',
dark: '#a855f7',
light: '#d8b4fe',
},
},
animation: {
'float': 'float 6s ease-in-out infinite',
'glow': 'glow 2s ease-in-out infinite alternate',
'slide-up': 'slideUp 0.3s ease-out',
'fade-in': 'fadeIn 0.3s ease-out',
},
keyframes: {
float: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-10px)' },
},
glow: {
'0%': { boxShadow: '0 0 20px rgba(139, 92, 246, 0.4)' },
'100%': { boxShadow: '0 0 40px rgba(139, 92, 246, 0.8)' },
},
slideUp: {
'0%': { transform: 'translateY(10px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
},
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
},
}
}
}
// Theme management - apply immediately
(function() {
const theme = localStorage.getItem('theme') || 'dark';
if (theme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
})();
function toggleTheme() {
const isDark = document.documentElement.classList.contains('dark');
if (isDark) {
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
} else {
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
}
}
</script>
</head>
<body class="bg-slate-100 dark:bg-slate-950 min-h-screen text-slate-900 dark:text-slate-100 font-sans transition-colors duration-300">
<!-- Animated background elements (dark mode only) -->
<div class="fixed inset-0 overflow-hidden pointer-events-none hidden dark:block">
<div class="absolute -top-40 -right-40 w-96 h-96 bg-violet-600/30 rounded-full blur-3xl"></div>
<div class="absolute top-1/2 -left-40 w-96 h-96 bg-indigo-600/30 rounded-full blur-3xl"></div>
<div class="absolute -bottom-40 right-1/3 w-96 h-96 bg-fuchsia-600/20 rounded-full blur-3xl"></div>
</div>
<div class="relative z-10">
{ children... }
</div>
</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">
<header class="backdrop-blur-xl bg-white/80 dark:bg-slate-900/80 border-b border-slate-200 dark:border-slate-700/50 px-6 py-4 mb-8 sticky top-0 z-40 transition-colors duration-300">
<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 class="flex items-center gap-4">
<div class="relative">
<div class="w-12 h-12 bg-gradient-to-br from-violet-500 via-purple-500 to-fuchsia-500 rounded-2xl flex items-center justify-center shadow-lg shadow-violet-500/30">
<i class="fas fa-database text-white text-xl"></i>
</div>
<div class="absolute -bottom-1 -right-1 w-4 h-4 bg-emerald-400 rounded-full border-2 border-white dark:border-slate-900"></div>
</div>
<div>
<h1 class="text-xl font-bold text-white">ZFS Backup</h1>
<p class="text-xs text-gray-400">Admin Panel</p>
<h1 class="text-xl font-bold text-slate-900 dark:text-white">ZFS Backup</h1>
<p class="text-xs text-slate-500 dark:text-slate-400 font-medium tracking-wide">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>
<!-- Theme Toggle -->
<button
onclick="toggleTheme()"
class="px-4 py-2 rounded-xl bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 flex items-center gap-2 hover:bg-slate-200 dark:hover:bg-slate-700 transition-all duration-200"
title="Toggle theme"
>
<i class="fas fa-moon text-violet-500 dark:hidden"></i>
<i class="fas fa-sun text-amber-400 hidden dark:block"></i>
<span class="text-sm font-medium text-slate-600 dark:text-slate-300 dark:hidden">Dark</span>
<span class="text-sm font-medium text-slate-300 hidden dark:block">Light</span>
</button>
<div class="flex items-center gap-3 px-4 py-2 rounded-xl bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700">
<div class="w-8 h-8 rounded-full bg-gradient-to-br from-violet-500 to-fuchsia-500 flex items-center justify-center">
<i class="fas fa-user text-white text-sm"></i>
</div>
<span class="text-sm font-medium text-slate-700 dark:text-slate-200">{ 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"
class="group px-5 py-2.5 bg-red-50 dark:bg-red-500/10 hover:bg-red-100 dark:hover:bg-red-500/20 text-red-600 dark:text-red-400 rounded-xl transition-all duration-300 flex items-center gap-2 border border-red-200 dark:border-red-500/30"
onclick="logout()"
>
<i class="fas fa-sign-out-alt"></i>
<span>Logout</span>
<i class="fas fa-sign-out-alt group-hover:scale-110 transition-transform"></i>
<span class="font-medium">Logout</span>
</button>
</div>
</div>
@@ -76,12 +150,17 @@ templ Header(username string) {
// 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 class="group relative bg-white dark:bg-slate-900/80 backdrop-blur-xl rounded-2xl p-6 border border-slate-200 dark:border-slate-700/50 hover:border-violet-400 dark:hover:border-violet-500/50 transition-all duration-300 overflow-hidden shadow-sm dark:shadow-none">
<div class="absolute inset-0 bg-gradient-to-br from-violet-500/5 via-purple-500/5 to-transparent dark:from-violet-600/10 dark:via-purple-600/5"></div>
<div class="relative">
<div class="flex items-center justify-between mb-4">
<span class="text-slate-500 dark:text-slate-400 text-sm font-medium">{ title }</span>
<div class="w-10 h-10 rounded-xl bg-violet-100 dark:bg-violet-500/20 flex items-center justify-center">
<i class={ "fas " + statsIcon(title) + " text-violet-600 dark:text-violet-400" }></i>
</div>
</div>
<div class="text-3xl font-bold text-slate-900 dark:text-white">{ value }</div>
</div>
<div class="text-3xl font-bold text-white">{ value }</div>
</div>
}
@@ -102,7 +181,7 @@ func statsIcon(title string) string {
// 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) }
class={ "px-6 py-3 rounded-xl font-medium transition-all duration-300 flex items-center gap-2.5", tabButtonClass(active) }
data-tab={ id }
>
<i class={ "fas " + tabIcon(id) }></i>
@@ -127,20 +206,20 @@ func tabIcon(id string) string {
// 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-gradient-to-r from-violet-600 to-fuchsia-600 text-white shadow-lg shadow-violet-500/25"
}
return "bg-surface text-gray-400 hover:bg-surface-lighter hover:text-white"
return "bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700 hover:text-slate-900 dark:hover:text-white border border-slate-200 dark:border-slate-700"
}
// 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>
<div id={ id } class="fixed inset-0 bg-black/50 dark:bg-black/70 backdrop-blur-sm hidden items-center justify-center z-50" data-modal-id={ id }>
<div class="bg-white dark:bg-slate-900 rounded-3xl max-w-lg w-full max-h-[90vh] overflow-y-auto border border-slate-200 dark:border-slate-700 shadow-2xl modal-content">
<div class="flex justify-between items-center p-6 border-b border-slate-200 dark:border-slate-700">
<h3 class="text-lg font-semibold text-slate-900 dark: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"
class="w-10 h-10 flex items-center justify-center rounded-xl text-slate-400 hover:text-slate-600 dark:hover:text-white hover:bg-slate-100 dark:hover:bg-slate-800 transition-all duration-200 modal-close"
data-modal-id={ id }
>
<i class="fas fa-times"></i>
@@ -155,27 +234,27 @@ templ Modal(id string, title string) {
// 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>
<div class="mb-5">
<label for={ id } class="block text-sm font-medium text-slate-600 dark:text-slate-300 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"
class="w-full px-4 py-3.5 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-600 rounded-xl text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-violet-500 transition-all duration-200"
/>
</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>
<div class="mb-5">
<label for={ id } class="block text-sm font-medium text-slate-600 dark:text-slate-300 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"
class="w-full px-4 py-3.5 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-600 rounded-xl text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-violet-500 transition-all duration-200 appearance-none cursor-pointer"
>
for _, opt := range options {
<option value={ opt.Value } if opt.Selected { selected }>{ opt.Label }</option>
@@ -193,22 +272,22 @@ type SelectOption struct {
// FormCheckbox renders a form checkbox
templ FormCheckbox(id string, label string, checked bool) {
<div class="mb-4 flex items-center gap-3">
<div class="mb-5 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"
class="w-5 h-5 bg-slate-100 dark:bg-slate-800 border-slate-300 dark:border-slate-600 rounded text-violet-600 focus:ring-violet-500 focus:ring-2 cursor-pointer"
/>
<label for={ id } class="text-sm text-gray-300">{ label }</label>
<label for={ id } class="text-sm text-slate-600 dark:text-slate-300 font-medium cursor-pointer">{ 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) }
class={ "px-5 py-2.5 rounded-xl font-medium transition-all duration-300 flex items-center gap-2 " + buttonVariantClass(variant) }
>
{ label }
</button>
@@ -218,25 +297,25 @@ templ Button(label string, variant string) {
func buttonVariantClass(variant string) string {
switch variant {
case "primary":
return "bg-primary hover:bg-primary-dark text-white shadow-lg shadow-primary/25"
return "bg-gradient-to-r from-violet-600 to-fuchsia-600 hover:from-violet-500 hover:to-fuchsia-500 text-white shadow-lg shadow-violet-500/25"
case "danger":
return "bg-red-500 hover:bg-red-600 text-white shadow-lg shadow-red-500/25"
return "bg-gradient-to-r from-red-500 to-rose-600 hover:from-red-400 hover:to-rose-500 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"
return "bg-gradient-to-r from-emerald-500 to-green-600 hover:from-emerald-400 hover:to-green-500 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"
return "bg-gradient-to-r from-amber-500 to-orange-600 hover:from-amber-400 hover:to-orange-500 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"
return "bg-gradient-to-r from-purple-500 to-violet-600 hover:from-purple-400 hover:to-violet-500 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"
return "bg-gradient-to-r from-orange-500 to-amber-600 hover:from-orange-400 hover:to-amber-500 text-white shadow-lg shadow-orange-500/25"
default:
return "bg-gray-500 hover:bg-gray-600 text-white"
return "bg-slate-500 hover:bg-slate-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) }>
<span class={ "px-3 py-1.5 rounded-lg text-xs font-semibold " + badgeVariantClass(variant) }>
{ text }
</span>
}
@@ -245,21 +324,23 @@ templ Badge(text string, variant string) {
func badgeVariantClass(variant string) string {
switch variant {
case "success":
return "bg-emerald-500/20 text-emerald-400"
return "bg-emerald-100 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-500/30"
case "danger":
return "bg-red-500/20 text-red-400"
return "bg-red-100 text-red-700 dark:bg-red-500/20 dark:text-red-400 border border-red-200 dark:border-red-500/30"
case "info":
return "bg-blue-500/20 text-blue-400"
return "bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-400 border border-blue-200 dark:border-blue-500/30"
case "warning":
return "bg-amber-500/20 text-amber-400"
return "bg-amber-100 text-amber-700 dark:bg-amber-500/20 dark:text-amber-400 border border-amber-200 dark:border-amber-500/30"
case "purple":
return "bg-purple-100 text-purple-700 dark:bg-purple-500/20 dark:text-purple-400 border border-purple-200 dark:border-purple-500/30"
default:
return "bg-gray-500/20 text-gray-400"
return "bg-slate-100 text-slate-700 dark:bg-slate-500/20 dark:text-slate-400 border border-slate-200 dark:border-slate-500/30"
}
}
// 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 class="w-full h-2 bg-slate-200 dark:bg-slate-700 rounded-full overflow-hidden">
<div class="h-full bg-gradient-to-r from-violet-500 to-fuchsia-500 transition-all duration-500 rounded-full" style={ fmt.Sprintf("width: %.1f%%", percent) }></div>
</div>
}

View File

@@ -32,7 +32,7 @@ func Layout(title string, username string) templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\" class=\"dark\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -45,7 +45,7 @@ func Layout(title string, username string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><script src=\"https://cdn.tailwindcss.com\"></script></head><body class=\"bg-gray-100 min-h-screen\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</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\"><link rel=\"preconnect\" href=\"https://fonts.googleapis.com\"><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin><link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap\" rel=\"stylesheet\"><script>\n\t\t\t\t// Tailwind config - must be set before DOM loads\n\t\t\t\ttailwind.config = {\n\t\t\t\t\tdarkMode: 'class',\n\t\t\t\t\ttheme: {\n\t\t\t\t\t\textend: {\n\t\t\t\t\t\t\tfontFamily: {\n\t\t\t\t\t\t\t\tsans: ['Inter', 'system-ui', 'sans-serif'],\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tcolors: {\n\t\t\t\t\t\t\t\tprimary: {\n\t\t\t\t\t\t\t\t\tDEFAULT: '#8b5cf6',\n\t\t\t\t\t\t\t\t\tdark: '#7c3aed',\n\t\t\t\t\t\t\t\t\tlight: '#a78bfa',\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\taccent: {\n\t\t\t\t\t\t\t\t\tDEFAULT: '#c084fc',\n\t\t\t\t\t\t\t\t\tdark: '#a855f7',\n\t\t\t\t\t\t\t\t\tlight: '#d8b4fe',\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tanimation: {\n\t\t\t\t\t\t\t\t'float': 'float 6s ease-in-out infinite',\n\t\t\t\t\t\t\t\t'glow': 'glow 2s ease-in-out infinite alternate',\n\t\t\t\t\t\t\t\t'slide-up': 'slideUp 0.3s ease-out',\n\t\t\t\t\t\t\t\t'fade-in': 'fadeIn 0.3s ease-out',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tkeyframes: {\n\t\t\t\t\t\t\t\tfloat: {\n\t\t\t\t\t\t\t\t\t'0%, 100%': { transform: 'translateY(0)' },\n\t\t\t\t\t\t\t\t\t'50%': { transform: 'translateY(-10px)' },\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tglow: {\n\t\t\t\t\t\t\t\t\t'0%': { boxShadow: '0 0 20px rgba(139, 92, 246, 0.4)' },\n\t\t\t\t\t\t\t\t\t'100%': { boxShadow: '0 0 40px rgba(139, 92, 246, 0.8)' },\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tslideUp: {\n\t\t\t\t\t\t\t\t\t'0%': { transform: 'translateY(10px)', opacity: '0' },\n\t\t\t\t\t\t\t\t\t'100%': { transform: 'translateY(0)', opacity: '1' },\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tfadeIn: {\n\t\t\t\t\t\t\t\t\t'0%': { opacity: '0' },\n\t\t\t\t\t\t\t\t\t'100%': { opacity: '1' },\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// Theme management - apply immediately\n\t\t\t\t(function() {\n\t\t\t\t\tconst theme = localStorage.getItem('theme') || 'dark';\n\t\t\t\t\tif (theme === 'dark') {\n\t\t\t\t\t\tdocument.documentElement.classList.add('dark');\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdocument.documentElement.classList.remove('dark');\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t\t\n\t\t\t\tfunction toggleTheme() {\n\t\t\t\t\tconst isDark = document.documentElement.classList.contains('dark');\n\t\t\t\t\tif (isDark) {\n\t\t\t\t\t\tdocument.documentElement.classList.remove('dark');\n\t\t\t\t\t\tlocalStorage.setItem('theme', 'light');\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdocument.documentElement.classList.add('dark');\n\t\t\t\t\t\tlocalStorage.setItem('theme', 'dark');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</script></head><body class=\"bg-slate-100 dark:bg-slate-950 min-h-screen text-slate-900 dark:text-slate-100 font-sans transition-colors duration-300\"><!-- Animated background elements (dark mode only) --><div class=\"fixed inset-0 overflow-hidden pointer-events-none hidden dark:block\"><div class=\"absolute -top-40 -right-40 w-96 h-96 bg-violet-600/30 rounded-full blur-3xl\"></div><div class=\"absolute top-1/2 -left-40 w-96 h-96 bg-indigo-600/30 rounded-full blur-3xl\"></div><div class=\"absolute -bottom-40 right-1/3 w-96 h-96 bg-fuchsia-600/20 rounded-full blur-3xl\"></div></div><div class=\"relative z-10\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -53,7 +53,7 @@ func Layout(title string, username string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<script>\n\t\t\t\t// Tailwind config for custom styles\n\t\t\t\ttailwind.config = {\n\t\t\t\t\ttheme: {\n\t\t\t\t\t\textend: {\n\t\t\t\t\t\t\tcolors: {\n\t\t\t\t\t\t\t\tprimary: '#3498db',\n\t\t\t\t\t\t\t\tdanger: '#e74c3c',\n\t\t\t\t\t\t\t\tsuccess: '#27ae60',\n\t\t\t\t\t\t\t\twarning: '#f39c12',\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</script></body></html>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -83,20 +83,20 @@ func Header(username string) templ.Component {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<header class=\"bg-slate-800 text-white p-4 mb-4 rounded-lg flex justify-between items-center\"><h1 class=\"text-xl font-bold\">ZFS Backup Admin Panel</h1><div class=\"flex items-center gap-4\"><span>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<header class=\"backdrop-blur-xl bg-white/80 dark:bg-slate-900/80 border-b border-slate-200 dark:border-slate-700/50 px-6 py-4 mb-8 sticky top-0 z-40 transition-colors duration-300\"><div class=\"max-w-7xl mx-auto flex justify-between items-center\"><div class=\"flex items-center gap-4\"><div class=\"relative\"><div class=\"w-12 h-12 bg-gradient-to-br from-violet-500 via-purple-500 to-fuchsia-500 rounded-2xl flex items-center justify-center shadow-lg shadow-violet-500/30\"><i class=\"fas fa-database text-white text-xl\"></i></div><div class=\"absolute -bottom-1 -right-1 w-4 h-4 bg-emerald-400 rounded-full border-2 border-white dark:border-slate-900\"></div></div><div><h1 class=\"text-xl font-bold text-slate-900 dark:text-white\">ZFS Backup</h1><p class=\"text-xs text-slate-500 dark:text-slate-400 font-medium tracking-wide\">Admin Panel</p></div></div><div class=\"flex items-center gap-4\"><!-- Theme Toggle --><button onclick=\"toggleTheme()\" class=\"px-4 py-2 rounded-xl bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 flex items-center gap-2 hover:bg-slate-200 dark:hover:bg-slate-700 transition-all duration-200\" title=\"Toggle theme\"><i class=\"fas fa-moon text-violet-500 dark:hidden\"></i> <i class=\"fas fa-sun text-amber-400 hidden dark:block\"></i> <span class=\"text-sm font-medium text-slate-600 dark:text-slate-300 dark:hidden\">Dark</span> <span class=\"text-sm font-medium text-slate-300 hidden dark:block\">Light</span></button><div class=\"flex items-center gap-3 px-4 py-2 rounded-xl bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700\"><div class=\"w-8 h-8 rounded-full bg-gradient-to-br from-violet-500 to-fuchsia-500 flex items-center justify-center\"><i class=\"fas fa-user text-white text-sm\"></i></div><span class=\"text-sm font-medium text-slate-700 dark:text-slate-200\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(username)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 41, Col: 19}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 137, Col: 84}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</span> <button onclick=\"logout()\" class=\"bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded transition-colors\">Logout</button></div></header>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</span></div><button class=\"group px-5 py-2.5 bg-red-50 dark:bg-red-500/10 hover:bg-red-100 dark:hover:bg-red-500/20 text-red-600 dark:text-red-400 rounded-xl transition-all duration-300 flex items-center gap-2 border border-red-200 dark:border-red-500/30\" onclick=\"logout()\"><i class=\"fas fa-sign-out-alt group-hover:scale-110 transition-transform\"></i> <span class=\"font-medium\">Logout</span></button></div></div></header>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -126,33 +126,55 @@ func StatsCard(title string, value string) templ.Component {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"bg-white p-4 rounded-lg shadow text-center\"><h4 class=\"text-gray-500 text-sm mb-2\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"group relative bg-white dark:bg-slate-900/80 backdrop-blur-xl rounded-2xl p-6 border border-slate-200 dark:border-slate-700/50 hover:border-violet-400 dark:hover:border-violet-500/50 transition-all duration-300 overflow-hidden shadow-sm dark:shadow-none\"><div class=\"absolute inset-0 bg-gradient-to-br from-violet-500/5 via-purple-500/5 to-transparent dark:from-violet-600/10 dark:via-purple-600/5\"></div><div class=\"relative\"><div class=\"flex items-center justify-between mb-4\"><span class=\"text-slate-500 dark:text-slate-400 text-sm font-medium\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 55, Col: 48}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 157, Col: 80}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</h4><div class=\"text-2xl font-bold text-slate-800\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</span><div class=\"w-10 h-10 rounded-xl bg-violet-100 dark:bg-violet-500/20 flex items-center justify-center\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(value)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 56, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
var templ_7745c5c3_Var7 = []any{"fas " + statsIcon(title) + " text-violet-600 dark:text-violet-400"}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var7...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<i class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var7).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\"></i></div></div><div class=\"text-3xl font-bold text-slate-900 dark:text-white\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(value)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 162, Col: 73}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -160,6 +182,20 @@ func StatsCard(title string, value string) templ.Component {
})
}
// 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
func TabButton(id string, label string, active bool) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
@@ -177,56 +213,78 @@ func TabButton(id string, label string, active bool) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var8 := templ.GetChildren(ctx)
if templ_7745c5c3_Var8 == nil {
templ_7745c5c3_Var8 = templ.NopComponent
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
if templ_7745c5c3_Var10 == nil {
templ_7745c5c3_Var10 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var9 = []any{"px-4 py-2 rounded transition-colors " + tabButtonClass(active)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...)
var templ_7745c5c3_Var11 = []any{"px-6 py-3 rounded-xl font-medium transition-all duration-300 flex items-center gap-2.5", tabButtonClass(active)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var11...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<button class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var9).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" data-tab=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 64, Col: 15}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<button class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(label)
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var11).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 66, Col: 9}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</button>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" data-tab=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 185, Col: 15}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 = []any{"fas " + tabIcon(id)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var14...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<i class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var14).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\"></i> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 188, Col: 9}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -234,12 +292,26 @@ func TabButton(id string, label string, active bool) templ.Component {
})
}
// 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"
return "bg-gradient-to-r from-violet-600 to-fuchsia-600 text-white shadow-lg shadow-violet-500/25"
}
return "bg-white text-gray-600 hover:bg-gray-50"
return "bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700 hover:text-slate-900 dark:hover:text-white border border-slate-200 dark:border-slate-700"
}
// Modal renders a modal dialog
@@ -259,72 +331,72 @@ func Modal(id string, title string) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var13 := templ.GetChildren(ctx)
if templ_7745c5c3_Var13 == nil {
templ_7745c5c3_Var13 = templ.NopComponent
templ_7745c5c3_Var17 := templ.GetChildren(ctx)
if templ_7745c5c3_Var17 == nil {
templ_7745c5c3_Var17 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div id=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(id)
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 80, Col: 13}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 216, Col: 13}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" class=\"fixed inset-0 bg-black/50 hidden items-center justify-center z-50\" data-modal-id=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\" class=\"fixed inset-0 bg-black/50 dark:bg-black/70 backdrop-blur-sm hidden items-center justify-center z-50\" data-modal-id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(id)
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 80, Col: 108}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 216, Col: 142}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\"><div class=\"bg-white p-6 rounded-lg max-w-lg w-full max-h-[90vh] overflow-y-auto modal-content\"><div class=\"flex justify-between items-center mb-4\"><h3 class=\"text-lg font-semibold text-slate-800\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\"><div class=\"bg-white dark:bg-slate-900 rounded-3xl max-w-lg w-full max-h-[90vh] overflow-y-auto border border-slate-200 dark:border-slate-700 shadow-2xl modal-content\"><div class=\"flex justify-between items-center p-6 border-b border-slate-200 dark:border-slate-700\"><h3 class=\"text-lg font-semibold text-slate-900 dark:text-white\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(title)
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 83, Col: 60}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 219, Col: 76}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</h3><button type=\"button\" class=\"text-gray-400 hover:text-gray-600 text-2xl leading-none modal-close\" data-modal-id=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</h3><button type=\"button\" class=\"w-10 h-10 flex items-center justify-center rounded-xl text-slate-400 hover:text-slate-600 dark:hover:text-white hover:bg-slate-100 dark:hover:bg-slate-800 transition-all duration-200 modal-close\" data-modal-id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(id)
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 87, Col: 23}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 223, Col: 23}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\">&times;</button></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\"><i class=\"fas fa-times\"></i></button></div><div class=\"p-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var13.Render(ctx, templ_7745c5c3_Buffer)
templ_7745c5c3_Err = templ_7745c5c3_Var17.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -349,100 +421,100 @@ func FormInput(id string, label string, inputType string, placeholder string, re
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var18 := templ.GetChildren(ctx)
if templ_7745c5c3_Var18 == nil {
templ_7745c5c3_Var18 = templ.NopComponent
templ_7745c5c3_Var22 := templ.GetChildren(ctx)
if templ_7745c5c3_Var22 == nil {
templ_7745c5c3_Var22 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<div class=\"mb-4\"><label for=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 100, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\" class=\"block text-sm text-gray-600 mb-1\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 100, Col: 68}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</label> <input type=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(inputType)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 102, Col: 19}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\" id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 103, Col: 10}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" name=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<div class=\"mb-5\"><label for=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 104, Col: 12}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 238, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\" placeholder=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\" class=\"block text-sm font-medium text-slate-600 dark:text-slate-300 mb-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(placeholder)
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 105, Col: 28}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 238, Col: 101}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</label> <input type=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(inputType)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 240, Col: 19}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\" id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var26 string
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 241, Col: 10}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "\" name=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var27 string
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 242, Col: 12}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\" placeholder=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var28 string
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(placeholder)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 243, Col: 28}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if required {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " required")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, " required")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, " class=\"w-full px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-primary\"></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, " class=\"w-full px-4 py-3.5 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-600 rounded-xl text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-violet-500 transition-all duration-200\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -467,110 +539,110 @@ func FormSelect(id string, label string, options []SelectOption) templ.Component
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var25 := templ.GetChildren(ctx)
if templ_7745c5c3_Var25 == nil {
templ_7745c5c3_Var25 = templ.NopComponent
templ_7745c5c3_Var29 := templ.GetChildren(ctx)
if templ_7745c5c3_Var29 == nil {
templ_7745c5c3_Var29 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<div class=\"mb-4\"><label for=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<div class=\"mb-5\"><label for=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var26 string
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(id)
var templ_7745c5c3_Var30 string
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 115, Col: 17}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 253, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\" class=\"block text-sm text-gray-600 mb-1\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "\" class=\"block text-sm font-medium text-slate-600 dark:text-slate-300 mb-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var27 string
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(label)
var templ_7745c5c3_Var31 string
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 115, Col: 68}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 253, Col: 101}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</label> <select id=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</label> <select id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var28 string
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(id)
var templ_7745c5c3_Var32 string
templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 117, Col: 10}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 255, Col: 10}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "\" name=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\" name=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var29 string
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(id)
var templ_7745c5c3_Var33 string
templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 118, Col: 12}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 256, Col: 12}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "\" class=\"w-full px-3 py-2 border border-gray-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-primary\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "\" class=\"w-full px-4 py-3.5 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-600 rounded-xl text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-violet-500 transition-all duration-200 appearance-none cursor-pointer\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, opt := range options {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<option value=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<option value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var30 string
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(opt.Value)
var templ_7745c5c3_Var34 string
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(opt.Value)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 122, Col: 29}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 260, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if opt.Selected {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, " selected")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, " selected")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, ">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, ">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var31 string
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(opt.Label)
var templ_7745c5c3_Var35 string
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(opt.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 122, Col: 72}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 260, Col: 72}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</option>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</option>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</select></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</select></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -602,74 +674,74 @@ func FormCheckbox(id string, label string, checked bool) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var32 := templ.GetChildren(ctx)
if templ_7745c5c3_Var32 == nil {
templ_7745c5c3_Var32 = templ.NopComponent
templ_7745c5c3_Var36 := templ.GetChildren(ctx)
if templ_7745c5c3_Var36 == nil {
templ_7745c5c3_Var36 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<div class=\"mb-4 flex items-center gap-2\"><input type=\"checkbox\" id=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<div class=\"mb-5 flex items-center gap-3\"><input type=\"checkbox\" id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var33 string
templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(id)
var templ_7745c5c3_Var37 string
templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 140, Col: 10}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 278, Col: 10}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "\" name=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "\" name=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var34 string
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(id)
var templ_7745c5c3_Var38 string
templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 141, Col: 12}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 279, Col: 12}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if checked {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, " checked")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, " checked")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, " class=\"w-4 h-4 text-primary rounded focus:ring-primary\"> <label for=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, " class=\"w-5 h-5 bg-slate-100 dark:bg-slate-800 border-slate-300 dark:border-slate-600 rounded text-violet-600 focus:ring-violet-500 focus:ring-2 cursor-pointer\"> <label for=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var35 string
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(id)
var templ_7745c5c3_Var39 string
templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(id)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 145, Col: 17}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 283, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "\" class=\"text-sm text-gray-600\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "\" class=\"text-sm text-slate-600 dark:text-slate-300 font-medium cursor-pointer\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var36 string
templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(label)
var templ_7745c5c3_Var40 string
templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 145, Col: 57}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 283, Col: 105}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "</label></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</label></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -694,43 +766,43 @@ func Button(label string, variant string) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var37 := templ.GetChildren(ctx)
if templ_7745c5c3_Var37 == nil {
templ_7745c5c3_Var37 = templ.NopComponent
templ_7745c5c3_Var41 := templ.GetChildren(ctx)
if templ_7745c5c3_Var41 == nil {
templ_7745c5c3_Var41 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var38 = []any{"px-3 py-1.5 text-sm rounded transition-colors " + buttonVariantClass(variant)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var38...)
var templ_7745c5c3_Var42 = []any{"px-5 py-2.5 rounded-xl font-medium transition-all duration-300 flex items-center gap-2 " + buttonVariantClass(variant)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var42...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "<button class=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<button class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var39 string
templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var38).String())
var templ_7745c5c3_Var43 string
templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var42).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var40 string
templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(label)
var templ_7745c5c3_Var44 string
templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 154, Col: 9}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 292, Col: 9}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</button>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -742,19 +814,19 @@ func Button(label string, variant string) templ.Component {
func buttonVariantClass(variant string) string {
switch variant {
case "primary":
return "bg-primary hover:bg-blue-600 text-white"
return "bg-gradient-to-r from-violet-600 to-fuchsia-600 hover:from-violet-500 hover:to-fuchsia-500 text-white shadow-lg shadow-violet-500/25"
case "danger":
return "bg-danger hover:bg-red-600 text-white"
return "bg-gradient-to-r from-red-500 to-rose-600 hover:from-red-400 hover:to-rose-500 text-white shadow-lg shadow-red-500/25"
case "success":
return "bg-success hover:bg-green-600 text-white"
return "bg-gradient-to-r from-emerald-500 to-green-600 hover:from-emerald-400 hover:to-green-500 text-white shadow-lg shadow-emerald-500/25"
case "warning":
return "bg-warning hover:bg-yellow-600 text-white"
return "bg-gradient-to-r from-amber-500 to-orange-600 hover:from-amber-400 hover:to-orange-500 text-white shadow-lg shadow-amber-500/25"
case "purple":
return "bg-purple-500 hover:bg-purple-600 text-white"
return "bg-gradient-to-r from-purple-500 to-violet-600 hover:from-purple-400 hover:to-violet-500 text-white shadow-lg shadow-purple-500/25"
case "orange":
return "bg-orange-500 hover:bg-orange-600 text-white"
return "bg-gradient-to-r from-orange-500 to-amber-600 hover:from-orange-400 hover:to-amber-500 text-white shadow-lg shadow-orange-500/25"
default:
return "bg-gray-500 hover:bg-gray-600 text-white"
return "bg-slate-500 hover:bg-slate-600 text-white"
}
}
@@ -775,43 +847,43 @@ func Badge(text string, variant string) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var41 := templ.GetChildren(ctx)
if templ_7745c5c3_Var41 == nil {
templ_7745c5c3_Var41 = templ.NopComponent
templ_7745c5c3_Var45 := templ.GetChildren(ctx)
if templ_7745c5c3_Var45 == nil {
templ_7745c5c3_Var45 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var42 = []any{"px-2 py-0.5 rounded text-xs font-semibold " + badgeVariantClass(variant)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var42...)
var templ_7745c5c3_Var46 = []any{"px-3 py-1.5 rounded-lg text-xs font-semibold " + badgeVariantClass(variant)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var46...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "<span class=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "<span class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var43 string
templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var42).String())
var templ_7745c5c3_Var47 string
templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var46).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var44 string
templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(text)
var templ_7745c5c3_Var48 string
templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 181, Col: 8}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 319, Col: 8}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "</span>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -823,15 +895,17 @@ func Badge(text string, variant string) templ.Component {
func badgeVariantClass(variant string) string {
switch variant {
case "success":
return "bg-green-100 text-green-800"
return "bg-emerald-100 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-500/30"
case "danger":
return "bg-red-100 text-red-800"
return "bg-red-100 text-red-700 dark:bg-red-500/20 dark:text-red-400 border border-red-200 dark:border-red-500/30"
case "info":
return "bg-blue-100 text-blue-800"
return "bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-400 border border-blue-200 dark:border-blue-500/30"
case "warning":
return "bg-yellow-100 text-yellow-800"
return "bg-amber-100 text-amber-700 dark:bg-amber-500/20 dark:text-amber-400 border border-amber-200 dark:border-amber-500/30"
case "purple":
return "bg-purple-100 text-purple-700 dark:bg-purple-500/20 dark:text-purple-400 border border-purple-200 dark:border-purple-500/30"
default:
return "bg-gray-100 text-gray-800"
return "bg-slate-100 text-slate-700 dark:bg-slate-500/20 dark:text-slate-400 border border-slate-200 dark:border-slate-500/30"
}
}
@@ -852,25 +926,25 @@ func ProgressBar(percent float64) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var45 := templ.GetChildren(ctx)
if templ_7745c5c3_Var45 == nil {
templ_7745c5c3_Var45 = templ.NopComponent
templ_7745c5c3_Var49 := templ.GetChildren(ctx)
if templ_7745c5c3_Var49 == nil {
templ_7745c5c3_Var49 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "<div class=\"w-24 h-2 bg-gray-200 rounded overflow-hidden\"><div class=\"h-full bg-primary transition-all\" style=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<div class=\"w-full h-2 bg-slate-200 dark:bg-slate-700 rounded-full overflow-hidden\"><div class=\"h-full bg-gradient-to-r from-violet-500 to-fuchsia-500 transition-all duration-500 rounded-full\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var46 string
templ_7745c5c3_Var46, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("width: %.1f%%", percent))
var templ_7745c5c3_Var50 string
templ_7745c5c3_Var50, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("width: %.1f%%", percent))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 204, Col: 93}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/server/templates/components/layout.templ`, Line: 344, Col: 156}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\"></div></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "\"></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@@ -18,22 +18,24 @@ templ AdminPage(username string) {
</div>
<!-- Tabs -->
<div class="flex gap-3 mb-6">
<div class="flex gap-3 mb-8 flex-wrap">
@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>
<div id="clients-tab" class="animate-fade-in">
<div class="bg-white dark:bg-slate-900/80 backdrop-blur-xl rounded-2xl border border-slate-200 dark:border-slate-700/50 overflow-hidden shadow-sm dark:shadow-none transition-colors duration-300">
<div class="p-6 border-b border-slate-200 dark:border-slate-700/50 flex justify-between items-center">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-violet-100 dark:bg-violet-500/20 flex items-center justify-center">
<i class="fas fa-users text-violet-600 dark:text-violet-400"></i>
</div>
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"
class="px-5 py-2.5 bg-gradient-to-r from-emerald-500 to-green-600 hover:from-emerald-400 hover:to-green-500 text-white rounded-xl transition-all duration-300 flex items-center gap-2 shadow-lg shadow-emerald-500/25"
data-action="show-modal"
data-modal="add-client-modal"
>
@@ -44,14 +46,14 @@ templ AdminPage(username string) {
<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 class="border-b border-slate-200 dark:border-slate-700/50">
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">Client ID</th>
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">Storage Type</th>
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">Quota</th>
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">Used</th>
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">Snapshots</th>
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">Status</th>
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">Actions</th>
</tr>
</thead>
<tbody id="clients-table"></tbody>
@@ -61,27 +63,29 @@ templ AdminPage(username string) {
</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>
<div id="snapshots-tab" class="hidden animate-fade-in">
<div class="bg-white dark:bg-slate-900/80 backdrop-blur-xl rounded-2xl border border-slate-200 dark:border-slate-700/50 overflow-hidden shadow-sm dark:shadow-none transition-colors duration-300">
<div class="p-6 border-b border-slate-200 dark:border-slate-700/50 flex justify-between items-center">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-violet-100 dark:bg-violet-500/20 flex items-center justify-center">
<i class="fas fa-images text-violet-600 dark:text-violet-400"></i>
</div>
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">
<select id="snapshot-client-filter" class="px-4 py-2.5 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-600 rounded-xl text-slate-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-violet-500 focus:border-violet-500 transition-all duration-200 cursor-pointer">
<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 class="border-b border-slate-200 dark:border-slate-700/50">
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">Client</th>
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">Snapshot ID</th>
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">Timestamp</th>
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">Size</th>
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">Type</th>
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">Actions</th>
</tr>
</thead>
<tbody id="snapshots-table"></tbody>
@@ -91,15 +95,17 @@ templ AdminPage(username string) {
</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>
<div id="admins-tab" class="hidden animate-fade-in">
<div class="bg-white dark:bg-slate-900/80 backdrop-blur-xl rounded-2xl border border-slate-200 dark:border-slate-700/50 overflow-hidden shadow-sm dark:shadow-none transition-colors duration-300">
<div class="p-6 border-b border-slate-200 dark:border-slate-700/50 flex justify-between items-center">
<h3 class="text-lg font-semibold text-slate-900 dark:text-white flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-violet-100 dark:bg-violet-500/20 flex items-center justify-center">
<i class="fas fa-user-shield text-violet-600 dark:text-violet-400"></i>
</div>
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"
class="px-5 py-2.5 bg-gradient-to-r from-emerald-500 to-green-600 hover:from-emerald-400 hover:to-green-500 text-white rounded-xl transition-all duration-300 flex items-center gap-2 shadow-lg shadow-emerald-500/25"
data-action="show-modal"
data-modal="add-admin-modal"
>
@@ -110,12 +116,12 @@ templ AdminPage(username string) {
<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 class="border-b border-slate-200 dark:border-slate-700/50">
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">ID</th>
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">Username</th>
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">Role</th>
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">Created</th>
<th class="text-left py-4 px-4 text-slate-500 dark:text-slate-400 font-medium text-sm">Actions</th>
</tr>
</thead>
<tbody id="admins-table"></tbody>
@@ -150,8 +156,8 @@ templ AddClientModal() {
@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>
<h4 class="text-slate-500 dark:text-slate-400 text-sm mt-6 mb-4 flex items-center gap-2 font-medium">
<i class="fas fa-clock-rotate-left text-violet-500 dark:text-violet-400"></i>
Rotation Policy
</h4>
<div class="grid grid-cols-2 gap-4">
@@ -161,7 +167,7 @@ templ AddClientModal() {
@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">
<button type="submit" class="w-full mt-6 px-4 py-3.5 bg-gradient-to-r from-emerald-500 to-green-600 hover:from-emerald-400 hover:to-green-500 text-white rounded-xl transition-all duration-300 flex items-center justify-center gap-2 shadow-lg shadow-emerald-500/25 font-medium">
<i class="fas fa-plus"></i>
Create Client
</button>
@@ -183,8 +189,8 @@ templ EditClientModal() {
@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>
<h4 class="text-slate-500 dark:text-slate-400 text-sm mt-6 mb-4 flex items-center gap-2 font-medium">
<i class="fas fa-clock-rotate-left text-violet-500 dark:text-violet-400"></i>
Rotation Policy
</h4>
<div class="grid grid-cols-2 gap-4">
@@ -194,7 +200,7 @@ templ EditClientModal() {
@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">
<button type="submit" class="w-full mt-6 px-4 py-3.5 bg-gradient-to-r from-violet-600 to-fuchsia-600 hover:from-violet-500 hover:to-fuchsia-500 text-white rounded-xl transition-all duration-300 flex items-center justify-center gap-2 shadow-lg shadow-violet-500/25 font-medium">
<i class="fas fa-save"></i>
Update Client
</button>
@@ -211,7 +217,7 @@ templ AddAdminModal() {
@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">
<button type="submit" class="w-full mt-6 px-4 py-3.5 bg-gradient-to-r from-emerald-500 to-green-600 hover:from-emerald-400 hover:to-green-500 text-white rounded-xl transition-all duration-300 flex items-center justify-center gap-2 shadow-lg shadow-emerald-500/25 font-medium">
<i class="fas fa-plus"></i>
Create Admin
</button>
@@ -227,7 +233,7 @@ templ ChangePasswordModal() {
@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">
<button type="submit" class="w-full mt-6 px-4 py-3.5 bg-gradient-to-r from-violet-600 to-fuchsia-600 hover:from-violet-500 hover:to-fuchsia-500 text-white rounded-xl transition-all duration-300 flex items-center justify-center gap-2 shadow-lg shadow-violet-500/25 font-medium">
<i class="fas fa-key"></i>
Change Password
</button>
@@ -243,7 +249,7 @@ templ ClientPasswordModal() {
@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">
<button type="submit" class="w-full mt-6 px-4 py-3.5 bg-gradient-to-r from-violet-600 to-fuchsia-600 hover:from-violet-500 hover:to-fuchsia-500 text-white rounded-xl transition-all duration-300 flex items-center justify-center gap-2 shadow-lg shadow-violet-500/25 font-medium">
<i class="fas fa-key"></i>
Set API Key
</button>

File diff suppressed because one or more lines are too long

View File

@@ -8,38 +8,42 @@ templ LoginPage() {
<div class="min-h-screen flex items-center justify-center px-4">
<div class="w-full max-w-md">
<!-- Logo and Title -->
<div class="text-center mb-8">
<div class="w-16 h-16 bg-gradient-to-br from-primary to-accent rounded-2xl flex items-center justify-center mx-auto mb-4 shadow-lg shadow-primary/25">
<i class="fas fa-database text-white text-2xl"></i>
<div class="text-center mb-10">
<div class="relative inline-block mb-6">
<div class="w-20 h-20 bg-gradient-to-br from-violet-500 via-purple-500 to-fuchsia-500 rounded-3xl flex items-center justify-center shadow-2xl shadow-violet-500/30 animate-float">
<i class="fas fa-database text-white text-3xl"></i>
</div>
<div class="absolute -bottom-1 -right-1 w-6 h-6 bg-emerald-400 rounded-full border-4 border-white dark:border-slate-950 animate-pulse"></div>
</div>
<h1 class="text-2xl font-bold text-white mb-2">ZFS Backup Admin</h1>
<p class="text-gray-400">Sign in to manage your backups</p>
<h1 class="text-3xl font-bold text-slate-900 dark:text-white mb-3">ZFS Backup Admin</h1>
<p class="text-slate-500 dark:text-slate-400 font-medium">Sign in to manage your backups</p>
</div>
<!-- Login Form -->
<div class="bg-surface rounded-2xl border border-gray-700 p-8 shadow-xl">
<div class="bg-white/90 dark:bg-slate-900/80 backdrop-blur-xl rounded-3xl border border-slate-200 dark:border-slate-700/50 p-8 shadow-xl dark:shadow-none transition-colors duration-300">
<form id="login-form">
@components.FormInput("username", "Username", "text", "", true)
@components.FormInput("password", "Password", "password", "", true)
<button
type="submit"
class="w-full mt-2 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"
class="w-full mt-4 px-4 py-4 bg-gradient-to-r from-violet-600 to-fuchsia-600 hover:from-violet-500 hover:to-fuchsia-500 text-white rounded-xl transition-all duration-300 flex items-center justify-center gap-2 shadow-lg shadow-violet-500/25 hover:shadow-violet-500/40 font-semibold text-lg"
>
<i class="fas fa-sign-in-alt"></i>
Sign In
</button>
</form>
<div class="mt-6 pt-6 border-t border-gray-700 text-center">
<p class="text-gray-500 text-sm">
<i class="fas fa-info-circle mr-1"></i>
Default: <span class="text-gray-400">admin / admin123</span>
<div class="mt-8 pt-6 border-t border-slate-200 dark:border-slate-700/50 text-center">
<p class="text-slate-500 dark:text-slate-500 text-sm flex items-center justify-center gap-2">
<i class="fas fa-info-circle text-violet-500 dark:text-violet-400"></i>
Default: <span class="text-slate-700 dark:text-slate-300 font-mono bg-slate-100 dark:bg-slate-800 px-2 py-0.5 rounded">admin / admin123</span>
</p>
</div>
</div>
<!-- Footer -->
<p class="text-center text-gray-600 text-xs mt-8">
<p class="text-center text-slate-400 dark:text-slate-600 text-xs mt-10 flex items-center justify-center gap-2">
<i class="fas fa-shield-halved text-violet-400 dark:text-violet-500/50"></i>
Powered by ZFS Backup System
</p>
</div>

View File

@@ -44,7 +44,7 @@ func LoginPage() templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"max-w-md mx-auto mt-24 bg-white p-8 rounded-lg shadow\"><h2 class=\"text-xl font-semibold text-center text-slate-800 mb-6\">Admin Login</h2><form id=\"login-form\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"min-h-screen flex items-center justify-center px-4\"><div class=\"w-full max-w-md\"><!-- Logo and Title --><div class=\"text-center mb-10\"><div class=\"relative inline-block mb-6\"><div class=\"w-20 h-20 bg-gradient-to-br from-violet-500 via-purple-500 to-fuchsia-500 rounded-3xl flex items-center justify-center shadow-2xl shadow-violet-500/30 animate-float\"><i class=\"fas fa-database text-white text-3xl\"></i></div><div class=\"absolute -bottom-1 -right-1 w-6 h-6 bg-emerald-400 rounded-full border-4 border-white dark:border-slate-950 animate-pulse\"></div></div><h1 class=\"text-3xl font-bold text-slate-900 dark:text-white mb-3\">ZFS Backup Admin</h1><p class=\"text-slate-500 dark:text-slate-400 font-medium\">Sign in to manage your backups</p></div><!-- Login Form --><div class=\"bg-white/90 dark:bg-slate-900/80 backdrop-blur-xl rounded-3xl border border-slate-200 dark:border-slate-700/50 p-8 shadow-xl dark:shadow-none transition-colors duration-300\"><form id=\"login-form\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -56,7 +56,7 @@ func LoginPage() templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<button type=\"submit\" class=\"w-full bg-primary hover:bg-blue-600 text-white py-2.5 rounded transition-colors\">Login</button></form><p class=\"mt-4 text-center text-gray-400 text-xs\">Default: admin / admin123</p></div><script src=\"/admin/static/login.js\"></script>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<button type=\"submit\" class=\"w-full mt-4 px-4 py-4 bg-gradient-to-r from-violet-600 to-fuchsia-600 hover:from-violet-500 hover:to-fuchsia-500 text-white rounded-xl transition-all duration-300 flex items-center justify-center gap-2 shadow-lg shadow-violet-500/25 hover:shadow-violet-500/40 font-semibold text-lg\"><i class=\"fas fa-sign-in-alt\"></i> Sign In</button></form><div class=\"mt-8 pt-6 border-t border-slate-200 dark:border-slate-700/50 text-center\"><p class=\"text-slate-500 dark:text-slate-500 text-sm flex items-center justify-center gap-2\"><i class=\"fas fa-info-circle text-violet-500 dark:text-violet-400\"></i> Default: <span class=\"text-slate-700 dark:text-slate-300 font-mono bg-slate-100 dark:bg-slate-800 px-2 py-0.5 rounded\">admin / admin123</span></p></div></div><!-- Footer --><p class=\"text-center text-slate-400 dark:text-slate-600 text-xs mt-10 flex items-center justify-center gap-2\"><i class=\"fas fa-shield-halved text-violet-400 dark:text-violet-500/50\"></i> Powered by ZFS Backup System</p></div></div><script src=\"/admin/static/login.js\"></script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@@ -25,9 +25,42 @@ async function loadStats() {
const res = await fetch('/admin/stats');
const data = await res.json();
document.getElementById('stats-grid').innerHTML =
'<div class="bg-white p-4 rounded-lg shadow text-center"><h4 class="text-gray-500 text-sm mb-2">Clients</h4><div class="text-2xl font-bold text-slate-800">' + data.client_count + '</div></div>' +
'<div class="bg-white p-4 rounded-lg shadow text-center"><h4 class="text-gray-500 text-sm mb-2">Total Snapshots</h4><div class="text-2xl font-bold text-slate-800">' + data.total_snapshots + '</div></div>' +
'<div class="bg-white p-4 rounded-lg shadow text-center"><h4 class="text-gray-500 text-sm mb-2">Total Storage</h4><div class="text-2xl font-bold text-slate-800">' + data.total_storage_gb.toFixed(2) + ' GB</div></div>';
'<div class="group relative bg-white dark:bg-slate-900/80 backdrop-blur-xl rounded-2xl p-6 border border-slate-200 dark:border-slate-700/50 hover:border-violet-400 dark:hover:border-violet-500/50 transition-all duration-300 overflow-hidden shadow-sm dark:shadow-none">' +
'<div class="absolute inset-0 bg-gradient-to-br from-violet-500/5 via-purple-500/5 to-transparent dark:from-violet-600/10 dark:via-purple-600/5"></div>' +
'<div class="relative">' +
'<div class="flex items-center justify-between mb-4">' +
'<span class="text-slate-500 dark:text-slate-400 text-sm font-medium">Clients</span>' +
'<div class="w-10 h-10 rounded-xl bg-violet-100 dark:bg-violet-500/20 flex items-center justify-center">' +
'<i class="fas fa-users text-violet-600 dark:text-violet-400"></i>' +
'</div>' +
'</div>' +
'<div class="text-3xl font-bold text-slate-900 dark:text-white">' + data.client_count + '</div>' +
'</div>' +
'</div>' +
'<div class="group relative bg-white dark:bg-slate-900/80 backdrop-blur-xl rounded-2xl p-6 border border-slate-200 dark:border-slate-700/50 hover:border-violet-400 dark:hover:border-violet-500/50 transition-all duration-300 overflow-hidden shadow-sm dark:shadow-none">' +
'<div class="absolute inset-0 bg-gradient-to-br from-violet-500/5 via-purple-500/5 to-transparent dark:from-violet-600/10 dark:via-purple-600/5"></div>' +
'<div class="relative">' +
'<div class="flex items-center justify-between mb-4">' +
'<span class="text-slate-500 dark:text-slate-400 text-sm font-medium">Total Snapshots</span>' +
'<div class="w-10 h-10 rounded-xl bg-violet-100 dark:bg-violet-500/20 flex items-center justify-center">' +
'<i class="fas fa-camera text-violet-600 dark:text-violet-400"></i>' +
'</div>' +
'</div>' +
'<div class="text-3xl font-bold text-slate-900 dark:text-white">' + data.total_snapshots + '</div>' +
'</div>' +
'</div>' +
'<div class="group relative bg-white dark:bg-slate-900/80 backdrop-blur-xl rounded-2xl p-6 border border-slate-200 dark:border-slate-700/50 hover:border-violet-400 dark:hover:border-violet-500/50 transition-all duration-300 overflow-hidden shadow-sm dark:shadow-none">' +
'<div class="absolute inset-0 bg-gradient-to-br from-violet-500/5 via-purple-500/5 to-transparent dark:from-violet-600/10 dark:via-purple-600/5"></div>' +
'<div class="relative">' +
'<div class="flex items-center justify-between mb-4">' +
'<span class="text-slate-500 dark:text-slate-400 text-sm font-medium">Total Storage</span>' +
'<div class="w-10 h-10 rounded-xl bg-violet-100 dark:bg-violet-500/20 flex items-center justify-center">' +
'<i class="fas fa-hard-drive text-violet-600 dark:text-violet-400"></i>' +
'</div>' +
'</div>' +
'<div class="text-3xl font-bold text-slate-900 dark:text-white">' + data.total_storage_gb.toFixed(2) + ' GB</div>' +
'</div>' +
'</div>';
} catch (e) {
console.error('Failed to load stats:', e);
}
@@ -44,21 +77,21 @@ async function loadClients() {
const usedPercent = c.max_size_bytes > 0 ? (c.current_usage / c.max_size_bytes * 100).toFixed(1) : 0;
const usedGB = (c.current_usage / (1024*1024*1024)).toFixed(2);
const maxGB = (c.max_size_bytes / (1024*1024*1024)).toFixed(0);
return '<tr class="border-b hover:bg-gray-50">' +
'<td class="py-2 px-2 font-semibold">' + c.client_id + '</td>' +
'<td class="py-2 px-2"><span class="px-2 py-0.5 rounded text-xs font-semibold bg-blue-100 text-blue-800">' + c.storage_type + '</span></td>' +
'<td class="py-2 px-2">' + maxGB + ' GB</td>' +
'<td class="py-2 px-2">' +
'<div>' + usedGB + ' GB (' + usedPercent + '%)</div>' +
'<div class="w-24 h-2 bg-gray-200 rounded overflow-hidden mt-1"><div class="h-full bg-primary transition-all" style="width: ' + Math.min(usedPercent, 100) + '%"></div></div>' +
return '<tr class="border-b border-slate-100 dark:border-slate-700/30 hover:bg-slate-50 dark:hover:bg-slate-800/30 transition-colors">' +
'<td class="py-4 px-4 font-semibold text-slate-900 dark:text-white">' + c.client_id + '</td>' +
'<td class="py-4 px-4"><span class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-400 border border-blue-200 dark:border-blue-500/30">' + c.storage_type + '</span></td>' +
'<td class="py-4 px-4 text-slate-600 dark:text-slate-300">' + maxGB + ' GB</td>' +
'<td class="py-4 px-4">' +
'<div class="text-slate-600 dark:text-slate-300">' + usedGB + ' GB (' + usedPercent + '%)</div>' +
'<div class="w-24 h-2 bg-slate-200 dark:bg-slate-700 rounded-full overflow-hidden mt-2"><div class="h-full bg-gradient-to-r from-violet-500 to-fuchsia-500 transition-all rounded-full" style="width: ' + Math.min(usedPercent, 100) + '%"></div></div>' +
'</td>' +
'<td class="py-2 px-2">' + c.snapshot_count + '</td>' +
'<td class="py-2 px-2">' + (c.enabled ? '<span class="px-2 py-0.5 rounded text-xs font-semibold bg-green-100 text-green-800">Enabled</span>' : '<span class="px-2 py-0.5 rounded text-xs font-semibold bg-red-100 text-red-800">Disabled</span>') + '</td>' +
'<td class="py-2 px-2 whitespace-nowrap">' +
'<button class="px-2 py-1 text-xs rounded bg-warning hover:bg-yellow-600 text-white mr-1" data-action="edit-client" data-client-id="' + c.client_id + '">Edit</button>' +
'<button class="px-2 py-1 text-xs rounded bg-purple-500 hover:bg-purple-600 text-white mr-1" data-action="set-client-key" data-client-id="' + c.client_id + '">Set Key</button>' +
'<button class="px-2 py-1 text-xs rounded bg-orange-500 hover:bg-orange-600 text-white mr-1" data-action="reset-client-key" data-client-id="' + c.client_id + '">Reset Key</button>' +
'<button class="px-2 py-1 text-xs rounded bg-danger hover:bg-red-600 text-white" data-action="delete-client" data-client-id="' + c.client_id + '">Delete</button>' +
'<td class="py-4 px-4 text-slate-600 dark:text-slate-300">' + c.snapshot_count + '</td>' +
'<td class="py-4 px-4">' + (c.enabled ? '<span class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-emerald-100 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-500/30">Enabled</span>' : '<span class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-red-100 text-red-700 dark:bg-red-500/20 dark:text-red-400 border border-red-200 dark:border-red-500/30">Disabled</span>') + '</td>' +
'<td class="py-4 px-4 whitespace-nowrap">' +
'<button class="px-3 py-1.5 text-xs rounded-lg bg-gradient-to-r from-amber-500 to-orange-500 hover:from-amber-400 hover:to-orange-400 text-white mr-1.5 shadow-lg shadow-amber-500/20 transition-all" data-action="edit-client" data-client-id="' + c.client_id + '"><i class="fas fa-edit mr-1"></i>Edit</button>' +
'<button class="px-3 py-1.5 text-xs rounded-lg bg-gradient-to-r from-purple-500 to-violet-500 hover:from-purple-400 hover:to-violet-400 text-white mr-1.5 shadow-lg shadow-purple-500/20 transition-all" data-action="set-client-key" data-client-id="' + c.client_id + '"><i class="fas fa-key mr-1"></i>Set Key</button>' +
'<button class="px-3 py-1.5 text-xs rounded-lg bg-gradient-to-r from-orange-500 to-amber-500 hover:from-orange-400 hover:to-amber-400 text-white mr-1.5 shadow-lg shadow-orange-500/20 transition-all" data-action="reset-client-key" data-client-id="' + c.client_id + '"><i class="fas fa-rotate mr-1"></i>Reset</button>' +
'<button class="px-3 py-1.5 text-xs rounded-lg bg-gradient-to-r from-red-500 to-rose-500 hover:from-red-400 hover:to-rose-400 text-white shadow-lg shadow-red-500/20 transition-all" data-action="delete-client" data-client-id="' + c.client_id + '"><i class="fas fa-trash mr-1"></i>Delete</button>' +
'</td>' +
'</tr>';
}).join('');
@@ -84,16 +117,16 @@ async function loadSnapshots() {
const tbody = document.getElementById('snapshots-table');
tbody.innerHTML = snapshots.map(s => {
const sizeGB = (s.size_bytes / (1024*1024*1024)).toFixed(2);
return '<tr class="border-b hover:bg-gray-50">' +
'<td class="py-2 px-2">' + s.client_id + '</td>' +
'<td class="py-2 px-2">' + s.snapshot_id + '</td>' +
'<td class="py-2 px-2">' + new Date(s.timestamp).toLocaleString() + '</td>' +
'<td class="py-2 px-2">' + sizeGB + ' GB</td>' +
'<td class="py-2 px-2">' +
(s.incremental ? '<span class="px-2 py-0.5 rounded text-xs font-semibold bg-blue-100 text-blue-800">Incremental</span>' : '<span class="px-2 py-0.5 rounded text-xs font-semibold bg-green-100 text-green-800">Full</span>') +
(s.compressed ? ' <span class="px-2 py-0.5 rounded text-xs font-semibold bg-blue-100 text-blue-800">LZ4</span>' : '') +
return '<tr class="border-b border-slate-100 dark:border-slate-700/30 hover:bg-slate-50 dark:hover:bg-slate-800/30 transition-colors">' +
'<td class="py-4 px-4 text-slate-600 dark:text-slate-300">' + s.client_id + '</td>' +
'<td class="py-4 px-4 font-semibold text-slate-900 dark:text-white">' + s.snapshot_id + '</td>' +
'<td class="py-4 px-4 text-slate-600 dark:text-slate-300">' + new Date(s.timestamp).toLocaleString() + '</td>' +
'<td class="py-4 px-4 text-slate-600 dark:text-slate-300">' + sizeGB + ' GB</td>' +
'<td class="py-4 px-4">' +
(s.incremental ? '<span class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-400 border border-blue-200 dark:border-blue-500/30">Incremental</span>' : '<span class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-emerald-100 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-500/30">Full</span>') +
(s.compressed ? ' <span class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-purple-100 text-purple-700 dark:bg-purple-500/20 dark:text-purple-400 border border-purple-200 dark:border-purple-500/30">LZ4</span>' : '') +
'</td>' +
'<td class="py-2 px-2"><button class="px-2 py-1 text-xs rounded bg-danger hover:bg-red-600 text-white" data-action="delete-snapshot" data-client-id="' + s.client_id + '" data-snapshot-id="' + s.snapshot_id + '">Delete</button></td>' +
'<td class="py-4 px-4"><button class="px-3 py-1.5 text-xs rounded-lg bg-gradient-to-r from-red-500 to-rose-500 hover:from-red-400 hover:to-rose-400 text-white shadow-lg shadow-red-500/20 transition-all" data-action="delete-snapshot" data-client-id="' + s.client_id + '" data-snapshot-id="' + s.snapshot_id + '"><i class="fas fa-trash mr-1"></i>Delete</button></td>' +
'</tr>';
}).join('');
} catch (e) {
@@ -109,14 +142,14 @@ async function loadAdmins() {
const tbody = document.getElementById('admins-table');
tbody.innerHTML = admins.map(a =>
'<tr class="border-b hover:bg-gray-50">' +
'<td class="py-2 px-2">' + a.id + '</td>' +
'<td class="py-2 px-2 font-semibold">' + a.username + '</td>' +
'<td class="py-2 px-2"><span class="px-2 py-0.5 rounded text-xs font-semibold bg-blue-100 text-blue-800">' + a.role + '</span></td>' +
'<td class="py-2 px-2">' + new Date(a.created_at).toLocaleDateString() + '</td>' +
'<td class="py-2 px-2 whitespace-nowrap">' +
'<button class="px-2 py-1 text-xs rounded bg-warning hover:bg-yellow-600 text-white mr-1" data-action="change-admin-password" data-admin-id="' + a.id + '" data-admin-username="' + a.username + '">Change Password</button>' +
'<button class="px-2 py-1 text-xs rounded bg-danger hover:bg-red-600 text-white" data-action="delete-admin" data-admin-id="' + a.id + '">Delete</button>' +
'<tr class="border-b border-slate-100 dark:border-slate-700/30 hover:bg-slate-50 dark:hover:bg-slate-800/30 transition-colors">' +
'<td class="py-4 px-4 text-slate-400 dark:text-slate-500">' + a.id + '</td>' +
'<td class="py-4 px-4 font-semibold text-slate-900 dark:text-white">' + a.username + '</td>' +
'<td class="py-4 px-4"><span class="px-3 py-1.5 rounded-lg text-xs font-semibold bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-400 border border-blue-200 dark:border-blue-500/30">' + a.role + '</span></td>' +
'<td class="py-4 px-4 text-slate-600 dark:text-slate-300">' + new Date(a.created_at).toLocaleDateString() + '</td>' +
'<td class="py-4 px-4 whitespace-nowrap">' +
'<button class="px-3 py-1.5 text-xs rounded-lg bg-gradient-to-r from-amber-500 to-orange-500 hover:from-amber-400 hover:to-orange-400 text-white mr-1.5 shadow-lg shadow-amber-500/20 transition-all" data-action="change-admin-password" data-admin-id="' + a.id + '" data-admin-username="' + a.username + '"><i class="fas fa-key mr-1"></i>Change Password</button>' +
'<button class="px-3 py-1.5 text-xs rounded-lg bg-gradient-to-r from-red-500 to-rose-500 hover:from-red-400 hover:to-rose-400 text-white shadow-lg shadow-red-500/20 transition-all" data-action="delete-admin" data-admin-id="' + a.id + '"><i class="fas fa-trash mr-1"></i>Delete</button>' +
'</td>' +
'</tr>'
).join('');
@@ -129,11 +162,12 @@ async function loadAdmins() {
function showTab(tab) {
currentTab = tab;
document.querySelectorAll('[data-tab]').forEach(t => {
t.classList.remove('bg-primary', 'text-white');
t.classList.add('bg-white', 'text-gray-600');
t.classList.remove('bg-gradient-to-r', 'from-violet-600', 'to-fuchsia-600', 'text-white', 'shadow-lg', 'shadow-violet-500/25');
t.classList.add('bg-slate-100', 'dark:bg-slate-800', 'text-slate-600', 'dark:text-slate-400', 'hover:bg-slate-200', 'dark:hover:bg-slate-700', 'hover:text-slate-900', 'dark:hover:text-white', 'border', 'border-slate-200', 'dark:border-slate-700');
});
document.querySelector('[data-tab="' + tab + '"]').classList.remove('bg-white', 'text-gray-600');
document.querySelector('[data-tab="' + tab + '"]').classList.add('bg-primary', 'text-white');
const activeTab = document.querySelector('[data-tab="' + tab + '"]');
activeTab.classList.remove('bg-slate-100', 'dark:bg-slate-800', 'text-slate-600', 'dark:text-slate-400', 'hover:bg-slate-200', 'dark:hover:bg-slate-700', 'hover:text-slate-900', 'dark:hover:text-white', 'border', 'border-slate-200', 'dark:border-slate-700');
activeTab.classList.add('bg-gradient-to-r', 'from-violet-600', 'to-fuchsia-600', 'text-white', 'shadow-lg', 'shadow-violet-500/25');
document.getElementById('clients-tab').classList.add('hidden');
document.getElementById('snapshots-tab').classList.add('hidden');
@@ -507,16 +541,18 @@ document.getElementById('change-password-form').addEventListener('submit', async
}
try {
const res = await fetch('/admin/admin/password', {
const res = await fetch('/admin/admin/change-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ admin_id: parseInt(adminId), new_password: newPassword })
body: JSON.stringify({
admin_id: parseInt(adminId),
new_password: newPassword
})
});
const data = await res.json();
if (data.success) {
closeModal('change-password-modal');
alert('Password changed successfully');
} else {
alert(data.message || 'Failed to change password');
}
@@ -539,24 +575,27 @@ document.getElementById('client-password-form').addEventListener('submit', async
}
try {
const res = await fetch('/admin/client/password', {
const res = await fetch('/admin/client/set-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ client_id: clientId, api_key: newKey })
body: JSON.stringify({
client_id: clientId,
new_api_key: newKey
})
});
const data = await res.json();
if (data.success) {
closeModal('client-password-modal');
alert('API key changed successfully for ' + clientId);
} else {
alert(data.message || 'Failed to change API key');
alert(data.message || 'Failed to set API key');
}
} catch (e) {
alert('Failed to change API key');
alert('Failed to set API key');
}
});
// Initialize
checkAuth();
loadStats();
loadClients();