diff options
| author | Chris Lu <chrislusf@users.noreply.github.com> | 2025-07-01 01:28:09 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-01 01:28:09 -0700 |
| commit | 1defee3d682d86c7e0cbc7db7ebdb9cae872a471 (patch) | |
| tree | dae266bee79c36a74214a47d3d9e9274b322d49d /weed/admin/view/layout | |
| parent | e5adc3872a79e062826a387e1e2bb68196f14014 (diff) | |
| download | seaweedfs-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.templ | 263 | ||||
| -rw-r--r-- | weed/admin/view/layout/layout_templ.go | 163 |
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"> + © {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\">© ") + 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 |
