2022-07-29 06:29:21 +02:00
// Copyright (C) 2022 NHR@FAU, University Erlangen-Nuremberg.
// All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2022-06-21 17:52:36 +02:00
package routerConfig
2022-02-03 07:25:36 +01:00
import (
2022-03-24 10:35:52 +01:00
"fmt"
2022-02-03 07:25:36 +01:00
"net/http"
2022-02-03 11:35:42 +01:00
"net/url"
"strconv"
2022-02-03 07:25:36 +01:00
"strings"
2022-02-28 11:08:43 +01:00
"time"
2022-02-03 07:25:36 +01:00
2023-02-09 10:11:11 +01:00
"github.com/ClusterCockpit/cc-backend/internal/api"
2023-02-17 10:45:27 +01:00
"github.com/ClusterCockpit/cc-backend/internal/auth"
2022-06-21 17:52:36 +02:00
"github.com/ClusterCockpit/cc-backend/internal/graph"
"github.com/ClusterCockpit/cc-backend/internal/graph/model"
"github.com/ClusterCockpit/cc-backend/internal/repository"
2022-09-05 17:46:38 +02:00
"github.com/ClusterCockpit/cc-backend/pkg/archive"
2022-06-21 17:52:36 +02:00
"github.com/ClusterCockpit/cc-backend/pkg/log"
"github.com/ClusterCockpit/cc-backend/pkg/schema"
2022-07-06 15:00:08 +02:00
"github.com/ClusterCockpit/cc-backend/web"
2022-02-03 07:25:36 +01:00
"github.com/gorilla/mux"
)
type InfoType map [ string ] interface { }
type Route struct {
Route string
Template string
Title string
Filter bool
Setup func ( i InfoType , r * http . Request ) InfoType
}
2022-03-15 08:29:29 +01:00
var routes [ ] Route = [ ] Route {
{ "/" , "home.tmpl" , "ClusterCockpit" , false , setupHomeRoute } ,
{ "/config" , "config.tmpl" , "Settings" , false , func ( i InfoType , r * http . Request ) InfoType { return i } } ,
{ "/monitoring/jobs/" , "monitoring/jobs.tmpl" , "Jobs - ClusterCockpit" , true , func ( i InfoType , r * http . Request ) InfoType { return i } } ,
{ "/monitoring/job/{id:[0-9]+}" , "monitoring/job.tmpl" , "Job <ID> - ClusterCockpit" , false , setupJobRoute } ,
{ "/monitoring/users/" , "monitoring/list.tmpl" , "Users - ClusterCockpit" , true , func ( i InfoType , r * http . Request ) InfoType { i [ "listType" ] = "USER" ; return i } } ,
{ "/monitoring/projects/" , "monitoring/list.tmpl" , "Projects - ClusterCockpit" , true , func ( i InfoType , r * http . Request ) InfoType { i [ "listType" ] = "PROJECT" ; return i } } ,
{ "/monitoring/tags/" , "monitoring/taglist.tmpl" , "Tags - ClusterCockpit" , false , setupTaglistRoute } ,
{ "/monitoring/user/{id}" , "monitoring/user.tmpl" , "User <ID> - ClusterCockpit" , true , setupUserRoute } ,
{ "/monitoring/systems/{cluster}" , "monitoring/systems.tmpl" , "Cluster <ID> - ClusterCockpit" , false , setupClusterRoute } ,
{ "/monitoring/node/{cluster}/{hostname}" , "monitoring/node.tmpl" , "Node <ID> - ClusterCockpit" , false , setupNodeRoute } ,
2023-02-01 15:46:50 +01:00
{ "/monitoring/analysis/{cluster}" , "monitoring/analysis.tmpl" , "Analysis - ClusterCockpit" , true , setupAnalysisRoute } ,
2022-03-24 10:35:52 +01:00
{ "/monitoring/status/{cluster}" , "monitoring/status.tmpl" , "Status of <ID> - ClusterCockpit" , false , setupClusterRoute } ,
2022-03-15 08:29:29 +01:00
}
func setupHomeRoute ( i InfoType , r * http . Request ) InfoType {
type cluster struct {
Name string
RunningJobs int
TotalJobs int
RecentShortJobs int
}
2022-09-05 17:46:38 +02:00
jobRepo := repository . GetJobRepository ( )
2022-03-15 08:29:29 +01:00
runningJobs , err := jobRepo . CountGroupedJobs ( r . Context ( ) , model . AggregateCluster , [ ] * model . JobFilter { {
State : [ ] schema . JobState { schema . JobStateRunning } ,
2022-03-25 10:20:33 +01:00
} } , nil , nil )
2022-03-15 08:29:29 +01:00
if err != nil {
2023-02-01 11:58:27 +01:00
log . Warnf ( "failed to count jobs: %s" , err . Error ( ) )
2022-03-15 08:29:29 +01:00
runningJobs = map [ string ] int { }
}
2022-03-25 10:20:33 +01:00
totalJobs , err := jobRepo . CountGroupedJobs ( r . Context ( ) , model . AggregateCluster , nil , nil , nil )
2022-03-15 08:29:29 +01:00
if err != nil {
2023-02-01 11:58:27 +01:00
log . Warnf ( "failed to count jobs: %s" , err . Error ( ) )
2022-03-15 08:29:29 +01:00
totalJobs = map [ string ] int { }
}
from := time . Now ( ) . Add ( - 24 * time . Hour )
recentShortJobs , err := jobRepo . CountGroupedJobs ( r . Context ( ) , model . AggregateCluster , [ ] * model . JobFilter { {
2022-09-07 12:24:45 +02:00
StartTime : & schema . TimeRange { From : & from , To : nil } ,
Duration : & schema . IntRange { From : 0 , To : graph . ShortJobDuration } ,
2022-03-25 10:20:33 +01:00
} } , nil , nil )
2022-03-15 08:29:29 +01:00
if err != nil {
2023-02-01 11:58:27 +01:00
log . Warnf ( "failed to count jobs: %s" , err . Error ( ) )
2022-03-15 08:29:29 +01:00
recentShortJobs = map [ string ] int { }
}
clusters := make ( [ ] cluster , 0 )
2022-09-05 17:46:38 +02:00
for _ , c := range archive . Clusters {
2022-03-15 08:29:29 +01:00
clusters = append ( clusters , cluster {
Name : c . Name ,
RunningJobs : runningJobs [ c . Name ] ,
TotalJobs : totalJobs [ c . Name ] ,
RecentShortJobs : recentShortJobs [ c . Name ] ,
} )
}
i [ "clusters" ] = clusters
return i
}
func setupJobRoute ( i InfoType , r * http . Request ) InfoType {
i [ "id" ] = mux . Vars ( r ) [ "id" ]
return i
}
func setupUserRoute ( i InfoType , r * http . Request ) InfoType {
2022-09-05 17:46:38 +02:00
jobRepo := repository . GetJobRepository ( )
2022-03-15 11:04:54 +01:00
username := mux . Vars ( r ) [ "id" ]
i [ "id" ] = username
i [ "username" ] = username
if user , _ := auth . FetchUser ( r . Context ( ) , jobRepo . DB , username ) ; user != nil {
i [ "name" ] = user . Name
i [ "email" ] = user . Email
}
2022-03-15 08:29:29 +01:00
return i
}
func setupClusterRoute ( i InfoType , r * http . Request ) InfoType {
vars := mux . Vars ( r )
i [ "id" ] = vars [ "cluster" ]
i [ "cluster" ] = vars [ "cluster" ]
from , to := r . URL . Query ( ) . Get ( "from" ) , r . URL . Query ( ) . Get ( "to" )
if from != "" || to != "" {
i [ "from" ] = from
i [ "to" ] = to
}
return i
}
func setupNodeRoute ( i InfoType , r * http . Request ) InfoType {
vars := mux . Vars ( r )
i [ "cluster" ] = vars [ "cluster" ]
i [ "hostname" ] = vars [ "hostname" ]
2022-03-24 10:35:52 +01:00
i [ "id" ] = fmt . Sprintf ( "%s (%s)" , vars [ "cluster" ] , vars [ "hostname" ] )
2022-03-15 08:29:29 +01:00
from , to := r . URL . Query ( ) . Get ( "from" ) , r . URL . Query ( ) . Get ( "to" )
if from != "" || to != "" {
i [ "from" ] = from
i [ "to" ] = to
}
return i
}
func setupAnalysisRoute ( i InfoType , r * http . Request ) InfoType {
i [ "cluster" ] = mux . Vars ( r ) [ "cluster" ]
return i
}
func setupTaglistRoute ( i InfoType , r * http . Request ) InfoType {
var username * string = nil
2022-09-05 17:46:38 +02:00
jobRepo := repository . GetJobRepository ( )
2022-03-15 08:29:29 +01:00
if user := auth . GetUser ( r . Context ( ) ) ; user != nil && ! user . HasRole ( auth . RoleAdmin ) {
username = & user . Username
}
tags , counts , err := jobRepo . CountTags ( username )
tagMap := make ( map [ string ] [ ] map [ string ] interface { } )
if err != nil {
2023-02-01 11:58:27 +01:00
log . Warnf ( "GetTags failed: %s" , err . Error ( ) )
2022-03-15 08:29:29 +01:00
i [ "tagmap" ] = tagMap
return i
}
for _ , tag := range tags {
tagItem := map [ string ] interface { } {
"id" : tag . ID ,
"name" : tag . Name ,
"count" : counts [ tag . Name ] ,
}
tagMap [ tag . Type ] = append ( tagMap [ tag . Type ] , tagItem )
}
i [ "tagmap" ] = tagMap
return i
}
2022-02-03 11:35:42 +01:00
func buildFilterPresets ( query url . Values ) map [ string ] interface { } {
filterPresets := map [ string ] interface { } { }
if query . Get ( "cluster" ) != "" {
filterPresets [ "cluster" ] = query . Get ( "cluster" )
}
if query . Get ( "partition" ) != "" {
filterPresets [ "partition" ] = query . Get ( "partition" )
}
if query . Get ( "project" ) != "" {
filterPresets [ "project" ] = query . Get ( "project" )
filterPresets [ "projectMatch" ] = "eq"
}
2023-01-11 16:25:02 +01:00
if query . Get ( "jobName" ) != "" {
filterPresets [ "jobName" ] = query . Get ( "jobName" )
}
2023-02-20 10:20:08 +01:00
if len ( query [ "user" ] ) != 0 {
if len ( query [ "user" ] ) == 1 {
filterPresets [ "user" ] = query . Get ( "user" )
filterPresets [ "userMatch" ] = "contains"
} else {
filterPresets [ "user" ] = query [ "user" ]
filterPresets [ "userMatch" ] = "in"
}
2022-02-03 16:00:08 +01:00
}
2022-03-21 13:30:19 +01:00
if len ( query [ "state" ] ) != 0 {
filterPresets [ "state" ] = query [ "state" ]
2022-02-03 11:35:42 +01:00
}
if rawtags , ok := query [ "tag" ] ; ok {
tags := make ( [ ] int , len ( rawtags ) )
for i , tid := range rawtags {
var err error
tags [ i ] , err = strconv . Atoi ( tid )
if err != nil {
tags [ i ] = - 1
}
}
filterPresets [ "tags" ] = tags
}
2022-03-21 13:30:19 +01:00
if query . Get ( "duration" ) != "" {
parts := strings . Split ( query . Get ( "duration" ) , "-" )
if len ( parts ) == 2 {
a , e1 := strconv . Atoi ( parts [ 0 ] )
b , e2 := strconv . Atoi ( parts [ 1 ] )
if e1 == nil && e2 == nil {
filterPresets [ "duration" ] = map [ string ] int { "from" : a , "to" : b }
}
}
}
2022-02-03 11:35:42 +01:00
if query . Get ( "numNodes" ) != "" {
parts := strings . Split ( query . Get ( "numNodes" ) , "-" )
if len ( parts ) == 2 {
a , e1 := strconv . Atoi ( parts [ 0 ] )
b , e2 := strconv . Atoi ( parts [ 1 ] )
if e1 == nil && e2 == nil {
filterPresets [ "numNodes" ] = map [ string ] int { "from" : a , "to" : b }
}
}
}
2022-03-21 13:30:19 +01:00
if query . Get ( "numAccelerators" ) != "" {
parts := strings . Split ( query . Get ( "numAccelerators" ) , "-" )
if len ( parts ) == 2 {
a , e1 := strconv . Atoi ( parts [ 0 ] )
b , e2 := strconv . Atoi ( parts [ 1 ] )
if e1 == nil && e2 == nil {
filterPresets [ "numAccelerators" ] = map [ string ] int { "from" : a , "to" : b }
}
}
}
2022-02-03 11:35:42 +01:00
if query . Get ( "jobId" ) != "" {
filterPresets [ "jobId" ] = query . Get ( "jobId" )
}
if query . Get ( "arrayJobId" ) != "" {
if num , err := strconv . Atoi ( query . Get ( "arrayJobId" ) ) ; err == nil {
filterPresets [ "arrayJobId" ] = num
}
}
2022-02-28 11:08:43 +01:00
if query . Get ( "startTime" ) != "" {
parts := strings . Split ( query . Get ( "startTime" ) , "-" )
if len ( parts ) == 2 {
a , e1 := strconv . ParseInt ( parts [ 0 ] , 10 , 64 )
b , e2 := strconv . ParseInt ( parts [ 1 ] , 10 , 64 )
if e1 == nil && e2 == nil {
filterPresets [ "startTime" ] = map [ string ] string {
"from" : time . Unix ( a , 0 ) . Format ( time . RFC3339 ) ,
"to" : time . Unix ( b , 0 ) . Format ( time . RFC3339 ) ,
}
}
}
}
2022-02-03 11:35:42 +01:00
return filterPresets
}
2022-09-27 09:56:17 +02:00
func SetupRoutes ( router * mux . Router , version string , hash string , buildTime string ) {
2022-09-05 17:46:38 +02:00
userCfgRepo := repository . GetUserCfgRepo ( )
2022-02-03 07:25:36 +01:00
for _ , route := range routes {
2022-02-03 11:35:42 +01:00
route := route
router . HandleFunc ( route . Route , func ( rw http . ResponseWriter , r * http . Request ) {
2022-09-12 13:34:21 +02:00
conf , err := userCfgRepo . GetUIConfig ( auth . GetUser ( r . Context ( ) ) )
2022-02-03 07:25:36 +01:00
if err != nil {
http . Error ( rw , err . Error ( ) , http . StatusInternalServerError )
return
}
2022-03-03 13:45:41 +01:00
title := route . Title
2022-02-15 10:03:09 +01:00
infos := route . Setup ( map [ string ] interface { } { } , r )
2022-02-03 10:25:52 +01:00
if id , ok := infos [ "id" ] ; ok {
2022-03-03 13:45:41 +01:00
title = strings . Replace ( route . Title , "<ID>" , id . ( string ) , 1 )
2022-02-03 11:35:42 +01:00
}
2022-08-23 13:33:25 +02:00
username , isAdmin , isSupporter := "" , true , true
2022-09-27 09:56:17 +02:00
2022-02-15 10:03:09 +01:00
if user := auth . GetUser ( r . Context ( ) ) ; user != nil {
username = user . Username
isAdmin = user . HasRole ( auth . RoleAdmin )
2022-08-23 13:33:25 +02:00
isSupporter = user . HasRole ( auth . RoleSupport )
2022-02-15 10:03:09 +01:00
}
2022-02-11 16:51:49 +01:00
2022-07-06 15:00:08 +02:00
page := web . Page {
2022-03-03 13:45:41 +01:00
Title : title ,
2022-08-23 13:33:25 +02:00
User : web . User { Username : username , IsAdmin : isAdmin , IsSupporter : isSupporter } ,
2022-09-27 09:56:17 +02:00
Build : web . Build { Version : version , Hash : hash , Buildtime : buildTime } ,
2022-02-03 11:35:42 +01:00
Config : conf ,
Infos : infos ,
}
if route . Filter {
page . FilterPresets = buildFilterPresets ( r . URL . Query ( ) )
2022-02-03 10:25:52 +01:00
}
2022-02-03 07:25:36 +01:00
2022-07-06 15:00:08 +02:00
web . RenderTemplate ( rw , r , route . Template , & page )
2022-02-03 07:25:36 +01:00
} )
}
}
2023-02-09 10:11:11 +01:00
func HandleSearchBar ( rw http . ResponseWriter , r * http . Request , api * api . RestApi ) {
if search := r . URL . Query ( ) . Get ( "searchId" ) ; search != "" {
splitSearch := strings . Split ( search , ":" )
2023-02-17 10:45:27 +01:00
if len ( splitSearch ) == 2 {
2023-02-09 10:11:11 +01:00
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
}
case "username" :
2023-02-17 10:45:27 +01:00
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
2023-02-09 10:11:11 +01:00
return
} else {
2023-02-17 10:45:27 +01:00
http . Redirect ( rw , r , "/monitoring/users/?user=NotFound" , http . StatusTemporaryRedirect ) // Workaround to display correctly empty table
}
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 {
2023-02-20 10:20:08 +01:00
joinedNames := strings . Join ( usernames , "&user=" )
http . Redirect ( rw , r , "/monitoring/users/?user=" + joinedNames , http . StatusTemporaryRedirect ) // > 1 Matches: Redirect to user table
2023-02-17 10:45:27 +01:00
return
} else {
http . Redirect ( rw , r , "/monitoring/users/?user=NotFound" , http . StatusTemporaryRedirect ) // Workaround to display correctly empty table
2023-02-09 10:11:11 +01:00
}
default :
http . Error ( rw , "'searchId' type parameter unknown" , http . StatusBadRequest )
}
2023-02-17 10:45:27 +01:00
} else if len ( splitSearch ) == 1 {
2023-02-09 10:11:11 +01:00
jobname , username , project , err := api . JobRepository . FindJobnameOrUserOrProject ( r . Context ( ) , strings . Trim ( search , " " ) ) // Determine Access within
if err != nil {
http . Error ( rw , err . Error ( ) , http . StatusInternalServerError )
return
}
if username != "" {
http . Redirect ( rw , r , "/monitoring/user/" + username , http . StatusTemporaryRedirect ) // User: Redirect to user page
return
2023-02-17 10:45:27 +01:00
} else if project != "" {
2023-02-09 10:11:11 +01:00
http . Redirect ( rw , r , "/monitoring/jobs/?projectMatch=eq&project=" + url . QueryEscape ( strings . Trim ( search , " " ) ) , http . StatusTemporaryRedirect ) // projectId (equal)
return
2023-02-17 10:45:27 +01:00
} else if jobname != "" {
2023-02-09 10:11:11 +01:00
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 )
}
} else {
http . Redirect ( rw , r , "/monitoring/jobs/?" , http . StatusTemporaryRedirect )
}
}