mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-26 13:29:05 +01:00
Makeover of searchbar, add "not found" results-redirects
- Tagged Search has priority - Best guess search if no tag provided - "Hard" errors only on searchtag-malformed or searchtag-unknown, or if best guess search fails
This commit is contained in:
parent
a5298a3630
commit
bad3f9ea95
@ -298,8 +298,38 @@ func main() {
|
|||||||
// Send a searchId and then reply with a redirect to a user, or directly send query to job table for jobid and project.
|
// Send a searchId and then reply with a redirect to a user, or directly send query to job table for jobid and project.
|
||||||
secured.HandleFunc("/search", func(rw http.ResponseWriter, r *http.Request) {
|
secured.HandleFunc("/search", func(rw http.ResponseWriter, r *http.Request) {
|
||||||
if search := r.URL.Query().Get("searchId"); search != "" {
|
if search := r.URL.Query().Get("searchId"); search != "" {
|
||||||
|
splitSearch := strings.Split(search, ":")
|
||||||
|
|
||||||
_, username, project, err := api.JobRepository.FindJobOrUserOrProject(r.Context(), search)
|
if (len(splitSearch) == 2) {
|
||||||
|
switch splitSearch[0] {
|
||||||
|
case "jobId":
|
||||||
|
http.Redirect(rw, r, "/monitoring/jobs/?jobId="+url.QueryEscape(splitSearch[1]), http.StatusTemporaryRedirect) // All Users: Redirect to Tablequery
|
||||||
|
return
|
||||||
|
case "jobName":
|
||||||
|
http.Redirect(rw, r, "/monitoring/jobs/?jobName="+url.QueryEscape(splitSearch[1]), http.StatusTemporaryRedirect) // All Users: Redirect to Tablequery
|
||||||
|
return
|
||||||
|
case "projectId":
|
||||||
|
project, _ := api.JobRepository.FindProject(r.Context(), 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
|
||||||
|
}
|
||||||
|
case "username":
|
||||||
|
username, _ := api.JobRepository.FindUser(r.Context(), splitSearch[1]) // Restricted: username
|
||||||
|
if username != "" {
|
||||||
|
http.Redirect(rw, r, "/monitoring/user/"+username, http.StatusTemporaryRedirect)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
http.Redirect(rw, r, "/monitoring/jobs/?jobId=NotFound", http.StatusTemporaryRedirect) // Workaround to display correctly empty table
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
http.Error(rw, "'searchId' type parameter unknown", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (len(splitSearch) == 1) {
|
||||||
|
jobname, username, project, err := api.JobRepository.FindJobnameOrUserOrProject(r.Context(), search) // Determine Access within
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
@ -307,18 +337,25 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if username != "" {
|
if username != "" {
|
||||||
http.Redirect(rw, r, "/monitoring/user/"+username, http.StatusTemporaryRedirect)
|
http.Redirect(rw, r, "/monitoring/user/"+username, http.StatusTemporaryRedirect) // User: Redirect to user page
|
||||||
return
|
return
|
||||||
} else if (project != "") {
|
} else if (project != "") {
|
||||||
http.Redirect(rw, r, "/monitoring/jobs/?projectMatch=eq&project="+project, http.StatusTemporaryRedirect) // Directly to table!
|
http.Redirect(rw, r, "/monitoring/jobs/?projectMatch=eq&project="+url.QueryEscape(search), http.StatusTemporaryRedirect) // projectId (equal)
|
||||||
|
return
|
||||||
|
} else if (jobname != "") {
|
||||||
|
http.Redirect(rw, r, "/monitoring/jobs/?jobName="+url.QueryEscape(search), http.StatusTemporaryRedirect) // JobName (contains)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
http.Redirect(rw, r, "/monitoring/jobs/?jobId="+url.QueryEscape(search), http.StatusTemporaryRedirect) // Directly to table!
|
http.Redirect(rw, r, "/monitoring/jobs/?jobId="+url.QueryEscape(search), http.StatusTemporaryRedirect) // No Result: Probably jobId
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
http.Error(rw, "'searchId' query parameter missing", http.StatusBadRequest)
|
http.Error(rw, "'searchId' query parameter malformed", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
http.Redirect(rw, r, "/monitoring/jobs/?", http.StatusTemporaryRedirect)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -393,38 +393,27 @@ func (r *JobRepository) Archive(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrNotFound = errors.New("no such job, project or user")
|
var ErrNotFound = errors.New("no such jobname, project or user")
|
||||||
|
var ErrForbidden = errors.New("not authorized")
|
||||||
|
|
||||||
// FindJobOrUserOrProject returns a job database ID or a username or a projectId if a job or user or project matches the search term.
|
// FindJobOrUserOrProject returns a job database ID or a username or a projectId if a job or user or project matches the search term.
|
||||||
// As 0 is a valid job id, check if username/projectId is "" instead in order to check what matched.
|
// As 0 is a valid job id, check if username/projectId is "" instead in order to check what matched.
|
||||||
// If nothing matches the search, `ErrNotFound` is returned.
|
// If nothing matches the search, `ErrNotFound` is returned.
|
||||||
|
|
||||||
// TO BE IMPROVED; Search by JobNAME
|
func (r *JobRepository) FindJobnameOrUserOrProject(ctx context.Context, searchterm string) (metasnip string, username string, project string, err error) {
|
||||||
|
|
||||||
func (r *JobRepository) FindJobOrUserOrProject(ctx context.Context, searchterm string) (job int64, username string, project string, err error) {
|
|
||||||
user := auth.GetUser(ctx)
|
user := auth.GetUser(ctx)
|
||||||
if id, err := strconv.Atoi(searchterm); err == nil {
|
if _, err := strconv.Atoi(searchterm); err == nil { // Return empty on successful conversion: parent method will redirect for integer jobId
|
||||||
qb := sq.Select("job.id").From("job").Where("job.job_id = ?", id)
|
return "", "", "", nil
|
||||||
if user != nil && !user.HasRole(auth.RoleAdmin) && !user.HasRole(auth.RoleSupport) {
|
} else { // has to have letters
|
||||||
qb = qb.Where("job.user = ?", user.Username)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := qb.RunWith(r.stmtCache).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) || user.HasRole(auth.RoleSupport) {
|
if user == nil || user.HasRole(auth.RoleAdmin) || user.HasRole(auth.RoleSupport) {
|
||||||
err := sq.Select("job.user").Distinct().From("job").
|
err := sq.Select("job.user").Distinct().From("job").
|
||||||
Where("job.user = ?", searchterm).
|
Where("job.user = ?", searchterm).
|
||||||
RunWith(r.stmtCache).QueryRow().Scan(&username)
|
RunWith(r.stmtCache).QueryRow().Scan(&username)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
return 0, "", "", err
|
return "", "", "", err
|
||||||
} else if err == nil {
|
} else if err == nil {
|
||||||
return 0, username, "", nil
|
return "", username, "", nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,13 +422,62 @@ func (r *JobRepository) FindJobOrUserOrProject(ctx context.Context, searchterm s
|
|||||||
Where("job.project = ?", searchterm).
|
Where("job.project = ?", searchterm).
|
||||||
RunWith(r.stmtCache).QueryRow().Scan(&project)
|
RunWith(r.stmtCache).QueryRow().Scan(&project)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
return 0, "", "", err
|
return "", "", "", err
|
||||||
} else if err == nil {
|
} else if err == nil {
|
||||||
return 0, "", project, nil
|
return "", "", project, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, "", "", ErrNotFound
|
// 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.HasRole(auth.RoleAdmin) || user.HasRole(auth.RoleSupport) {
|
||||||
|
err := sq.Select("job.user").Distinct().From("job").
|
||||||
|
Where("job.user = ?", 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 Username -> %s: Forbidden", user, username)
|
||||||
|
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, project)
|
||||||
|
return "", ErrForbidden
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *JobRepository) Partitions(cluster string) ([]string, error) {
|
func (r *JobRepository) Partitions(cluster string) ([]string, error) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { Icon, Button, InputGroup, Input, Collapse,
|
import { Icon, Button, InputGroup, Input, Collapse,
|
||||||
Navbar, NavbarBrand, Nav, NavItem, NavLink, NavbarToggler,
|
Navbar, NavbarBrand, Nav, NavItem, NavLink, NavbarToggler,
|
||||||
Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'sveltestrap'
|
Dropdown, DropdownToggle, DropdownMenu, DropdownItem, InputGroupText } from 'sveltestrap'
|
||||||
|
|
||||||
export let username // empty string if auth. is disabled, otherwise the username as string
|
export let username // empty string if auth. is disabled, otherwise the username as string
|
||||||
export let isAdmin // boolean
|
export let isAdmin // boolean
|
||||||
@ -55,8 +55,9 @@
|
|||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<form method="GET" action="/search">
|
<form method="GET" action="/search">
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<Input type="text" placeholder={isAdmin ? "Search jobId/username/projectId" : "Search jobId"} name="searchId"/>
|
<Input type="text" placeholder="Search 'type:<query>' ..." name="searchId"/>
|
||||||
<Button outline type="submit"><Icon name="search"/></Button>
|
<Button outline type="submit"><Icon name="search"/></Button>
|
||||||
|
<InputGroupText style="cursor:help;" title={isAdmin ? "Example: 'projectId:a100cd', Types are: jobId | jobName | projectId | username" : "Example: 'jobName:myjob', Types are jobId | jobName"}><Icon name="info-circle"/></InputGroupText>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</form>
|
</form>
|
||||||
{#if username}
|
{#if username}
|
||||||
|
Loading…
Reference in New Issue
Block a user