diff --git a/cmd/cc-backend/main.go b/cmd/cc-backend/main.go index 9d084f2..ed471ac 100644 --- a/cmd/cc-backend/main.go +++ b/cmd/cc-backend/main.go @@ -76,6 +76,9 @@ const configString = ` "kind": "file", "path": "./var/job-archive" }, + "jwts": { + "max-age": "2000h" + }, "clusters": [ { "name": "name", @@ -115,15 +118,15 @@ func initEnv() { os.Exit(0) } - if err := os.WriteFile("config.json", []byte(configString), 0666); err != nil { + if err := os.WriteFile("config.json", []byte(configString), 0o666); err != nil { log.Fatalf("Writing config.json failed: %s", err.Error()) } - if err := os.WriteFile(".env", []byte(envString), 0666); err != nil { + if err := os.WriteFile(".env", []byte(envString), 0o666); err != nil { log.Fatalf("Writing .env failed: %s", err.Error()) } - if err := os.Mkdir("var", 0777); err != nil { + if err := os.Mkdir("var", 0o777); err != nil { log.Fatalf("Mkdir var failed: %s", err.Error()) } diff --git a/internal/metricdata/metricdata.go b/internal/metricdata/metricdata.go index a93b1ac..f54ae41 100644 --- a/internal/metricdata/metricdata.go +++ b/internal/metricdata/metricdata.go @@ -109,8 +109,8 @@ func LoadData(job *schema.Job, jd, err = repo.LoadData(job, metrics, scopes, ctx) if err != nil { if len(jd) != 0 { - log.Errorf("partial error: %s", err.Error()) - return err, 0, 0 + log.Warnf("partial error: %s", err.Error()) + // return err, 0, 0 // Reactivating will block archiving on one partial error } else { log.Error("Error while loading job data from metric repository") return err, 0, 0 diff --git a/internal/repository/job.go b/internal/repository/job.go index ce5e416..b42598d 100644 --- a/internal/repository/job.go +++ b/internal/repository/job.go @@ -520,7 +520,7 @@ func (r *JobRepository) archivingWorker() { // not using meta data, called to load JobMeta into Cache? // will fail if job meta not in repository if _, err := r.FetchMetadata(job); err != nil { - log.Errorf("archiving job (dbid: %d) failed: %s", job.ID, err.Error()) + log.Errorf("archiving job (dbid: %d) failed at check metadata step: %s", job.ID, err.Error()) r.UpdateMonitoringStatus(job.ID, schema.MonitoringStatusArchivingFailed) continue } @@ -529,14 +529,14 @@ func (r *JobRepository) archivingWorker() { // TODO: Maybe use context with cancel/timeout here jobMeta, err := metricdata.ArchiveJob(job, context.Background()) if err != nil { - log.Errorf("archiving job (dbid: %d) failed: %s", job.ID, err.Error()) + log.Errorf("archiving job (dbid: %d) failed at archiving job step: %s", job.ID, err.Error()) r.UpdateMonitoringStatus(job.ID, schema.MonitoringStatusArchivingFailed) continue } // Update the jobs database entry one last time: if err := r.MarkArchived(job.ID, schema.MonitoringStatusArchivingSuccessful, jobMeta.Statistics); err != nil { - log.Errorf("archiving job (dbid: %d) failed: %s", job.ID, err.Error()) + log.Errorf("archiving job (dbid: %d) failed at marking archived step: %s", job.ID, err.Error()) continue } log.Debugf("archiving job %d took %s", job.JobID, time.Since(start)) diff --git a/internal/repository/query.go b/internal/repository/query.go index 94aa742..5ca98fb 100644 --- a/internal/repository/query.go +++ b/internal/repository/query.go @@ -236,6 +236,9 @@ func buildStringCondition(field string, cond *model.StringInput, query sq.Select } func buildMetaJsonCondition(jsonField string, cond *model.StringInput, query sq.SelectBuilder) sq.SelectBuilder { + // Verify and Search Only in Valid Jsons + query = query.Where("JSON_VALID(meta_data)") + // add "AND" Sql query Block for field match if cond.Eq != nil { return query.Where("JSON_EXTRACT(meta_data, \"$."+jsonField+"\") = ?", *cond.Eq) } diff --git a/internal/routerConfig/routes.go b/internal/routerConfig/routes.go index fe374ac..1dd6dee 100644 --- a/internal/routerConfig/routes.go +++ b/internal/routerConfig/routes.go @@ -302,11 +302,19 @@ func HandleSearchBar(rw http.ResponseWriter, r *http.Request, buildInfo web.Buil case "jobId": http.Redirect(rw, r, "/monitoring/jobs/?jobId="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusFound) // All Users: Redirect to Tablequery case "jobName": - http.Redirect(rw, r, "/monitoring/jobs/?jobName="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusFound) // All Users: Redirect to Tablequery + // Add Last 30 Days to migitate timeouts + untilTime := strconv.FormatInt(time.Now().Unix(), 10) + fromTime := strconv.FormatInt((time.Now().Unix() - int64(30*24*3600)), 10) + + http.Redirect(rw, r, "/monitoring/jobs/?startTime="+fromTime+"-"+untilTime+"&jobName="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusFound) // All Users: Redirect to Tablequery case "projectId": http.Redirect(rw, r, "/monitoring/jobs/?projectMatch=eq&project="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusFound) // All Users: Redirect to Tablequery case "arrayJobId": - http.Redirect(rw, r, "/monitoring/jobs/?arrayJobId="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusFound) // All Users: Redirect to Tablequery + // Add Last 30 Days to migitate timeouts + untilTime := strconv.FormatInt(time.Now().Unix(), 10) + fromTime := strconv.FormatInt((time.Now().Unix() - int64(30*24*3600)), 10) + + http.Redirect(rw, r, "/monitoring/jobs/?startTime="+fromTime+"-"+untilTime+"&arrayJobId="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusFound) // All Users: Redirect to Tablequery case "username": if user.HasAnyRole([]schema.Role{schema.RoleAdmin, schema.RoleSupport, schema.RoleManager}) { http.Redirect(rw, r, "/monitoring/users/?user="+url.QueryEscape(strings.Trim(splitSearch[1], " ")), http.StatusFound) @@ -339,7 +347,11 @@ func HandleSearchBar(rw http.ResponseWriter, r *http.Request, buildInfo web.Buil } else if project != "" { http.Redirect(rw, r, "/monitoring/jobs/?projectMatch=eq&project="+url.QueryEscape(project), http.StatusFound) // projectId (equal) } else if jobname != "" { - http.Redirect(rw, r, "/monitoring/jobs/?jobName="+url.QueryEscape(jobname), http.StatusFound) // JobName (contains) + // Add Last 30 Days to migitate timeouts + untilTime := strconv.FormatInt(time.Now().Unix(), 10) + fromTime := strconv.FormatInt((time.Now().Unix() - int64(30*24*3600)), 10) + + http.Redirect(rw, r, "/monitoring/jobs/?startTime="+fromTime+"-"+untilTime+"&jobName="+url.QueryEscape(jobname), http.StatusFound) // 30D Fitler + JobName (contains) } else { web.RenderTemplate(rw, "message.tmpl", &web.Page{Title: "Info", MsgType: "alert-info", Message: "Search without result", User: *user, Roles: availableRoles, Build: buildInfo}) } diff --git a/web/frontend/src/Job.root.svelte b/web/frontend/src/Job.root.svelte index 8d090ac..8cf8f87 100644 --- a/web/frontend/src/Job.root.svelte +++ b/web/frontend/src/Job.root.svelte @@ -165,10 +165,11 @@ .find((c) => c.name == job.cluster) .metricConfig.map((mc) => mc.name); - // Metric not found in JobMetrics && Metric not explicitly disabled: Was expected, but is Missing + // Metric not found in JobMetrics && Metric not explicitly disabled in config or deselected: Was expected, but is Missing missingMetrics = metricNames.filter( (metric) => !metrics.some((jm) => jm.name == metric) && + selectedMetrics.includes(metric) && !checkMetricDisabled( metric, $initq.data.job.cluster, @@ -306,9 +307,6 @@ {/if} - diff --git a/web/frontend/src/Jobs.root.svelte b/web/frontend/src/Jobs.root.svelte index 204a4e3..f7c99ff 100644 --- a/web/frontend/src/Jobs.root.svelte +++ b/web/frontend/src/Jobs.root.svelte @@ -14,7 +14,7 @@ import Refresher from "./joblist/Refresher.svelte"; import Sorting from "./joblist/SortSelection.svelte"; import MetricSelection from "./MetricSelection.svelte"; - import UserOrProject from "./filters/UserOrProject.svelte"; + import TextFilter from "./filters/TextFilter.svelte"; const { query: initq } = init(); @@ -38,6 +38,7 @@ ? !!ccconfig[`plot_list_showFootprint:${filterPresets.cluster}`] : !!ccconfig.plot_list_showFootprint; let selectedCluster = filterPresets?.cluster ? filterPresets.cluster : null; + let presetProject = filterPresets?.project ? filterPresets.project : "" // The filterPresets are handled by the Filters component, // so we need to wait for it to be ready before we can start a query. @@ -86,7 +87,8 @@ - filterComponent.update(detail)} diff --git a/web/frontend/src/Metric.svelte b/web/frontend/src/Metric.svelte index 6022ffb..a3bedaa 100644 --- a/web/frontend/src/Metric.svelte +++ b/web/frontend/src/Metric.svelte @@ -33,8 +33,17 @@ error = null; let selectedScope = minScope(scopes); + let statsPattern = /(.*)-stats$/ + let statsSeries = rawData.map((data) => data?.statisticsSeries ? data.statisticsSeries : null) + let selectedScopeIndex + $: availableScopes = scopes; - $: selectedScopeIndex = scopes.findIndex((s) => s == selectedScope); + $: patternMatches = statsPattern.exec(selectedScope) + $: if (!patternMatches) { + selectedScopeIndex = scopes.findIndex((s) => s == selectedScope); + } else { + selectedScopeIndex = scopes.findIndex((s) => s == patternMatches[1]); + } $: data = rawData[selectedScopeIndex]; $: series = data?.series.filter( (series) => selectedHost == null || series.hostname == selectedHost, @@ -62,6 +71,7 @@ if (jm.scope != "node") { scopes = [...scopes, jm.scope]; rawData.push(jm.metric); + statsSeries = rawData.map((data) => data?.statisticsSeries ? data.statisticsSeries : null) selectedScope = jm.scope; selectedScopeIndex = scopes.findIndex((s) => s == jm.scope); dispatch("more-loaded", jm); @@ -79,15 +89,18 @@ : "") + (metricConfig?.unit?.base ? metricConfig.unit.base : "")}) {#if job.resources.length > 1} - {#each job.resources as { hostname }} @@ -100,7 +113,7 @@ {:else if error != null} {error.message} - {:else if series != null} + {:else if series != null && !patternMatches} + {:else if statsSeries[selectedScopeIndex] != null && patternMatches} + {/if} {/key} diff --git a/web/frontend/src/Status.root.svelte b/web/frontend/src/Status.root.svelte index 4121ead..48c3711 100644 --- a/web/frontend/src/Status.root.svelte +++ b/web/frontend/src/Status.root.svelte @@ -315,20 +315,11 @@ - - + +

Current utilization of cluster "{cluster}"

- - {#if $initq.fetching || $mainQuery.fetching} - - {:else if $initq.error} - {$initq.error.message} - {:else} - - {/if} - - + - + { @@ -347,6 +338,17 @@ />
+ + + {#if $initq.fetching || $mainQuery.fetching} + + {:else if $initq.error} + {$initq.error.message} + {:else} + + {/if} + + {#if $mainQuery.error} @@ -361,8 +363,8 @@ {#if $initq.data && $mainQuery.data} {#each $initq.data.clusters.find((c) => c.name == cluster).subClusters as subCluster, i} - - + + SubCluster "{subCluster.name}" @@ -433,7 +435,7 @@ - +
{#key $mainQuery.data.nodeMetrics} - +

@@ -580,7 +582,7 @@
- +
{#key $mainQuery.data.stats} @@ -610,7 +612,7 @@ {/key} - +
{#key $mainQuery.data.stats} @@ -642,7 +644,7 @@
{#if metricsInHistograms} - + {#key $mainQuery.data.stats[0].histMetrics} + filterComponent.update(detail)} + /> + + jobList.refresh()} /> diff --git a/web/frontend/src/config/admin/ShowUsersRow.svelte b/web/frontend/src/config/admin/ShowUsersRow.svelte index 782ea56..c79e292 100644 --- a/web/frontend/src/config/admin/ShowUsersRow.svelte +++ b/web/frontend/src/config/admin/ShowUsersRow.svelte @@ -18,7 +18,7 @@ {user.username} {user.name} -{user.projects} +{user.projects} {user.email} {user?.roles ? user.roles.join(", ") : "No Roles"} diff --git a/web/frontend/src/filters/Filters.svelte b/web/frontend/src/filters/Filters.svelte index 8e7a8ef..7253ff7 100644 --- a/web/frontend/src/filters/Filters.svelte +++ b/web/frontend/src/filters/Filters.svelte @@ -193,7 +193,8 @@ opts.push(`userMatch=${filters.userMatch}`); if (filters.project) opts.push(`project=${filters.project}`); if (filters.jobName) opts.push(`jobName=${filters.jobName}`); - if (filters.projectMatch != "contains") + if (filters.arrayJobId) opts.push(`arrayJobId=${filters.arrayJobId}`); + if (filters.project && filters.projectMatch != "contains") opts.push(`projectMatch=${filters.projectMatch}`); if (opts.length == 0 && window.location.search.length <= 1) return; diff --git a/web/frontend/src/filters/TextFilter.svelte b/web/frontend/src/filters/TextFilter.svelte new file mode 100644 index 0000000..db1f184 --- /dev/null +++ b/web/frontend/src/filters/TextFilter.svelte @@ -0,0 +1,101 @@ + + + + + termChanged()} + on:keyup={(event) => termChanged(event.key == "Enter" ? 0 : throttle)} + placeholder={presetProject ? `Filter ${mode} in ${scrambleNames ? scramble(presetProject) : presetProject} ...` : `Filter ${mode} ...`} + /> + {#if presetProject} + + {/if} + + diff --git a/web/frontend/src/filters/UserOrProject.svelte b/web/frontend/src/filters/UserOrProject.svelte deleted file mode 100644 index 983192c..0000000 --- a/web/frontend/src/filters/UserOrProject.svelte +++ /dev/null @@ -1,84 +0,0 @@ - - -{#if authlevel >= roles.manager} - - - termChanged()} - on:keyup={(event) => termChanged(event.key == "Enter" ? 0 : throttle)} - placeholder={mode == "user" ? "filter username..." : "filter project..."} - /> - -{:else} - - - termChanged()} - on:keyup={(event) => termChanged(event.key == "Enter" ? 0 : throttle)} - placeholder="filter project..." - /> - -{/if}