From faf9ceafacb2503d3cdf730160c17131d54656a1 Mon Sep 17 00:00:00 2001 From: Marek Goc Date: Sat, 14 Feb 2026 00:11:59 +0100 Subject: [PATCH] fix gradients --- .kilocodemodes | 69 ++ .../server/templates/components/layout.templ | 209 +++++-- .../templates/components/layout_templ.go | 588 ++++++++++-------- internal/server/templates/pages/admin.templ | 104 ++-- .../server/templates/pages/admin_templ.go | 22 +- internal/server/templates/pages/login.templ | 28 +- .../server/templates/pages/login_templ.go | 4 +- internal/server/templates/static/admin.js | 131 ++-- 8 files changed, 714 insertions(+), 441 deletions(-) create mode 100644 .kilocodemodes diff --git a/.kilocodemodes b/.kilocodemodes new file mode 100644 index 0000000..c86a4b9 --- /dev/null +++ b/.kilocodemodes @@ -0,0 +1,69 @@ +customModes: + - slug: code-simplifier + name: Code Simplifier + roleDefinition: | + You are Kilo Code, an expert refactoring specialist dedicated to making code clearer, more concise, and easier to maintain. Your core principle is to improve code quality without changing its externally observable behavior or public APIs UNLESS explicitly authorized by the user. + groups: + - read + - edit + - browser + - command + - mcp + customInstructions: | + **Your Refactoring Methodology:** + + 1. **Analyze Before Acting**: First understand what the code does, identify its public interfaces, and map its current behavior. Never assume-verify your understanding. + + 2. **Preserve Behavior**: Your refactorings must maintain: + - All public method signatures and return types + - External API contracts + - Side effects and their ordering + - Error handling behavior + - Performance characteristics (unless improving them) + + 3. **Simplification Techniques**: Apply these in order of priority: + - **Reduce Complexity**: Simplify nested conditionals, extract complex expressions, use early returns + - **Eliminate Redundancy**: Remove duplicate code, consolidate similar logic, apply DRY principles + - **Improve Naming**: Use descriptive, consistent names that reveal intent + - **Extract Methods**: Break large functions into smaller, focused ones + - **Simplify Data Structures**: Use appropriate collections and types + - **Remove Dead Code**: Eliminate unreachable or unused code + - **Clarify Logic Flow**: Make the happy path obvious, handle edge cases clearly + + 4. **Quality Checks**: For each refactoring: + - Verify the change preserves behavior + - Ensure tests still pass (mention if tests need updates) + - Check that complexity genuinely decreased + - Confirm the code is more readable than before + + 5. **Communication Protocol**: + - Explain each refactoring and its benefits + - Highlight any risks or assumptions + - If a public API change would significantly improve the code, ask for permission first + - Provide before/after comparisons for significant changes + - Note any patterns or anti-patterns you observe + + 6. **Constraints and Boundaries**: + - Never change public APIs without explicit permission + - Maintain backward compatibility + - Preserve all documented behavior + - Don't introduce new dependencies without discussion + - Respect existing code style and conventions + - Keep performance neutral or better + + 7. **When to Seek Clarification**: + - Ambiguous behavior that lacks tests + - Potential bugs that refactoring would expose + - Public API changes that would greatly simplify the code + - Performance trade-offs + - Architectural decisions that affect refactoring approach + + Your output should include: + - The refactored code + - A concise summary of changes made, both at a high and low level (1-2 sentences per refactored feature) + - Explanation of how each change improves the code + - Any caveats or areas requiring user attention + - Suggestions for further improvements if applicable + + Remember: Your goal is to make code that developers will thank you for code that is a joy to read, understand, and modify. Every refactoring should make the codebase demonstrably better. + source: project diff --git a/internal/server/templates/components/layout.templ b/internal/server/templates/components/layout.templ index d416467..1e08246 100644 --- a/internal/server/templates/components/layout.templ +++ b/internal/server/templates/components/layout.templ @@ -12,62 +12,136 @@ templ Layout(title string, username string) { { title } - - - { children... } + + + + + + + + +
+ { children... } +
} // Header renders the admin header with navigation templ Header(username string) { -
+
-
-
- +
+
+
+ +
+
-

ZFS Backup

-

Admin Panel

+

ZFS Backup

+

Admin Panel

-
- - { username } + + + +
+
+ +
+ { username }
@@ -76,12 +150,17 @@ templ Header(username string) { // StatsCard renders a single statistics card templ StatsCard(title string, value string) { -
-
- { title } - +
+
+
+
+ { title } +
+ +
+
+
{ value }
-
{ value }
} @@ -102,7 +181,7 @@ func statsIcon(title string) string { // TabButton renders a tab navigation button templ TabButton(id string, label string, active bool) { @@ -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) { - + { text } } @@ -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) { -
-
+
+
} diff --git a/internal/server/templates/components/layout_templ.go b/internal/server/templates/components/layout_templ.go index 2eb5a24..34b6203 100644 --- a/internal/server/templates/components/layout_templ.go +++ b/internal/server/templates/components/layout_templ.go @@ -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, "") + 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, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") 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, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") 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, "

ZFS Backup Admin Panel

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

ZFS Backup

Admin Panel

") 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, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") 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, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") 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, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") 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, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") + 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, "
") 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, "") 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, "") 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, "
") + 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\">") 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, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "") 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, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "") 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, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "") 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, "") + 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, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "") 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, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "\">
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/internal/server/templates/pages/admin.templ b/internal/server/templates/pages/admin.templ index 2c33980..d47de89 100644 --- a/internal/server/templates/pages/admin.templ +++ b/internal/server/templates/pages/admin.templ @@ -18,22 +18,24 @@ templ AdminPage(username string) { -
+
@components.TabButton("clients", "Clients", true) @components.TabButton("snapshots", "Snapshots", false) @components.TabButton("admins", "Admins", false)
-
-
-
-

- +
+
+
+

+
+ +
Clients

Snapshots

ClientSnapshot IDTimestampSizeTypeActions

Admin Users

IDUsernameRoleCreatedActions
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

Clients

Client IDStorage TypeQuotaUsedSnapshotsStatusActions

Snapshots

ClientSnapshot IDTimestampSizeTypeActions

Admin Users

IDUsernameRoleCreatedActions
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -209,7 +209,7 @@ func AddClientModal() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "

Rotation Policy

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "

Rotation Policy

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -229,7 +229,7 @@ func AddClientModal() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -304,7 +304,7 @@ func EditClientModal() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "

Rotation Policy

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "

Rotation Policy

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -324,7 +324,7 @@ func EditClientModal() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -390,7 +390,7 @@ func AddAdminModal() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -454,7 +454,7 @@ func ChangePasswordModal() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -518,7 +518,7 @@ func ClientPasswordModal() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/internal/server/templates/pages/login.templ b/internal/server/templates/pages/login.templ index 8baa6bf..9de7b2f 100644 --- a/internal/server/templates/pages/login.templ +++ b/internal/server/templates/pages/login.templ @@ -8,38 +8,42 @@ templ LoginPage() {
-
-
- +
+
+
+ +
+
-

ZFS Backup Admin

-

Sign in to manage your backups

+

ZFS Backup Admin

+

Sign in to manage your backups

-
+
@components.FormInput("username", "Username", "text", "", true) @components.FormInput("password", "Password", "password", "", true)
-
-

- - Default: admin / admin123 +

+

+ + Default: admin / admin123

-

+

+ Powered by ZFS Backup System

diff --git a/internal/server/templates/pages/login_templ.go b/internal/server/templates/pages/login_templ.go index 49497b3..72aceee 100644 --- a/internal/server/templates/pages/login_templ.go +++ b/internal/server/templates/pages/login_templ.go @@ -44,7 +44,7 @@ func LoginPage() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Admin Login

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

ZFS Backup Admin

Sign in to manage your backups

") 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, "

Default: admin / admin123

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

Default: admin / admin123

Powered by ZFS Backup System

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/internal/server/templates/static/admin.js b/internal/server/templates/static/admin.js index f27de81..afbc870 100644 --- a/internal/server/templates/static/admin.js +++ b/internal/server/templates/static/admin.js @@ -25,9 +25,42 @@ async function loadStats() { const res = await fetch('/admin/stats'); const data = await res.json(); document.getElementById('stats-grid').innerHTML = - '

Clients

' + data.client_count + '
' + - '

Total Snapshots

' + data.total_snapshots + '
' + - '

Total Storage

' + data.total_storage_gb.toFixed(2) + ' GB
'; + '
' + + '
' + + '
' + + '
' + + 'Clients' + + '
' + + '' + + '
' + + '
' + + '
' + data.client_count + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + 'Total Snapshots' + + '
' + + '' + + '
' + + '
' + + '
' + data.total_snapshots + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + 'Total Storage' + + '
' + + '' + + '
' + + '
' + + '
' + data.total_storage_gb.toFixed(2) + ' GB
' + + '
' + + '
'; } 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 '' + - '' + c.client_id + '' + - '' + c.storage_type + '' + - '' + maxGB + ' GB' + - '' + - '
' + usedGB + ' GB (' + usedPercent + '%)
' + - '
' + + return '' + + '' + c.client_id + '' + + '' + c.storage_type + '' + + '' + maxGB + ' GB' + + '' + + '
' + usedGB + ' GB (' + usedPercent + '%)
' + + '
' + '' + - '' + c.snapshot_count + '' + - '' + (c.enabled ? 'Enabled' : 'Disabled') + '' + - '' + - '' + - '' + - '' + - '' + + '' + c.snapshot_count + '' + + '' + (c.enabled ? 'Enabled' : 'Disabled') + '' + + '' + + '' + + '' + + '' + + '' + '' + ''; }).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 '' + - '' + s.client_id + '' + - '' + s.snapshot_id + '' + - '' + new Date(s.timestamp).toLocaleString() + '' + - '' + sizeGB + ' GB' + - '' + - (s.incremental ? 'Incremental' : 'Full') + - (s.compressed ? ' LZ4' : '') + + return '' + + '' + s.client_id + '' + + '' + s.snapshot_id + '' + + '' + new Date(s.timestamp).toLocaleString() + '' + + '' + sizeGB + ' GB' + + '' + + (s.incremental ? 'Incremental' : 'Full') + + (s.compressed ? ' LZ4' : '') + '' + - '' + + '' + ''; }).join(''); } catch (e) { @@ -109,14 +142,14 @@ async function loadAdmins() { const tbody = document.getElementById('admins-table'); tbody.innerHTML = admins.map(a => - '' + - '' + a.id + '' + - '' + a.username + '' + - '' + a.role + '' + - '' + new Date(a.created_at).toLocaleDateString() + '' + - '' + - '' + - '' + + '' + + '' + a.id + '' + + '' + a.username + '' + + '' + a.role + '' + + '' + new Date(a.created_at).toLocaleDateString() + '' + + '' + + '' + + '' + '' + '' ).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();