mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-10-31 16:05:06 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			131 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			131 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (C) 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.
 | |
| package web
 | |
| 
 | |
| import (
 | |
| 	"embed"
 | |
| 	"html/template"
 | |
| 	"io/fs"
 | |
| 	"net/http"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/ClusterCockpit/cc-backend/internal/config"
 | |
| 	"github.com/ClusterCockpit/cc-backend/internal/util"
 | |
| 	"github.com/ClusterCockpit/cc-backend/pkg/archive"
 | |
| 	"github.com/ClusterCockpit/cc-backend/pkg/log"
 | |
| 	"github.com/ClusterCockpit/cc-backend/pkg/schema"
 | |
| )
 | |
| 
 | |
| /// 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
 | |
| 
 | |
| func ServeFiles() http.Handler {
 | |
| 	publicFiles, err := fs.Sub(frontendFiles, "frontend/public")
 | |
| 	if err != nil {
 | |
| 		log.Abortf("Serve Files: Could not find 'frontend/public' file directory.\nError: %s\n", err.Error())
 | |
| 	}
 | |
| 	return http.FileServer(http.FS(publicFiles))
 | |
| }
 | |
| 
 | |
| //go:embed templates/*
 | |
| var templateFiles embed.FS
 | |
| 
 | |
| var templates map[string]*template.Template = map[string]*template.Template{}
 | |
| 
 | |
| func init() {
 | |
| 	base := template.Must(template.ParseFS(templateFiles, "templates/base.tmpl"))
 | |
| 	if err := fs.WalkDir(templateFiles, "templates", func(path string, d fs.DirEntry, err error) error {
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		if d.IsDir() || path == "templates/base.tmpl" {
 | |
| 			return nil
 | |
| 		}
 | |
| 
 | |
| 		if path == "templates/login.tmpl" {
 | |
| 			if util.CheckFileExists("./var/login.tmpl") {
 | |
| 				log.Info("overwrite login.tmpl with local file")
 | |
| 				templates[strings.TrimPrefix(path, "templates/")] =
 | |
| 					template.Must(template.Must(base.Clone()).ParseFiles("./var/login.tmpl"))
 | |
| 				return nil
 | |
| 			}
 | |
| 		}
 | |
| 		if path == "templates/imprint.tmpl" {
 | |
| 			if util.CheckFileExists("./var/imprint.tmpl") {
 | |
| 				log.Info("overwrite imprint.tmpl with local file")
 | |
| 				templates[strings.TrimPrefix(path, "templates/")] =
 | |
| 					template.Must(template.Must(base.Clone()).ParseFiles("./var/imprint.tmpl"))
 | |
| 				return nil
 | |
| 			}
 | |
| 		}
 | |
| 		if path == "templates/privacy.tmpl" {
 | |
| 			if util.CheckFileExists("./var/privacy.tmpl") {
 | |
| 				log.Info("overwrite privacy.tmpl with local file")
 | |
| 				templates[strings.TrimPrefix(path, "templates/")] =
 | |
| 					template.Must(template.Must(base.Clone()).ParseFiles("./var/privacy.tmpl"))
 | |
| 				return nil
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		templates[strings.TrimPrefix(path, "templates/")] = template.Must(template.Must(base.Clone()).ParseFS(templateFiles, path))
 | |
| 		return nil
 | |
| 	}); err != nil {
 | |
| 		log.Abortf("Web init(): Could not find frontend template files.\nError: %s\n", err.Error())
 | |
| 	}
 | |
| 
 | |
| 	_ = base
 | |
| }
 | |
| 
 | |
| type Build struct {
 | |
| 	Version   string
 | |
| 	Hash      string
 | |
| 	Buildtime string
 | |
| }
 | |
| 
 | |
| type Page struct {
 | |
| 	Title         string                 // Page title
 | |
| 	MsgType       string                 // For generic use in message boxes
 | |
| 	Message       string                 // For generic use in message boxes
 | |
| 	User          schema.User            // Information about the currently logged in user (Full User Info)
 | |
| 	Roles         map[string]schema.Role // Available roles for frontend render checks
 | |
| 	Build         Build                  // Latest information about the application
 | |
| 	Clusters      []schema.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/<id>, job id for /monitoring/job/<id>)
 | |
| 	Config        map[string]interface{} // UI settings for the currently logged in user (e.g. line width, ...)
 | |
| 	Resampling    *schema.ResampleConfig // If not nil, defines resampling trigger and resolutions
 | |
| 	Redirect      string                 // The originally requested URL, for intermediate login handling
 | |
| }
 | |
| 
 | |
| func RenderTemplate(rw http.ResponseWriter, file string, page *Page) {
 | |
| 	t, ok := templates[file]
 | |
| 	if !ok {
 | |
| 		log.Errorf("WEB/WEB > template '%s' not found", file)
 | |
| 	}
 | |
| 
 | |
| 	if page.Clusters == nil {
 | |
| 		for _, c := range config.Keys.Clusters {
 | |
| 			page.Clusters = append(page.Clusters, schema.ClusterConfig{Name: c.Name, FilterRanges: c.FilterRanges, MetricDataRepository: nil})
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if page.SubClusters == nil {
 | |
| 		page.SubClusters = make(map[string][]string)
 | |
| 		for _, cluster := range archive.Clusters {
 | |
| 			for _, sc := range cluster.SubClusters {
 | |
| 				page.SubClusters[cluster.Name] = append(page.SubClusters[cluster.Name], sc.Name)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	log.Debugf("Page config : %v\n", page.Config)
 | |
| 	if err := t.Execute(rw, page); err != nil {
 | |
| 		log.Errorf("Template error: %s", err.Error())
 | |
| 	}
 | |
| }
 |