mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-11-10 08:57:25 +01:00
embed frontend files into binary (issue #2)
This commit is contained in:
parent
c1b7c8c6ae
commit
4f61580b2b
@ -33,6 +33,7 @@ import (
|
||||
"github.com/ClusterCockpit/cc-backend/internal/runtimeEnv"
|
||||
"github.com/ClusterCockpit/cc-backend/internal/templates"
|
||||
"github.com/ClusterCockpit/cc-backend/pkg/log"
|
||||
"github.com/ClusterCockpit/cc-backend/web"
|
||||
"github.com/google/gops/agent"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
@ -53,8 +54,11 @@ type ProgramConfig struct {
|
||||
// Disable authentication (for everything: API, Web-UI, ...)
|
||||
DisableAuthentication bool `json:"disable-authentication"`
|
||||
|
||||
// Folder where static assets can be found, will be served directly
|
||||
StaticFiles string `json:"static-files"`
|
||||
// If `embed-static-files` is true (default), the frontend files are directly
|
||||
// embeded into the go binary and expected to be in web/frontend. Only if
|
||||
// it is false the files in `static-files` are served instead.
|
||||
EmbedStaticFiles bool `json:"embed-static-files"`
|
||||
StaticFiles string `json:"static-files"`
|
||||
|
||||
// 'sqlite3' or 'mysql' (mysql will work for mariadb as well)
|
||||
DBDriver string `json:"db-driver"`
|
||||
@ -100,7 +104,7 @@ type ProgramConfig struct {
|
||||
var programConfig ProgramConfig = ProgramConfig{
|
||||
Addr: ":8080",
|
||||
DisableAuthentication: false,
|
||||
StaticFiles: "./web/frontend/public",
|
||||
EmbedStaticFiles: true,
|
||||
DBDriver: "sqlite3",
|
||||
DB: "./var/job.db",
|
||||
JobArchive: "./var/job-archive",
|
||||
@ -379,7 +383,12 @@ func main() {
|
||||
routerConfig.SetupRoutes(secured)
|
||||
api.MountRoutes(secured)
|
||||
|
||||
r.PathPrefix("/").Handler(http.FileServer(http.Dir(programConfig.StaticFiles)))
|
||||
if programConfig.EmbedStaticFiles {
|
||||
r.PathPrefix("/").Handler(web.ServeFiles())
|
||||
} else {
|
||||
r.PathPrefix("/").Handler(http.FileServer(http.Dir(programConfig.StaticFiles)))
|
||||
}
|
||||
|
||||
r.Use(handlers.CompressHandler)
|
||||
r.Use(handlers.RecoveryHandler(handlers.PrintRecoveryStack(true)))
|
||||
r.Use(handlers.CORS(
|
||||
|
1
web/frontend/public/uPlot.min.css
vendored
1
web/frontend/public/uPlot.min.css
vendored
@ -1 +0,0 @@
|
||||
../node_modules/uplot/dist/uPlot.min.css
|
1
web/frontend/public/uPlot.min.css
vendored
Normal file
1
web/frontend/public/uPlot.min.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.uplot, .uplot *, .uplot *::before, .uplot *::after {box-sizing: border-box;}.uplot {font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";line-height: 1.5;width: min-content;}.u-title {text-align: center;font-size: 18px;font-weight: bold;}.u-wrap {position: relative;user-select: none;}.u-over, .u-under {position: absolute;}.u-under {overflow: hidden;}.uplot canvas {display: block;position: relative;width: 100%;height: 100%;}.u-axis {position: absolute;}.u-legend {font-size: 14px;margin: auto;text-align: center;}.u-inline {display: block;}.u-inline * {display: inline-block;}.u-inline tr {margin-right: 16px;}.u-legend th {font-weight: 600;}.u-legend th > * {vertical-align: middle;display: inline-block;}.u-legend .u-marker {width: 1em;height: 1em;margin-right: 4px;background-clip: padding-box !important;}.u-inline.u-live th::after {content: ":";vertical-align: middle;}.u-inline:not(.u-live) .u-value {display: none;}.u-series > * {padding: 4px;}.u-series th {cursor: pointer;}.u-legend .u-off > * {opacity: 0.3;}.u-select {background: rgba(0,0,0,0.07);position: absolute;pointer-events: none;}.u-cursor-x, .u-cursor-y {position: absolute;left: 0;top: 0;pointer-events: none;will-change: transform;z-index: 100;}.u-hz .u-cursor-x, .u-vt .u-cursor-y {height: 100%;border-right: 1px dashed #607D8B;}.u-hz .u-cursor-y, .u-vt .u-cursor-x {width: 100%;border-bottom: 1px dashed #607D8B;}.u-cursor-pt {position: absolute;top: 0;left: 0;border-radius: 50%;border: 0 solid;pointer-events: none;will-change: transform;z-index: 100;/*this has to be !important since we set inline "background" shorthand */background-clip: padding-box !important;}.u-axis.u-off, .u-select.u-off, .u-cursor-x.u-off, .u-cursor-y.u-off, .u-cursor-pt.u-off {display: none;}
|
82
web/web.go
Normal file
82
web/web.go
Normal file
@ -0,0 +1,82 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/ClusterCockpit/cc-backend/internal/config"
|
||||
"github.com/ClusterCockpit/cc-backend/pkg/log"
|
||||
)
|
||||
|
||||
/// 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 {
|
||||
panic(err)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
templates[strings.TrimPrefix(path, "templates/")] = template.Must(template.Must(base.Clone()).ParseFS(templateFiles, path))
|
||||
return nil
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_ = base
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Username string // Username of the currently logged in user
|
||||
IsAdmin bool
|
||||
}
|
||||
|
||||
type Page struct {
|
||||
Title string // Page title
|
||||
Error string // For generic use (e.g. the exact error message on /login)
|
||||
Info string // For generic use (e.g. "Logout successfull" on /login)
|
||||
User User // Information about the currently logged in user
|
||||
Clusters []string // List of all clusters 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, ...)
|
||||
}
|
||||
|
||||
func RenderTemplate(rw http.ResponseWriter, r *http.Request, file string, page *Page) {
|
||||
t, ok := templates[file]
|
||||
if !ok {
|
||||
panic("template not found")
|
||||
}
|
||||
|
||||
if page.Clusters == nil {
|
||||
for _, c := range config.Clusters {
|
||||
page.Clusters = append(page.Clusters, c.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if err := t.Execute(rw, page); err != nil {
|
||||
log.Errorf("template error: %s", err.Error())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user