aboutsummaryrefslogtreecommitdiff
path: root/weed/admin/view/layout
diff options
context:
space:
mode:
authorChris Lu <chrislusf@users.noreply.github.com>2025-07-01 01:28:09 -0700
committerGitHub <noreply@github.com>2025-07-01 01:28:09 -0700
commit1defee3d682d86c7e0cbc7db7ebdb9cae872a471 (patch)
treedae266bee79c36a74214a47d3d9e9274b322d49d /weed/admin/view/layout
parente5adc3872a79e062826a387e1e2bb68196f14014 (diff)
downloadseaweedfs-1defee3d682d86c7e0cbc7db7ebdb9cae872a471.tar.xz
seaweedfs-1defee3d682d86c7e0cbc7db7ebdb9cae872a471.zip
Add admin component (#6928)
* init version * relocate * add s3 bucket link * refactor handlers into weed/admin folder * fix login logout * adding favicon * remove fall back to http get topology * grpc dial option, disk total capacity * show filer count * fix each volume disk usage * add filers to dashboard * adding hosts, volumes, collections * refactor code and menu * remove "refresh" button * fix data for collections * rename cluster hosts into volume servers * add masters, filers * reorder * adding file browser * create folder and upload files * add filer version, created at time * remove mock data * remove fields * fix submenu item highlighting * fix bucket creation * purge files * delete multiple * fix bucket creation * remove region from buckets * add object store with buckets and users * rendering permission * refactor * get bucket objects and size * link to file browser * add file size and count for collections page * paginate the volumes * fix possible SSRF https://github.com/seaweedfs/seaweedfs/pull/6928/checks?check_run_id=45108469801 * Update weed/command/admin.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update weed/command/admin.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix build * import * remove filer CLI option * remove filer option * remove CLI options --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Diffstat (limited to 'weed/admin/view/layout')
-rw-r--r--weed/admin/view/layout/layout.templ263
-rw-r--r--weed/admin/view/layout/layout_templ.go163
2 files changed, 426 insertions, 0 deletions
diff --git a/weed/admin/view/layout/layout.templ b/weed/admin/view/layout/layout.templ
new file mode 100644
index 000000000..acc4f1a81
--- /dev/null
+++ b/weed/admin/view/layout/layout.templ
@@ -0,0 +1,263 @@
+package layout
+
+import (
+ "fmt"
+ "time"
+ "github.com/gin-gonic/gin"
+)
+
+templ Layout(c *gin.Context, content templ.Component) {
+{{
+ username := c.GetString("username")
+ if username == "" {
+ username = "admin"
+ }
+}}
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>SeaweedFS Admin</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <link rel="icon" href="/static/favicon.ico" type="image/x-icon">
+
+ <!-- Bootstrap CSS -->
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <!-- Font Awesome CSS -->
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
+ <!-- HTMX -->
+ <script src="https://unpkg.com/htmx.org@1.9.8/dist/htmx.min.js"></script>
+ <!-- Custom CSS -->
+ <link rel="stylesheet" href="/static/css/admin.css">
+</head>
+<body>
+ <div class="container-fluid">
+ <!-- Header -->
+ <header class="navbar navbar-expand-lg navbar-dark bg-primary sticky-top">
+ <div class="container-fluid">
+ <a class="navbar-brand fw-bold" href="/admin">
+ <i class="fas fa-server me-2"></i>
+ SeaweedFS Admin
+ </a>
+
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
+ <span class="navbar-toggler-icon"></span>
+ </button>
+
+ <div class="collapse navbar-collapse" id="navbarNav">
+ <ul class="navbar-nav ms-auto">
+ <li class="nav-item dropdown">
+ <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
+ <i class="fas fa-user me-1"></i>{username}
+ </a>
+ <ul class="dropdown-menu">
+ <li><a class="dropdown-item" href="/logout">
+ <i class="fas fa-sign-out-alt me-2"></i>Logout
+ </a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </header>
+
+ <div class="row g-0">
+ <!-- Sidebar -->
+ <div class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
+ <div class="position-sticky pt-3">
+ <h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted">
+ <span>MAIN</span>
+ </h6>
+ <ul class="nav flex-column">
+ <li class="nav-item">
+ <a class="nav-link" href="/admin">
+ <i class="fas fa-tachometer-alt me-2"></i>Dashboard
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link collapsed" href="#" data-bs-toggle="collapse" data-bs-target="#clusterSubmenu" aria-expanded="false" aria-controls="clusterSubmenu">
+ <i class="fas fa-sitemap me-2"></i>Cluster
+ <i class="fas fa-chevron-down ms-auto"></i>
+ </a>
+ <div class="collapse" id="clusterSubmenu">
+ <ul class="nav flex-column ms-3">
+ <li class="nav-item">
+ <a class="nav-link py-2" href="/cluster/masters">
+ <i class="fas fa-crown me-2"></i>Masters
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link py-2" href="/cluster/volume-servers">
+ <i class="fas fa-server me-2"></i>Volume Servers
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link py-2" href="/cluster/filers">
+ <i class="fas fa-folder-open me-2"></i>Filers
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link py-2" href="/cluster/volumes">
+ <i class="fas fa-database me-2"></i>Volumes
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link py-2" href="/cluster/collections">
+ <i class="fas fa-layer-group me-2"></i>Collections
+ </a>
+ </li>
+ </ul>
+ </div>
+ </li>
+ </ul>
+
+ <h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted">
+ <span>MANAGEMENT</span>
+ </h6>
+ <ul class="nav flex-column">
+ <li class="nav-item">
+ <a class="nav-link" href="/files">
+ <i class="fas fa-folder me-2"></i>File Browser
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link collapsed" href="#" data-bs-toggle="collapse" data-bs-target="#objectStoreSubmenu" aria-expanded="false" aria-controls="objectStoreSubmenu">
+ <i class="fas fa-cloud me-2"></i>Object Store
+ <i class="fas fa-chevron-down ms-auto"></i>
+ </a>
+ <div class="collapse" id="objectStoreSubmenu">
+ <ul class="nav flex-column ms-3">
+ <li class="nav-item">
+ <a class="nav-link py-2" href="/object-store/buckets">
+ <i class="fas fa-cube me-2"></i>Buckets
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link py-2" href="/object-store/users">
+ <i class="fas fa-users me-2"></i>Users
+ </a>
+ </li>
+ </ul>
+ </div>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="/metrics">
+ <i class="fas fa-chart-line me-2"></i>Metrics
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="/logs">
+ <i class="fas fa-file-alt me-2"></i>Logs
+ </a>
+ </li>
+ </ul>
+
+ <h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted">
+ <span>SYSTEM</span>
+ </h6>
+ <ul class="nav flex-column">
+ <li class="nav-item">
+ <a class="nav-link" href="/config">
+ <i class="fas fa-cog me-2"></i>Configuration
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="/maintenance">
+ <i class="fas fa-tools me-2"></i>Maintenance
+ </a>
+ </li>
+ </ul>
+ </div>
+ </div>
+
+ <!-- Main content -->
+ <main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
+ <div class="pt-3">
+ @content
+ </div>
+ </main>
+ </div>
+ </div>
+
+ <!-- Footer -->
+ <footer class="footer mt-auto py-3 bg-light">
+ <div class="container-fluid text-center">
+ <small class="text-muted">
+ &copy; {fmt.Sprintf("%d", time.Now().Year())} SeaweedFS Admin
+ </small>
+ </div>
+ </footer>
+
+ <!-- Bootstrap JS -->
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
+ <!-- Custom JS -->
+ <script src="/static/js/admin.js"></script>
+</body>
+</html>
+}
+
+templ LoginForm(c *gin.Context, title string, errorMessage string) {
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>{title} - Login</title>
+ <link rel="icon" href="/static/favicon.ico" type="image/x-icon">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
+</head>
+<body class="bg-light">
+ <div class="container">
+ <div class="row justify-content-center min-vh-100 align-items-center">
+ <div class="col-md-6 col-lg-4">
+ <div class="card shadow">
+ <div class="card-body p-5">
+ <div class="text-center mb-4">
+ <i class="fas fa-server fa-3x text-primary mb-3"></i>
+ <h4 class="card-title">{title}</h4>
+ <p class="text-muted">Please sign in to continue</p>
+ </div>
+
+ if errorMessage != "" {
+ <div class="alert alert-danger" role="alert">
+ <i class="fas fa-exclamation-triangle me-2"></i>
+ {errorMessage}
+ </div>
+ }
+
+ <form method="POST" action="/login">
+ <div class="mb-3">
+ <label for="username" class="form-label">Username</label>
+ <div class="input-group">
+ <span class="input-group-text">
+ <i class="fas fa-user"></i>
+ </span>
+ <input type="text" class="form-control" id="username" name="username" required>
+ </div>
+ </div>
+
+ <div class="mb-4">
+ <label for="password" class="form-label">Password</label>
+ <div class="input-group">
+ <span class="input-group-text">
+ <i class="fas fa-lock"></i>
+ </span>
+ <input type="password" class="form-control" id="password" name="password" required>
+ </div>
+ </div>
+
+ <button type="submit" class="btn btn-primary w-100">
+ <i class="fas fa-sign-in-alt me-2"></i>Sign In
+ </button>
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
+</body>
+</html>
+} \ No newline at end of file
diff --git a/weed/admin/view/layout/layout_templ.go b/weed/admin/view/layout/layout_templ.go
new file mode 100644
index 000000000..9a8afb241
--- /dev/null
+++ b/weed/admin/view/layout/layout_templ.go
@@ -0,0 +1,163 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.3.833
+package layout
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import (
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "time"
+)
+
+func Layout(c *gin.Context, content templ.Component) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+
+ username := c.GetString("username")
+ if username == "" {
+ username = "admin"
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title>SeaweedFS Admin</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link rel=\"icon\" href=\"/static/favicon.ico\" type=\"image/x-icon\"><!-- Bootstrap CSS --><link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css\" rel=\"stylesheet\"><!-- Font Awesome CSS --><link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" rel=\"stylesheet\"><!-- HTMX --><script src=\"https://unpkg.com/htmx.org@1.9.8/dist/htmx.min.js\"></script><!-- Custom CSS --><link rel=\"stylesheet\" href=\"/static/css/admin.css\"></head><body><div class=\"container-fluid\"><!-- Header --><header class=\"navbar navbar-expand-lg navbar-dark bg-primary sticky-top\"><div class=\"container-fluid\"><a class=\"navbar-brand fw-bold\" href=\"/admin\"><i class=\"fas fa-server me-2\"></i> SeaweedFS Admin</a> <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" data-bs-target=\"#navbarNav\"><span class=\"navbar-toggler-icon\"></span></button><div class=\"collapse navbar-collapse\" id=\"navbarNav\"><ul class=\"navbar-nav ms-auto\"><li class=\"nav-item dropdown\"><a class=\"nav-link dropdown-toggle\" href=\"#\" role=\"button\" data-bs-toggle=\"dropdown\"><i class=\"fas fa-user me-1\"></i>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var2 string
+ templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(username)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layout/layout.templ`, Line: 51, Col: 73}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</a><ul class=\"dropdown-menu\"><li><a class=\"dropdown-item\" href=\"/logout\"><i class=\"fas fa-sign-out-alt me-2\"></i>Logout</a></li></ul></li></ul></div></div></header><div class=\"row g-0\"><!-- Sidebar --><div class=\"col-md-3 col-lg-2 d-md-block bg-light sidebar collapse\"><div class=\"position-sticky pt-3\"><h6 class=\"sidebar-heading px-3 mt-4 mb-1 text-muted\"><span>MAIN</span></h6><ul class=\"nav flex-column\"><li class=\"nav-item\"><a class=\"nav-link\" href=\"/admin\"><i class=\"fas fa-tachometer-alt me-2\"></i>Dashboard</a></li><li class=\"nav-item\"><a class=\"nav-link collapsed\" href=\"#\" data-bs-toggle=\"collapse\" data-bs-target=\"#clusterSubmenu\" aria-expanded=\"false\" aria-controls=\"clusterSubmenu\"><i class=\"fas fa-sitemap me-2\"></i>Cluster <i class=\"fas fa-chevron-down ms-auto\"></i></a><div class=\"collapse\" id=\"clusterSubmenu\"><ul class=\"nav flex-column ms-3\"><li class=\"nav-item\"><a class=\"nav-link py-2\" href=\"/cluster/masters\"><i class=\"fas fa-crown me-2\"></i>Masters</a></li><li class=\"nav-item\"><a class=\"nav-link py-2\" href=\"/cluster/volume-servers\"><i class=\"fas fa-server me-2\"></i>Volume Servers</a></li><li class=\"nav-item\"><a class=\"nav-link py-2\" href=\"/cluster/filers\"><i class=\"fas fa-folder-open me-2\"></i>Filers</a></li><li class=\"nav-item\"><a class=\"nav-link py-2\" href=\"/cluster/volumes\"><i class=\"fas fa-database me-2\"></i>Volumes</a></li><li class=\"nav-item\"><a class=\"nav-link py-2\" href=\"/cluster/collections\"><i class=\"fas fa-layer-group me-2\"></i>Collections</a></li></ul></div></li></ul><h6 class=\"sidebar-heading px-3 mt-4 mb-1 text-muted\"><span>MANAGEMENT</span></h6><ul class=\"nav flex-column\"><li class=\"nav-item\"><a class=\"nav-link\" href=\"/files\"><i class=\"fas fa-folder me-2\"></i>File Browser</a></li><li class=\"nav-item\"><a class=\"nav-link collapsed\" href=\"#\" data-bs-toggle=\"collapse\" data-bs-target=\"#objectStoreSubmenu\" aria-expanded=\"false\" aria-controls=\"objectStoreSubmenu\"><i class=\"fas fa-cloud me-2\"></i>Object Store <i class=\"fas fa-chevron-down ms-auto\"></i></a><div class=\"collapse\" id=\"objectStoreSubmenu\"><ul class=\"nav flex-column ms-3\"><li class=\"nav-item\"><a class=\"nav-link py-2\" href=\"/object-store/buckets\"><i class=\"fas fa-cube me-2\"></i>Buckets</a></li><li class=\"nav-item\"><a class=\"nav-link py-2\" href=\"/object-store/users\"><i class=\"fas fa-users me-2\"></i>Users</a></li></ul></div></li><li class=\"nav-item\"><a class=\"nav-link\" href=\"/metrics\"><i class=\"fas fa-chart-line me-2\"></i>Metrics</a></li><li class=\"nav-item\"><a class=\"nav-link\" href=\"/logs\"><i class=\"fas fa-file-alt me-2\"></i>Logs</a></li></ul><h6 class=\"sidebar-heading px-3 mt-4 mb-1 text-muted\"><span>SYSTEM</span></h6><ul class=\"nav flex-column\"><li class=\"nav-item\"><a class=\"nav-link\" href=\"/config\"><i class=\"fas fa-cog me-2\"></i>Configuration</a></li><li class=\"nav-item\"><a class=\"nav-link\" href=\"/maintenance\"><i class=\"fas fa-tools me-2\"></i>Maintenance</a></li></ul></div></div><!-- Main content --><main class=\"col-md-9 ms-sm-auto col-lg-10 px-md-4\"><div class=\"pt-3\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = content.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div></main></div></div><!-- Footer --><footer class=\"footer mt-auto py-3 bg-light\"><div class=\"container-fluid text-center\"><small class=\"text-muted\">&copy; ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var3 string
+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", time.Now().Year()))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layout/layout.templ`, Line: 186, Col: 60}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " SeaweedFS Admin</small></div></footer><!-- Bootstrap JS --><script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js\"></script><!-- Custom JS --><script src=\"/static/js/admin.js\"></script></body></html>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func LoginForm(c *gin.Context, title string, errorMessage string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var4 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var4 == nil {
+ templ_7745c5c3_Var4 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var5 string
+ templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(title)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layout/layout.templ`, Line: 204, Col: 17}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, " - Login</title><link rel=\"icon\" href=\"/static/favicon.ico\" type=\"image/x-icon\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css\" rel=\"stylesheet\"><link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" rel=\"stylesheet\"></head><body class=\"bg-light\"><div class=\"container\"><div class=\"row justify-content-center min-vh-100 align-items-center\"><div class=\"col-md-6 col-lg-4\"><div class=\"card shadow\"><div class=\"card-body p-5\"><div class=\"text-center mb-4\"><i class=\"fas fa-server fa-3x text-primary mb-3\"></i><h4 class=\"card-title\">")
+ 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: `view/layout/layout.templ`, Line: 218, Col: 57}
+ }
+ _, 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><p class=\"text-muted\">Please sign in to continue</p></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if errorMessage != "" {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<div class=\"alert alert-danger\" role=\"alert\"><i class=\"fas fa-exclamation-triangle me-2\"></i> ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var7 string
+ templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(errorMessage)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layout/layout.templ`, Line: 225, Col: 45}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<form method=\"POST\" action=\"/login\"><div class=\"mb-3\"><label for=\"username\" class=\"form-label\">Username</label><div class=\"input-group\"><span class=\"input-group-text\"><i class=\"fas fa-user\"></i></span> <input type=\"text\" class=\"form-control\" id=\"username\" name=\"username\" required></div></div><div class=\"mb-4\"><label for=\"password\" class=\"form-label\">Password</label><div class=\"input-group\"><span class=\"input-group-text\"><i class=\"fas fa-lock\"></i></span> <input type=\"password\" class=\"form-control\" id=\"password\" name=\"password\" required></div></div><button type=\"submit\" class=\"btn btn-primary w-100\"><i class=\"fas fa-sign-in-alt me-2\"></i>Sign In</button></form></div></div></div></div></div><script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js\"></script></body></html>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+var _ = templruntime.GeneratedTemplate