mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-26 13:29:05 +01:00
add cli option for generating a JWT; simplify templates
This commit is contained in:
parent
b7432fca5f
commit
290e9b89bf
33
api/rest.go
33
api/rest.go
@ -9,7 +9,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ClusterCockpit/cc-jobarchive/config"
|
"github.com/ClusterCockpit/cc-jobarchive/config"
|
||||||
"github.com/ClusterCockpit/cc-jobarchive/graph"
|
"github.com/ClusterCockpit/cc-jobarchive/graph"
|
||||||
@ -28,15 +27,18 @@ type RestApi struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *RestApi) MountRoutes(r *mux.Router) {
|
func (api *RestApi) MountRoutes(r *mux.Router) {
|
||||||
r.HandleFunc("/api/jobs/start_job/", api.startJob).Methods(http.MethodPost, http.MethodPut)
|
r = r.PathPrefix("/api").Subrouter()
|
||||||
r.HandleFunc("/api/jobs/stop_job/", api.stopJob).Methods(http.MethodPost, http.MethodPut)
|
r.StrictSlash(true)
|
||||||
r.HandleFunc("/api/jobs/stop_job/{id}", api.stopJob).Methods(http.MethodPost, http.MethodPut)
|
|
||||||
|
|
||||||
r.HandleFunc("/api/jobs/{id}", api.getJob).Methods(http.MethodGet)
|
r.HandleFunc("/jobs/start_job/", api.startJob).Methods(http.MethodPost, http.MethodPut)
|
||||||
r.HandleFunc("/api/jobs/tag_job/{id}", api.tagJob).Methods(http.MethodPost, http.MethodPatch)
|
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("/jobs/{id}", api.getJob).Methods(http.MethodGet)
|
||||||
r.HandleFunc("/api/machine_state/{cluster}/{host}", api.putMachineState).Methods(http.MethodPut, http.MethodPost)
|
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 {
|
type StartJobApiRespone struct {
|
||||||
@ -158,17 +160,18 @@ func (api *RestApi) startJob(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
job := schema.Job{
|
req.RawResources, err = json.Marshal(req.Resources)
|
||||||
BaseJob: req.BaseJob,
|
|
||||||
StartTime: time.Unix(req.StartTime, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
job.RawResources, err = json.Marshal(req.Resources)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
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 {
|
if err != nil {
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
22
auth/auth.go
22
auth/auth.go
@ -27,6 +27,7 @@ type User struct {
|
|||||||
Password string
|
Password string
|
||||||
Name string
|
Name string
|
||||||
IsAdmin bool
|
IsAdmin bool
|
||||||
|
IsAPIUser bool
|
||||||
ViaLdap bool
|
ViaLdap bool
|
||||||
Email string
|
Email string
|
||||||
}
|
}
|
||||||
@ -110,6 +111,9 @@ func AddUserToDB(db *sqlx.DB, arg string) error {
|
|||||||
if parts[1] == "admin" {
|
if parts[1] == "admin" {
|
||||||
roles = "[\"ROLE_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()
|
_, err = sq.Insert("user").Columns("username", "password", "roles").Values(parts[0], string(password), roles).RunWith(db).Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -124,7 +128,7 @@ func DelUserFromDB(db *sqlx.DB, username string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchUserFromDB(db *sqlx.DB, username string) (*User, error) {
|
func FetchUserFromDB(db *sqlx.DB, username string) (*User, error) {
|
||||||
user := &User{Username: username}
|
user := &User{Username: username}
|
||||||
var hashedPassword, name, rawRoles, email sql.NullString
|
var hashedPassword, name, rawRoles, email sql.NullString
|
||||||
if err := sq.Select("password", "ldap", "name", "roles", "email").From("user").
|
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)
|
json.Unmarshal([]byte(rawRoles.String), &roles)
|
||||||
}
|
}
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
if role == "ROLE_ADMIN" {
|
switch role {
|
||||||
|
case "ROLE_ADMIN":
|
||||||
user.IsAdmin = true
|
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 {
|
func Login(db *sqlx.DB) http.Handler {
|
||||||
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
username, password := r.FormValue("username"), r.FormValue("password")
|
username, password := r.FormValue("username"), r.FormValue("password")
|
||||||
user, err := fetchUserFromDB(db, username)
|
user, err := FetchUserFromDB(db, username)
|
||||||
if err == nil && user.ViaLdap && ldapAuthEnabled {
|
if err == nil && user.ViaLdap && ldapAuthEnabled {
|
||||||
err = loginViaLdap(user, password)
|
err = loginViaLdap(user, password)
|
||||||
} else if err == nil && !user.ViaLdap && user.Password != "" {
|
} else if err == nil && !user.ViaLdap && user.Password != "" {
|
||||||
@ -168,7 +175,7 @@ func Login(db *sqlx.DB) http.Handler {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("login failed: %s\n", err.Error())
|
log.Printf("login failed: %s\n", err.Error())
|
||||||
rw.WriteHeader(http.StatusUnauthorized)
|
rw.WriteHeader(http.StatusUnauthorized)
|
||||||
templates.Render(rw, r, "login", &templates.Page{
|
templates.Render(rw, r, "login.html", &templates.Page{
|
||||||
Title: "Login failed",
|
Title: "Login failed",
|
||||||
Login: &templates.LoginPage{
|
Login: &templates.LoginPage{
|
||||||
Error: "Username or password incorrect",
|
Error: "Username or password incorrect",
|
||||||
@ -231,9 +238,11 @@ func authViaToken(r *http.Request) (*User, error) {
|
|||||||
claims := token.Claims.(jwt.MapClaims)
|
claims := token.Claims.(jwt.MapClaims)
|
||||||
sub, _ := claims["sub"].(string)
|
sub, _ := claims["sub"].(string)
|
||||||
isAdmin, _ := claims["is_admin"].(bool)
|
isAdmin, _ := claims["is_admin"].(bool)
|
||||||
|
isAPIUser, _ := claims["is_api"].(bool)
|
||||||
return &User{
|
return &User{
|
||||||
Username: sub,
|
Username: sub,
|
||||||
IsAdmin: isAdmin,
|
IsAdmin: isAdmin,
|
||||||
|
IsAPIUser: isAPIUser,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,7 +273,7 @@ func Auth(next http.Handler) http.Handler {
|
|||||||
log.Printf("authentication failed: no session or jwt found\n")
|
log.Printf("authentication failed: no session or jwt found\n")
|
||||||
|
|
||||||
rw.WriteHeader(http.StatusUnauthorized)
|
rw.WriteHeader(http.StatusUnauthorized)
|
||||||
templates.Render(rw, r, "login", &templates.Page{
|
templates.Render(rw, r, "login.html", &templates.Page{
|
||||||
Title: "Authentication failed",
|
Title: "Authentication failed",
|
||||||
Login: &templates.LoginPage{
|
Login: &templates.LoginPage{
|
||||||
Error: "No valid session or JWT provided",
|
Error: "No valid session or JWT provided",
|
||||||
@ -290,6 +299,7 @@ func ProvideJWT(user *User) (string, error) {
|
|||||||
tok := jwt.NewWithClaims(jwt.SigningMethodEdDSA, jwt.MapClaims{
|
tok := jwt.NewWithClaims(jwt.SigningMethodEdDSA, jwt.MapClaims{
|
||||||
"sub": user.Username,
|
"sub": user.Username,
|
||||||
"is_admin": user.IsAdmin,
|
"is_admin": user.IsAdmin,
|
||||||
|
"is_api": user.IsAPIUser,
|
||||||
})
|
})
|
||||||
|
|
||||||
return tok.SignedString(JwtPrivateKey)
|
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",
|
Title: "Logout successful",
|
||||||
Login: &templates.LoginPage{
|
Login: &templates.LoginPage{
|
||||||
Info: "Logout successful",
|
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/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 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
|
||||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
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/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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
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/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/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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/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/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 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
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/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/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/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
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/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/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/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=
|
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-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-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-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/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=
|
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=
|
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() {
|
func main() {
|
||||||
var flagReinitDB, flagStopImmediately, flagSyncLDAP bool
|
var flagReinitDB, flagStopImmediately, flagSyncLDAP bool
|
||||||
var flagConfigFile string
|
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(&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(&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.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(&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(&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()
|
flag.Parse()
|
||||||
|
|
||||||
if flagConfigFile != "" {
|
if flagConfigFile != "" {
|
||||||
@ -156,6 +157,24 @@ func main() {
|
|||||||
if flagSyncLDAP {
|
if flagSyncLDAP {
|
||||||
auth.SyncWithLDAP(db)
|
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 != "" {
|
} else if flagNewUser != "" || flagDelUser != "" {
|
||||||
log.Fatalln("arguments --add-user and --del-user can only be used if authentication is enabled")
|
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}
|
resolver := &graph.Resolver{DB: db}
|
||||||
graphQLEndpoint := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: resolver}))
|
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")
|
graphQLPlayground := playground.Handler("GraphQL playground", "/query")
|
||||||
api := &api.RestApi{
|
api := &api.RestApi{
|
||||||
DB: db,
|
DB: db,
|
||||||
@ -191,7 +222,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleGetLogin := func(rw http.ResponseWriter, r *http.Request) {
|
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",
|
Title: "Login",
|
||||||
Login: &templates.LoginPage{},
|
Login: &templates.LoginPage{},
|
||||||
})
|
})
|
||||||
@ -199,7 +230,7 @@ func main() {
|
|||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
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",
|
Title: "Not found",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -215,8 +246,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
secured.Handle("/query", graphQLEndpoint)
|
secured.Handle("/query", graphQLEndpoint)
|
||||||
|
|
||||||
secured.HandleFunc("/config.json", config.ServeConfig).Methods(http.MethodGet)
|
|
||||||
|
|
||||||
secured.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
|
secured.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
|
||||||
conf, err := config.GetUIConfig(r)
|
conf, err := config.GetUIConfig(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -235,7 +264,7 @@ func main() {
|
|||||||
infos["admin"] = user.IsAdmin
|
infos["admin"] = user.IsAdmin
|
||||||
}
|
}
|
||||||
|
|
||||||
templates.Render(rw, r, "home", &templates.Page{
|
templates.Render(rw, r, "home.html", &templates.Page{
|
||||||
Title: "ClusterCockpit",
|
Title: "ClusterCockpit",
|
||||||
Config: conf,
|
Config: conf,
|
||||||
Infos: infos,
|
Infos: infos,
|
||||||
@ -297,7 +326,7 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
templates.Render(rw, r, "monitoring/jobs/", &templates.Page{
|
templates.Render(rw, r, "monitoring/jobs.html", &templates.Page{
|
||||||
Title: "Jobs - ClusterCockpit",
|
Title: "Jobs - ClusterCockpit",
|
||||||
Config: conf,
|
Config: conf,
|
||||||
FilterPresets: buildFilterPresets(r.URL.Query()),
|
FilterPresets: buildFilterPresets(r.URL.Query()),
|
||||||
@ -318,7 +347,7 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
|
|||||||
return
|
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),
|
Title: fmt.Sprintf("Job %d - ClusterCockpit", job.JobID),
|
||||||
Config: conf,
|
Config: conf,
|
||||||
Infos: map[string]interface{}{
|
Infos: map[string]interface{}{
|
||||||
@ -336,7 +365,7 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
templates.Render(rw, r, "monitoring/users/", &templates.Page{
|
templates.Render(rw, r, "monitoring/users.html", &templates.Page{
|
||||||
Title: "Users - ClusterCockpit",
|
Title: "Users - ClusterCockpit",
|
||||||
Config: conf,
|
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
|
// 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.
|
// 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),
|
Title: fmt.Sprintf("User %s - ClusterCockpit", id),
|
||||||
Config: conf,
|
Config: conf,
|
||||||
Infos: map[string]interface{}{"username": id},
|
Infos: map[string]interface{}{"username": id},
|
||||||
@ -374,7 +403,7 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
|
|||||||
filterPresets["clusterId"] = query.Get("cluster")
|
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",
|
Title: "Analysis View - ClusterCockpit",
|
||||||
Config: conf,
|
Config: conf,
|
||||||
FilterPresets: filterPresets,
|
FilterPresets: filterPresets,
|
||||||
@ -394,7 +423,7 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
|
|||||||
filterPresets["clusterId"] = query.Get("cluster")
|
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",
|
Title: "System View - ClusterCockpit",
|
||||||
Config: conf,
|
Config: conf,
|
||||||
FilterPresets: filterPresets,
|
FilterPresets: filterPresets,
|
||||||
@ -409,7 +438,7 @@ func monitoringRoutes(router *mux.Router, resolver *graph.Resolver) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
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"]),
|
Title: fmt.Sprintf("Node %s - ClusterCockpit", vars["nodeId"]),
|
||||||
Config: conf,
|
Config: conf,
|
||||||
Infos: map[string]interface{}{
|
Infos: map[string]interface{}{
|
||||||
|
@ -8,13 +8,7 @@
|
|||||||
{{define "javascript"}}
|
{{define "javascript"}}
|
||||||
<script>
|
<script>
|
||||||
const filterPresets = {{ .FilterPresets }};
|
const filterPresets = {{ .FilterPresets }};
|
||||||
const clusterCockpitConfigPromise = Promise.resolve({
|
const clusterCockpitConfig = {{ .Config }};
|
||||||
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 }}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
<script src='/build/jobs.js'></script>
|
<script src='/build/jobs.js'></script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -7,16 +7,9 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
{{define "javascript"}}
|
{{define "javascript"}}
|
||||||
<script>
|
<script>
|
||||||
const userInfos = {
|
const userInfos = {{ .Infos }};
|
||||||
userId: "{{ .Infos.userId }}"
|
const filterPresets = {{ .FilterPresets }};
|
||||||
};
|
const clusterCockpitConfig = {{ .Config }};
|
||||||
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 }}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
<script src='/build/user.js'></script>
|
<script src='/build/user.js'></script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -6,7 +6,9 @@ import (
|
|||||||
"net/http"
|
"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 {
|
type Page struct {
|
||||||
Title string
|
Title string
|
||||||
@ -22,27 +24,32 @@ type LoginPage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
base := template.Must(template.ParseFiles("./templates/base.html"))
|
templatesDir = "./templates/"
|
||||||
templates = map[string]*template.Template{
|
base := template.Must(template.ParseFiles(templatesDir + "base.html"))
|
||||||
"home": template.Must(template.Must(base.Clone()).ParseFiles("./templates/home.html")),
|
files := []string{
|
||||||
"404": template.Must(template.Must(base.Clone()).ParseFiles("./templates/404.html")),
|
"home.html", "404.html", "login.html",
|
||||||
"login": template.Must(template.Must(base.Clone()).ParseFiles("./templates/login.html")),
|
"monitoring/jobs.html", "monitoring/job.html",
|
||||||
"monitoring/jobs/": template.Must(template.Must(base.Clone()).ParseFiles("./templates/monitoring/jobs.html")),
|
"monitoring/users.html", "monitoring/user.html",
|
||||||
"monitoring/job/": template.Must(template.Must(base.Clone()).ParseFiles("./templates/monitoring/job.html")),
|
"monitoring/analysis.html",
|
||||||
"monitoring/users/": template.Must(template.Must(base.Clone()).ParseFiles("./templates/monitoring/users.html")),
|
"monitoring/systems.html",
|
||||||
"monitoring/user/": template.Must(template.Must(base.Clone()).ParseFiles("./templates/monitoring/user.html")),
|
"monitoring/node.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")),
|
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) {
|
func Render(rw http.ResponseWriter, r *http.Request, file string, page *Page) {
|
||||||
t, ok := templates[name]
|
t, ok := templates[file]
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("templates must be predefinied!")
|
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 {
|
if err := t.Execute(rw, page); err != nil {
|
||||||
log.Printf("template error: %s\n", err.Error())
|
log.Printf("template error: %s\n", err.Error())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user