mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-26 13:29:05 +01:00
Merge branch 'master' of github.com:ClusterCockpit/cc-backend
This commit is contained in:
commit
d1d6520ab5
23
auth/ldap.go
23
auth/ldap.go
@ -1,7 +1,6 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@ -13,15 +12,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type LdapConfig struct {
|
type LdapConfig struct {
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
UserBase string `json:"user_base"`
|
UserBase string `json:"user_base"`
|
||||||
SearchDN string `json:"search_dn"`
|
SearchDN string `json:"search_dn"`
|
||||||
UserBind string `json:"user_bind"`
|
UserBind string `json:"user_bind"`
|
||||||
UserFilter string `json:"user_filter"`
|
UserFilter string `json:"user_filter"`
|
||||||
TLS bool `json:"tls"`
|
SyncInterval string `json:"sync_interval"` // Parsed using time.ParseDuration.
|
||||||
|
|
||||||
// Parsed using time.ParseDuration.
|
|
||||||
SyncInterval string `json:"sync_interval"`
|
|
||||||
SyncDelOldUsers bool `json:"sync_del_old_users"`
|
SyncDelOldUsers bool `json:"sync_del_old_users"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,13 +60,6 @@ func (auth *Authentication) getLdapConnection(admin bool) (*ldap.Conn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if auth.ldapConfig.TLS {
|
|
||||||
if err := conn.StartTLS(&tls.Config{InsecureSkipVerify: true}); err != nil {
|
|
||||||
conn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if admin {
|
if admin {
|
||||||
if err := conn.Bind(auth.ldapConfig.SearchDN, auth.ldapSyncUserPassword); err != nil {
|
if err := conn.Bind(auth.ldapConfig.SearchDN, auth.ldapSyncUserPassword); err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
|
2
frontend
2
frontend
@ -1 +1 @@
|
|||||||
Subproject commit 42f56f0a0a162bec0871949cade791590bea6a89
|
Subproject commit cb0447516b5102b7869eb6068df3de08b7736caf
|
21
routes.go
21
routes.go
@ -86,27 +86,20 @@ func setupRoutes(router *mux.Router, routes []Route) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
infos := map[string]interface{}{
|
infos := route.Setup(map[string]interface{}{}, r)
|
||||||
"admin": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
if user := auth.GetUser(r.Context()); user != nil {
|
|
||||||
infos["loginId"] = user.Username
|
|
||||||
infos["admin"] = user.HasRole(auth.RoleAdmin)
|
|
||||||
} else {
|
|
||||||
infos["loginId"] = false
|
|
||||||
infos["admin"] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
infos = route.Setup(infos, r)
|
|
||||||
if id, ok := infos["id"]; ok {
|
if id, ok := infos["id"]; ok {
|
||||||
route.Title = strings.Replace(route.Title, "<ID>", id.(string), 1)
|
route.Title = strings.Replace(route.Title, "<ID>", id.(string), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
infos["clusters"] = config.Clusters
|
username, isAdmin := "", true
|
||||||
|
if user := auth.GetUser(r.Context()); user != nil {
|
||||||
|
username = user.Username
|
||||||
|
isAdmin = user.HasRole(auth.RoleAdmin)
|
||||||
|
}
|
||||||
|
|
||||||
page := templates.Page{
|
page := templates.Page{
|
||||||
Title: route.Title,
|
Title: route.Title,
|
||||||
|
User: templates.User{Username: username, IsAdmin: isAdmin},
|
||||||
Config: conf,
|
Config: conf,
|
||||||
Infos: infos,
|
Infos: infos,
|
||||||
}
|
}
|
||||||
|
35
server.go
35
server.go
@ -95,15 +95,9 @@ var programConfig ProgramConfig = ProgramConfig{
|
|||||||
JobArchive: "./var/job-archive",
|
JobArchive: "./var/job-archive",
|
||||||
AsyncArchiving: true,
|
AsyncArchiving: true,
|
||||||
DisableArchive: false,
|
DisableArchive: false,
|
||||||
LdapConfig: &auth.LdapConfig{
|
LdapConfig: nil,
|
||||||
Url: "ldap://localhost",
|
HttpsCertFile: "",
|
||||||
UserBase: "ou=hpc,dc=rrze,dc=uni-erlangen,dc=de",
|
HttpsKeyFile: "",
|
||||||
SearchDN: "cn=admin,dc=rrze,dc=uni-erlangen,dc=de",
|
|
||||||
UserBind: "uid={username},ou=hpc,dc=rrze,dc=uni-erlangen,dc=de",
|
|
||||||
UserFilter: "(&(objectclass=posixAccount)(uid=*))",
|
|
||||||
},
|
|
||||||
HttpsCertFile: "",
|
|
||||||
HttpsKeyFile: "",
|
|
||||||
UiDefaults: map[string]interface{}{
|
UiDefaults: map[string]interface{}{
|
||||||
"analysis_view_histogramMetrics": []string{"flops_any", "mem_bw", "mem_used"},
|
"analysis_view_histogramMetrics": []string{"flops_any", "mem_bw", "mem_used"},
|
||||||
"analysis_view_scatterPlotMetrics": [][]string{{"flops_any", "mem_bw"}, {"flops_any", "cpu_load"}, {"cpu_load", "mem_bw"}},
|
"analysis_view_scatterPlotMetrics": [][]string{{"flops_any", "mem_bw"}, {"flops_any", "cpu_load"}, {"cpu_load", "mem_bw"}},
|
||||||
@ -401,6 +395,29 @@ func main() {
|
|||||||
}
|
}
|
||||||
secured.Handle("/query", graphQLEndpoint)
|
secured.Handle("/query", graphQLEndpoint)
|
||||||
|
|
||||||
|
secured.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
conf, err := config.GetUIConfig(r)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username, isAdmin := "", true
|
||||||
|
if user := auth.GetUser(r.Context()); user != nil {
|
||||||
|
username = user.Username
|
||||||
|
isAdmin = user.HasRole(auth.RoleAdmin)
|
||||||
|
}
|
||||||
|
|
||||||
|
templates.Render(rw, r, "home.tmpl", &templates.Page{
|
||||||
|
Title: "ClusterCockpit",
|
||||||
|
User: templates.User{Username: username, IsAdmin: isAdmin},
|
||||||
|
Config: conf,
|
||||||
|
Infos: map[string]interface{}{
|
||||||
|
"clusters": config.Clusters,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
secured.HandleFunc("/search", func(rw http.ResponseWriter, r *http.Request) {
|
secured.HandleFunc("/search", func(rw http.ResponseWriter, r *http.Request) {
|
||||||
if search := r.URL.Query().Get("searchId"); search != "" {
|
if search := r.URL.Query().Get("searchId"); search != "" {
|
||||||
job, username, err := api.JobRepository.FindJobOrUser(r.Context(), search)
|
job, username, err := api.JobRepository.FindJobOrUser(r.Context(), search)
|
||||||
|
@ -13,101 +13,16 @@
|
|||||||
|
|
||||||
{{block "stylesheets" .}}
|
{{block "stylesheets" .}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
<script>
|
||||||
|
const header = {
|
||||||
|
"username": "{{ .User.Username }}",
|
||||||
|
"isAdmin": "{{ .User.IsAdmin }}",
|
||||||
|
"clusters": {{ .Clusters }},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body class="Site">
|
<body class="Site">
|
||||||
<header>
|
<header id="svelte-header"></header>
|
||||||
<nav class="navbar navbar-expand-lg navbar-light fixed-top bg-light">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<a class="navbar-brand" href="/">
|
|
||||||
{{block "brand" .}}
|
|
||||||
<img alt="ClusterCockpit Logo" src="/img/logo.png" class="d-inline-block align-top">
|
|
||||||
{{end}}
|
|
||||||
</a>
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
||||||
{{block "navigation" .}}
|
|
||||||
<ul class="navbar-nav mr-auto">
|
|
||||||
{{block "navitem_joblist" .}}
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link fs-5" href="/monitoring/jobs/">
|
|
||||||
<span class="cc-nav-text">Joblist</span>
|
|
||||||
<i class="bi-card-list"></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{{end}}
|
|
||||||
{{if .Infos.admin }}
|
|
||||||
{{block "navitem_analysis" .}}
|
|
||||||
<li class="nav-item ">
|
|
||||||
<a class="nav-link disabled fs-5" href="/monitoring/analysis/">
|
|
||||||
<span class="cc-nav-text">Analysis</span>
|
|
||||||
<i class="bi-graph-up"></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{{end}}
|
|
||||||
{{block "navitem_systems" .}}
|
|
||||||
<li class="nav-item ">
|
|
||||||
<a class="nav-link disabled fs-5" href="/monitoring/systems/">
|
|
||||||
<span class="cc-nav-text">Systems</span>
|
|
||||||
<i class="bi-graph-up"></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{{end}}
|
|
||||||
{{block "navitem_users" .}}
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link fs-5" href="/monitoring/users/">
|
|
||||||
<span class="cc-nav-text">Users</span>
|
|
||||||
<i class="bi-people-fill"></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{{end}}
|
|
||||||
{{block "navitem_tags" .}}
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link fs-5" href="/monitoring/tags/">
|
|
||||||
<span class="cc-nav-text">Tags</span>
|
|
||||||
<i class="bi-tag-fill"></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{{end}}
|
|
||||||
{{else}}
|
|
||||||
{{block "navitem_stats" .}}
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link fs-5" href="/monitoring/user/admin">
|
|
||||||
<span class="cc-nav-text">Statistics</span>
|
|
||||||
<i class="bi-bar-chart-line-fill"></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{{if .Infos.loginId }}
|
|
||||||
<div class="d-flex align-items-end">
|
|
||||||
<ul class="navbar-nav ml-auto">
|
|
||||||
<li class="nav-item">
|
|
||||||
<form method="post" action="/logout">
|
|
||||||
<button type="submit" class="btn btn-link nav-link fs-5">
|
|
||||||
<span class="cc-nav-text">{{ .Infos.loginId }} Logout</span>
|
|
||||||
<i class="bi-box-arrow-right"></i>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<form class="d-flex my-0" onsubmit="this.action='/search';">
|
|
||||||
{{if .Infos.admin }}
|
|
||||||
<input class="form-control me-2" type="search" name="searchId" placeholder="jobId / userId" id="searchId" aria-label="Search">
|
|
||||||
{{else}}
|
|
||||||
<input class="form-control me-2" type="search" name="searchId" placeholder="jobId" id="searchId" aria-label="Search">
|
|
||||||
{{end}}
|
|
||||||
<button class="btn btn-outline-success fs-6" type="submit">Search</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="Site-content">
|
<main class="Site-content">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -127,6 +42,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{block "javascript" .}}
|
{{block "javascript" .}}
|
||||||
|
<script src='/build/header.js'></script>
|
||||||
{{end}}
|
{{end}}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-8">
|
<div class="col">
|
||||||
<h2>Clusters</h2>
|
<h2>Clusters</h2>
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -38,5 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
{{define "javascript"}}
|
{{define "javascript"}}
|
||||||
<script>
|
<script>
|
||||||
|
header.currentView = 'analysis';
|
||||||
const cluster = {{ .Infos.cluster }};
|
const cluster = {{ .Infos.cluster }};
|
||||||
const filterPresets = {{ .FilterPresets }};
|
const filterPresets = {{ .FilterPresets }};
|
||||||
const clusterCockpitConfig = {{ .Config }};
|
const clusterCockpitConfig = {{ .Config }};
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
{{define "javascript"}}
|
{{define "javascript"}}
|
||||||
<script>
|
<script>
|
||||||
|
header.currentView = 'job';
|
||||||
const jobInfos = {
|
const jobInfos = {
|
||||||
id: "{{ .Infos.id }}",
|
id: "{{ .Infos.id }}",
|
||||||
jobId: "{{ .Infos.jobId }}",
|
jobId: "{{ .Infos.jobId }}",
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
{{define "javascript"}}
|
{{define "javascript"}}
|
||||||
<script>
|
<script>
|
||||||
|
header.currentView = 'jobs';
|
||||||
const filterPresets = {{ .FilterPresets }};
|
const filterPresets = {{ .FilterPresets }};
|
||||||
const clusterCockpitConfig = {{ .Config }};
|
const clusterCockpitConfig = {{ .Config }};
|
||||||
</script>
|
</script>
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
{{define "javascript"}}
|
{{define "javascript"}}
|
||||||
<script>
|
<script>
|
||||||
|
header.currentView = {{ .Infos.listType }}.toLowerCase() + 's';
|
||||||
const listType = {{ .Infos.listType }};
|
const listType = {{ .Infos.listType }};
|
||||||
const filterPresets = {{ .FilterPresets }};
|
const filterPresets = {{ .FilterPresets }};
|
||||||
const clusterCockpitConfig = {{ .Config }};
|
const clusterCockpitConfig = {{ .Config }};
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
{{define "javascript"}}
|
{{define "javascript"}}
|
||||||
<script>
|
<script>
|
||||||
|
header.currentView = 'node';
|
||||||
const infos = {{ .Infos }};
|
const infos = {{ .Infos }};
|
||||||
const clusterCockpitConfig = {{ .Config }};
|
const clusterCockpitConfig = {{ .Config }};
|
||||||
</script>
|
</script>
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
{{define "javascript"}}
|
{{define "javascript"}}
|
||||||
<script>
|
<script>
|
||||||
|
header.currentView = 'system';
|
||||||
const infos = {{ .Infos }};
|
const infos = {{ .Infos }};
|
||||||
const clusterCockpitConfig = {{ .Config }};
|
const clusterCockpitConfig = {{ .Config }};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
{{define "javascript"}}
|
||||||
|
<script>
|
||||||
|
header.currentView = 'tags';
|
||||||
|
</script>
|
||||||
|
<script src='/build/header.js'></script>
|
||||||
|
{{end}}
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
{{define "javascript"}}
|
{{define "javascript"}}
|
||||||
<script>
|
<script>
|
||||||
|
header.currentView = 'user';
|
||||||
const userInfos = {{ .Infos }};
|
const userInfos = {{ .Infos }};
|
||||||
const filterPresets = {{ .FilterPresets }};
|
const filterPresets = {{ .FilterPresets }};
|
||||||
const clusterCockpitConfig = {{ .Config }};
|
const clusterCockpitConfig = {{ .Config }};
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/ClusterCockpit/cc-backend/config"
|
||||||
"github.com/ClusterCockpit/cc-backend/log"
|
"github.com/ClusterCockpit/cc-backend/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -12,13 +13,20 @@ var templatesDir string
|
|||||||
var debugMode bool = os.Getenv("DEBUG") == "1"
|
var debugMode bool = os.Getenv("DEBUG") == "1"
|
||||||
var templates map[string]*template.Template = map[string]*template.Template{}
|
var templates map[string]*template.Template = map[string]*template.Template{}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Username string // Username of the currently logged in user
|
||||||
|
IsAdmin bool
|
||||||
|
}
|
||||||
|
|
||||||
type Page struct {
|
type Page struct {
|
||||||
Title string
|
Title string // Page title
|
||||||
Error string
|
Error string // For generic use (e.g. the exact error message on /login)
|
||||||
Info string
|
Info string // For generic use (e.g. "Logout successfull" on /login)
|
||||||
FilterPresets map[string]interface{}
|
User User // Information about the currently logged in user
|
||||||
Infos map[string]interface{}
|
Clusters []string // List of all clusters for use in the Header
|
||||||
Config map[string]interface{}
|
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/<id>, job id for /monitoring/job/<id>)
|
||||||
|
Config map[string]interface{} // UI settings for the currently logged in user (e.g. line width, ...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -58,6 +66,12 @@ func Render(rw http.ResponseWriter, r *http.Request, file string, page *Page) {
|
|||||||
t = template.Must(template.ParseFiles(templatesDir+"base.tmpl", templatesDir+file))
|
t = template.Must(template.ParseFiles(templatesDir+"base.tmpl", templatesDir+file))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if page.Clusters == nil {
|
||||||
|
for _, c := range config.Clusters {
|
||||||
|
page.Clusters = append(page.Clusters, c.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := t.Execute(rw, page); err != nil {
|
if err := t.Execute(rw, page); err != nil {
|
||||||
log.Errorf("template error: %s", err.Error())
|
log.Errorf("template error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user