From 50d000e7e2f992e05e907ce27ce8cde0344b960e Mon Sep 17 00:00:00 2001 From: Jan Eitzinger Date: Sat, 27 Sep 2025 09:26:42 +0200 Subject: [PATCH] Implement UI config handling --- web/configSchema.go | 177 ++++++++++++++++++++++++++++++++++++++++++++ web/web.go | 120 ++++++++++++++++++++++++++++-- 2 files changed, 292 insertions(+), 5 deletions(-) create mode 100644 web/configSchema.go diff --git a/web/configSchema.go b/web/configSchema.go new file mode 100644 index 0000000..9e88e79 --- /dev/null +++ b/web/configSchema.go @@ -0,0 +1,177 @@ +// Copyright (C) NHR@FAU, University Erlangen-Nuremberg. +// All rights reserved. This file is part of cc-backend. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package web + +var configSchema = ` + { + "type": "object", + "properties": { + "jobList": { + "description": "Job list defaults. Applies to user- and jobs views.", + "type": "object", + "properties": { + "usePaging": { + "description": "If classic paging is used instead of continuous scrolling by default.", + "type": "boolean" + }, + "showFootprint": { + "description": "If footprint bars are shown as first column by default.", + "type": "boolean" + } + }, + "required": ["usePaging", "showFootprint"] + }, + "nodeList": { + "description": "Node list defaults. Applies to node list view.", + "type": "object", + "properties": { + "usePaging": { + "description": "If classic paging is used instead of continuous scrolling by default.", + "type": "boolean" + } + }, + "required": ["usePaging"] + }, + "jobView": { + "description": "Job view defaults.", + "type": "object", + "properties": { + "showPolarPlot": { + "description": "If the job metric footprints polar plot is shown by default.", + "type": "boolean" + }, + "showFootprint": { + "description": "If the annotated job metric footprint bars are shown by default.", + "type": "boolean" + }, + "showRoofline": { + "description": "If the job roofline plot is shown by default.", + "type": "boolean" + }, + "showStatTable": { + "description": "If the job metric statistics table is shown by default.", + "type": "boolean" + } + }, + "required": ["showFootprint"] + }, + "metricConfig": { + "description": "Global initial metric selections for primary views of all clusters.", + "type": "object", + "properties": { + "jobListMetrics": { + "description": "Initial metrics shown for new users in job lists (User and jobs view).", + "type": "array", + "items": { + "type": "string", + "minItems": 1 + } + }, + "jobViewPlotMetrics": { + "description": "Initial metrics shown for new users as job view metric plots.", + "type": "array", + "items": { + "type": "string", + "minItems": 1 + } + }, + "jobViewTableMetrics": { + "description": "Initial metrics shown for new users in job view statistics table.", + "type": "array", + "items": { + "type": "string", + "minItems": 1 + } + }, + "clusters": { + "description": "Overrides for global defaults by cluster and subcluster.", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "description": "The name of the cluster.", + "kind": { + "type": "string", + "enum": ["influxdb", "prometheus", "cc-metric-store", "cc-metric-store-internal", "test"] + }, + "url": { + "type": "string" + }, + "subClusters" { + "description": "The array of overrides per subcluster.", + "type":"array", + "items": { + "type": "object", + "properties": { + "name": { + "description": "The name of the subcluster.", + "type": "string" + }, + "jobListMetrics": { + "description": "Initial metrics shown for new users in job lists (User and jobs view) for subcluster.", + "type": "array", + "items": { + "type": "string", + "minItems": 1 + } + }, + "jobViewPlotMetrics": { + "description": "Initial metrics shown for new users as job view timeplots for subcluster.", + "type": "array", + "items": { + "type": "string", + "minItems": 1 + } + }, + "jobViewTableMetrics": { + "description": "Initial metrics shown for new users in job view statistics table for subcluster.", + "type": "array", + "items": { + "type": "string", + "minItems": 1 + } + } + }, + "required": ["name"], + "minItems": 1 + } + } + }, + "required": ["name", "subClusters"], + "minItems": 1 + } + }, + "required": ["jobListMetrics", "jobViewPlotMetrics", "jobViewTableMetrics"] + } + }, + "plotConfiguration": { + "description": "Initial settings for plot render options.", + "type": "object", + "properties": { + "colorBackground": { + "description": "If the metric plot backgrounds are initially colored by threshold limits.", + "type": "boolean" + }, + "plotsPerRow": { + "description": "How many plots are initially rendered in per row. Applies to job, single node, and analysis views.", + "type": "integer" + }, + "lineWidth": { + "description": "Initial thickness of rendered plotlines. Applies to metric plot, job compare plot and roofline.", + "type": "integer" + }, + "colorScheme": { + "description": "Initial colorScheme to be used for metric plots.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["colorBackground", "plotsPerRow", "lineWidth"] + } + }` diff --git a/web/web.go b/web/web.go index 0c8c160..a6512f8 100644 --- a/web/web.go +++ b/web/web.go @@ -1,15 +1,19 @@ // Copyright (C) NHR@FAU, University Erlangen-Nuremberg. -// All rights reserved. +// All rights reserved. This file is part of cc-backend. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. + +// Package web implements the HTML templating and web frontend configuration package web import ( "embed" + "encoding/json" "html/template" "io/fs" "net/http" "strings" + "sync" "github.com/ClusterCockpit/cc-backend/internal/config" "github.com/ClusterCockpit/cc-backend/pkg/archive" @@ -18,8 +22,114 @@ import ( "github.com/ClusterCockpit/cc-lib/util" ) -/// Go's embed is only allowed to embed files in a subdirectory of the embedding package ([see here](https://github.com/golang/go/issues/46056)). +type WebConfig struct { + JobList JobListConfig `json:"jobList"` + NodeList NodeListConfig `json:"nodeList"` + JobView JobViewConfig `json:"jobView"` + MetricConfig MetricConfig `json:"metricConfig"` + PlotConfiguration PlotConfiguration `json:"plotConfiguration"` +} +type JobListConfig struct { + UsePaging bool `json:"usePaging"` + ShowFootprint bool `json:"showFootprint"` +} + +type NodeListConfig struct { + UsePaging bool `json:"usePaging"` +} + +type JobViewConfig struct { + ShowPolarPlot bool `json:"showPolarPlot"` + ShowFootprint bool `json:"showFootprint"` + ShowRoofline bool `json:"showRoofline"` + ShowStatTable bool `json:"showStatTable"` +} + +type MetricConfig struct { + JobListMetrics []string `json:"jobListMetrics"` + JobViewPlotMetrics []string `json:"jobViewPlotMetrics"` + JobViewTableMetrics []string `json:"jobViewTableMetrics"` + Clusters []ClusterConfig `json:"clusters"` +} + +type ClusterConfig struct { + Name string `json:"name"` + SubClusters []SubClusterConfig `json:"subClusters"` +} + +type SubClusterConfig struct { + Name string `json:"name"` + JobListMetrics []string `json:"jobListMetrics"` + JobViewPlotMetrics []string `json:"jobViewPlotMetrics"` + JobViewTableMetrics []string `json:"jobViewTableMetrics"` +} + +type PlotConfiguration struct { + ColorBackground bool `json:"colorBackground"` + PlotsPerRow int `json:"plotsPerRow"` + LineWidth int `json:"lineWidth"` + ColorScheme []string `json:"colorScheme"` +} + +var initOnce sync.Once + +var Keys = WebConfig{ + JobList: JobListConfig{ + UsePaging: false, + ShowFootprint: true, + }, + NodeList: NodeListConfig{ + UsePaging: true, + }, + JobView: JobViewConfig{ + ShowPolarPlot: true, + ShowFootprint: true, + ShowRoofline: true, + ShowStatTable: true, + }, + MetricConfig: MetricConfig{ + JobListMetrics: []string{"flops_any", "mem_bw", "mem_used"}, + JobViewPlotMetrics: []string{"flops_any", "mem_bw", "mem_used"}, + JobViewTableMetrics: []string{"flops_any", "mem_bw", "mem_used"}, + }, + PlotConfiguration: PlotConfiguration{ + ColorBackground: true, + PlotsPerRow: 3, + LineWidth: 3, + ColorScheme: []string{"#00bfff", "#0000ff", "#ff00ff", "#ff0000", "#ff8000", "#ffff00", "#80ff00"}, + }, +} + +// +// map[string]any{ +// "analysis_view_histogramMetrics": []string{"flops_any", "mem_bw", "mem_used"}, +// "analysis_view_scatterPlotMetrics": [][]string{{"flops_any", "mem_bw"}, {"flops_any", "cpu_load"}, {"cpu_load", "mem_bw"}}, +// "job_view_nodestats_selectedMetrics": []string{"flops_any", "mem_bw", "mem_used"}, +// "plot_list_jobsPerPage": 50, +// "system_view_selectedMetric": "cpu_load", +// "analysis_view_selectedTopEntity": "user", +// "analysis_view_selectedTopCategory": "totalWalltime", +// "status_view_selectedTopUserCategory": "totalJobs", +// "status_view_selectedTopProjectCategory": "totalJobs", +// } + +func Init(rawConfig json.RawMessage, disableArchive bool) error { + var err error + + initOnce.Do(func() { + config.Validate(configSchema, rawConfig) + if err = json.Unmarshal(rawConfig, &Keys); err != nil { + cclog.Warn("Error while unmarshaling raw config json") + return + } + }) + + return err +} + +// / Go's embed is only allowed to embed files in a subdirectory of the embedding package ([see here](https://github.com/golang/go/issues/46056)). +// //go:embed frontend/public/* var frontendFiles embed.FS @@ -92,9 +202,9 @@ type Page struct { Build Build // Latest information about the application Clusters []config.ClusterConfig // List of all clusters for use in the Header SubClusters map[string][]string // Map per cluster of all subClusters for use in the Header - FilterPresets map[string]interface{} // For pages with the Filter component, this can be used to set initial filters. - Infos map[string]interface{} // For generic use (e.g. username for /monitoring/user/, job id for /monitoring/job/) - Config map[string]interface{} // UI settings for the currently logged in user (e.g. line width, ...) + FilterPresets map[string]any // For pages with the Filter component, this can be used to set initial filters. + Infos map[string]any // For generic use (e.g. username for /monitoring/user/, job id for /monitoring/job/) + Config map[string]any // UI settings for the currently logged in user (e.g. line width, ...) Resampling *config.ResampleConfig // If not nil, defines resampling trigger and resolutions Redirect string // The originally requested URL, for intermediate login handling }