Merge branch 'master' of github.com:ClusterCockpit/cc-backend

This commit is contained in:
Jan Eitzinger 2022-02-15 10:12:36 +01:00
commit d1d6520ab5
16 changed files with 85 additions and 141 deletions

View File

@ -1,7 +1,6 @@
package auth package auth
import ( import (
"crypto/tls"
"errors" "errors"
"os" "os"
"strings" "strings"
@ -18,10 +17,7 @@ type LdapConfig struct {
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()

@ -1 +1 @@
Subproject commit 42f56f0a0a162bec0871949cade791590bea6a89 Subproject commit cb0447516b5102b7869eb6068df3de08b7736caf

View File

@ -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,
} }

View File

@ -95,13 +95,7 @@ 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",
UserBase: "ou=hpc,dc=rrze,dc=uni-erlangen,dc=de",
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: "", HttpsCertFile: "",
HttpsKeyFile: "", HttpsKeyFile: "",
UiDefaults: map[string]interface{}{ UiDefaults: map[string]interface{}{
@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -38,5 +38,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
</section> </section>
{{end}} {{end}}

View File

@ -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 }};

View File

@ -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 }}",

View File

@ -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>

View File

@ -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 }};

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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 }};

View File

@ -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())
} }