From 24f9d4f934bbfbc46b484c8d974b62d1acc66752 Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Tue, 27 Sep 2022 09:56:17 +0200 Subject: [PATCH 01/12] Add buildInfo to frontend footer --- cmd/cc-backend/main.go | 22 +++++++++++++--------- internal/routerConfig/routes.go | 5 +++-- web/frontend/public/global.css | 18 ++++++++++++++++++ web/templates/base.tmpl | 5 +++++ web/web.go | 7 +++++++ 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/cmd/cc-backend/main.go b/cmd/cc-backend/main.go index c7c4b8e..e051bb8 100644 --- a/cmd/cc-backend/main.go +++ b/cmd/cc-backend/main.go @@ -47,12 +47,12 @@ import ( ) const logoString = ` - ____ _ _ ____ _ _ _ -/ ___| |_ _ ___| |_ ___ _ __ / ___|___ ___| | ___ __ (_) |_ + ____ _ _ ____ _ _ _ +/ ___| |_ _ ___| |_ ___ _ __ / ___|___ ___| | ___ __ (_) |_ | | | | | | / __| __/ _ \ '__| | / _ \ / __| |/ / '_ \| | __| -| |___| | |_| \__ \ || __/ | | |__| (_) | (__| <| |_) | | |_ +| |___| | |_| \__ \ || __/ | | |__| (_) | (__| <| |_) | | |_ \____|_|\__,_|___/\__\___|_| \____\___/ \___|_|\_\ .__/|_|\__| - |_| + |_| ` var ( @@ -226,18 +226,19 @@ func main() { } r := mux.NewRouter() + buildInfo := web.Build{Version: version, Hash: hash, Buildtime: buildTime} r.HandleFunc("/login", func(rw http.ResponseWriter, r *http.Request) { rw.Header().Add("Content-Type", "text/html; charset=utf-8") - web.RenderTemplate(rw, r, "login.tmpl", &web.Page{Title: "Login"}) + web.RenderTemplate(rw, r, "login.tmpl", &web.Page{Title: "Login", Build: buildInfo}) }).Methods(http.MethodGet) r.HandleFunc("/imprint", func(rw http.ResponseWriter, r *http.Request) { rw.Header().Add("Content-Type", "text/html; charset=utf-8") - web.RenderTemplate(rw, r, "imprint.tmpl", &web.Page{Title: "Imprint"}) + web.RenderTemplate(rw, r, "imprint.tmpl", &web.Page{Title: "Imprint", Build: buildInfo}) }) r.HandleFunc("/privacy", func(rw http.ResponseWriter, r *http.Request) { rw.Header().Add("Content-Type", "text/html; charset=utf-8") - web.RenderTemplate(rw, r, "privacy.tmpl", &web.Page{Title: "Privacy"}) + web.RenderTemplate(rw, r, "privacy.tmpl", &web.Page{Title: "Privacy", Build: buildInfo}) }) // Some routes, such as /login or /query, should only be accessible to a user that is logged in. @@ -256,6 +257,7 @@ func main() { web.RenderTemplate(rw, r, "login.tmpl", &web.Page{ Title: "Login failed - ClusterCockpit", Error: err.Error(), + Build: buildInfo, }) })).Methods(http.MethodPost) @@ -265,6 +267,7 @@ func main() { web.RenderTemplate(rw, r, "login.tmpl", &web.Page{ Title: "Bye - ClusterCockpit", Info: "Logout sucessful", + Build: buildInfo, }) }))).Methods(http.MethodPost) @@ -279,6 +282,7 @@ func main() { web.RenderTemplate(rw, r, "login.tmpl", &web.Page{ Title: "Authentication failed - ClusterCockpit", Error: err.Error(), + Build: buildInfo, }) }) }) @@ -287,7 +291,7 @@ func main() { if flagDev { r.Handle("/playground", playground.Handler("GraphQL playground", "/query")) r.PathPrefix("/swagger/").Handler(httpSwagger.Handler( - httpSwagger.URL("http://localhost:8080/swagger/doc.json"))).Methods(http.MethodGet) + httpSwagger.URL("http://clustercockpit.localhost:8082/swagger/doc.json"))).Methods(http.MethodGet) } secured.Handle("/query", graphQLEndpoint) @@ -316,7 +320,7 @@ func main() { }) // Mount all /monitoring/... and /api/... routes. - routerConfig.SetupRoutes(secured) + routerConfig.SetupRoutes(secured, version, hash, buildTime) api.MountRoutes(secured) if config.Keys.EmbedStaticFiles { diff --git a/internal/routerConfig/routes.go b/internal/routerConfig/routes.go index a5ea524..9424df7 100644 --- a/internal/routerConfig/routes.go +++ b/internal/routerConfig/routes.go @@ -253,7 +253,7 @@ func buildFilterPresets(query url.Values) map[string]interface{} { return filterPresets } -func SetupRoutes(router *mux.Router) { +func SetupRoutes(router *mux.Router, version string, hash string, buildTime string) { userCfgRepo := repository.GetUserCfgRepo() for _, route := range routes { route := route @@ -271,7 +271,7 @@ func SetupRoutes(router *mux.Router) { } username, isAdmin, isSupporter := "", true, true - + if user := auth.GetUser(r.Context()); user != nil { username = user.Username isAdmin = user.HasRole(auth.RoleAdmin) @@ -281,6 +281,7 @@ func SetupRoutes(router *mux.Router) { page := web.Page{ Title: title, User: web.User{Username: username, IsAdmin: isAdmin, IsSupporter: isSupporter}, + Build: web.Build{Version: version, Hash: hash, Buildtime: buildTime}, Config: conf, Infos: infos, } diff --git a/web/frontend/public/global.css b/web/frontend/public/global.css index 8feecf6..7e4e805 100644 --- a/web/frontend/public/global.css +++ b/web/frontend/public/global.css @@ -52,3 +52,21 @@ footer { margin: 0rem 0.8rem; white-space: nowrap; } + +.build-list { + color: gray; + font-size: 12px; + list-style-type: none; + padding-left: 0; + width: 100%; + display: flex; + flex-wrap: wrap; + justify-content: right; + margin-top: 0px; + margin-bottom: 5px; +} + +.build-list-item { + margin: 0rem 0.8rem; + white-space: nowrap; +} diff --git a/web/templates/base.tmpl b/web/templates/base.tmpl index c3a5bae..17268c8 100644 --- a/web/templates/base.tmpl +++ b/web/templates/base.tmpl @@ -40,6 +40,11 @@ + {{end}} diff --git a/web/web.go b/web/web.go index 25bbc68..61e0128 100644 --- a/web/web.go +++ b/web/web.go @@ -59,11 +59,18 @@ type User struct { IsSupporter bool } +type Build struct { + Version string + Hash string + Buildtime string +} + type Page struct { Title string // Page title Error string // For generic use (e.g. the exact error message on /login) Info string // For generic use (e.g. "Logout successfull" on /login) User User // Information about the currently logged in user + Build Build // Latest information about the application Clusters []schema.ClusterConfig // List of all clusters for use in the Header FilterPresets map[string]interface{} // For pages with the Filter component, this can be used to set initial filters. Infos map[string]interface{} // For generic use (e.g. username for /monitoring/user/, job id for /monitoring/job/) From d405f0da41b9c7e09052c0db81cc2c163e9ef81e Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Tue, 27 Sep 2022 10:02:01 +0200 Subject: [PATCH 02/12] Fix leftover local dev address --- cmd/cc-backend/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/cc-backend/main.go b/cmd/cc-backend/main.go index e051bb8..ddb5d65 100644 --- a/cmd/cc-backend/main.go +++ b/cmd/cc-backend/main.go @@ -291,7 +291,7 @@ func main() { if flagDev { r.Handle("/playground", playground.Handler("GraphQL playground", "/query")) r.PathPrefix("/swagger/").Handler(httpSwagger.Handler( - httpSwagger.URL("http://clustercockpit.localhost:8082/swagger/doc.json"))).Methods(http.MethodGet) + httpSwagger.URL("http://localhost:8080/swagger/doc.json"))).Methods(http.MethodGet) } secured.Handle("/query", graphQLEndpoint) From 21b03d02b2ade53a65f347b79c57072f14c635f6 Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Tue, 27 Sep 2022 10:33:36 +0200 Subject: [PATCH 03/12] Swagger definition URL now based on config.json Addr field --- cmd/cc-backend/main.go | 2 +- internal/config/config.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/cc-backend/main.go b/cmd/cc-backend/main.go index ddb5d65..9c7ce34 100644 --- a/cmd/cc-backend/main.go +++ b/cmd/cc-backend/main.go @@ -291,7 +291,7 @@ func main() { if flagDev { r.Handle("/playground", playground.Handler("GraphQL playground", "/query")) r.PathPrefix("/swagger/").Handler(httpSwagger.Handler( - httpSwagger.URL("http://localhost:8080/swagger/doc.json"))).Methods(http.MethodGet) + httpSwagger.URL("http://" + config.Keys.Addr + "/swagger/doc.json"))).Methods(http.MethodGet) } secured.Handle("/query", graphQLEndpoint) diff --git a/internal/config/config.go b/internal/config/config.go index ab996ff..ca78495 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -14,7 +14,7 @@ import ( ) var Keys schema.ProgramConfig = schema.ProgramConfig{ - Addr: ":8080", + Addr: "localhost:8080", DisableAuthentication: false, EmbedStaticFiles: true, DBDriver: "sqlite3", From 46baa6e53400e37b9dbd940fbb784e0896393841 Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Wed, 28 Sep 2022 16:13:46 +0200 Subject: [PATCH 04/12] First optical layout Iteration --- web/frontend/src/Status.root.svelte | 148 ++++++++++++++++------------ 1 file changed, 86 insertions(+), 62 deletions(-) diff --git a/web/frontend/src/Status.root.svelte b/web/frontend/src/Status.root.svelte index 26842c8..573914d 100644 --- a/web/frontend/src/Status.root.svelte +++ b/web/frontend/src/Status.root.svelte @@ -2,7 +2,7 @@ import Refresher from './joblist/Refresher.svelte' import Roofline, { transformPerNodeData } from './plots/Roofline.svelte' import Histogram from './plots/Histogram.svelte' - import { Row, Col, Spinner, Card, Table, Progress } from 'sveltestrap' + import { Row, Col, Spinner, Card, CardHeader, CardTitle, CardBody, Table, Progress, Icon } from 'sveltestrap' import { init } from './utils.js' import { operationStore, query } from '@urql/svelte' @@ -60,7 +60,12 @@ query(mainQuery) + + + +

Current usage of cluster "{cluster}"

+ {#if $initq.fetching || $mainQuery.fetching} @@ -89,54 +94,71 @@
{/if} + +
+ + + {#if $initq.data && $mainQuery.data} {#each $initq.data.clusters.find(c => c.name == cluster).subClusters as subCluster, i} - - - - - - - - - - - - - - - - - - - - - - -
SubCluster{subCluster.name}
Allocated Nodes
({allocatedNodes[subCluster.name]} / {subCluster.numberOfNodes})
Flop Rate
({flopRate[subCluster.name]} / {subCluster.flopRateSimd * subCluster.numberOfNodes})
MemBw Rate
({memBwRate[subCluster.name]} / {subCluster.memoryBandwidth * subCluster.numberOfNodes})
+ + + + + SubCluster "{subCluster.name}" + + + + + + + + + + + + + + + + + + +
Allocated Nodes
({allocatedNodes[subCluster.name]} / {subCluster.numberOfNodes})
Flop Rate (Any)
({flopRate[subCluster.name]} / {subCluster.flopRateSimd * subCluster.numberOfNodes})
MemBw Rate
({memBwRate[subCluster.name]} / {subCluster.memoryBandwidth * subCluster.numberOfNodes})
+
+
+ + +
+ {#key $mainQuery.data.nodeMetrics} + data.subCluster == subCluster.name))} /> + {/key} +
-
- {#key $mainQuery.data.nodeMetrics} - data.subCluster == subCluster.name))} /> - {/key} -
{/each} - -
-

Top Users

- {#key $mainQuery.data} - b.count - a.count).map(({ count }, idx) => ({ count, value: idx }))} - label={(x) => x < $mainQuery.data.topUsers.length ? $mainQuery.data.topUsers[Math.floor(x)].name : '0'} /> - {/key} -
-
+ +
+ + + + + +
+

Top Users

+ {#key $mainQuery.data} + b.count - a.count).map(({ count }, idx) => ({ count, value: idx }))} + label={(x) => x < $mainQuery.data.topUsers.length ? $mainQuery.data.topUsers[Math.floor(x)].name : '0'} /> + {/key} +
+ + - + {#each $mainQuery.data.topUsers.sort((a, b) => b.count - a.count) as { name, count }} @@ -144,41 +166,43 @@ {/each}
NameNumber of Nodes
User NameNumber of Nodes
{name}
-
-
-

Top Projects

+ + +

Top Projects

{#key $mainQuery.data} b.count - a.count).map(({ count }, idx) => ({ count, value: idx }))} label={(x) => x < $mainQuery.data.topProjects.length ? $mainQuery.data.topProjects[Math.floor(x)].name : '0'} /> {/key} -
-
+ + - + {#each $mainQuery.data.topProjects.sort((a, b) => b.count - a.count) as { name, count }} {/each}
NameNumber of Nodes
Project CodeNumber of Nodes
{name}{count}
-
+
- -
-

Duration Distribution

- {#key $mainQuery.data.stats} - - {/key} -
-
-

Number of Nodes Distribution

+ + +
+

Duration Distribution

+ {#key $mainQuery.data.stats} + + {/key} +
+ + +

Number of Nodes Distribution

{#key $mainQuery.data.stats} {/key} -
+
{/if} From d1c47f4359047fb1c9c88ea65fd42e831ca8ea72 Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Fri, 30 Sep 2022 17:00:15 +0200 Subject: [PATCH 05/12] Improve Histogram.svelte - Add bold axis labels - Labels defined as element props xlabel and ylabel - Label offset dynamic to plot height (10 percent) - Add adjustable gaps between bars --- web/frontend/src/Status.root.svelte | 12 +++++--- web/frontend/src/plots/Histogram.svelte | 38 ++++++++++++++++++------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/web/frontend/src/Status.root.svelte b/web/frontend/src/Status.root.svelte index 573914d..28c6251 100644 --- a/web/frontend/src/Status.root.svelte +++ b/web/frontend/src/Status.root.svelte @@ -152,7 +152,8 @@ b.count - a.count).map(({ count }, idx) => ({ count, value: idx }))} - label={(x) => x < $mainQuery.data.topUsers.length ? $mainQuery.data.topUsers[Math.floor(x)].name : '0'} /> + label={(x) => x < $mainQuery.data.topUsers.length ? $mainQuery.data.topUsers[Math.floor(x)].name : '0'} + xlabel="User Name" ylabel="Number of Jobs" /> {/key} @@ -173,7 +174,8 @@ b.count - a.count).map(({ count }, idx) => ({ count, value: idx }))} - label={(x) => x < $mainQuery.data.topProjects.length ? $mainQuery.data.topProjects[Math.floor(x)].name : '0'} /> + label={(x) => x < $mainQuery.data.topProjects.length ? $mainQuery.data.topProjects[Math.floor(x)].name : '0'} + xlabel="Project Code" ylabel="Number of Jobs" /> {/key} @@ -192,7 +194,8 @@ {#key $mainQuery.data.stats} + data={$mainQuery.data.stats[0].histDuration} + xlabel="Current Runtime in Hours [h]" ylabel="Number of Jobs" /> {/key} @@ -201,7 +204,8 @@ {#key $mainQuery.data.stats} + data={$mainQuery.data.stats[0].histNumNodes} + xlabel="Allocated Nodes" ylabel="Number of Jobs" /> {/key}
diff --git a/web/frontend/src/plots/Histogram.svelte b/web/frontend/src/plots/Histogram.svelte index c00de12..a6aa49f 100644 --- a/web/frontend/src/plots/Histogram.svelte +++ b/web/frontend/src/plots/Histogram.svelte @@ -1,4 +1,4 @@ -