From b450cdd20f9ced4f35417cdbd3cb298c45a0cd56 Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Tue, 20 Jun 2023 13:16:00 +0200 Subject: [PATCH 1/9] Add logic step to findThresholds - fixes reference lines for subclusters w/o extra config --- web/frontend/src/plots/MetricPlot.svelte | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/web/frontend/src/plots/MetricPlot.svelte b/web/frontend/src/plots/MetricPlot.svelte index d6f3ffe..c0440fd 100644 --- a/web/frontend/src/plots/MetricPlot.svelte +++ b/web/frontend/src/plots/MetricPlot.svelte @@ -270,6 +270,7 @@ } export function findThresholds(metricConfig, scope, subCluster) { + // console.log('NAME ' + metricConfig.name + ' / SCOPE ' + scope + ' / SUBCLUSTER ' + subCluster.name) if (!metricConfig || !scope || !subCluster) { console.warn('Argument missing for findThresholds!') return null @@ -280,8 +281,10 @@ // console.log('subClusterConfigs array empty, use metricConfig defaults') return { normal: metricConfig.normal, caution: metricConfig.caution, alert: metricConfig.alert } } else if (metricConfig.subClusters && metricConfig.subClusters.length > 0) { - // console.log('subClusterConfigs found, find and use subCluster Settings') - return metricConfig.subClusters.find(sc => sc.name == subCluster.name) + // console.log('subClusterConfigs found, use subCluster Settings if matching jobs subcluster:') + let forSubCluster = metricConfig.subClusters.find(sc => sc.name == subCluster.name) + if (forSubCluster && forSubCluster.normal && forSubCluster.caution && forSubCluster.alert) return forSubCluster + else return { normal: metricConfig.normal, caution: metricConfig.caution, alert: metricConfig.alert } } else { console.warn('metricConfig.subClusters not found!') return null From bcadb1addaab819bf7c41e5932f8df7673b1c4aa Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Thu, 22 Jun 2023 10:58:36 +0200 Subject: [PATCH 2/9] Remove errorcase from single searchterm logic --- internal/repository/job.go | 3 +-- internal/routerConfig/routes.go | 40 +++++++++++++++++---------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/internal/repository/job.go b/internal/repository/job.go index 9ae7c1e..728d1c0 100644 --- a/internal/repository/job.go +++ b/internal/repository/job.go @@ -523,11 +523,10 @@ var ErrForbidden = errors.New("not authorized") // If query is found to be an integer (= conversion to INT datatype succeeds), skip back to parent call // If nothing matches the search, `ErrNotFound` is returned. -func (r *JobRepository) FindUserOrProjectOrJobname(ctx context.Context, searchterm string) (username string, project string, metasnip string, err error) { +func (r *JobRepository) FindUserOrProjectOrJobname(user *auth.User, searchterm string) (username string, project string, metasnip string, err error) { if _, err := strconv.Atoi(searchterm); err == nil { // Return empty on successful conversion: parent method will redirect for integer jobId return "", "", "", nil } else { // Has to have letters and logged-in user for other guesses - user := auth.GetUser(ctx) if user != nil { // Find username in jobs (match) uresult, _ := r.FindColumnValue(user, searchterm, "job", "user", "user", false) diff --git a/internal/routerConfig/routes.go b/internal/routerConfig/routes.go index 2aa3f05..94d8259 100644 --- a/internal/routerConfig/routes.go +++ b/internal/routerConfig/routes.go @@ -278,55 +278,57 @@ func HandleSearchBar(rw http.ResponseWriter, r *http.Request, api *api.RestApi) if len(splitSearch) == 2 { switch strings.Trim(splitSearch[0], " ") { case "jobId": - http.Redirect(rw, r, "/monitoring/jobs/?jobId="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusTemporaryRedirect) // All Users: Redirect to Tablequery + http.Redirect(rw, r, "/monitoring/jobs/?jobId="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusFound) // All Users: Redirect to Tablequery case "jobName": - http.Redirect(rw, r, "/monitoring/jobs/?jobName="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusTemporaryRedirect) // All Users: Redirect to Tablequery + http.Redirect(rw, r, "/monitoring/jobs/?jobName="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusFound) // All Users: Redirect to Tablequery case "projectId": - http.Redirect(rw, r, "/monitoring/jobs/?projectMatch=eq&project="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusTemporaryRedirect) // All Users: Redirect to Tablequery + http.Redirect(rw, r, "/monitoring/jobs/?projectMatch=eq&project="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusFound) // All Users: Redirect to Tablequery case "username": if user.HasAnyRole([]auth.Role{auth.RoleAdmin, auth.RoleSupport, auth.RoleManager}) { - http.Redirect(rw, r, "/monitoring/users/?user="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusTemporaryRedirect) + http.Redirect(rw, r, "/monitoring/users/?user="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusFound) } else { - http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusTemporaryRedirect) // Users: Redirect to Tablequery + http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusPermanentRedirect) // Users: Redirect to Tablequery } case "name": usernames, _ := api.JobRepository.FindColumnValues(user, strings.Trim(splitSearch[1], " "), "user", "username", "name") if len(usernames) != 0 { joinedNames := strings.Join(usernames, "&user=") - http.Redirect(rw, r, "/monitoring/users/?user="+joinedNames, http.StatusTemporaryRedirect) + http.Redirect(rw, r, "/monitoring/users/?user="+joinedNames, http.StatusFound) } else { if user.HasAnyRole([]auth.Role{auth.RoleAdmin, auth.RoleSupport, auth.RoleManager}) { - http.Redirect(rw, r, "/monitoring/users/?user=NoUserNameFound", http.StatusTemporaryRedirect) + http.Redirect(rw, r, "/monitoring/users/?user=NoUserNameFound", http.StatusPermanentRedirect) } else { - http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusTemporaryRedirect) // Users: Redirect to Tablequery + http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusPermanentRedirect) // Users: Redirect to Tablequery } } default: log.Warnf("Searchbar type parameter '%s' unknown", strings.Trim(splitSearch[0], " ")) - http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusTemporaryRedirect) // Unknown: Redirect to Tablequery + http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusPermanentRedirect) // Unknown: Redirect to Tablequery } } else if len(splitSearch) == 1 { - username, project, jobname, err := api.JobRepository.FindUserOrProjectOrJobname(r.Context(), strings.Trim(search, " ")) - if err != nil { - log.Errorf("Error while searchbar best guess: %v", err.Error()) - http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusTemporaryRedirect) // Unknown: Redirect to Tablequery - } + username, project, jobname, _ := api.JobRepository.FindUserOrProjectOrJobname(user, strings.Trim(search, " ")) + + /* Causes 'http: superfluous response.WriteHeader call' causing SSL error and frontend crash: Cause unknown*/ + // if err != nil { + // log.Errorf("Error while searchbar best guess: %v", err.Error()) + // http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusPermanentRedirect) // Unknown: Redirect to Tablequery + //} if username != "" { - http.Redirect(rw, r, "/monitoring/user/"+username, http.StatusTemporaryRedirect) // User: Redirect to user page + http.Redirect(rw, r, "/monitoring/user/"+username, http.StatusFound) // User: Redirect to user page } else if project != "" { - http.Redirect(rw, r, "/monitoring/jobs/?projectMatch=eq&project="+url.QueryEscape(strings.Trim(search, " ")), http.StatusTemporaryRedirect) // projectId (equal) + http.Redirect(rw, r, "/monitoring/jobs/?projectMatch=eq&project="+url.QueryEscape(strings.Trim(search, " ")), http.StatusFound) // projectId (equal) } else if jobname != "" { - http.Redirect(rw, r, "/monitoring/jobs/?jobName="+url.QueryEscape(strings.Trim(search, " ")), http.StatusTemporaryRedirect) // JobName (contains) + http.Redirect(rw, r, "/monitoring/jobs/?jobName="+url.QueryEscape(strings.Trim(search, " ")), http.StatusFound) // JobName (contains) } else { - http.Redirect(rw, r, "/monitoring/jobs/?jobId="+url.QueryEscape(strings.Trim(search, " ")), http.StatusTemporaryRedirect) // No Result: Probably jobId + http.Redirect(rw, r, "/monitoring/jobs/?jobId="+url.QueryEscape(strings.Trim(search, " ")), http.StatusFound) // No Result: Probably jobId } } else { log.Warnf("Searchbar query parameters malformed: %v", search) - http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusTemporaryRedirect) // Unknown: Redirect to Tablequery + http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusPermanentRedirect) // Unknown: Redirect to Tablequery } } else { From 82b8e8c284a573593186129f9fa38dd8b97edad7 Mon Sep 17 00:00:00 2001 From: Jan Eitzinger Date: Thu, 22 Jun 2023 16:26:09 +0200 Subject: [PATCH 3/9] Fix bug in SearchBar Handler Introduce Message boxes Incomplete and needs cleanup --- cmd/cc-backend/main.go | 2 +- internal/routerConfig/routes.go | 37 +++++++++++++++------------ web/templates/message.tmpl | 21 +++++++++++++++ web/web.go | 45 ++++++++++++++++++++++++++++++--- 4 files changed, 85 insertions(+), 20 deletions(-) create mode 100644 web/templates/message.tmpl diff --git a/cmd/cc-backend/main.go b/cmd/cc-backend/main.go index f9868e6..164d7f1 100644 --- a/cmd/cc-backend/main.go +++ b/cmd/cc-backend/main.go @@ -316,7 +316,7 @@ func main() { // Send a searchId and then reply with a redirect to a user, or directly send query to job table for jobid and project. secured.HandleFunc("/search", func(rw http.ResponseWriter, r *http.Request) { - routerConfig.HandleSearchBar(rw, r, api) + routerConfig.HandleSearchBar(rw, r) }) // Mount all /monitoring/... and /api/... routes. diff --git a/internal/routerConfig/routes.go b/internal/routerConfig/routes.go index 94d8259..9000c18 100644 --- a/internal/routerConfig/routes.go +++ b/internal/routerConfig/routes.go @@ -12,7 +12,6 @@ import ( "strings" "time" - "github.com/ClusterCockpit/cc-backend/internal/api" "github.com/ClusterCockpit/cc-backend/internal/auth" "github.com/ClusterCockpit/cc-backend/internal/graph/model" "github.com/ClusterCockpit/cc-backend/internal/repository" @@ -270,8 +269,9 @@ func SetupRoutes(router *mux.Router, version string, hash string, buildTime stri } } -func HandleSearchBar(rw http.ResponseWriter, r *http.Request, api *api.RestApi) { +func HandleSearchBar(rw http.ResponseWriter, r *http.Request) { if search := r.URL.Query().Get("searchId"); search != "" { + repo := repository.GetJobRepository() user := auth.GetUser(r.Context()) splitSearch := strings.Split(search, ":") @@ -287,10 +287,11 @@ func HandleSearchBar(rw http.ResponseWriter, r *http.Request, api *api.RestApi) if user.HasAnyRole([]auth.Role{auth.RoleAdmin, auth.RoleSupport, auth.RoleManager}) { http.Redirect(rw, r, "/monitoring/users/?user="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusFound) } else { - http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusPermanentRedirect) // Users: Redirect to Tablequery + web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Warn", Info: "Missing Access Rights"}) + // web.RenderMessage(rw, "error", "Missing access rights!") } case "name": - usernames, _ := api.JobRepository.FindColumnValues(user, strings.Trim(splitSearch[1], " "), "user", "username", "name") + usernames, _ := repo.FindColumnValues(user, strings.Trim(splitSearch[1], " "), "user", "username", "name") if len(usernames) != 0 { joinedNames := strings.Join(usernames, "&user=") http.Redirect(rw, r, "/monitoring/users/?user="+joinedNames, http.StatusFound) @@ -298,23 +299,27 @@ func HandleSearchBar(rw http.ResponseWriter, r *http.Request, api *api.RestApi) if user.HasAnyRole([]auth.Role{auth.RoleAdmin, auth.RoleSupport, auth.RoleManager}) { http.Redirect(rw, r, "/monitoring/users/?user=NoUserNameFound", http.StatusPermanentRedirect) } else { - http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusPermanentRedirect) // Users: Redirect to Tablequery + web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Warn", Info: "Missing Access Rights"}) + // web.RenderMessage(rw, "error", "Missing access rights!") } } default: - log.Warnf("Searchbar type parameter '%s' unknown", strings.Trim(splitSearch[0], " ")) - http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusPermanentRedirect) // Unknown: Redirect to Tablequery + web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Warn", Info: fmt.Sprintf("Unknown search term %s", strings.Trim(splitSearch[0], " "))}) + // web.RenderMessage(rw, "error", fmt.Sprintf("Unknown search term %s", strings.Trim(splitSearch[0], " "))) } } else if len(splitSearch) == 1 { - username, project, jobname, _ := api.JobRepository.FindUserOrProjectOrJobname(user, strings.Trim(search, " ")) + username, project, jobname, err := repo.FindUserOrProjectOrJobname(user, strings.Trim(search, " ")) + // err := fmt.Errorf("Blabla") /* Causes 'http: superfluous response.WriteHeader call' causing SSL error and frontend crash: Cause unknown*/ - // if err != nil { - // log.Errorf("Error while searchbar best guess: %v", err.Error()) - // http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusPermanentRedirect) // Unknown: Redirect to Tablequery - //} + if err != nil { + web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Warn", Info: "No search result"}) + return + // web.RenderMessage(rw, "info", "Search with no result") + // log.Errorf("Error while searchbar best guess: %v", err.Error()) + } if username != "" { http.Redirect(rw, r, "/monitoring/user/"+username, http.StatusFound) // User: Redirect to user page @@ -327,11 +332,11 @@ func HandleSearchBar(rw http.ResponseWriter, r *http.Request, api *api.RestApi) } } else { - log.Warnf("Searchbar query parameters malformed: %v", search) - http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusPermanentRedirect) // Unknown: Redirect to Tablequery + web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Warn", Info: "Searchbar query parameters malformed"}) + // web.RenderMessage(rw, "warn", "Searchbar query parameters malformed") } - } else { - http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusTemporaryRedirect) + web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Warn", Info: "Empty search"}) + // web.RenderMessage(rw, "warn", "Empty search") } } diff --git a/web/templates/message.tmpl b/web/templates/message.tmpl new file mode 100644 index 0000000..72e5d69 --- /dev/null +++ b/web/templates/message.tmpl @@ -0,0 +1,21 @@ +{{define "navigation"}} +
+ +
+{{end}} + +{{define "content"}} +
+
+ +
+
+{{end}} diff --git a/web/web.go b/web/web.go index 3547b85..04293d1 100644 --- a/web/web.go +++ b/web/web.go @@ -96,8 +96,7 @@ type Page struct { func RenderTemplate(rw http.ResponseWriter, r *http.Request, file string, page *Page) { t, ok := templates[file] if !ok { - log.Fatalf("WEB/WEB > template '%s' not found", file) - panic("template not found") + log.Errorf("WEB/WEB > template '%s' not found", file) } if page.Clusters == nil { @@ -106,7 +105,47 @@ func RenderTemplate(rw http.ResponseWriter, r *http.Request, file string, page * } } - log.Infof("Page config : %v\n", page.Config) + log.Debugf("Page config : %v\n", page.Config) + if err := t.Execute(rw, page); err != nil { + log.Errorf("Template error: %s", err.Error()) + } +} + +type Message struct { + Title string + Type string + Message string + Icon string +} + +func RenderMessage(rw http.ResponseWriter, msgType string, msg string) { + var page Message + log.Info("render message template") + + switch msgType { + case "success": + page.Title = "Success" + page.Type = "alert-success" + case "info": + page.Title = "Info" + page.Type = "alert-info" + case "warn": + page.Title = "Warning" + page.Type = "alert-warning" + case "error": + page.Title = "Error" + page.Type = "alert-danger" + default: + page.Title = "Message" + page.Type = "alert-secondary" + } + t, ok := templates["message.tmpl"] + if !ok { + log.Error("WEB/WEB > template message.tmpl not found") + } + page.Message = msg + rw.Header().Add("Content-Type", "text/html; charset=utf-8") + if err := t.Execute(rw, page); err != nil { log.Errorf("Template error: %s", err.Error()) } From 8eda4b306d0c56dce2a76ef846a569e8b0de7323 Mon Sep 17 00:00:00 2001 From: Jan Eitzinger Date: Thu, 22 Jun 2023 18:09:40 +0200 Subject: [PATCH 4/9] Unify and cleanup message template --- cmd/cc-backend/main.go | 21 +++++++++------- internal/routerConfig/routes.go | 24 +++++------------- web/templates/login.tmpl | 12 +++------ web/templates/message.tmpl | 4 +-- web/web.go | 44 ++------------------------------- 5 files changed, 25 insertions(+), 80 deletions(-) diff --git a/cmd/cc-backend/main.go b/cmd/cc-backend/main.go index 164d7f1..b591246 100644 --- a/cmd/cc-backend/main.go +++ b/cmd/cc-backend/main.go @@ -274,9 +274,10 @@ func main() { rw.Header().Add("Content-Type", "text/html; charset=utf-8") rw.WriteHeader(http.StatusUnauthorized) web.RenderTemplate(rw, r, "login.tmpl", &web.Page{ - Title: "Login failed - ClusterCockpit", - Error: err.Error(), - Build: buildInfo, + Title: "Login failed - ClusterCockpit", + MsgType: "alert-warning", + Message: err.Error(), + Build: buildInfo, }) })).Methods(http.MethodPost) @@ -284,9 +285,10 @@ func main() { rw.Header().Add("Content-Type", "text/html; charset=utf-8") rw.WriteHeader(http.StatusOK) web.RenderTemplate(rw, r, "login.tmpl", &web.Page{ - Title: "Bye - ClusterCockpit", - Info: "Logout sucessful", - Build: buildInfo, + Title: "Bye - ClusterCockpit", + MsgType: "alert-info", + Message: "Logout successful", + Build: buildInfo, }) }))).Methods(http.MethodPost) @@ -299,9 +301,10 @@ func main() { func(rw http.ResponseWriter, r *http.Request, err error) { rw.WriteHeader(http.StatusUnauthorized) web.RenderTemplate(rw, r, "login.tmpl", &web.Page{ - Title: "Authentication failed - ClusterCockpit", - Error: err.Error(), - Build: buildInfo, + Title: "Authentication failed - ClusterCockpit", + MsgType: "alert-danger", + Message: err.Error(), + Build: buildInfo, }) }) }) diff --git a/internal/routerConfig/routes.go b/internal/routerConfig/routes.go index 9000c18..45f77c5 100644 --- a/internal/routerConfig/routes.go +++ b/internal/routerConfig/routes.go @@ -287,8 +287,7 @@ func HandleSearchBar(rw http.ResponseWriter, r *http.Request) { if user.HasAnyRole([]auth.Role{auth.RoleAdmin, auth.RoleSupport, auth.RoleManager}) { http.Redirect(rw, r, "/monitoring/users/?user="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusFound) } else { - web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Warn", Info: "Missing Access Rights"}) - // web.RenderMessage(rw, "error", "Missing access rights!") + web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Error", MsgType: "alert-danger", Message: "Missing Access Rights"}) } case "name": usernames, _ := repo.FindColumnValues(user, strings.Trim(splitSearch[1], " "), "user", "username", "name") @@ -299,26 +298,18 @@ func HandleSearchBar(rw http.ResponseWriter, r *http.Request) { if user.HasAnyRole([]auth.Role{auth.RoleAdmin, auth.RoleSupport, auth.RoleManager}) { http.Redirect(rw, r, "/monitoring/users/?user=NoUserNameFound", http.StatusPermanentRedirect) } else { - web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Warn", Info: "Missing Access Rights"}) - // web.RenderMessage(rw, "error", "Missing access rights!") + web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Error", MsgType: "alert-danger", Message: "Missing Access Rights"}) } } default: - web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Warn", Info: fmt.Sprintf("Unknown search term %s", strings.Trim(splitSearch[0], " "))}) - // web.RenderMessage(rw, "error", fmt.Sprintf("Unknown search term %s", strings.Trim(splitSearch[0], " "))) + web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Warning", MsgType: "alert-warning", Message: fmt.Sprintf("Unknown search term %s", strings.Trim(splitSearch[0], " "))}) } - } else if len(splitSearch) == 1 { username, project, jobname, err := repo.FindUserOrProjectOrJobname(user, strings.Trim(search, " ")) - // err := fmt.Errorf("Blabla") - - /* Causes 'http: superfluous response.WriteHeader call' causing SSL error and frontend crash: Cause unknown*/ if err != nil { - web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Warn", Info: "No search result"}) + web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Info", MsgType: "alert-info", Message: "Search without result"}) return - // web.RenderMessage(rw, "info", "Search with no result") - // log.Errorf("Error while searchbar best guess: %v", err.Error()) } if username != "" { @@ -330,13 +321,10 @@ func HandleSearchBar(rw http.ResponseWriter, r *http.Request) { } else { http.Redirect(rw, r, "/monitoring/jobs/?jobId="+url.QueryEscape(strings.Trim(search, " ")), http.StatusFound) // No Result: Probably jobId } - } else { - web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Warn", Info: "Searchbar query parameters malformed"}) - // web.RenderMessage(rw, "warn", "Searchbar query parameters malformed") + web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Error", MsgType: "alert-danger", Message: "Searchbar query parameters malformed"}) } } else { - web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Warn", Info: "Empty search"}) - // web.RenderMessage(rw, "warn", "Empty search") + web.RenderTemplate(rw, r, "message.tmpl", &web.Page{Title: "Warning", MsgType: "alert-warning", Message: "Empty search"}) } } diff --git a/web/templates/login.tmpl b/web/templates/login.tmpl index 1e689b8..304a96f 100644 --- a/web/templates/login.tmpl +++ b/web/templates/login.tmpl @@ -17,15 +17,9 @@
- {{if .Error}} - - {{end}} - - {{if .Info}} -