diff --git a/api/rest.go b/api/rest.go index a871b5b..fc2f064 100644 --- a/api/rest.go +++ b/api/rest.go @@ -266,6 +266,11 @@ func (api *RestApi) stopJob(rw http.ResponseWriter, r *http.Request) { job, err = api.JobRepository.FindById(id) } else { + if req.JobId == nil || req.Cluster == nil || req.StartTime == nil { + http.Error(rw, "'jobId', 'cluster' and 'startTime' are required", http.StatusBadRequest) + return + } + job, err = api.JobRepository.Find(*req.JobId, *req.Cluster, *req.StartTime) } diff --git a/init-db.go b/init-db.go index 9ce3dfb..acde242 100644 --- a/init-db.go +++ b/init-db.go @@ -65,6 +65,12 @@ const JOBS_DB_SCHEMA string = ` FOREIGN KEY (tag_id) REFERENCES tag (id) ON DELETE CASCADE); ` +const JOBS_DB_INDEXES string = ` + CREATE INDEX job_by_user ON job (user); + CREATE INDEX job_by_starttime ON job (start_time); + CREATE INDEX job_by_job_id ON job (job_id); +` + // Delete the tables "job", "tag" and "jobtag" from the database and // repopulate them using the jobs found in `archive`. func initDB(db *sqlx.DB, archive string) error { @@ -178,9 +184,7 @@ func initDB(db *sqlx.DB, archive string) error { // Create indexes after inserts so that they do not // need to be continually updated. - if _, err := db.Exec(` - CREATE INDEX job_by_user ON job (user); - CREATE INDEX job_by_starttime ON job (start_time);`); err != nil { + if _, err := db.Exec(JOBS_DB_INDEXES); err != nil { return err } diff --git a/repository/job.go b/repository/job.go index ae42b5b..881b5e5 100644 --- a/repository/job.go +++ b/repository/job.go @@ -1,6 +1,12 @@ package repository import ( + "context" + "database/sql" + "errors" + "strconv" + + "github.com/ClusterCockpit/cc-backend/auth" "github.com/ClusterCockpit/cc-backend/log" "github.com/ClusterCockpit/cc-backend/schema" sq "github.com/Masterminds/squirrel" @@ -141,3 +147,38 @@ func (r *JobRepository) TagId(tagType string, tagName string) (tagId int64, exis } return } + +var ErrNotFound = errors.New("no such job or user") + +// FindJobOrUser returns a job database ID or a username if a job or user machtes the search term. +// As 0 is a valid job id, check if username is "" instead in order to check what machted. +// If nothing matches the search, `ErrNotFound` is returned. +func (r *JobRepository) FindJobOrUser(ctx context.Context, searchterm string) (job int64, username string, err error) { + user := auth.GetUser(ctx) + if id, err := strconv.Atoi(searchterm); err == nil { + qb := sq.Select("job.id").From("job").Where("job.job_id = ?", id) + if user != nil && !user.HasRole(auth.RoleAdmin) { + qb = qb.Where("job.user = ?", user.Username) + } + + err := qb.RunWith(r.DB).QueryRow().Scan(&job) + if err != nil && err != sql.ErrNoRows { + return 0, "", err + } else if err == nil { + return job, "", nil + } + } + + if user == nil || user.HasRole(auth.RoleAdmin) { + err := sq.Select("job.user").Distinct().From("job"). + Where("job.user = ?", searchterm). + RunWith(r.DB).QueryRow().Scan(&username) + if err != nil && err != sql.ErrNoRows { + return 0, "", err + } else if err == nil { + return 0, username, nil + } + } + + return 0, "", ErrNotFound +} diff --git a/server.go b/server.go index 61f3dc3..1427bba 100644 --- a/server.go +++ b/server.go @@ -10,6 +10,7 @@ import ( "io" "net" "net/http" + "net/url" "os" "os/signal" "strings" @@ -375,6 +376,29 @@ func main() { }) }) + 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) + if err == repository.ErrNotFound { + http.Redirect(rw, r, "/monitoring/jobs/?jobId="+url.QueryEscape(search), http.StatusTemporaryRedirect) + return + } else if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + if username != "" { + http.Redirect(rw, r, "/monitoring/user/"+username, http.StatusTemporaryRedirect) + return + } else { + http.Redirect(rw, r, fmt.Sprintf("/monitoring/job/%d", job), http.StatusTemporaryRedirect) + return + } + } else { + http.Error(rw, "'searchId' query parameter missing", http.StatusBadRequest) + } + }) + setupRoutes(secured, routes) api.MountRoutes(secured)