diff --git a/internal/api/rest.go b/internal/api/rest.go index b255694..3525fba 100644 --- a/internal/api/rest.go +++ b/internal/api/rest.go @@ -718,14 +718,26 @@ func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) { return } - // TODO: Handle anything but roles... + // Get Values newrole := r.FormValue("add-role") - if err := api.Authentication.AddRole(r.Context(), mux.Vars(r)["id"], newrole); err != nil { - http.Error(rw, err.Error(), http.StatusUnprocessableEntity) - return - } + delrole := r.FormValue("remove-role") - rw.Write([]byte("success")) + // TODO: Handle anything but roles... + if (newrole != "") { + if err := api.Authentication.AddRole(r.Context(), mux.Vars(r)["id"], newrole); err != nil { + http.Error(rw, err.Error(), http.StatusUnprocessableEntity) + return + } + rw.Write([]byte("Add Role Success")) + } else if (delrole != "") { + if err := api.Authentication.RemoveRole(r.Context(), mux.Vars(r)["id"], delrole); err != nil { + http.Error(rw, err.Error(), http.StatusUnprocessableEntity) + return + } + rw.Write([]byte("Remove Role Success")) + } else { + http.Error(rw, "Not Add or Del?", http.StatusInternalServerError) + } } func (api *RestApi) updateConfiguration(rw http.ResponseWriter, r *http.Request) { diff --git a/internal/auth/users.go b/internal/auth/users.go index f0468d8..fbdc08d 100644 --- a/internal/auth/users.go +++ b/internal/auth/users.go @@ -120,7 +120,7 @@ func (auth *Authentication) AddRole( return err } - if role != RoleAdmin && role != RoleApi && role != RoleUser { + if role != RoleAdmin && role != RoleApi && role != RoleUser && role != RoleSupport { return fmt.Errorf("invalid user role: %#v", role) } @@ -137,13 +137,40 @@ func (auth *Authentication) AddRole( return nil } -func FetchUser( - ctx context.Context, - db *sqlx.DB, - username string) (*model.User, error) { +func (auth *Authentication) RemoveRole(ctx context.Context, username string, role string) error { + user, err := auth.GetUser(username) + if err != nil { + return err + } + if role != RoleAdmin && role != RoleApi && role != RoleUser { + return fmt.Errorf("invalid user role: %#v", role) + } + + var exists bool + var newroles []string + for _, r := range user.Roles { + if r != role { + newroles = append(newroles, r) // Append all roles not matching requested delete role + } else { + exists = true + } + } + + if (exists == true) { + var mroles, _ = json.Marshal(newroles) + if _, err := sq.Update("user").Set("roles", mroles).Where("user.username = ?", username).RunWith(auth.db).Exec(); err != nil { + return err + } + return nil + } else { + return fmt.Errorf("user %#v already does not have role %#v", username, role) + } +} + +func FetchUser(ctx context.Context, db *sqlx.DB, username string) (*model.User, error) { me := GetUser(ctx) - if me != nil && !me.HasRole(RoleAdmin) && me.Username != username { + if me != nil && !me.HasRole(RoleAdmin) && !me.HasRole(RoleSupport) && me.Username != username { return nil, errors.New("forbidden") } diff --git a/internal/graph/schema.resolvers.go b/internal/graph/schema.resolvers.go index da682e9..1aa8a04 100644 --- a/internal/graph/schema.resolvers.go +++ b/internal/graph/schema.resolvers.go @@ -152,9 +152,7 @@ func (r *queryResolver) Job(ctx context.Context, id string) (*schema.Job, error) return nil, err } - if user := auth.GetUser(ctx); user != nil && - !user.HasRole(auth.RoleAdmin) && - job.User != user.Username { + if user := auth.GetUser(ctx); user != nil && !user.HasRole(auth.RoleAdmin) && !user.HasRole(auth.RoleSupport) && job.User != user.Username { return nil, errors.New("you are not allowed to see this job") } diff --git a/internal/repository/job.go b/internal/repository/job.go index d28fdfd..0496698 100644 --- a/internal/repository/job.go +++ b/internal/repository/job.go @@ -308,7 +308,7 @@ func (r *JobRepository) FindJobOrUser(ctx context.Context, searchterm string) (j user := auth.GetUser(ctx) if id, err := strconv.Atoi(searchterm); err == nil { qb := sq.Select("job.id").From("job").Where("job.job_id = ?", id) - if user != nil && !user.HasRole(auth.RoleAdmin) { + if user != nil && !user.HasRole(auth.RoleAdmin) && !user.HasRole(auth.RoleSupport) { qb = qb.Where("job.user = ?", user.Username) } @@ -320,7 +320,7 @@ func (r *JobRepository) FindJobOrUser(ctx context.Context, searchterm string) (j } } - if user == nil || user.HasRole(auth.RoleAdmin) { + if user == nil || user.HasRole(auth.RoleAdmin) || user.HasRole(auth.RoleSupport) { err := sq.Select("job.user").Distinct().From("job"). Where("job.user = ?", searchterm). RunWith(r.stmtCache).QueryRow().Scan(&username) diff --git a/internal/repository/query.go b/internal/repository/query.go index 917bbaf..fad6091 100644 --- a/internal/repository/query.go +++ b/internal/repository/query.go @@ -94,7 +94,7 @@ func (r *JobRepository) CountJobs( func SecurityCheck(ctx context.Context, query sq.SelectBuilder) sq.SelectBuilder { user := auth.GetUser(ctx) - if user == nil || user.HasRole(auth.RoleAdmin) || user.HasRole(auth.RoleApi) { + if user == nil || user.HasRole(auth.RoleAdmin) || user.HasRole(auth.RoleApi) || user.HasRole(auth.RoleSupport) { return query } diff --git a/internal/routerConfig/routes.go b/internal/routerConfig/routes.go index 9a4557a..a5ea524 100644 --- a/internal/routerConfig/routes.go +++ b/internal/routerConfig/routes.go @@ -270,15 +270,17 @@ func SetupRoutes(router *mux.Router) { title = strings.Replace(route.Title, "", id.(string), 1) } - username, isAdmin := "", true + username, isAdmin, isSupporter := "", true, true + if user := auth.GetUser(r.Context()); user != nil { username = user.Username isAdmin = user.HasRole(auth.RoleAdmin) + isSupporter = user.HasRole(auth.RoleSupport) } page := web.Page{ Title: title, - User: web.User{Username: username, IsAdmin: isAdmin}, + User: web.User{Username: username, IsAdmin: isAdmin, IsSupporter: isSupporter}, Config: conf, Infos: infos, } diff --git a/pkg/lrucache/README.md b/pkg/lrucache/README.md index ad58bc9..855a185 100644 --- a/pkg/lrucache/README.md +++ b/pkg/lrucache/README.md @@ -1,7 +1,5 @@ # In-Memory LRU Cache for Golang Applications -[![](https://pkg.go.dev/badge/github.com/iamlouk/lrucache?utm_source=godoc)](https://pkg.go.dev/github.com/iamlouk/lrucache) - This library can be embedded into your existing go applications and play the role *Memcached* or *Redis* might play for others. It is inspired by [PHP Symfony's Cache Components](https://symfony.com/doc/current/components/cache/adapters/array_cache_adapter.html), diff --git a/pkg/lrucache/cache.go b/pkg/lrucache/cache.go index aedfd5c..679bd2e 100644 --- a/pkg/lrucache/cache.go +++ b/pkg/lrucache/cache.go @@ -1,3 +1,7 @@ +// Copyright (C) 2022 NHR@FAU, University Erlangen-Nuremberg. +// All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. package lrucache import ( diff --git a/pkg/lrucache/cache_test.go b/pkg/lrucache/cache_test.go index bfab653..7ba5504 100644 --- a/pkg/lrucache/cache_test.go +++ b/pkg/lrucache/cache_test.go @@ -1,3 +1,7 @@ +// Copyright (C) 2022 NHR@FAU, University Erlangen-Nuremberg. +// All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. package lrucache import ( diff --git a/pkg/lrucache/handler.go b/pkg/lrucache/handler.go index e83ba10..db6687f 100644 --- a/pkg/lrucache/handler.go +++ b/pkg/lrucache/handler.go @@ -1,3 +1,7 @@ +// Copyright (C) 2022 NHR@FAU, University Erlangen-Nuremberg. +// All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. package lrucache import ( diff --git a/pkg/lrucache/handler_test.go b/pkg/lrucache/handler_test.go index cb05f31..4013c63 100644 --- a/pkg/lrucache/handler_test.go +++ b/pkg/lrucache/handler_test.go @@ -1,3 +1,7 @@ +// Copyright (C) 2022 NHR@FAU, University Erlangen-Nuremberg. +// All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. package lrucache import ( diff --git a/web/frontend/package.json b/web/frontend/package.json index 2f2ab55..174fa5f 100644 --- a/web/frontend/package.json +++ b/web/frontend/package.json @@ -1,6 +1,7 @@ { - "name": "svelte-app", + "name": "cc-frontend", "version": "1.0.0", + "license": "MIT", "scripts": { "build": "rollup -c", "dev": "rollup -c -w" @@ -12,7 +13,7 @@ "rollup-plugin-css-only": "^3.1.0", "rollup-plugin-svelte": "^7.0.0", "rollup-plugin-terser": "^7.0.0", - "svelte": "^3.42.6" + "svelte": "^3.49.0" }, "dependencies": { "@rollup/plugin-replace": "^2.4.1", diff --git a/web/frontend/rollup.config.js b/web/frontend/rollup.config.js index 8144e9c..2737c8a 100644 --- a/web/frontend/rollup.config.js +++ b/web/frontend/rollup.config.js @@ -66,6 +66,6 @@ export default [ entrypoint('systems', 'src/systems.entrypoint.js'), entrypoint('node', 'src/node.entrypoint.js'), entrypoint('analysis', 'src/analysis.entrypoint.js'), - entrypoint('status', 'src/status.entrypoint.js') + entrypoint('status', 'src/status.entrypoint.js'), + entrypoint('config', 'src/config.entrypoint.js') ]; - diff --git a/web/frontend/src/Config.root.svelte b/web/frontend/src/Config.root.svelte new file mode 100644 index 0000000..6b1eb40 --- /dev/null +++ b/web/frontend/src/Config.root.svelte @@ -0,0 +1,31 @@ + + +{#if user.IsAdmin} + + + Admin Options + + + +{/if} + + + + Plotting Options + + + diff --git a/web/frontend/src/config.entrypoint.js b/web/frontend/src/config.entrypoint.js new file mode 100644 index 0000000..5c9e525 --- /dev/null +++ b/web/frontend/src/config.entrypoint.js @@ -0,0 +1,12 @@ +import {} from './header.entrypoint.js' +import Config from './Config.root.svelte' + +new Config({ + target: document.getElementById('svelte-app'), + props: { + user: user + }, + context: new Map([ + ['cc-config', clusterCockpitConfig] + ]) +}) diff --git a/web/frontend/src/config/AdminSettings.svelte b/web/frontend/src/config/AdminSettings.svelte new file mode 100644 index 0000000..d1ce542 --- /dev/null +++ b/web/frontend/src/config/AdminSettings.svelte @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + diff --git a/web/frontend/src/config/PlotSettings.svelte b/web/frontend/src/config/PlotSettings.svelte new file mode 100644 index 0000000..36326bd --- /dev/null +++ b/web/frontend/src/config/PlotSettings.svelte @@ -0,0 +1,171 @@ + + + + + + +
handleSettingSubmit('#line-width-form', 'lw')}> + + +
Line Width
+ + {#if displayMessage && message.target == 'lw'} +
+ + Update: {message.msg} + +
+ {/if} +
+ +
+ + +
Width of the lines in the timeseries plots.
+
+ +
+
+ + + +
handleSettingSubmit('#plots-per-row-form', 'ppr')}> + + +
Plots per Row
+ {#if displayMessage && message.target == 'ppr'}
Update: {message.msg}
{/if} +
+ +
+ + +
How many plots to show next to each other on pages such as /monitoring/job/, /monitoring/system/...
+
+ +
+
+ + + +
handleSettingSubmit('#backgrounds-form', 'bg')}> + + +
Colored Backgrounds
+ {#if displayMessage && message.target == 'bg'}
Update: {message.msg}
{/if} +
+ +
+
+ {#if config.plot_general_colorBackground} + + {:else} + + {/if} + +
+
+ {#if config.plot_general_colorBackground} + + {:else} + + {/if} + +
+
+ +
+
+
+ + + + +
+ + +
Color Scheme for Timeseries Plots
+ {#if displayMessage && message.target == 'cs'}
Update: {message.msg}
{/if} +
+ + + + {#each Object.entries(colorschemes) as [name, rgbrow]} + + + + + + {/each} + +
{name} + {#if rgbrow.join(',') == config.plot_general_colorscheme} + handleSettingSubmit("#colorscheme-form", "cs")}/> + {:else} + handleSettingSubmit("#colorscheme-form", "cs")}/> + {/if} + + {#each rgbrow as rgb} + + {/each} +
+
+
+
+ + diff --git a/web/frontend/src/config/admin/AddUser.svelte b/web/frontend/src/config/admin/AddUser.svelte new file mode 100644 index 0000000..bcbd240 --- /dev/null +++ b/web/frontend/src/config/admin/AddUser.svelte @@ -0,0 +1,91 @@ + + + +
+ Create User +
+ + +
Optional, can be blank.
+
+
+ + +
Optional, can be blank.
+
+
+ + +
Must be unique.
+
+
+ + +
Only API users are allowed to have a blank password. Users with a blank password can only authenticate via Tokens.
+
+
+

Role:

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+

+ + {#if displayMessage}

{message.msg}
{/if} +

+
+
diff --git a/web/frontend/src/config/admin/EditRole.svelte b/web/frontend/src/config/admin/EditRole.svelte new file mode 100644 index 0000000..4615c0a --- /dev/null +++ b/web/frontend/src/config/admin/EditRole.svelte @@ -0,0 +1,103 @@ + + + + + Edit User Roles +
+ + + + + + +
+

+ {#if displayMessage}Update: {message.msg}{/if} +

+
+
diff --git a/web/frontend/src/config/admin/Options.svelte b/web/frontend/src/config/admin/Options.svelte new file mode 100644 index 0000000..44f9650 --- /dev/null +++ b/web/frontend/src/config/admin/Options.svelte @@ -0,0 +1,29 @@ + + + + + Scramble Names / Presentation Mode + + Active? + + diff --git a/web/frontend/src/config/admin/ShowUsers.svelte b/web/frontend/src/config/admin/ShowUsers.svelte new file mode 100644 index 0000000..5726fc4 --- /dev/null +++ b/web/frontend/src/config/admin/ShowUsers.svelte @@ -0,0 +1,67 @@ + + + + + Special Users +

+ Not created by an LDAP sync and/or having a role other than user + +

+
+ + + + + + + + + + + + + {#each userList as user} + + + + + {:else} + + + + {/each} + +
UsernameNameEmailRolesJWTDelete
+
Loading...
+
+
+
+
diff --git a/web/frontend/src/config/admin/ShowUsersRow.svelte b/web/frontend/src/config/admin/ShowUsersRow.svelte new file mode 100644 index 0000000..64b5dd4 --- /dev/null +++ b/web/frontend/src/config/admin/ShowUsersRow.svelte @@ -0,0 +1,27 @@ + + +{user.username} +{user.name} +{user.email} +{user.roles.join(', ')} + + {#if ! jwt} + + {:else} + + {/if} + diff --git a/web/frontend/yarn.lock b/web/frontend/yarn.lock index f80e078..7113711 100644 --- a/web/frontend/yarn.lock +++ b/web/frontend/yarn.lock @@ -28,6 +28,46 @@ resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.1.tgz#076d78ce99822258cf813ecc1e7fa460fa74d052" integrity sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg== +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" + integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@popperjs/core@^2.9.2": version "2.11.0" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.0.tgz#6734f8ebc106a0860dff7f92bf90df193f0935d7" @@ -121,6 +161,11 @@ "@urql/core" "^2.3.4" wonka "^4.0.14" +acorn@^8.5.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== + ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -432,11 +477,6 @@ source-map@^0.6.0: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@~0.7.2: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - sourcemap-codec@^1.4.4: version "1.4.8" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" @@ -456,10 +496,10 @@ supports-color@^7.0.0: dependencies: has-flag "^4.0.0" -svelte@^3.42.6: - version "3.44.2" - resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.44.2.tgz#3e69be2598308dfc8354ba584cec54e648a50f7f" - integrity sha512-jrZhZtmH3ZMweXg1Q15onb8QlWD+a5T5Oca4C1jYvSURp2oD35h4A5TV6t6MEa93K4LlX6BkafZPdQoFjw/ylA== +svelte@^3.49.0: + version "3.49.0" + resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.49.0.tgz#5baee3c672306de1070c3b7888fc2204e36a4029" + integrity sha512-+lmjic1pApJWDfPCpUUTc1m8azDqYCG1JN9YEngrx/hUyIcFJo6VZhj0A1Ai0wqoHcEIuQy+e9tk+4uDgdtsFA== sveltestrap@^5.6.1: version "5.6.3" @@ -469,12 +509,13 @@ sveltestrap@^5.6.1: "@popperjs/core" "^2.9.2" terser@^5.0.0: - version "5.10.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.10.0.tgz#b86390809c0389105eb0a0b62397563096ddafcc" - integrity sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA== + version "5.14.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10" + integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA== dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" commander "^2.20.0" - source-map "~0.7.2" source-map-support "~0.5.20" uplot@^1.6.7: diff --git a/web/templates/config.tmpl b/web/templates/config.tmpl index deccf92..2f3eaf1 100644 --- a/web/templates/config.tmpl +++ b/web/templates/config.tmpl @@ -1,295 +1,15 @@ {{define "content"}} -{{if .User.IsAdmin}} -
-
-
-
Create User
-
- - -
Optional, can be blank.
-
-
- - -
Optional, can be blank.
-
-
- - -
Must be unique.
-
-
- - -
Only API users are allowed to have a blank password. Users with a blank password can only authenticate via Tokens.
-
-
-

Role:

-
- - -
-
- - -
-
- - -
-
-

- -

- -
-
-
-
-
Special Users
-

Not created by an LDAP sync and/or having a role other than user

- -
- - - - - - - - - - - - - - - - -
UsernameNameEmailRolesJWTDelete
-
Loading...
-
-
- -
-
-
-
-
-
-
Add Role to User
-
- - - -
- -
-
-
-
-
Scramble Names / Presentation Mode
- - -
-
-
+
{{end}} -
-
-
-
Line Width
- -
- - -
Width of the lines in the timeseries plots.
-
-

- -

- -
-
-
-
-
Plots per Row
- -
- - -
How many plots to show next to each other on pages such as /monitoring/job/, /monitoring/system/...
-
-

- -

- -
-
-
-
-
Colored Backgrounds
- -
-
- - -
-
- - -
-
-

- -

- -
-
-
-
-
-
-
Colorscheme for Timeseries Plots
- - - - -
-

- -

- -
-
-
- +{{define "stylesheets"}} + +{{end}} +{{define "javascript"}} + + {{end}} \ No newline at end of file diff --git a/web/web.go b/web/web.go index 0afa549..25bbc68 100644 --- a/web/web.go +++ b/web/web.go @@ -56,6 +56,7 @@ func init() { type User struct { Username string // Username of the currently logged in user IsAdmin bool + IsSupporter bool } type Page struct {