mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-11-04 01:25:06 +01:00 
			
		
		
		
	add cli option for generating a JWT; simplify templates
This commit is contained in:
		
							
								
								
									
										33
									
								
								api/rest.go
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										33
									
								
								api/rest.go
									
									
									
									
									
								
							@@ -9,7 +9,6 @@ import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/ClusterCockpit/cc-jobarchive/config"
 | 
			
		||||
	"github.com/ClusterCockpit/cc-jobarchive/graph"
 | 
			
		||||
@@ -28,15 +27,18 @@ type RestApi struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api *RestApi) MountRoutes(r *mux.Router) {
 | 
			
		||||
	r.HandleFunc("/api/jobs/start_job/", api.startJob).Methods(http.MethodPost, http.MethodPut)
 | 
			
		||||
	r.HandleFunc("/api/jobs/stop_job/", api.stopJob).Methods(http.MethodPost, http.MethodPut)
 | 
			
		||||
	r.HandleFunc("/api/jobs/stop_job/{id}", api.stopJob).Methods(http.MethodPost, http.MethodPut)
 | 
			
		||||
	r = r.PathPrefix("/api").Subrouter()
 | 
			
		||||
	r.StrictSlash(true)
 | 
			
		||||
 | 
			
		||||
	r.HandleFunc("/api/jobs/{id}", api.getJob).Methods(http.MethodGet)
 | 
			
		||||
	r.HandleFunc("/api/jobs/tag_job/{id}", api.tagJob).Methods(http.MethodPost, http.MethodPatch)
 | 
			
		||||
	r.HandleFunc("/jobs/start_job/", api.startJob).Methods(http.MethodPost, http.MethodPut)
 | 
			
		||||
	r.HandleFunc("/jobs/stop_job/", api.stopJob).Methods(http.MethodPost, http.MethodPut)
 | 
			
		||||
	r.HandleFunc("/jobs/stop_job/{id}", api.stopJob).Methods(http.MethodPost, http.MethodPut)
 | 
			
		||||
 | 
			
		||||
	r.HandleFunc("/api/machine_state/{cluster}/{host}", api.getMachineState).Methods(http.MethodGet)
 | 
			
		||||
	r.HandleFunc("/api/machine_state/{cluster}/{host}", api.putMachineState).Methods(http.MethodPut, http.MethodPost)
 | 
			
		||||
	r.HandleFunc("/jobs/{id}", api.getJob).Methods(http.MethodGet)
 | 
			
		||||
	r.HandleFunc("/jobs/tag_job/{id}", api.tagJob).Methods(http.MethodPost, http.MethodPatch)
 | 
			
		||||
 | 
			
		||||
	r.HandleFunc("/machine_state/{cluster}/{host}", api.getMachineState).Methods(http.MethodGet)
 | 
			
		||||
	r.HandleFunc("/machine_state/{cluster}/{host}", api.putMachineState).Methods(http.MethodPut, http.MethodPost)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type StartJobApiRespone struct {
 | 
			
		||||
@@ -158,17 +160,18 @@ func (api *RestApi) startJob(rw http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	job := schema.Job{
 | 
			
		||||
		BaseJob:   req.BaseJob,
 | 
			
		||||
		StartTime: time.Unix(req.StartTime, 0),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	job.RawResources, err = json.Marshal(req.Resources)
 | 
			
		||||
	req.RawResources, err = json.Marshal(req.Resources)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res, err := api.DB.NamedExec(schema.JobInsertStmt, job)
 | 
			
		||||
	res, err := api.DB.NamedExec(`INSERT INTO job (
 | 
			
		||||
		job_id, user, project, cluster, partition, array_job_id, num_nodes, num_hwthreads, num_acc,
 | 
			
		||||
		exclusive, monitoring_status, smt, job_state, start_time, duration, resources, meta_data
 | 
			
		||||
	) VALUES (
 | 
			
		||||
		:job_id, :user, :project, :cluster, :partition, :array_job_id, :num_nodes, :num_hwthreads, :num_acc,
 | 
			
		||||
		:exclusive, :monitoring_status, :smt, :job_state, :start_time, :duration, :resources, :meta_data
 | 
			
		||||
	);`, req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		http.Error(rw, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								auth/auth.go
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										22
									
								
								auth/auth.go
									
									
									
									
									
								
							@@ -27,6 +27,7 @@ type User struct {
 | 
			
		||||
	Password  string
 | 
			
		||||
	Name      string
 | 
			
		||||
	IsAdmin   bool
 | 
			
		||||
	IsAPIUser bool
 | 
			
		||||
	ViaLdap   bool
 | 
			
		||||
	Email     string
 | 
			
		||||
}
 | 
			
		||||
@@ -110,6 +111,9 @@ func AddUserToDB(db *sqlx.DB, arg string) error {
 | 
			
		||||
	if parts[1] == "admin" {
 | 
			
		||||
		roles = "[\"ROLE_ADMIN\"]"
 | 
			
		||||
	}
 | 
			
		||||
	if parts[1] == "api" {
 | 
			
		||||
		roles = "[\"ROLE_API\"]"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = sq.Insert("user").Columns("username", "password", "roles").Values(parts[0], string(password), roles).RunWith(db).Exec()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -124,7 +128,7 @@ func DelUserFromDB(db *sqlx.DB, username string) error {
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fetchUserFromDB(db *sqlx.DB, username string) (*User, error) {
 | 
			
		||||
func FetchUserFromDB(db *sqlx.DB, username string) (*User, error) {
 | 
			
		||||
	user := &User{Username: username}
 | 
			
		||||
	var hashedPassword, name, rawRoles, email sql.NullString
 | 
			
		||||
	if err := sq.Select("password", "ldap", "name", "roles", "email").From("user").
 | 
			
		||||
@@ -141,8 +145,11 @@ func fetchUserFromDB(db *sqlx.DB, username string) (*User, error) {
 | 
			
		||||
		json.Unmarshal([]byte(rawRoles.String), &roles)
 | 
			
		||||
	}
 | 
			
		||||
	for _, role := range roles {
 | 
			
		||||
		if role == "ROLE_ADMIN" {
 | 
			
		||||
		switch role {
 | 
			
		||||
		case "ROLE_ADMIN":
 | 
			
		||||
			user.IsAdmin = true
 | 
			
		||||
		case "ROLE_API":
 | 
			
		||||
			user.IsAPIUser = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -154,7 +161,7 @@ func fetchUserFromDB(db *sqlx.DB, username string) (*User, error) {
 | 
			
		||||
func Login(db *sqlx.DB) http.Handler {
 | 
			
		||||
	return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		username, password := r.FormValue("username"), r.FormValue("password")
 | 
			
		||||
		user, err := fetchUserFromDB(db, username)
 | 
			
		||||
		user, err := FetchUserFromDB(db, username)
 | 
			
		||||
		if err == nil && user.ViaLdap && ldapAuthEnabled {
 | 
			
		||||
			err = loginViaLdap(user, password)
 | 
			
		||||
		} else if err == nil && !user.ViaLdap && user.Password != "" {
 | 
			
		||||
@@ -168,7 +175,7 @@ func Login(db *sqlx.DB) http.Handler {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Printf("login failed: %s\n", err.Error())
 | 
			
		||||
			rw.WriteHeader(http.StatusUnauthorized)
 | 
			
		||||
			templates.Render(rw, r, "login", &templates.Page{
 | 
			
		||||
			templates.Render(rw, r, "login.html", &templates.Page{
 | 
			
		||||
				Title: "Login failed",
 | 
			
		||||
				Login: &templates.LoginPage{
 | 
			
		||||
					Error: "Username or password incorrect",
 | 
			
		||||
@@ -231,9 +238,11 @@ func authViaToken(r *http.Request) (*User, error) {
 | 
			
		||||
	claims := token.Claims.(jwt.MapClaims)
 | 
			
		||||
	sub, _ := claims["sub"].(string)
 | 
			
		||||
	isAdmin, _ := claims["is_admin"].(bool)
 | 
			
		||||
	isAPIUser, _ := claims["is_api"].(bool)
 | 
			
		||||
	return &User{
 | 
			
		||||
		Username:  sub,
 | 
			
		||||
		IsAdmin:   isAdmin,
 | 
			
		||||
		IsAPIUser: isAPIUser,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -264,7 +273,7 @@ func Auth(next http.Handler) http.Handler {
 | 
			
		||||
			log.Printf("authentication failed: no session or jwt found\n")
 | 
			
		||||
 | 
			
		||||
			rw.WriteHeader(http.StatusUnauthorized)
 | 
			
		||||
			templates.Render(rw, r, "login", &templates.Page{
 | 
			
		||||
			templates.Render(rw, r, "login.html", &templates.Page{
 | 
			
		||||
				Title: "Authentication failed",
 | 
			
		||||
				Login: &templates.LoginPage{
 | 
			
		||||
					Error: "No valid session or JWT provided",
 | 
			
		||||
@@ -290,6 +299,7 @@ func ProvideJWT(user *User) (string, error) {
 | 
			
		||||
	tok := jwt.NewWithClaims(jwt.SigningMethodEdDSA, jwt.MapClaims{
 | 
			
		||||
		"sub":      user.Username,
 | 
			
		||||
		"is_admin": user.IsAdmin,
 | 
			
		||||
		"is_api":   user.IsAPIUser,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return tok.SignedString(JwtPrivateKey)
 | 
			
		||||
@@ -320,7 +330,7 @@ func Logout(rw http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	templates.Render(rw, r, "login", &templates.Page{
 | 
			
		||||
	templates.Render(rw, r, "login.html", &templates.Page{
 | 
			
		||||
		Title: "Logout successful",
 | 
			
		||||
		Login: &templates.LoginPage{
 | 
			
		||||
			Info: "Logout successful",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								go.sum
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										6
									
								
								go.sum
									
									
									
									
									
								
							@@ -12,6 +12,7 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNg
 | 
			
		||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
 | 
			
		||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
 | 
			
		||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
 | 
			
		||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
 | 
			
		||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
			
		||||
@@ -68,14 +69,17 @@ github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 h1:zCoDWFD5
 | 
			
		||||
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 | 
			
		||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
 | 
			
		||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 | 
			
		||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 | 
			
		||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
 | 
			
		||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
 | 
			
		||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 | 
			
		||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
 | 
			
		||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 | 
			
		||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
 | 
			
		||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
 | 
			
		||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 | 
			
		||||
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
@@ -84,6 +88,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
 | 
			
		||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 | 
			
		||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 | 
			
		||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 | 
			
		||||
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
 | 
			
		||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
 | 
			
		||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
 | 
			
		||||
github.com/vektah/gqlparser/v2 v2.1.0 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns=
 | 
			
		||||
@@ -112,6 +117,7 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM=
 | 
			
		||||
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										57
									
								
								server.go
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										57
									
								
								server.go
									
									
									
									
									
								
							@@ -106,13 +106,14 @@ var programConfig ProgramConfig = ProgramConfig{
 | 
			
		||||
func main() {
 | 
			
		||||
	var flagReinitDB, flagStopImmediately, flagSyncLDAP bool
 | 
			
		||||
	var flagConfigFile string
 | 
			
		||||
	var flagNewUser, flagDelUser string
 | 
			
		||||
	var flagNewUser, flagDelUser, flagGenJWT string
 | 
			
		||||
	flag.BoolVar(&flagReinitDB, "init-db", false, "Go through job-archive and re-initialize `job`, `tag`, and `jobtag` tables")
 | 
			
		||||
	flag.BoolVar(&flagSyncLDAP, "sync-ldap", false, "Sync the `user` table with ldap")
 | 
			
		||||
	flag.BoolVar(&flagStopImmediately, "no-server", false, "Do not start a server, stop right after initialization and argument handling")
 | 
			
		||||
	flag.StringVar(&flagConfigFile, "config", "", "Location of the config file for this server (overwrites the defaults)")
 | 
			
		||||
	flag.StringVar(&flagNewUser, "add-user", "", "Add a new user. Argument format: `<username>:[admin]:<password>`")
 | 
			
		||||
	flag.StringVar(&flagNewUser, "add-user", "", "Add a new user. Argument format: `<username>:[admin|api]:<password>`")
 | 
			
		||||
	flag.StringVar(&flagDelUser, "del-user", "", "Remove user by username")
 | 
			
		||||
	flag.StringVar(&flagGenJWT, "jwt", "", "Generate and print a JWT for the user specified by the username")
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	if flagConfigFile != "" {
 | 
			
		||||
@@ -156,6 +157,24 @@ func main() {
 | 
			
		||||
		if flagSyncLDAP {
 | 
			
		||||
			auth.SyncWithLDAP(db)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if flagGenJWT != "" {
 | 
			
		||||
			user, err := auth.FetchUserFromDB(db, flagGenJWT)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if !user.IsAPIUser {
 | 
			
		||||
				log.Println("warning: that user does not have the API role")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			jwt, err := auth.ProvideJWT(user)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			fmt.Printf("JWT for '%s': %s\n", user.Username, jwt)
 | 
			
		||||
		}
 | 
			
		||||
	} else if flagNewUser != "" || flagDelUser != "" {
 | 
			
		||||
		log.Fatalln("arguments --add-user and --del-user can only be used if authentication is enabled")
 | 
			
		||||
	}
 | 
			
		||||
@@ -182,6 +201,18 @@ func main() {
 | 
			
		||||
 | 
			
		||||
	resolver := &graph.Resolver{DB: db}
 | 
			
		||||
	graphQLEndpoint := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: resolver}))
 | 
			
		||||
 | 
			
		||||
	// graphQLEndpoint.SetRecoverFunc(func(ctx context.Context, err interface{}) error {
 | 
			
		||||
	// 	switch e := err.(type) {
 | 
			
		||||
	// 	case string:
 | 
			
		||||
	// 		return fmt.Errorf("panic: %s", e)
 | 
			
		||||
	// 	case error:
 | 
			
		||||
	// 		return fmt.Errorf("panic caused by: %w", e)
 | 
			
		||||
	// 	}
 | 
			
		||||
 | 
			
		||||
	// 	return errors.New("internal server error (panic)")
 | 
			
		||||
	// })
 | 
			
		||||
 | 
			
		||||
	graphQLPlayground := playground.Handler("GraphQL playground", "/query")
 | 
			
		||||
	api := &api.RestApi{
 | 
			
		||||
		DB:              db,
 | 
			
		||||
@@ -191,7 +222,7 @@ func main() {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	handleGetLogin := func(rw http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		templates.Render(rw, r, "login", &templates.Page{
 | 
			
		||||
		templates.Render(rw, r, "login.html", &templates.Page{
 | 
			
		||||
			Title: "Login",
 | 
			
		||||
			Login: &templates.LoginPage{},
 | 
			
		||||
		})
 | 
			
		||||
@@ -199,7 +230,7 @@ func main() {
 | 
			
		||||
 | 
			
		||||
	r := mux.NewRouter()
 | 
			
		||||
	r.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		templates.Render(rw, r, "404", &templates.Page{
 | 
			
		||||
		templates.Render(rw, r, "404.html", &templates.Page{
 | 
			
		||||
			Title: "Not found",
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
@@ -215,8 +246,6 @@ func main() {
 | 
			
		||||
	}
 | 
			
		||||
	secured.Handle("/query", graphQLEndpoint)
 | 
			
		||||
 | 
			
		||||
	secured.HandleFunc("/config.json", config.ServeConfig).Methods(http.MethodGet)
 | 
			
		||||
 | 
			
		||||
	secured.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		conf, err := config.GetUIConfig(r)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@@ -235,7 +264,7 @@ func main() {
 | 
			
		||||
			infos["admin"] = user.IsAdmin
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		templates.Render(rw, r, "home", &templates.Page{
 | 
			
		||||
		templates.Render(rw, r, "home.html", &templates.Page{
 | 
			
		||||
			Title:  "ClusterCockpit",
 | 
			
		||||
			Config: conf,
 | 
			
		||||
			Infos:  infos,
 | 
			
		||||
@@ -297,7 +326,7 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		templates.Render(rw, r, "monitoring/jobs/", &templates.Page{
 | 
			
		||||
		templates.Render(rw, r, "monitoring/jobs.html", &templates.Page{
 | 
			
		||||
			Title:         "Jobs - ClusterCockpit",
 | 
			
		||||
			Config:        conf,
 | 
			
		||||
			FilterPresets: buildFilterPresets(r.URL.Query()),
 | 
			
		||||
@@ -318,7 +347,7 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		templates.Render(rw, r, "monitoring/job/", &templates.Page{
 | 
			
		||||
		templates.Render(rw, r, "monitoring/job.html", &templates.Page{
 | 
			
		||||
			Title:  fmt.Sprintf("Job %d - ClusterCockpit", job.JobID),
 | 
			
		||||
			Config: conf,
 | 
			
		||||
			Infos: map[string]interface{}{
 | 
			
		||||
@@ -336,7 +365,7 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		templates.Render(rw, r, "monitoring/users/", &templates.Page{
 | 
			
		||||
		templates.Render(rw, r, "monitoring/users.html", &templates.Page{
 | 
			
		||||
			Title:  "Users - ClusterCockpit",
 | 
			
		||||
			Config: conf,
 | 
			
		||||
		})
 | 
			
		||||
@@ -353,7 +382,7 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
 | 
			
		||||
		// TODO: One could check if the user exists, but that would be unhelpfull if authentication
 | 
			
		||||
		// is disabled or the user does not exist but has started jobs.
 | 
			
		||||
 | 
			
		||||
		templates.Render(rw, r, "monitoring/user/", &templates.Page{
 | 
			
		||||
		templates.Render(rw, r, "monitoring/user.html", &templates.Page{
 | 
			
		||||
			Title:         fmt.Sprintf("User %s - ClusterCockpit", id),
 | 
			
		||||
			Config:        conf,
 | 
			
		||||
			Infos:         map[string]interface{}{"username": id},
 | 
			
		||||
@@ -374,7 +403,7 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
 | 
			
		||||
			filterPresets["clusterId"] = query.Get("cluster")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		templates.Render(rw, r, "monitoring/analysis/", &templates.Page{
 | 
			
		||||
		templates.Render(rw, r, "monitoring/analysis.html", &templates.Page{
 | 
			
		||||
			Title:         "Analysis View - ClusterCockpit",
 | 
			
		||||
			Config:        conf,
 | 
			
		||||
			FilterPresets: filterPresets,
 | 
			
		||||
@@ -394,7 +423,7 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
 | 
			
		||||
			filterPresets["clusterId"] = query.Get("cluster")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		templates.Render(rw, r, "monitoring/systems/", &templates.Page{
 | 
			
		||||
		templates.Render(rw, r, "monitoring/systems.html", &templates.Page{
 | 
			
		||||
			Title:         "System View - ClusterCockpit",
 | 
			
		||||
			Config:        conf,
 | 
			
		||||
			FilterPresets: filterPresets,
 | 
			
		||||
@@ -409,7 +438,7 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		vars := mux.Vars(r)
 | 
			
		||||
		templates.Render(rw, r, "monitoring/node/", &templates.Page{
 | 
			
		||||
		templates.Render(rw, r, "monitoring/node.html", &templates.Page{
 | 
			
		||||
			Title:  fmt.Sprintf("Node %s - ClusterCockpit", vars["nodeId"]),
 | 
			
		||||
			Config: conf,
 | 
			
		||||
			Infos: map[string]interface{}{
 | 
			
		||||
 
 | 
			
		||||
@@ -8,13 +8,7 @@
 | 
			
		||||
{{define "javascript"}}
 | 
			
		||||
    <script>
 | 
			
		||||
        const filterPresets = {{ .FilterPresets }};
 | 
			
		||||
        const clusterCockpitConfigPromise = Promise.resolve({
 | 
			
		||||
            plot_general_colorscheme:     {{ .Config.plot_general_colorscheme     }},
 | 
			
		||||
            plot_general_lineWidth:       {{ .Config.plot_general_lineWidth       }},
 | 
			
		||||
            plot_general_colorBackground: {{ .Config.plot_general_colorBackground }},
 | 
			
		||||
            plot_list_selectedMetrics:    {{ .Config.plot_list_selectedMetrics    }},
 | 
			
		||||
            plot_list_jobsPerPage:        {{ .Config.plot_list_jobsPerPage        }}
 | 
			
		||||
        });
 | 
			
		||||
        const clusterCockpitConfig = {{ .Config }};
 | 
			
		||||
    </script>
 | 
			
		||||
    <script src='/build/jobs.js'></script>
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,16 +7,9 @@
 | 
			
		||||
{{end}}
 | 
			
		||||
{{define "javascript"}}
 | 
			
		||||
    <script>
 | 
			
		||||
        const userInfos = {
 | 
			
		||||
            userId: "{{ .Infos.userId }}"
 | 
			
		||||
        };
 | 
			
		||||
        const clusterCockpitConfigPromise = Promise.resolve({
 | 
			
		||||
            plot_general_colorscheme:     {{ .Config.plot_general_colorscheme }},
 | 
			
		||||
            plot_general_lineWidth:       {{ .Config.plot_general_lineWidth }},
 | 
			
		||||
            plot_general_colorBackground: {{ .Config.plot_general_colorBackground }},
 | 
			
		||||
            plot_list_selectedMetrics:    {{ .Config.plot_list_selectedMetrics }},
 | 
			
		||||
            plot_list_jobsPerPage:        {{ .Config.plot_list_jobsPerPage }}
 | 
			
		||||
        });
 | 
			
		||||
        const userInfos = {{ .Infos }};
 | 
			
		||||
        const filterPresets = {{ .FilterPresets }};
 | 
			
		||||
        const clusterCockpitConfig = {{ .Config }};
 | 
			
		||||
    </script>
 | 
			
		||||
    <script src='/build/user.js'></script>
 | 
			
		||||
{{end}}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,9 @@ import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var templates map[string]*template.Template
 | 
			
		||||
var templatesDir string
 | 
			
		||||
var debugMode bool = true
 | 
			
		||||
var templates map[string]*template.Template = map[string]*template.Template{}
 | 
			
		||||
 | 
			
		||||
type Page struct {
 | 
			
		||||
	Title         string
 | 
			
		||||
@@ -22,27 +24,32 @@ type LoginPage struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	base := template.Must(template.ParseFiles("./templates/base.html"))
 | 
			
		||||
	templates = map[string]*template.Template{
 | 
			
		||||
		"home":                 template.Must(template.Must(base.Clone()).ParseFiles("./templates/home.html")),
 | 
			
		||||
		"404":                  template.Must(template.Must(base.Clone()).ParseFiles("./templates/404.html")),
 | 
			
		||||
		"login":                template.Must(template.Must(base.Clone()).ParseFiles("./templates/login.html")),
 | 
			
		||||
		"monitoring/jobs/":     template.Must(template.Must(base.Clone()).ParseFiles("./templates/monitoring/jobs.html")),
 | 
			
		||||
		"monitoring/job/":      template.Must(template.Must(base.Clone()).ParseFiles("./templates/monitoring/job.html")),
 | 
			
		||||
		"monitoring/users/":    template.Must(template.Must(base.Clone()).ParseFiles("./templates/monitoring/users.html")),
 | 
			
		||||
		"monitoring/user/":     template.Must(template.Must(base.Clone()).ParseFiles("./templates/monitoring/user.html")),
 | 
			
		||||
		"monitoring/analysis/": template.Must(template.Must(base.Clone()).ParseFiles("./templates/monitoring/analysis.html")),
 | 
			
		||||
		"monitoring/systems/":  template.Must(template.Must(base.Clone()).ParseFiles("./templates/monitoring/systems.html")),
 | 
			
		||||
		"monitoring/node/":     template.Must(template.Must(base.Clone()).ParseFiles("./templates/monitoring/node.html")),
 | 
			
		||||
	templatesDir = "./templates/"
 | 
			
		||||
	base := template.Must(template.ParseFiles(templatesDir + "base.html"))
 | 
			
		||||
	files := []string{
 | 
			
		||||
		"home.html", "404.html", "login.html",
 | 
			
		||||
		"monitoring/jobs.html", "monitoring/job.html",
 | 
			
		||||
		"monitoring/users.html", "monitoring/user.html",
 | 
			
		||||
		"monitoring/analysis.html",
 | 
			
		||||
		"monitoring/systems.html",
 | 
			
		||||
		"monitoring/node.html",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, file := range files {
 | 
			
		||||
		templates[file] = template.Must(template.Must(base.Clone()).ParseFiles(templatesDir + file))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Render(rw http.ResponseWriter, r *http.Request, name string, page *Page) {
 | 
			
		||||
	t, ok := templates[name]
 | 
			
		||||
func Render(rw http.ResponseWriter, r *http.Request, file string, page *Page) {
 | 
			
		||||
	t, ok := templates[file]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic("templates must be predefinied!")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if debugMode {
 | 
			
		||||
		t = template.Must(template.ParseFiles(templatesDir+"base.html", templatesDir+file))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := t.Execute(rw, page); err != nil {
 | 
			
		||||
		log.Printf("template error: %s\n", err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user