mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-11-10 08:57:25 +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
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
@ -13,15 +12,12 @@ import (
|
||||
)
|
||||
|
||||
type LdapConfig struct {
|
||||
Url string `json:"url"`
|
||||
UserBase string `json:"user_base"`
|
||||
SearchDN string `json:"search_dn"`
|
||||
UserBind string `json:"user_bind"`
|
||||
UserFilter string `json:"user_filter"`
|
||||
TLS bool `json:"tls"`
|
||||
|
||||
// Parsed using time.ParseDuration.
|
||||
SyncInterval string `json:"sync_interval"`
|
||||
Url string `json:"url"`
|
||||
UserBase string `json:"user_base"`
|
||||
SearchDN string `json:"search_dn"`
|
||||
UserBind string `json:"user_bind"`
|
||||
UserFilter string `json:"user_filter"`
|
||||
SyncInterval string `json:"sync_interval"` // Parsed using time.ParseDuration.
|
||||
SyncDelOldUsers bool `json:"sync_del_old_users"`
|
||||
}
|
||||
|
||||
@ -64,13 +60,6 @@ func (auth *Authentication) getLdapConnection(admin bool) (*ldap.Conn, error) {
|
||||
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 err := conn.Bind(auth.ldapConfig.SearchDN, auth.ldapSyncUserPassword); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
infos := map[string]interface{}{
|
||||
"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)
|
||||
infos := route.Setup(map[string]interface{}{}, r)
|
||||
if id, ok := infos["id"]; ok {
|
||||
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{
|
||||
Title: route.Title,
|
||||
User: templates.User{Username: username, IsAdmin: isAdmin},
|
||||
Config: conf,
|
||||
Infos: infos,
|
||||
}
|
||||
|
35
server.go
35
server.go
@ -95,15 +95,9 @@ var programConfig ProgramConfig = ProgramConfig{
|
||||
JobArchive: "./var/job-archive",
|
||||
AsyncArchiving: true,
|
||||
DisableArchive: false,
|
||||
LdapConfig: &auth.LdapConfig{
|
||||
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: "",
|
||||
HttpsKeyFile: "",
|
||||
LdapConfig: nil,
|
||||
HttpsCertFile: "",
|
||||
HttpsKeyFile: "",
|
||||
UiDefaults: map[string]interface{}{
|
||||
"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"}},
|
||||
@ -401,6 +395,29 @@ func main() {
|
||||
}
|
||||
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) {
|
||||
if search := r.URL.Query().Get("searchId"); search != "" {
|
||||
job, username, err := api.JobRepository.FindJobOrUser(r.Context(), search)
|
||||
|
@ -13,101 +13,16 @@
|
||||
|
||||
{{block "stylesheets" .}}
|
||||
{{end}}
|
||||
<script>
|
||||
const header = {
|
||||
"username": "{{ .User.Username }}",
|
||||
"isAdmin": "{{ .User.IsAdmin }}",
|
||||
"clusters": {{ .Clusters }},
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body class="Site">
|
||||
<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>
|
||||
<header id="svelte-header"></header>
|
||||
|
||||
<main class="Site-content">
|
||||
<div class="container">
|
||||
@ -127,6 +42,7 @@
|
||||
{{end}}
|
||||
|
||||
{{block "javascript" .}}
|
||||
<script src='/build/header.js'></script>
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{{define "content"}}
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<div class="col">
|
||||
<h2>Clusters</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
|
@ -38,5 +38,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
@ -7,6 +7,7 @@
|
||||
{{end}}
|
||||
{{define "javascript"}}
|
||||
<script>
|
||||
header.currentView = 'analysis';
|
||||
const cluster = {{ .Infos.cluster }};
|
||||
const filterPresets = {{ .FilterPresets }};
|
||||
const clusterCockpitConfig = {{ .Config }};
|
||||
|
@ -7,6 +7,7 @@
|
||||
{{end}}
|
||||
{{define "javascript"}}
|
||||
<script>
|
||||
header.currentView = 'job';
|
||||
const jobInfos = {
|
||||
id: "{{ .Infos.id }}",
|
||||
jobId: "{{ .Infos.jobId }}",
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
{{define "javascript"}}
|
||||
<script>
|
||||
header.currentView = 'jobs';
|
||||
const filterPresets = {{ .FilterPresets }};
|
||||
const clusterCockpitConfig = {{ .Config }};
|
||||
</script>
|
||||
|
@ -7,6 +7,7 @@
|
||||
{{end}}
|
||||
{{define "javascript"}}
|
||||
<script>
|
||||
header.currentView = {{ .Infos.listType }}.toLowerCase() + 's';
|
||||
const listType = {{ .Infos.listType }};
|
||||
const filterPresets = {{ .FilterPresets }};
|
||||
const clusterCockpitConfig = {{ .Config }};
|
||||
|
@ -7,6 +7,7 @@
|
||||
{{end}}
|
||||
{{define "javascript"}}
|
||||
<script>
|
||||
header.currentView = 'node';
|
||||
const infos = {{ .Infos }};
|
||||
const clusterCockpitConfig = {{ .Config }};
|
||||
</script>
|
||||
|
@ -7,6 +7,7 @@
|
||||
{{end}}
|
||||
{{define "javascript"}}
|
||||
<script>
|
||||
header.currentView = 'system';
|
||||
const infos = {{ .Infos }};
|
||||
const clusterCockpitConfig = {{ .Config }};
|
||||
</script>
|
||||
|
@ -1,3 +1,9 @@
|
||||
{{define "javascript"}}
|
||||
<script>
|
||||
header.currentView = 'tags';
|
||||
</script>
|
||||
<script src='/build/header.js'></script>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
|
@ -7,6 +7,7 @@
|
||||
{{end}}
|
||||
{{define "javascript"}}
|
||||
<script>
|
||||
header.currentView = 'user';
|
||||
const userInfos = {{ .Infos }};
|
||||
const filterPresets = {{ .FilterPresets }};
|
||||
const clusterCockpitConfig = {{ .Config }};
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/ClusterCockpit/cc-backend/config"
|
||||
"github.com/ClusterCockpit/cc-backend/log"
|
||||
)
|
||||
|
||||
@ -12,13 +13,20 @@ var templatesDir string
|
||||
var debugMode bool = os.Getenv("DEBUG") == "1"
|
||||
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 {
|
||||
Title string
|
||||
Error string
|
||||
Info string
|
||||
FilterPresets map[string]interface{}
|
||||
Infos map[string]interface{}
|
||||
Config map[string]interface{}
|
||||
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
|
||||
Clusters []string // 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/<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() {
|
||||
@ -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))
|
||||
}
|
||||
|
||||
if page.Clusters == nil {
|
||||
for _, c := range config.Clusters {
|
||||
page.Clusters = append(page.Clusters, c.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if err := t.Execute(rw, page); err != nil {
|
||||
log.Errorf("template error: %s", err.Error())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user