From 768393adc8280b7dd066a04b8a30cf4abb48599a Mon Sep 17 00:00:00 2001 From: William Petit Date: Fri, 26 Apr 2024 12:10:59 +0200 Subject: [PATCH] feat: allow homepage customization --- doc/configuration.md | 11 +- go.mod | 2 + go.sum | 2 + internal/command/player/run.go | 30 ++-- modd.conf | 2 +- pkg/config/config.go | 10 +- pkg/config/custom_files.go | 28 ++++ pkg/config/default_transforms.go | 1 + pkg/config/selfsigned.go | 10 +- pkg/server/embed/_partials/base.gohtml | 64 +++++++++ pkg/server/embed/_templates/index.gohtml | 3 + pkg/server/embed/logo.png | Bin 0 -> 25826 bytes pkg/server/embed/style.css | 121 ++++++++++++++++ pkg/server/fs.go | 90 ++++++++++++ pkg/server/http.go | 51 +------ pkg/server/options.go | 8 ++ pkg/server/server.go | 13 +- pkg/server/templates/idle.html.gotmpl | 173 ----------------------- 18 files changed, 376 insertions(+), 243 deletions(-) create mode 100644 pkg/config/custom_files.go create mode 100644 pkg/server/embed/_partials/base.gohtml create mode 100644 pkg/server/embed/_templates/index.gohtml create mode 100644 pkg/server/embed/logo.png create mode 100644 pkg/server/embed/style.css create mode 100644 pkg/server/fs.go delete mode 100644 pkg/server/templates/idle.html.gotmpl diff --git a/doc/configuration.md b/doc/configuration.md index fd74a56..52640a3 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -19,7 +19,10 @@ Voici un exemple commenté du fichier de configuration: "http": { // Couple
: d'écoute // Par défaut ":" i.e. toutes les adresses avec port aléatoire - "address": ":" + "address": ":", + // Répertoire de personnalisation de la page d'accueil + // Voir section "Personnalisation" ci-dessous + "customDir": "${CONFIG_DIR}/custom" }, // Configuration du serveur HTTPS "https": { @@ -48,3 +51,9 @@ Voici un exemple commenté du fichier de configuration: } } ``` + +## Personnalisation + +Il est possible de personnaliser la page d'accueil du player Arcast en créant des fichiers dans le répertoire définit par l'attribut de configuration `http.customDir`. + +Le contenu de ce répertoire doit répliquer l'arborescence embarquée par défaut (voir https://forge.cadoles.com/arcad/arcast/src/branch/develop/pkg/server/embed). Chaque fichier présent remplacera celui embarqué par défaut. diff --git a/go.mod b/go.mod index 4db6730..cbc2c70 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.21.4 require ( gioui.org v0.4.1 + github.com/davecgh/go-spew v1.1.1 github.com/gioui-plugins/gio-plugins v0.0.0-20240323070753-3331d8c2df5d github.com/go-chi/cors v1.2.1 github.com/gorilla/websocket v1.5.1 @@ -23,6 +24,7 @@ require ( github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/charmbracelet/lipgloss v0.7.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/dschmidt/go-layerfs v0.1.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 // indirect diff --git a/go.sum b/go.sum index 3b0db09..6d627d3 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dschmidt/go-layerfs v0.1.0 h1:jE6aHDfjNzS/31DS48th6EkmELwTa1Uf+aO4jRkBs3U= +github.com/dschmidt/go-layerfs v0.1.0/go.mod h1:m62aff0hn23Q/tQBRiNSeLD7EUuimDvsuCvCpzBr3Gw= github.com/gioui-plugins/gio-plugins v0.0.0-20240323070753-3331d8c2df5d h1:8b7owUJ8sNmgqEk+1d7ylr3TCH3vliCvY/6ycfize8o= github.com/gioui-plugins/gio-plugins v0.0.0-20240323070753-3331d8c2df5d/go.mod h1:3XVleuCdPpdajFL+ASh2wmXZNskitXQQ4jhVss0VHZg= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= diff --git a/internal/command/player/run.go b/internal/command/player/run.go index 8601007..0106521 100644 --- a/internal/command/player/run.go +++ b/internal/command/player/run.go @@ -68,6 +68,11 @@ func Run() *cli.Command { EnvVars: []string{"ARCAST_DESKTOP_ALLOWED_ORIGINS"}, Value: cli.NewStringSlice(), }, + &cli.StringFlag{ + Name: "custom-files-dir", + EnvVars: []string{"ARCAST_DESKTOP_CUSTOM_FILES_DIR"}, + Value: "", + }, &cli.BoolFlag{ Name: "dummy-browser", EnvVars: []string{"ARCAST_DESKTOP_DUMMY_BROWSER"}, @@ -114,21 +119,11 @@ func Run() *cli.Command { conf := config.DefaultConfig() - logger.Info(ctx.Context, "loading or creating configuration file", logger.F("filename", configFile)) - if err := config.LoadOrCreate(ctx.Context, configFile, conf, config.DefaultTransforms...); err != nil { - logger.Error(ctx.Context, "could not load configuration file", logger.CapturedE(errors.WithStack(err))) - } - instanceID := ctx.String("instance-id") if instanceID != "" { conf.InstanceID = instanceID } - cert, err := tls.X509KeyPair(conf.HTTPS.Cert, conf.HTTPS.Key) - if err != nil { - return errors.Wrap(err, "could not parse tls cert/key pair") - } - if ctx.IsSet("apps") { conf.Apps.Enabled = ctx.Bool("apps") } @@ -145,6 +140,20 @@ func Run() *cli.Command { conf.AllowedOrigins = ctx.StringSlice("allowed-origins") } + if ctx.IsSet("custom-dir") { + conf.HTTP.CustomDir = ctx.String("custom-dir") + } + + logger.Info(ctx.Context, "loading or creating configuration file", logger.F("filename", configFile)) + if err := config.LoadOrCreate(ctx.Context, configFile, conf, config.DefaultTransforms...); err != nil { + logger.Error(ctx.Context, "could not load configuration file", logger.CapturedE(errors.WithStack(err))) + } + + cert, err := tls.X509KeyPair(conf.HTTPS.Cert, conf.HTTPS.Key) + if err != nil { + return errors.Wrap(err, "could not parse tls cert/key pair") + } + server := server.New(browser, server.WithInstanceID(conf.InstanceID), server.WithAppsEnabled(conf.Apps.Enabled), @@ -154,6 +163,7 @@ func Run() *cli.Command { server.WithTLSAddress(conf.HTTPS.Address), server.WithTLSCertificate(&cert), server.WithAllowedOrigins(conf.AllowedOrigins...), + server.WithUpperLayerDir(conf.HTTP.CustomDir), ) if err := server.Start(); err != nil { diff --git a/modd.conf b/modd.conf index ee0595c..31b201b 100644 --- a/modd.conf +++ b/modd.conf @@ -3,7 +3,7 @@ } **/*.go -pkg/server/templates/**.gotmpl +pkg/server/embed/** modd.conf .env { prep: make build-client diff --git a/pkg/config/config.go b/pkg/config/config.go index db89080..c4cf427 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -20,7 +20,8 @@ type Config struct { } type HTTPConfig struct { - Address string `json:"address"` + Address string `json:"address"` + CustomDir string `json:"customDir"` } type HTTPSConfig struct { @@ -40,7 +41,7 @@ type AppsConfig struct { DefaultApp string `json:"defaultApp"` } -type TransformFunc func(ctx context.Context, conf *Config) error +type TransformFunc func(ctx context.Context, filename string, conf *Config) error func DefaultConfigFile(ctx context.Context) string { configDir, err := os.UserConfigDir() @@ -69,7 +70,7 @@ func LoadOrCreate(ctx context.Context, filename string, conf *Config, funcs ...T } for _, fn := range funcs { - if err := fn(ctx, conf); err != nil { + if err := fn(ctx, filename, conf); err != nil { return errors.WithStack(err) } } @@ -99,7 +100,8 @@ func DefaultConfig() *Config { InstanceID: server.NewRandomInstanceID(), AllowedOrigins: []string{}, HTTP: HTTPConfig{ - Address: ":45555", + Address: ":45555", + CustomDir: "", }, HTTPS: HTTPSConfig{ Address: ":45556", diff --git a/pkg/config/custom_files.go b/pkg/config/custom_files.go new file mode 100644 index 0000000..00e48fc --- /dev/null +++ b/pkg/config/custom_files.go @@ -0,0 +1,28 @@ +package config + +import ( + "context" + "os" + "path/filepath" + + "github.com/pkg/errors" + "gitlab.com/wpetit/goweb/logger" +) + +func CreateCustomDir(ctx context.Context, filename string, conf *Config) error { + if conf.HTTP.CustomDir != "" { + return nil + } + + configDir := filepath.Dir(filename) + customFilesDir := filepath.Join(configDir, "custom") + + if err := os.MkdirAll(customFilesDir, 0755); err != nil { + logger.Error(ctx, "could not create custom files directory", logger.CapturedE(errors.WithStack(err))) + return nil + } + + conf.HTTP.CustomDir = customFilesDir + + return nil +} diff --git a/pkg/config/default_transforms.go b/pkg/config/default_transforms.go index f36a0e6..5a33a0d 100644 --- a/pkg/config/default_transforms.go +++ b/pkg/config/default_transforms.go @@ -3,4 +3,5 @@ package config var DefaultTransforms = []TransformFunc{ GenerateSelfSignedCert, RenewExpiredSelfSignedCert, + CreateCustomDir, } diff --git a/pkg/config/selfsigned.go b/pkg/config/selfsigned.go index 42ffa98..5ba88ee 100644 --- a/pkg/config/selfsigned.go +++ b/pkg/config/selfsigned.go @@ -15,7 +15,7 @@ import ( "gitlab.com/wpetit/goweb/logger" ) -func GenerateSelfSignedCert(ctx context.Context, conf *Config) error { +func GenerateSelfSignedCert(ctx context.Context, filename string, conf *Config) error { if !conf.HTTPS.SelfSigned.Enabled { return nil } @@ -42,13 +42,13 @@ func GenerateSelfSignedCert(ctx context.Context, conf *Config) error { return nil } -func RenewExpiredSelfSignedCert(ctx context.Context, conf *Config) error { +func RenewExpiredSelfSignedCert(ctx context.Context, filename string, conf *Config) error { if !conf.HTTPS.SelfSigned.Enabled { return nil } if conf.HTTPS.Cert == nil || conf.HTTPS.Key == nil { - if err := GenerateSelfSignedCert(ctx, conf); err != nil { + if err := GenerateSelfSignedCert(ctx, filename, conf); err != nil { return errors.WithStack(err) } } @@ -62,7 +62,7 @@ func RenewExpiredSelfSignedCert(ctx context.Context, conf *Config) error { if err != nil { logger.Error(ctx, "could not parse x509 certificate, regenerating one", logger.CapturedE(errors.WithStack(err))) - if err := GenerateSelfSignedCert(ctx, conf); err != nil { + if err := GenerateSelfSignedCert(ctx, filename, conf); err != nil { return errors.WithStack(err) } } @@ -74,7 +74,7 @@ func RenewExpiredSelfSignedCert(ctx context.Context, conf *Config) error { logger.Warn(ctx, "self-signed certificate has expired, regenerating one", logger.CapturedE(errors.WithStack(err))) - if err := GenerateSelfSignedCert(ctx, conf); err != nil { + if err := GenerateSelfSignedCert(ctx, filename, conf); err != nil { return errors.WithStack(err) } diff --git a/pkg/server/embed/_partials/base.gohtml b/pkg/server/embed/_partials/base.gohtml new file mode 100644 index 0000000..25d01d4 --- /dev/null +++ b/pkg/server/embed/_partials/base.gohtml @@ -0,0 +1,64 @@ +{{ define "base" }} + + + + + + + Ready to cast ! + + {{ block "head" . + }}{{ + end + }} + + +
+
+
+ {{ block "message" . }} +

Ready to cast !

+ {{ end }} + {{ block "info" . }} +

Instance ID

+

+ {{ .ID }} +

+

Addresses

+
    + {{ $port := .Port }} + {{ + range.IPs + }} +
  • + {{ . }}:{{ $port }} +
  • + {{ + end + }} +
+ {{ end }} + {{if .Apps }} + {{ block "apps" . }} +

Apps

+ + {{ end }} + {{ end }} +
+
+ + +{{ end }} diff --git a/pkg/server/embed/_templates/index.gohtml b/pkg/server/embed/_templates/index.gohtml new file mode 100644 index 0000000..96c49ba --- /dev/null +++ b/pkg/server/embed/_templates/index.gohtml @@ -0,0 +1,3 @@ +{{ define "index" }} +{{ template "base" . }} +{{ end }} diff --git a/pkg/server/embed/logo.png b/pkg/server/embed/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..564b6a748464b7ec22372642375324eb8b887514 GIT binary patch literal 25826 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 e=RSX-{{KJG&JhXxWG$Wm0000 - - - - - Arcast - Ready to cast ! - - - - -
-
-
-

Ready to cast !

-

Instance ID

-

- {{ .ID }} -

-

Addresses

-
    - {{ $port := .Port }} - {{ - range.IPs - }} -
  • - {{ . }}:{{ $port }} -
  • - {{ - end - }} -
- {{if .Apps }} -

Apps

- - {{ end }} -
-
- -