diff --git a/internal/repository/job.go b/internal/repository/job.go index 40c12b5..2545362 100644 --- a/internal/repository/job.go +++ b/internal/repository/job.go @@ -491,195 +491,94 @@ var ErrForbidden = errors.New("not authorized") // If query is found to be an integer (= conversion to INT datatype succeeds), skip back to parent call // If nothing matches the search, `ErrNotFound` is returned. -func (r *JobRepository) FindJobnameOrUserOrProject(ctx context.Context, searchterm string) (metasnip string, username string, project string, err error) { - user := auth.GetUser(ctx) +func (r *JobRepository) FindUserOrProjectOrJobname(ctx context.Context, searchterm string) (username string, project string, metasnip string, err error) { if _, err := strconv.Atoi(searchterm); err == nil { // Return empty on successful conversion: parent method will redirect for integer jobId return "", "", "", nil - } else { // has to have letters - - if user != nil && user.HasNotRoles([]string{auth.RoleAdmin, auth.RoleSupport}) { - err := sq.Select("job.user").Distinct().From("job"). - Where("job.user = ?", searchterm). - RunWith(r.stmtCache).QueryRow().Scan(&username) + } else { // Has to have letters and logged-in user for other guesses + user := auth.GetUser(ctx) + if user != nil { + // Find username in jobs (match) + uresult, _ := r.FindColumnValue(user, searchterm, "job", "user", "user", false) + if uresult != "" { + return uresult, "", "", nil + } + // Find username by name (like) + nresult, _ := r.FindColumnValue(user, searchterm, "user", "username", "name", true) + if nresult != "" { + return nresult, "", "", nil + } + // Find projectId in jobs (match) + presult, _ := r.FindColumnValue(user, searchterm, "job", "project", "project", false) + if presult != "" { + return "", presult, "", nil + } + // Still no return (or not authorized for above): Try JobName + // Match Metadata, on hit, parent method redirects to jobName GQL query + err := sq.Select("job.cluster").Distinct().From("job"). + Where("job.meta_data LIKE ?", "%"+searchterm+"%"). + RunWith(r.stmtCache).QueryRow().Scan(&metasnip) if err != nil && err != sql.ErrNoRows { return "", "", "", err } else if err == nil { - return "", username, "", nil - } - - if username == "" { // Try with Name2Username query - errtwo := sq.Select("user.username").Distinct().From("user"). - Where("user.name LIKE ?", fmt.Sprint("%"+searchterm+"%")). - RunWith(r.stmtCache).QueryRow().Scan(&username) - if errtwo != nil && errtwo != sql.ErrNoRows { - return "", "", "", errtwo - } else if errtwo == nil { - return "", username, "", nil - } + return "", "", metasnip[0:1], nil } } - - if user == nil || user.HasRole(auth.RoleAdmin) || user.HasRole(auth.RoleSupport) { - err := sq.Select("job.project").Distinct().From("job"). - Where("job.project = ?", searchterm). - RunWith(r.stmtCache).QueryRow().Scan(&project) - if err != nil && err != sql.ErrNoRows { - return "", "", "", err - } else if err == nil { - return "", "", project, nil - } - } - - // All Authorizations: If unlabeled query not username or projectId, try for jobname: Match Metadata, on hit, parent method redirects to jobName GQL query - err := sq.Select("job.cluster").Distinct().From("job"). - Where("job.meta_data LIKE ?", "%"+searchterm+"%"). - RunWith(r.stmtCache).QueryRow().Scan(&metasnip) - if err != nil && err != sql.ErrNoRows { - return "", "", "", err - } else if err == nil { - return metasnip[0:1], "", "", nil - } - return "", "", "", ErrNotFound } } -func (r *JobRepository) FindUser(ctx context.Context, searchterm string) (username string, err error) { - user := auth.GetUser(ctx) - if user == nil || user.HasAnyRole([]string{auth.RoleAdmin, auth.RoleSupport}) { - err := sq.Select("job.user").Distinct().From("job"). - Where("job.user = ?", searchterm). - RunWith(r.stmtCache).QueryRow().Scan(&username) +func (r *JobRepository) FindColumnValue(user *auth.User, searchterm string, table string, selectColumn string, whereColumn string, isLike bool) (result string, err error) { + compareStr := " = ?" + query := searchterm + if isLike == true { + compareStr = " LIKE ?" + query = "%" + searchterm + "%" + } + if user.HasAnyRole([]string{auth.RoleAdmin, auth.RoleSupport, auth.RoleManager}) { + err := sq.Select(table+"."+selectColumn).Distinct().From(table). + Where(table+"."+whereColumn+compareStr, query). + RunWith(r.stmtCache).QueryRow().Scan(&result) if err != nil && err != sql.ErrNoRows { return "", err } else if err == nil { - return username, nil + return result, nil } return "", ErrNotFound - } else { - log.Infof("Non-Admin User %s : Requested Query Username -> %s: Forbidden", user.Name, searchterm) + log.Infof("Non-Admin User %s : Requested Query '%s' on table '%s' : Forbidden", user.Name, query, table) return "", ErrForbidden } } -func (r *JobRepository) FindUserByName(ctx context.Context, searchterm string) (username string, err error) { - user := auth.GetUser(ctx) - if user == nil || user.HasRole(auth.RoleAdmin) || user.HasRole(auth.RoleSupport) { - err := sq.Select("user.username").Distinct().From("user"). - Where("user.name = ?", searchterm). - RunWith(r.stmtCache).QueryRow().Scan(&username) - if err != nil && err != sql.ErrNoRows { - return "", err - } else if err == nil { - return username, nil - } - return "", ErrNotFound - - } else { - log.Infof("Non-Admin User %s : Requested Query Name -> %s: Forbidden", user.Name, searchterm) - return "", ErrForbidden - } -} - -func (r *JobRepository) FindUsers(ctx context.Context, searchterm string) (usernames []string, err error) { - user := auth.GetUser(ctx) +func (r *JobRepository) FindColumnValues(user *auth.User, query string, table string, selectColumn string, whereColumn string) (results []string, err error) { emptyResult := make([]string, 0) - if user == nil || user.HasRole(auth.RoleAdmin) || user.HasRole(auth.RoleSupport) { - rows, err := sq.Select("job.user").Distinct().From("job"). - Where("job.user LIKE ?", fmt.Sprint("%", searchterm, "%")). + if user.HasAnyRole([]string{auth.RoleAdmin, auth.RoleSupport, auth.RoleManager}) { + rows, err := sq.Select(table+"."+selectColumn).Distinct().From(table). + Where(table+"."+whereColumn+" LIKE ?", fmt.Sprint("%", query, "%")). RunWith(r.stmtCache).Query() if err != nil && err != sql.ErrNoRows { return emptyResult, err } else if err == nil { for rows.Next() { - var name string - err := rows.Scan(&name) + var result string + err := rows.Scan(&result) if err != nil { rows.Close() log.Warnf("Error while scanning rows: %v", err) return emptyResult, err } - usernames = append(usernames, name) + results = append(results, result) } - return usernames, nil + return results, nil } return emptyResult, ErrNotFound } else { - log.Infof("Non-Admin User %s : Requested Query Usernames -> %s: Forbidden", user.Name, searchterm) + log.Infof("Non-Admin User %s : Requested Query '%s' on table '%s' : Forbidden", user.Name, query, table) return emptyResult, ErrForbidden } } -func (r *JobRepository) FindUsersByName(ctx context.Context, searchterm string) (usernames []string, err error) { - user := auth.GetUser(ctx) - emptyResult := make([]string, 0) - if user == nil || user.HasRole(auth.RoleAdmin) || user.HasRole(auth.RoleSupport) { - rows, err := sq.Select("user.username").Distinct().From("user"). - Where("user.name LIKE ?", fmt.Sprint("%", searchterm, "%")). - RunWith(r.stmtCache).Query() - if err != nil && err != sql.ErrNoRows { - return emptyResult, err - } else if err == nil { - for rows.Next() { - var username string - err := rows.Scan(&username) - if err != nil { - rows.Close() - log.Warnf("Error while scanning rows: %v", err) - return emptyResult, err - } - usernames = append(usernames, username) - } - return usernames, nil - } - return emptyResult, ErrNotFound - - } else { - log.Infof("Non-Admin User %s : Requested Query name -> %s: Forbidden", user.Name, searchterm) - return emptyResult, ErrForbidden - } -} - -func (r *JobRepository) FindNameByUser(ctx context.Context, searchterm string) (name string, err error) { - user := auth.GetUser(ctx) - if user == nil || user.HasRole(auth.RoleAdmin) || user.HasRole(auth.RoleSupport) { - err := sq.Select("user.name").Distinct().From("user"). - Where("user.username = ?", searchterm). - RunWith(r.stmtCache).QueryRow().Scan(&name) - if err != nil && err != sql.ErrNoRows { - return "", err - } else if err == nil { - return name, nil - } - return "", ErrNotFound - - } else { - log.Infof("Non-Admin User %s : Requested Query Name -> %s: Forbidden", user.Name, searchterm) - return "", ErrForbidden - } -} - -func (r *JobRepository) FindProject(ctx context.Context, searchterm string) (project string, err error) { - - user := auth.GetUser(ctx) - if user == nil || user.HasRole(auth.RoleAdmin) || user.HasRole(auth.RoleSupport) { - err := sq.Select("job.project").Distinct().From("job"). - Where("job.project = ?", searchterm). - RunWith(r.stmtCache).QueryRow().Scan(&project) - if err != nil && err != sql.ErrNoRows { - return "", err - } else if err == nil { - return project, nil - } - return "", ErrNotFound - } else { - log.Infof("Non-Admin User %s : Requested Query Project -> %s: Forbidden", user.Name, project) - return "", ErrForbidden - } -} - func (r *JobRepository) Partitions(cluster string) ([]string, error) { var err error start := time.Now() @@ -917,7 +816,8 @@ func (r *JobRepository) JobsStatistics(ctx context.Context, if col == "job.user" { for id := range stats { emptyDash := "-" - name, _ := r.FindNameByUser(ctx, id) + user := auth.GetUser(ctx) + name, _ := r.FindColumnValue(user, id, "user", "name", "username", false) if name != "" { stats[id].Name = &name } else { diff --git a/internal/routerConfig/routes.go b/internal/routerConfig/routes.go index fc406b7..009f348 100644 --- a/internal/routerConfig/routes.go +++ b/internal/routerConfig/routes.go @@ -312,75 +312,61 @@ func SetupRoutes(router *mux.Router, version string, hash string, buildTime stri func HandleSearchBar(rw http.ResponseWriter, r *http.Request, api *api.RestApi) { if search := r.URL.Query().Get("searchId"); search != "" { + user := auth.GetUser(r.Context()) splitSearch := strings.Split(search, ":") if len(splitSearch) == 2 { switch strings.Trim(splitSearch[0], " ") { case "jobId": http.Redirect(rw, r, "/monitoring/jobs/?jobId="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusTemporaryRedirect) // All Users: Redirect to Tablequery - return case "jobName": http.Redirect(rw, r, "/monitoring/jobs/?jobName="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusTemporaryRedirect) // All Users: Redirect to Tablequery - return case "projectId": - project, _ := api.JobRepository.FindProject(r.Context(), strings.Trim(splitSearch[1], " ")) // Restricted: projectId - if project != "" { - http.Redirect(rw, r, "/monitoring/jobs/?projectMatch=eq&project="+url.QueryEscape(project), http.StatusTemporaryRedirect) - return - } else { - http.Redirect(rw, r, "/monitoring/jobs/?jobId=NotFound", http.StatusTemporaryRedirect) // Workaround to display correctly empty table - } + http.Redirect(rw, r, "/monitoring/jobs/?projectMatch=eq&project="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusTemporaryRedirect) // All Users: Redirect to Tablequery case "username": - usernames, _ := api.JobRepository.FindUsers(r.Context(), strings.Trim(splitSearch[1], " ")) // Restricted: usernames - if len(usernames) == 1 { - http.Redirect(rw, r, "/monitoring/user/"+usernames[0], http.StatusTemporaryRedirect) // One Match: Redirect to User View - return - } else if len(usernames) > 1 { - http.Redirect(rw, r, "/monitoring/users/?user="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusTemporaryRedirect) // > 1 Matches: Redirect to user table - return + if user.HasAnyRole([]string{auth.RoleAdmin, auth.RoleSupport, auth.RoleManager}) { + http.Redirect(rw, r, "/monitoring/users/?user="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusTemporaryRedirect) } else { - http.Redirect(rw, r, "/monitoring/users/?user=NotFound", http.StatusTemporaryRedirect) // Workaround to display correctly empty table + http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusTemporaryRedirect) // Users: Redirect to Tablequery } case "name": - usernames, _ := api.JobRepository.FindUsersByName(r.Context(), strings.Trim(splitSearch[1], " ")) // Restricted: usernames queried by name - if len(usernames) == 1 { - http.Redirect(rw, r, "/monitoring/user/"+usernames[0], http.StatusTemporaryRedirect) - return - } else if len(usernames) > 1 { + usernames, _ := api.JobRepository.FindColumnValues(user, strings.Trim(splitSearch[1], " "), "user", "username", "name") + if len(usernames) != 0 { joinedNames := strings.Join(usernames, "&user=") - http.Redirect(rw, r, "/monitoring/users/?user="+joinedNames, http.StatusTemporaryRedirect) // > 1 Matches: Redirect to user table - return + http.Redirect(rw, r, "/monitoring/users/?user="+joinedNames, http.StatusTemporaryRedirect) } else { - http.Redirect(rw, r, "/monitoring/users/?user=NotFound", http.StatusTemporaryRedirect) // Workaround to display correctly empty table + if user.HasAnyRole([]string{auth.RoleAdmin, auth.RoleSupport, auth.RoleManager}) { + http.Redirect(rw, r, "/monitoring/users/?user=NoUserNameFound", http.StatusTemporaryRedirect) + } else { + http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusTemporaryRedirect) // Users: Redirect to Tablequery + } } default: - http.Error(rw, "'searchId' type parameter unknown", http.StatusBadRequest) + log.Warnf("Searchbar type parameter '%s' unknown", strings.Trim(splitSearch[0], " ")) + http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusTemporaryRedirect) // Unknown: Redirect to Tablequery } } else if len(splitSearch) == 1 { - jobname, username, project, err := api.JobRepository.FindJobnameOrUserOrProject(r.Context(), strings.Trim(search, " ")) // Determine Access within + username, project, jobname, err := api.JobRepository.FindUserOrProjectOrJobname(r.Context(), strings.Trim(search, " ")) if err != nil { - http.Error(rw, err.Error(), http.StatusInternalServerError) - return + log.Errorf("Error while searchbar best guess: %v", err.Error()) + http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusTemporaryRedirect) // Unknown: Redirect to Tablequery } if username != "" { http.Redirect(rw, r, "/monitoring/user/"+username, http.StatusTemporaryRedirect) // User: Redirect to user page - return } else if project != "" { http.Redirect(rw, r, "/monitoring/jobs/?projectMatch=eq&project="+url.QueryEscape(strings.Trim(search, " ")), http.StatusTemporaryRedirect) // projectId (equal) - return } else if jobname != "" { http.Redirect(rw, r, "/monitoring/jobs/?jobName="+url.QueryEscape(strings.Trim(search, " ")), http.StatusTemporaryRedirect) // JobName (contains) - return } else { http.Redirect(rw, r, "/monitoring/jobs/?jobId="+url.QueryEscape(strings.Trim(search, " ")), http.StatusTemporaryRedirect) // No Result: Probably jobId - return } } else { - http.Error(rw, "'searchId' query parameter malformed", http.StatusBadRequest) + log.Warnf("Searchbar query parameters malformed: %v", search) + http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusTemporaryRedirect) // Unknown: Redirect to Tablequery } } else { diff --git a/web/frontend/src/Header.svelte b/web/frontend/src/Header.svelte index 9fe4ec2..0500862 100644 --- a/web/frontend/src/Header.svelte +++ b/web/frontend/src/Header.svelte @@ -10,8 +10,9 @@ let isOpen = false const userviews = [ - { title: 'My Jobs', href: `/monitoring/user/${username}`, icon: 'bar-chart-line-fill' }, - { title: 'Tags', href: '/monitoring/tags/', icon: 'tags' } + { title: 'My Jobs', href: `/monitoring/user/${username}`, icon: 'bar-chart-line-fill' }, + { title: `Job Search`, href: '/monitoring/jobs/', icon: 'card-list' }, + { title: 'Tags', href: '/monitoring/tags/', icon: 'tags' } ] const managerviews = [ @@ -91,9 +92,9 @@
- = 4) ? "Search jobId / username" : "Search jobId"} name="searchId"/> + ' ..."} name="searchId"/> - = 4) ? "Example: 'projectId:a100cd', Types are: jobId | jobName | projectId | username" | "name" : "Example: 'jobName:myjob', Types are jobId | jobName"}> + = 4) ? "Example: 'projectId:a100cd', Types are: jobId | jobName | projectId | username | name" : "Example: 'jobName:myjob', Types are jobId | jobName | projectId"}>
{#if username} diff --git a/web/frontend/src/Jobs.root.svelte b/web/frontend/src/Jobs.root.svelte index 9ecaafa..320a5f6 100644 --- a/web/frontend/src/Jobs.root.svelte +++ b/web/frontend/src/Jobs.root.svelte @@ -14,6 +14,7 @@ const ccconfig = getContext('cc-config') export let filterPresets = {} + export let authLevel let filters, jobList, matchedJobs = null let sorting = { field: 'startTime', order: 'DESC' }, isSortingOpen = false, isMetricsSelectionOpen = false @@ -60,7 +61,7 @@ - filters.update(detail)}/> + filters.update(detail)}/> jobList.update()} /> diff --git a/web/frontend/src/filters/UserOrProject.svelte b/web/frontend/src/filters/UserOrProject.svelte index 7f9f183..fa5d123 100644 --- a/web/frontend/src/filters/UserOrProject.svelte +++ b/web/frontend/src/filters/UserOrProject.svelte @@ -6,6 +6,7 @@ export let user = '' export let project = '' + export let authLevel let mode = 'user', term = '' const throttle = 500 @@ -22,30 +23,53 @@ let timeoutId = null function termChanged(sleep = throttle) { - if (mode == 'user') - user = term - else + if (authLevel == 2) { project = term - if (timeoutId != null) - clearTimeout(timeoutId) + if (timeoutId != null) + clearTimeout(timeoutId) - timeoutId = setTimeout(() => { - dispatch('update', { - user, - project - }) - }, sleep) + timeoutId = setTimeout(() => { + dispatch('update', { + project + }) + }, sleep) + } else if (authLevel >= 3) { + if (mode == 'user') + user = term + else + project = term + + if (timeoutId != null) + clearTimeout(timeoutId) + + timeoutId = setTimeout(() => { + dispatch('update', { + user, + project + }) + }, sleep) + } } - - - termChanged()} on:keyup={(event) => termChanged(event.key == 'Enter' ? 0 : throttle)} - placeholder={mode == 'user' ? 'filter username...' : 'filter project...'} /> - +{#if authLevel == 2} + + termChanged()} on:keyup={(event) => termChanged(event.key == 'Enter' ? 0 : throttle)} placeholder='filter project...' + /> + +{:else if authLevel >= 3} + + + termChanged()} on:keyup={(event) => termChanged(event.key == 'Enter' ? 0 : throttle)} + placeholder={mode == 'user' ? 'filter username...' : 'filter project...'} /> + +{:else} + Unauthorized +{/if} \ No newline at end of file diff --git a/web/frontend/src/jobs.entrypoint.js b/web/frontend/src/jobs.entrypoint.js index 61c9796..e31d530 100644 --- a/web/frontend/src/jobs.entrypoint.js +++ b/web/frontend/src/jobs.entrypoint.js @@ -5,6 +5,7 @@ new Jobs({ target: document.getElementById('svelte-app'), props: { filterPresets: filterPresets, + authLevel: authLevel }, context: new Map([ ['cc-config', clusterCockpitConfig] diff --git a/web/templates/monitoring/jobs.tmpl b/web/templates/monitoring/jobs.tmpl index d71a91d..7621d51 100644 --- a/web/templates/monitoring/jobs.tmpl +++ b/web/templates/monitoring/jobs.tmpl @@ -10,6 +10,7 @@ {{end}}