mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-10-31 07:55:06 +01:00 
			
		
		
		
	Centralize project filter for manager role
- Remove all unnecessary frontend code for managerfilters
This commit is contained in:
		| @@ -202,7 +202,6 @@ input JobFilter { | ||||
|   arrayJobId:   Int | ||||
|   user:         StringInput | ||||
|   project:      StringInput | ||||
|   multiProject: [String] | ||||
|   cluster:      StringInput | ||||
|   partition:    StringInput | ||||
|   duration:     IntRange | ||||
|   | ||||
| @@ -1583,7 +1583,6 @@ input JobFilter { | ||||
|   arrayJobId:   Int | ||||
|   user:         StringInput | ||||
|   project:      StringInput | ||||
|   multiProject: [String] | ||||
|   cluster:      StringInput | ||||
|   partition:    StringInput | ||||
|   duration:     IntRange | ||||
| @@ -10390,7 +10389,7 @@ func (ec *executionContext) unmarshalInputJobFilter(ctx context.Context, obj int | ||||
| 		asMap[k] = v | ||||
| 	} | ||||
|  | ||||
| 	fieldsInOrder := [...]string{"tags", "jobId", "arrayJobId", "user", "project", "multiProject", "cluster", "partition", "duration", "minRunningFor", "numNodes", "numAccelerators", "numHWThreads", "startTime", "state", "flopsAnyAvg", "memBwAvg", "loadAvg", "memUsedMax"} | ||||
| 	fieldsInOrder := [...]string{"tags", "jobId", "arrayJobId", "user", "project", "cluster", "partition", "duration", "minRunningFor", "numNodes", "numAccelerators", "numHWThreads", "startTime", "state", "flopsAnyAvg", "memBwAvg", "loadAvg", "memUsedMax"} | ||||
| 	for _, k := range fieldsInOrder { | ||||
| 		v, ok := asMap[k] | ||||
| 		if !ok { | ||||
| @@ -10437,14 +10436,6 @@ func (ec *executionContext) unmarshalInputJobFilter(ctx context.Context, obj int | ||||
| 			if err != nil { | ||||
| 				return it, err | ||||
| 			} | ||||
| 		case "multiProject": | ||||
| 			var err error | ||||
|  | ||||
| 			ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("multiProject")) | ||||
| 			it.MultiProject, err = ec.unmarshalOString2ᚕᚖstring(ctx, v) | ||||
| 			if err != nil { | ||||
| 				return it, err | ||||
| 			} | ||||
| 		case "cluster": | ||||
| 			var err error | ||||
|  | ||||
| @@ -14591,38 +14582,6 @@ func (ec *executionContext) marshalOString2ᚕstringᚄ(ctx context.Context, sel | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| func (ec *executionContext) unmarshalOString2ᚕᚖstring(ctx context.Context, v interface{}) ([]*string, error) { | ||||
| 	if v == nil { | ||||
| 		return nil, nil | ||||
| 	} | ||||
| 	var vSlice []interface{} | ||||
| 	if v != nil { | ||||
| 		vSlice = graphql.CoerceList(v) | ||||
| 	} | ||||
| 	var err error | ||||
| 	res := make([]*string, len(vSlice)) | ||||
| 	for i := range vSlice { | ||||
| 		ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) | ||||
| 		res[i], err = ec.unmarshalOString2ᚖstring(ctx, vSlice[i]) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	return res, nil | ||||
| } | ||||
|  | ||||
| func (ec *executionContext) marshalOString2ᚕᚖstring(ctx context.Context, sel ast.SelectionSet, v []*string) graphql.Marshaler { | ||||
| 	if v == nil { | ||||
| 		return graphql.Null | ||||
| 	} | ||||
| 	ret := make(graphql.Array, len(v)) | ||||
| 	for i := range v { | ||||
| 		ret[i] = ec.marshalOString2ᚖstring(ctx, sel, v[i]) | ||||
| 	} | ||||
|  | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| func (ec *executionContext) unmarshalOString2ᚖstring(ctx context.Context, v interface{}) (*string, error) { | ||||
| 	if v == nil { | ||||
| 		return nil, nil | ||||
|   | ||||
| @@ -42,7 +42,6 @@ type JobFilter struct { | ||||
| 	ArrayJobID      *int              `json:"arrayJobId"` | ||||
| 	User            *StringInput      `json:"user"` | ||||
| 	Project         *StringInput      `json:"project"` | ||||
| 	MultiProject    []*string         `json:"multiProject"` | ||||
| 	Cluster         *StringInput      `json:"cluster"` | ||||
| 	Partition       *StringInput      `json:"partition"` | ||||
| 	Duration        *schema.IntRange  `json:"duration"` | ||||
|   | ||||
| @@ -101,13 +101,18 @@ func (r *JobRepository) CountJobs( | ||||
|  | ||||
| func SecurityCheck(ctx context.Context, query sq.SelectBuilder) (queryOut sq.SelectBuilder, err error) { | ||||
| 	user := auth.GetUser(ctx) | ||||
| 	if user == nil || user.HasAnyRole([]string{auth.RoleAdmin, auth.RoleSupport, auth.RoleApi}) { | ||||
| 	if user == nil || user.HasAnyRole([]string{auth.RoleAdmin, auth.RoleSupport, auth.RoleApi}) { // Admin & Co. : All jobs | ||||
| 		return query, nil | ||||
| 	} else if user.HasRole(auth.RoleManager) { // Manager (Might be doublefiltered by frontend: should not matter) | ||||
| 		return query.Where(sq.Or{sq.Eq{"job.project": user.Projects}}), nil // Only Jobs from manages projects | ||||
| 	} else if user.HasRole(auth.RoleUser) { // User | ||||
| 	} else if user.HasRole(auth.RoleManager) { // Manager : Add filter for managed projects' jobs only + personal jobs | ||||
| 		if len(user.Projects) != 0 { | ||||
| 			return query.Where(sq.Or{sq.Eq{"job.project": user.Projects}, sq.Eq{"job.user": user.Username}}), nil | ||||
| 		} else { | ||||
| 			log.Infof("Manager-User '%s' has no defined projects to lookup! Query only personal jobs ...", user.Username) | ||||
| 			return query.Where("job.user = ?", user.Username), nil | ||||
| 		} | ||||
| 	} else if user.HasRole(auth.RoleUser) { // User : Only personal jobs | ||||
| 		return query.Where("job.user = ?", user.Username), nil | ||||
| 	} else { // Unauthorized | ||||
| 	} else { // Unauthorized : Error | ||||
| 		var qnil sq.SelectBuilder | ||||
| 		return qnil, errors.New(fmt.Sprintf("User '%s' with unknown roles! [%#v]\n", user.Username, user.Roles)) | ||||
| 	} | ||||
| @@ -130,13 +135,6 @@ func BuildWhereClause(filter *model.JobFilter, query sq.SelectBuilder) sq.Select | ||||
| 	if filter.Project != nil { | ||||
| 		query = buildStringCondition("job.project", filter.Project, query) | ||||
| 	} | ||||
| 	if filter.MultiProject != nil { | ||||
| 		queryProjs := make([]string, len(filter.MultiProject)) | ||||
| 		for i, val := range filter.MultiProject { | ||||
| 			queryProjs[i] = *val | ||||
| 		} | ||||
| 		query = query.Where(sq.Or{sq.Eq{"job.project": queryProjs}}) | ||||
| 	} | ||||
| 	if filter.Cluster != nil { | ||||
| 		query = buildStringCondition("job.cluster", filter.Cluster, query) | ||||
| 	} | ||||
|   | ||||
| @@ -188,9 +188,6 @@ func buildFilterPresets(query url.Values) map[string]interface{} { | ||||
| 		filterPresets["project"] = query.Get("project") | ||||
| 		filterPresets["projectMatch"] = "eq" | ||||
| 	} | ||||
| 	if len(query["multiProject"]) != 0 { | ||||
| 		filterPresets["multiProject"] = query["multiProject"] | ||||
| 	} | ||||
| 	if query.Get("user") != "" { | ||||
| 		filterPresets["user"] = query.Get("user") | ||||
| 		filterPresets["userMatch"] = "eq" | ||||
| @@ -282,17 +279,15 @@ func SetupRoutes(router *mux.Router, version string, hash string, buildTime stri | ||||
| 			} | ||||
|  | ||||
| 			username, authLevel := "", 0 | ||||
| 			var projects []string | ||||
|  | ||||
| 			if user := auth.GetUser(r.Context()); user != nil { | ||||
| 				username = user.Username | ||||
| 				projects = user.Projects | ||||
| 				authLevel = user.GetAuthLevel() | ||||
| 			} | ||||
|  | ||||
| 			page := web.Page{ | ||||
| 				Title:  title, | ||||
| 				User:   web.User{Username: username, Projects: projects, AuthLevel: authLevel}, | ||||
| 				User:   web.User{Username: username, AuthLevel: authLevel}, | ||||
| 				Build:  web.Build{Version: version, Hash: hash, Buildtime: buildTime}, | ||||
| 				Config: conf, | ||||
| 				Infos:  infos, | ||||
|   | ||||
| @@ -14,8 +14,6 @@ | ||||
|     const ccconfig = getContext('cc-config') | ||||
|  | ||||
|     export let filterPresets = {} | ||||
|     export let projects = [] | ||||
|     export let isManager = false | ||||
|  | ||||
|     let filters, jobList, matchedJobs = null | ||||
|     let sorting = { field: 'startTime', order: 'DESC' }, isSortingOpen = false, isMetricsSelectionOpen = false | ||||
| @@ -72,8 +70,6 @@ | ||||
| <Row> | ||||
|     <Col> | ||||
|         <JobList | ||||
|             projects={projects} | ||||
|             isManager={isManager} | ||||
|             bind:metrics={metrics} | ||||
|             bind:sorting={sorting} | ||||
|             bind:matchedJobs={matchedJobs} | ||||
|   | ||||
| @@ -14,21 +14,9 @@ | ||||
|  | ||||
|     export let type | ||||
|     export let filterPresets | ||||
|     export let projects = [] | ||||
|     export let isManager = false | ||||
|  | ||||
|     console.assert(type == 'USER' || type == 'PROJECT', 'Invalid list type provided!') | ||||
|  | ||||
|     let projectsFilter = null | ||||
|     //Setup default filter | ||||
|     if (type == 'USER' && isManager == true && projects.length == 0) { | ||||
|         projectsFilter = { project: {eq: "noProjectForManager"} } | ||||
|     } else if (type == 'USER' && isManager == true && projects.length == 1) { | ||||
|         projectsFilter = { project: {eq: projects[0]} } | ||||
|     } else { | ||||
|         projectsFilter = { multiProject: projects } | ||||
|     } | ||||
|  | ||||
|     const stats = operationStore(`query($filter: [JobFilter!]!) { | ||||
|         rows: jobsStatistics(filter: $filter, groupBy: ${type}) { | ||||
|             id | ||||
| @@ -90,9 +78,6 @@ | ||||
|             menuText="Only {type.toLowerCase()}s with jobs that match the filters will show up" | ||||
|             on:update={({ detail }) => { | ||||
|                 $stats.variables = { filter: detail.filters } | ||||
|                 if (projectsFilter != null) { | ||||
|                   $stats.variables.filter.push(projectsFilter) | ||||
|                 } | ||||
|                 $stats.context.pause = false | ||||
|                 $stats.reexecute() | ||||
|             }} /> | ||||
|   | ||||
| @@ -45,7 +45,6 @@ | ||||
|         arrayJobId:   filterPresets.arrayJobId   || null, | ||||
|         user:         filterPresets.user         || '', | ||||
|         project:      filterPresets.project      || '', | ||||
|         multiProject: filterPresets.multiProject || [], | ||||
|  | ||||
|         numNodes:         filterPresets.numNodes         || { from: null, to: null }, | ||||
|         numHWThreads:     filterPresets.numHWThreads     || { from: null, to: null }, | ||||
| @@ -95,8 +94,6 @@ | ||||
|             items.push({ user: { [filters.userMatch]: filters.user } }) | ||||
|         if (filters.project) | ||||
|             items.push({ project: { [filters.projectMatch]: filters.project } }) | ||||
|         if (filters.multiProject.length != 0) | ||||
|             items.push({ multiProject: filters.multiProject }) | ||||
|         for (let stat of filters.stats) | ||||
|             items.push({ [stat.field]: { from: stat.from, to: stat.to } }) | ||||
|  | ||||
| @@ -132,9 +129,6 @@ | ||||
|             opts.push(`userMatch=${filters.userMatch}`) | ||||
|         if (filters.project) | ||||
|             opts.push(`project=${filters.project}`) | ||||
|         if (filters.multiProject.length != 0) | ||||
|             for (let singleProj of filters.multiProject) | ||||
|                 opts.push(`multiProject=${singleProj}`) | ||||
|         if (filters.projectMatch != 'contains') | ||||
|             opts.push(`projectMatch=${filters.projectMatch}`) | ||||
|  | ||||
|   | ||||
| @@ -23,25 +23,12 @@ | ||||
|     export let sorting = { field: "startTime", order: "DESC" } | ||||
|     export let matchedJobs = 0 | ||||
|     export let metrics = ccconfig.plot_list_selectedMetrics | ||||
|     export let projects = [] | ||||
|     export let isManager | ||||
|  | ||||
|     let itemsPerPage = ccconfig.plot_list_jobsPerPage | ||||
|     let page = 1 | ||||
|     let paging = { itemsPerPage, page } | ||||
|     let filter = [] | ||||
|  | ||||
|     //Setup default filter | ||||
|     if (isManager == true && projects.length == 0) { | ||||
|         filter.push({ project: {eq: "noProjectForManager"} }) | ||||
|     } else if (isManager == true && projects.length == 1) { | ||||
|         filter.push({ project: {eq: projects[0]} }) | ||||
|     } else { | ||||
|         filter.push({ multiProject: projects }) | ||||
|     } | ||||
|   | ||||
|  | ||||
|  | ||||
|     const jobs = operationStore(` | ||||
|     query($filter: [JobFilter!]!, $sorting: OrderByInput!, $paging: PageRequest! ){ | ||||
|         jobs(filter: $filter, order: $sorting, page: $paging) { | ||||
| @@ -81,15 +68,6 @@ | ||||
|                 filters.push({ minRunningFor }) | ||||
|             } | ||||
|  | ||||
|             // (Re-)Add Manager-Filter | ||||
|             if (isManager == true && projects.length == 0) { | ||||
|                 filter.push({ project: {eq: "noProjectForManager"} }) | ||||
|             } else if (isManager == true && projects.length == 1) { | ||||
|                 filter.push({ project: {eq: projects[0]} }) | ||||
|             } else { | ||||
|                 filter.push({ multiProject: projects }) | ||||
|             } | ||||
|  | ||||
|             $jobs.variables.filter = filters | ||||
|             // console.log('filters:', ...filters.map(f => Object.entries(f)).flat(2)) | ||||
|         } | ||||
|   | ||||
| @@ -5,8 +5,6 @@ new Jobs({ | ||||
|     target: document.getElementById('svelte-app'), | ||||
|     props: { | ||||
|         filterPresets: filterPresets, | ||||
|         projects: projects, | ||||
|         isManager: isManager | ||||
|     }, | ||||
|     context: new Map([ | ||||
|             ['cc-config', clusterCockpitConfig] | ||||
|   | ||||
| @@ -6,8 +6,6 @@ new List({ | ||||
|     props: { | ||||
|         filterPresets: filterPresets, | ||||
|         type: listType, | ||||
|         projects: projects, | ||||
|         isManager: isManager | ||||
|     }, | ||||
|     context: new Map([ | ||||
|             ['cc-config', clusterCockpitConfig] | ||||
|   | ||||
| @@ -16,7 +16,6 @@ | ||||
|         <script> | ||||
|             const header = { | ||||
|                 "username": "{{ .User.Username }}", | ||||
|                 "projects":  {{ .User.Projects }}, | ||||
|                 "authlevel": {{ .User.AuthLevel }}, | ||||
|                 "clusters":  {{ .Clusters }}, | ||||
|             }; | ||||
|   | ||||
| @@ -10,8 +10,6 @@ | ||||
|     <script> | ||||
|         const filterPresets = {{ .FilterPresets }}; | ||||
|         const clusterCockpitConfig = {{ .Config }}; | ||||
|         const projects = {{ .User.Projects }}; | ||||
|         const isManager = {{ eq .User.AuthLevel 3 }}; | ||||
|     </script> | ||||
|     <script src='/build/jobs.js'></script> | ||||
| {{end}} | ||||
|   | ||||
| @@ -10,8 +10,6 @@ | ||||
|         const listType = {{ .Infos.listType }}; | ||||
|         const filterPresets = {{ .FilterPresets }}; | ||||
|         const clusterCockpitConfig = {{ .Config }}; | ||||
|         const projects = {{ .User.Projects }}; | ||||
|         const isManager = {{ eq .User.AuthLevel 3 }}; | ||||
|     </script> | ||||
|     <script src='/build/list.js'></script> | ||||
| {{end}} | ||||
|   | ||||
| @@ -54,9 +54,8 @@ func init() { | ||||
| } | ||||
|  | ||||
| type User struct { | ||||
| 	Username  string   // Username of the currently logged in user | ||||
| 	Projects  []string // Project(s) of the user (relevant for managers only) | ||||
| 	AuthLevel int      // Level of authorization | ||||
| 	Username  string // Username of the currently logged in user | ||||
| 	AuthLevel int    // Level of authorization | ||||
| } | ||||
|  | ||||
| type Build struct { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user