mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-11-04 01:25:06 +01:00 
			
		
		
		
	Merge branch 'master' of github.com:ClusterCockpit/cc-backend
This commit is contained in:
		
							
								
								
									
										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
									
									
									
									
									
								
							 Submodule frontend updated: 42f56f0a0a...cb0447516b
									
								
							
							
								
								
									
										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())
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user