detached dfcd2ef

Version 0.02 from scratch

commit
dfcd2efb39add2618adf315934c9dd285950fff3
author
bnema · · 2023-09-11 16:44 UTC
committer
bnema · · 2023-09-11 19:05 UTC
parent
1df4e7b
signature
unsigned
69 files · +0 -4018

this diff is incomplete: 2 files are too large to show. the totals above count everything; clone the repository to read the rest.

D cmd/main.go +0 -46
@@ -1,46 +0,0 @@
1-package main
2-
3-import (
4- "os"
5-
6- "github.com/labstack/echo/v4"
7- root "gogs.bnema.dev/gordon-echo"
8- "gogs.bnema.dev/gordon-echo/internal/app"
9- "gogs.bnema.dev/gordon-echo/internal/http/routes"
10- "gogs.bnema.dev/gordon-echo/pkg/utils"
11-)
12-
13-func main() {
14- // Port = os.Getenv("PORT") or fallback to default port (1323)
15- port := os.Getenv("PORT")
16- if port == "" {
17- port = "1323"
18- }
19- // Initialize Gordon
20- gordon, err := app.NewApp(
21- &app.Config{
22- TemplateFS: root.TemplateFS,
23- PublicFS: root.PublicFS,
24- ModelFS: root.ModelFS,
25- },
26- )
27- if err != nil {
28- // Handle initialization error and exit
29- gordon.AppLogger.Error().Err(err).Msg("Failed to initialize app")
30- return
31- }
32- utils.CaptureSTDOUT(gordon.AppLogger)
33-
34- e := echo.New()
35- e.HideBanner = true
36- e.HidePort = true
37- e.Logger = utils.NewEchoLoggerWrapper(gordon.AppLogger)
38- e = routes.ConfigureRouter(e, gordon, gordon.Config)
39-
40- startFunc := func() error {
41- gordon.AppLogger.Info().Msgf("Server is listening on port %s", port)
42- return e.Start(":" + port)
43- }
44- utils.RunAppCatchSIGINT(startFunc, &gordon.AppLogger.Logger)
45-
46-}
@@ -1,46 +0,0 @@
1-package main
2-
3-import (
4- "os"
5-
6- "github.com/labstack/echo/v4"
7- root "gogs.bnema.dev/gordon-echo"
8- "gogs.bnema.dev/gordon-echo/internal/app"
9- "gogs.bnema.dev/gordon-echo/internal/http/routes"
10- "gogs.bnema.dev/gordon-echo/pkg/utils"
11-)
12-
13-func main() {
14- // Port = os.Getenv("PORT") or fallback to default port (1323)
15- port := os.Getenv("PORT")
16- if port == "" {
17- port = "1323"
18- }
19- // Initialize Gordon
20- gordon, err := app.NewApp(
21- &app.Config{
22- TemplateFS: root.TemplateFS,
23- PublicFS: root.PublicFS,
24- ModelFS: root.ModelFS,
25- },
26- )
27- if err != nil {
28- // Handle initialization error and exit
29- gordon.AppLogger.Error().Err(err).Msg("Failed to initialize app")
30- return
31- }
32- utils.CaptureSTDOUT(gordon.AppLogger)
33-
34- e := echo.New()
35- e.HideBanner = true
36- e.HidePort = true
37- e.Logger = utils.NewEchoLoggerWrapper(gordon.AppLogger)
38- e = routes.ConfigureRouter(e, gordon, gordon.Config)
39-
40- startFunc := func() error {
41- gordon.AppLogger.Info().Msgf("Server is listening on port %s", port)
42- return e.Start(":" + port)
43- }
44- utils.RunAppCatchSIGINT(startFunc, &gordon.AppLogger.Logger)
45-
46-}
D embed.go +0 -22
@@ -1,22 +0,0 @@
1-package embed
2-
3-import (
4- "embed"
5-
6- "github.com/labstack/echo/v4"
7-)
8-
9-// Embedding the public and templates directories
10-
11-//go:embed internal/ui/public/*
12-var public embed.FS
13-
14-//go:embed internal/ui/templates/*
15-var templates embed.FS
16-
17-//go:embed pkg/templating/models/*
18-var models embed.FS
19-
20-var PublicFS = echo.MustSubFS(public, "internal/ui/public")
21-var TemplateFS = echo.MustSubFS(templates, "internal/ui/templates")
22-var ModelFS = echo.MustSubFS(models, "pkg/templating/models")
@@ -1,22 +0,0 @@
1-package embed
2-
3-import (
4- "embed"
5-
6- "github.com/labstack/echo/v4"
7-)
8-
9-// Embedding the public and templates directories
10-
11-//go:embed internal/ui/public/*
12-var public embed.FS
13-
14-//go:embed internal/ui/templates/*
15-var templates embed.FS
16-
17-//go:embed pkg/templating/models/*
18-var models embed.FS
19-
20-var PublicFS = echo.MustSubFS(public, "internal/ui/public")
21-var TemplateFS = echo.MustSubFS(templates, "internal/ui/templates")
22-var ModelFS = echo.MustSubFS(models, "pkg/templating/models")
D go.mod +0 -58
@@ -1,58 +0,0 @@
1-module gogs.bnema.dev/gordon-echo
2-
3-go 1.21.0
4-
5-require (
6- github.com/PuerkitoBio/goquery v1.8.1
7- github.com/docker/docker v24.0.5+incompatible
8- github.com/labstack/echo/v4 v4.11.1
9- github.com/microcosm-cc/bluemonday v1.0.25
10- github.com/tidwall/gjson v1.16.0
11- gopkg.in/yaml.v3 v3.0.1
12-)
13-
14-require (
15- github.com/Microsoft/go-winio v0.6.1 // indirect
16- github.com/andybalholm/cascadia v1.3.1 // indirect
17- github.com/aymerick/douceur v0.2.0 // indirect
18- github.com/containerd/containerd v1.7.5 // indirect
19- github.com/docker/distribution v2.8.2+incompatible // indirect
20- github.com/docker/go-connections v0.4.0 // indirect
21- github.com/docker/go-units v0.5.0 // indirect
22- github.com/gogo/protobuf v1.3.2 // indirect
23- github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
24- github.com/gorilla/context v1.1.1 // indirect
25- github.com/gorilla/css v1.0.0 // indirect
26- github.com/gorilla/securecookie v1.1.1 // indirect
27- github.com/gorilla/sessions v1.2.1 // indirect
28- github.com/klauspost/compress v1.16.7 // indirect
29- github.com/moby/patternmatcher v0.6.0 // indirect
30- github.com/moby/sys/sequential v0.5.0 // indirect
31- github.com/moby/term v0.5.0 // indirect
32- github.com/morikuni/aec v1.0.0 // indirect
33- github.com/opencontainers/go-digest v1.0.0 // indirect
34- github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
35- github.com/opencontainers/runc v1.1.9 // indirect
36- github.com/pkg/errors v0.9.1 // indirect
37- github.com/sirupsen/logrus v1.9.0 // indirect
38- github.com/tidwall/match v1.1.1 // indirect
39- github.com/tidwall/pretty v1.2.1 // indirect
40- golang.org/x/mod v0.9.0 // indirect
41- golang.org/x/time v0.3.0 // indirect
42- golang.org/x/tools v0.7.0 // indirect
43- gotest.tools/v3 v3.5.0 // indirect
44-)
45-
46-require (
47- github.com/labstack/echo-contrib v0.15.0
48- github.com/labstack/gommon v0.4.0
49- github.com/mattn/go-colorable v0.1.13 // indirect
50- github.com/mattn/go-isatty v0.0.19 // indirect
51- github.com/rs/zerolog v1.30.0
52- github.com/valyala/bytebufferpool v1.0.0 // indirect
53- github.com/valyala/fasttemplate v1.2.2 // indirect
54- golang.org/x/crypto v0.11.0 // indirect
55- golang.org/x/net v0.12.0 // indirect
56- golang.org/x/sys v0.10.0 // indirect
57- golang.org/x/text v0.11.0 // indirect
58-)
@@ -1,58 +0,0 @@
1-module gogs.bnema.dev/gordon-echo
2-
3-go 1.21.0
4-
5-require (
6- github.com/PuerkitoBio/goquery v1.8.1
7- github.com/docker/docker v24.0.5+incompatible
8- github.com/labstack/echo/v4 v4.11.1
9- github.com/microcosm-cc/bluemonday v1.0.25
10- github.com/tidwall/gjson v1.16.0
11- gopkg.in/yaml.v3 v3.0.1
12-)
13-
14-require (
15- github.com/Microsoft/go-winio v0.6.1 // indirect
16- github.com/andybalholm/cascadia v1.3.1 // indirect
17- github.com/aymerick/douceur v0.2.0 // indirect
18- github.com/containerd/containerd v1.7.5 // indirect
19- github.com/docker/distribution v2.8.2+incompatible // indirect
20- github.com/docker/go-connections v0.4.0 // indirect
21- github.com/docker/go-units v0.5.0 // indirect
22- github.com/gogo/protobuf v1.3.2 // indirect
23- github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
24- github.com/gorilla/context v1.1.1 // indirect
25- github.com/gorilla/css v1.0.0 // indirect
26- github.com/gorilla/securecookie v1.1.1 // indirect
27- github.com/gorilla/sessions v1.2.1 // indirect
28- github.com/klauspost/compress v1.16.7 // indirect
29- github.com/moby/patternmatcher v0.6.0 // indirect
30- github.com/moby/sys/sequential v0.5.0 // indirect
31- github.com/moby/term v0.5.0 // indirect
32- github.com/morikuni/aec v1.0.0 // indirect
33- github.com/opencontainers/go-digest v1.0.0 // indirect
34- github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
35- github.com/opencontainers/runc v1.1.9 // indirect
36- github.com/pkg/errors v0.9.1 // indirect
37- github.com/sirupsen/logrus v1.9.0 // indirect
38- github.com/tidwall/match v1.1.1 // indirect
39- github.com/tidwall/pretty v1.2.1 // indirect
40- golang.org/x/mod v0.9.0 // indirect
41- golang.org/x/time v0.3.0 // indirect
42- golang.org/x/tools v0.7.0 // indirect
43- gotest.tools/v3 v3.5.0 // indirect
44-)
45-
46-require (
47- github.com/labstack/echo-contrib v0.15.0
48- github.com/labstack/gommon v0.4.0
49- github.com/mattn/go-colorable v0.1.13 // indirect
50- github.com/mattn/go-isatty v0.0.19 // indirect
51- github.com/rs/zerolog v1.30.0
52- github.com/valyala/bytebufferpool v1.0.0 // indirect
53- github.com/valyala/fasttemplate v1.2.2 // indirect
54- golang.org/x/crypto v0.11.0 // indirect
55- golang.org/x/net v0.12.0 // indirect
56- golang.org/x/sys v0.10.0 // indirect
57- golang.org/x/text v0.11.0 // indirect
58-)
D go.sum +0 -180 generated file: collapsed
D internal/app/app.go +0 -122
@@ -1,122 +0,0 @@
1-package app
2-
3-import (
4- "fmt"
5- "io"
6- "io/fs"
7- "os"
8- "path/filepath"
9-
10- "github.com/rs/zerolog"
11- "gogs.bnema.dev/gordon-echo/pkg/utils"
12-)
13-
14-const (
15- DefaultBuildDir = "tmp"
16- DefaultLogDir = "logs"
17- EnvVarName = "ENV"
18- ProdEnvValue = "prod"
19- BuildDirEnvVarName = "BUILD_DIR"
20- LogDirEnvVarName = "LOG_DIR"
21-)
22-
23-type App struct {
24- AppLogger *utils.Logger
25- HttpLogger *utils.Logger
26- Config *Config
27-}
28-type Config struct {
29- BuildDir string
30- LogDir string
31- TemplateFS fs.FS
32- PublicFS fs.FS
33- ModelFS fs.FS
34-}
35-
36-func (c *Config) GetTemplateFS() fs.FS {
37- return c.TemplateFS
38-}
39-func (c *Config) GetPublicFS() fs.FS {
40- return c.PublicFS
41-}
42-
43-func (c *Config) GetModelFS() fs.FS {
44- return c.ModelFS
45-}
46-
47-func defineEnv(config *Config) {
48- // Get the environment variable
49- env := os.Getenv(EnvVarName)
50- // If the environment variable is empty, consider it is dev -> (/tmp)
51- buildDir := os.Getenv(BuildDirEnvVarName)
52- if buildDir == "" {
53- buildDir = DefaultBuildDir
54- }
55- logDir := os.Getenv(LogDirEnvVarName)
56- if logDir == "" {
57- logDir = DefaultLogDir
58- }
59-
60- switch env {
61- case ProdEnvValue:
62- // If it is prod we want everything at the same level of the binary
63- buildDir = "."
64- logDir = "./" + logDir
65- default:
66- // If it is dev we want everything in /tmp
67- buildDir = "./" + buildDir
68- logDir = filepath.Join(buildDir, logDir)
69- }
70- config.BuildDir = buildDir
71- config.LogDir = logDir
72-}
73-
74-func NewApp(config *Config) (*App, error) {
75- defineEnv(config)
76- SetupLogging(config)
77- app := &App{
78- AppLogger: utils.NewLogger().SetOutput(initializeLoggerOutput(config, "app.log")),
79- HttpLogger: utils.NewLogger().SetOutput(initializeLoggerOutput(config, "http.log")),
80- Config: config,
81- }
82- return app, nil
83-}
84-
85-func initializeLoggerOutput(cfg *Config, logFile string) io.Writer {
86- logPath := filepath.Join(cfg.LogDir, logFile)
87- dir := filepath.Dir(logPath)
88- if _, err := os.Stat(dir); os.IsNotExist(err) {
89- if mkdirErr := os.MkdirAll(dir, 0755); mkdirErr != nil {
90- panic(mkdirErr)
91- }
92- }
93-
94- file, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
95- if err != nil {
96- panic(err)
97- }
98- consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}
99- return zerolog.MultiLevelWriter(file, consoleWriter)
100-}
101-func SetupLogging(config *Config) {
102- // Check if the log directory exists
103- if _, err := os.Stat(config.LogDir); os.IsNotExist(err) {
104- // If not, create it
105- err := os.MkdirAll(config.LogDir, 0755)
106- if err != nil {
107- panic(fmt.Sprintf("Failed to create log directory at path: %s, error: %s", config.LogDir, err))
108- }
109- }
110-}
111-
112-func GetBuildDir() string {
113- env := os.Getenv(EnvVarName)
114- buildDir := os.Getenv(BuildDirEnvVarName)
115- if buildDir == "" {
116- buildDir = DefaultBuildDir
117- }
118- if env == ProdEnvValue {
119- return "."
120- }
121- return "./" + buildDir
122-}
@@ -1,122 +0,0 @@
1-package app
2-
3-import (
4- "fmt"
5- "io"
6- "io/fs"
7- "os"
8- "path/filepath"
9-
10- "github.com/rs/zerolog"
11- "gogs.bnema.dev/gordon-echo/pkg/utils"
12-)
13-
14-const (
15- DefaultBuildDir = "tmp"
16- DefaultLogDir = "logs"
17- EnvVarName = "ENV"
18- ProdEnvValue = "prod"
19- BuildDirEnvVarName = "BUILD_DIR"
20- LogDirEnvVarName = "LOG_DIR"
21-)
22-
23-type App struct {
24- AppLogger *utils.Logger
25- HttpLogger *utils.Logger
26- Config *Config
27-}
28-type Config struct {
29- BuildDir string
30- LogDir string
31- TemplateFS fs.FS
32- PublicFS fs.FS
33- ModelFS fs.FS
34-}
35-
36-func (c *Config) GetTemplateFS() fs.FS {
37- return c.TemplateFS
38-}
39-func (c *Config) GetPublicFS() fs.FS {
40- return c.PublicFS
41-}
42-
43-func (c *Config) GetModelFS() fs.FS {
44- return c.ModelFS
45-}
46-
47-func defineEnv(config *Config) {
48- // Get the environment variable
49- env := os.Getenv(EnvVarName)
50- // If the environment variable is empty, consider it is dev -> (/tmp)
51- buildDir := os.Getenv(BuildDirEnvVarName)
52- if buildDir == "" {
53- buildDir = DefaultBuildDir
54- }
55- logDir := os.Getenv(LogDirEnvVarName)
56- if logDir == "" {
57- logDir = DefaultLogDir
58- }
59-
60- switch env {
61- case ProdEnvValue:
62- // If it is prod we want everything at the same level of the binary
63- buildDir = "."
64- logDir = "./" + logDir
65- default:
66- // If it is dev we want everything in /tmp
67- buildDir = "./" + buildDir
68- logDir = filepath.Join(buildDir, logDir)
69- }
70- config.BuildDir = buildDir
71- config.LogDir = logDir
72-}
73-
74-func NewApp(config *Config) (*App, error) {
75- defineEnv(config)
76- SetupLogging(config)
77- app := &App{
78- AppLogger: utils.NewLogger().SetOutput(initializeLoggerOutput(config, "app.log")),
79- HttpLogger: utils.NewLogger().SetOutput(initializeLoggerOutput(config, "http.log")),
80- Config: config,
81- }
82- return app, nil
83-}
84-
85-func initializeLoggerOutput(cfg *Config, logFile string) io.Writer {
86- logPath := filepath.Join(cfg.LogDir, logFile)
87- dir := filepath.Dir(logPath)
88- if _, err := os.Stat(dir); os.IsNotExist(err) {
89- if mkdirErr := os.MkdirAll(dir, 0755); mkdirErr != nil {
90- panic(mkdirErr)
91- }
92- }
93-
94- file, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
95- if err != nil {
96- panic(err)
97- }
98- consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}
99- return zerolog.MultiLevelWriter(file, consoleWriter)
100-}
101-func SetupLogging(config *Config) {
102- // Check if the log directory exists
103- if _, err := os.Stat(config.LogDir); os.IsNotExist(err) {
104- // If not, create it
105- err := os.MkdirAll(config.LogDir, 0755)
106- if err != nil {
107- panic(fmt.Sprintf("Failed to create log directory at path: %s, error: %s", config.LogDir, err))
108- }
109- }
110-}
111-
112-func GetBuildDir() string {
113- env := os.Getenv(EnvVarName)
114- buildDir := os.Getenv(BuildDirEnvVarName)
115- if buildDir == "" {
116- buildDir = DefaultBuildDir
117- }
118- if env == ProdEnvValue {
119- return "."
120- }
121- return "./" + buildDir
122-}
D internal/http/handlers/errors.go +0 -24
@@ -1,24 +0,0 @@
1-package handlers
2-
3-import (
4- "fmt"
5- "net/http"
6-
7- "github.com/labstack/echo/v4"
8-)
9-
10-func ErrorNumberHandler(err error, c echo.Context) {
11- code := http.StatusInternalServerError
12- if he, ok := err.(*echo.HTTPError); ok {
13- code = he.Code
14- }
15-
16- switch code {
17- case http.StatusNotFound:
18- c.String(code, fmt.Sprintf("An error occurred: %v", err))
19- case http.StatusForbidden:
20- c.String(code, fmt.Sprintf("An error occurred: %v", err))
21- default:
22- c.String(code, fmt.Sprintf("An error occurred: %v", err))
23- }
24-}
@@ -1,24 +0,0 @@
1-package handlers
2-
3-import (
4- "fmt"
5- "net/http"
6-
7- "github.com/labstack/echo/v4"
8-)
9-
10-func ErrorNumberHandler(err error, c echo.Context) {
11- code := http.StatusInternalServerError
12- if he, ok := err.(*echo.HTTPError); ok {
13- code = he.Code
14- }
15-
16- switch code {
17- case http.StatusNotFound:
18- c.String(code, fmt.Sprintf("An error occurred: %v", err))
19- case http.StatusForbidden:
20- c.String(code, fmt.Sprintf("An error occurred: %v", err))
21- default:
22- c.String(code, fmt.Sprintf("An error occurred: %v", err))
23- }
24-}
D internal/http/handlers/htmx-fragment-header.go +0 -60
@@ -1,60 +0,0 @@
1-package handlers
2-
3-import (
4- "net/http"
5-
6- "github.com/labstack/echo/v4"
7- "gogs.bnema.dev/gordon-echo/internal/app"
8- "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
9- "gogs.bnema.dev/gordon-echo/pkg/htmx"
10- "gogs.bnema.dev/gordon-echo/pkg/utils"
11-)
12-
13-func HTMXHandler(c echo.Context, a *app.App, ac *app.Config) error {
14- logger := a.AppLogger
15- // Detect and extract HTMX data from the request
16- htmxRequest, err := htmx.GetRequest(c)
17- if err != nil {
18- logger.Debug().Err(err).Msg("Failed to extract HTMX data")
19- return err
20- }
21-
22- // If the request is not an HTMX request, return an error
23- if !htmxRequest.Enabled {
24- logger.Error().Msg("Request is not an HTMX request")
25- return c.String(http.StatusBadRequest, "Not an HTMX request")
26- }
27-
28- // If it's a GET request, handle the fragment
29- if c.Request().Method == http.MethodGet {
30- return htmxFragmentHandler(c, a, ac)
31- }
32-
33- // For other HTTP methods, return an error
34- return c.String(http.StatusBadRequest, "Invalid request method")
35-}
36-
37-func htmxFragmentHandler(c echo.Context, a *app.App, ac *app.Config) error {
38- logger := a.AppLogger
39- fragment := c.Request().Header.Get("HX-Fragment")
40- if fragment == "" {
41- logger.Error().Msg("Missing fragment header")
42- return c.String(http.StatusBadRequest, "Missing fragment header")
43- }
44-
45- lang := c.Get(middlewares.LangKey).(string)
46- var data utils.StringsYamlData
47- err := utils.PopulateDataFromYAML(lang, ac.GetTemplateFS(), "strings.yml", &data)
48- if err != nil {
49- logger.Error().Err(err).Msg("Failed to populate data from YAML")
50- return err
51- }
52-
53- content, err := htmx.GetHTMLFragmentByID(fragment, data.CurrentLang, a, ac)
54- if err != nil { // <-- This check was missing
55- logger.Error().Err(err).Msg("Failed to get fragment")
56- return c.String(http.StatusInternalServerError, err.Error())
57- }
58-
59- return c.HTML(http.StatusOK, content)
60-}
@@ -1,60 +0,0 @@
1-package handlers
2-
3-import (
4- "net/http"
5-
6- "github.com/labstack/echo/v4"
7- "gogs.bnema.dev/gordon-echo/internal/app"
8- "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
9- "gogs.bnema.dev/gordon-echo/pkg/htmx"
10- "gogs.bnema.dev/gordon-echo/pkg/utils"
11-)
12-
13-func HTMXHandler(c echo.Context, a *app.App, ac *app.Config) error {
14- logger := a.AppLogger
15- // Detect and extract HTMX data from the request
16- htmxRequest, err := htmx.GetRequest(c)
17- if err != nil {
18- logger.Debug().Err(err).Msg("Failed to extract HTMX data")
19- return err
20- }
21-
22- // If the request is not an HTMX request, return an error
23- if !htmxRequest.Enabled {
24- logger.Error().Msg("Request is not an HTMX request")
25- return c.String(http.StatusBadRequest, "Not an HTMX request")
26- }
27-
28- // If it's a GET request, handle the fragment
29- if c.Request().Method == http.MethodGet {
30- return htmxFragmentHandler(c, a, ac)
31- }
32-
33- // For other HTTP methods, return an error
34- return c.String(http.StatusBadRequest, "Invalid request method")
35-}
36-
37-func htmxFragmentHandler(c echo.Context, a *app.App, ac *app.Config) error {
38- logger := a.AppLogger
39- fragment := c.Request().Header.Get("HX-Fragment")
40- if fragment == "" {
41- logger.Error().Msg("Missing fragment header")
42- return c.String(http.StatusBadRequest, "Missing fragment header")
43- }
44-
45- lang := c.Get(middlewares.LangKey).(string)
46- var data utils.StringsYamlData
47- err := utils.PopulateDataFromYAML(lang, ac.GetTemplateFS(), "strings.yml", &data)
48- if err != nil {
49- logger.Error().Err(err).Msg("Failed to populate data from YAML")
50- return err
51- }
52-
53- content, err := htmx.GetHTMLFragmentByID(fragment, data.CurrentLang, a, ac)
54- if err != nil { // <-- This check was missing
55- logger.Error().Err(err).Msg("Failed to get fragment")
56- return c.String(http.StatusInternalServerError, err.Error())
57- }
58-
59- return c.HTML(http.StatusOK, content)
60-}
D internal/http/handlers/htmx-installer.go +0 -75
@@ -1,75 +0,0 @@
1-package handlers
2-
3-import (
4- "fmt"
5- "net/http"
6-
7- "github.com/labstack/echo/v4"
8- "gogs.bnema.dev/gordon-echo/internal/app"
9- "gogs.bnema.dev/gordon-echo/pkg/scripts"
10-)
11-
12-var eventsChan = make(chan string)
13-
14-// TraefikInstallerHandler handles the installation of Traefik
15-func TraefikInstallerHandler(c echo.Context, a *app.App) error {
16- logger := a.AppLogger
17- eventsChan <- "Starting Traefik installation..."
18- // Retrieve the logger from the context
19- // Reject POST requests if they are not allowed with the RejectPOSTPolicy function
20- if err := RejectPOSTPolicy(c); err != nil {
21- logger.Error().Err(err).Msg("POST request rejected")
22- return err
23- }
24-
25- // Get data from the form install-traefik-form (input fields : topdomain, adminemail) and sanitize it
26- topDomain, err := SanitizeUserInput(c.FormValue("topdomain"))
27- if err != nil {
28- logger.Error().Err(err).Msg("Failed to sanitize user input")
29- return err
30- }
31- adminEmail, err := SanitizeUserInput(c.FormValue("adminemail"))
32- if err != nil {
33- logger.Error().Err(err).Msg("Failed to sanitize user input")
34- return err
35- }
36-
37- // Call the InstallTraefik function from pkg/scripts/install-traefik.go
38- if success, err := scripts.InstallTraefik(topDomain, adminEmail, a); err != nil {
39- logger.Error().Err(err).Msg("Failed to install Traefik")
40- return err
41- } else if !success {
42- logger.Error().Msg("Failed to install Traefik")
43- return err
44- } else {
45- eventsChan <- "Traefik installation completed!"
46- return c.String(200, "Traefik installed successfully")
47- }
48-}
49-
50-func TraefikInstallerSSEHandler(c echo.Context, a *app.App) error {
51- w := c.Response().Writer
52- flusher, ok := w.(http.Flusher)
53- if !ok {
54- return c.String(http.StatusInternalServerError, "Streaming unsupported!")
55- }
56-
57- c.Response().Header().Set("Content-Type", "text/event-stream")
58- c.Response().Header().Set("Cache-Control", "no-cache")
59- c.Response().Header().Set("Connection", "keep-alive")
60-
61- for {
62- // Listen for a new message to be sent to the client
63- msg, open := <-eventsChan
64- if !open {
65- fmt.Println("Connection closed")
66- break
67- }
68- _, err := fmt.Fprintf(w, "data: %s\n\n", msg)
69- if err != nil {
70- return err
71- }
72- flusher.Flush()
73- }
74- return nil
75-}
@@ -1,75 +0,0 @@
1-package handlers
2-
3-import (
4- "fmt"
5- "net/http"
6-
7- "github.com/labstack/echo/v4"
8- "gogs.bnema.dev/gordon-echo/internal/app"
9- "gogs.bnema.dev/gordon-echo/pkg/scripts"
10-)
11-
12-var eventsChan = make(chan string)
13-
14-// TraefikInstallerHandler handles the installation of Traefik
15-func TraefikInstallerHandler(c echo.Context, a *app.App) error {
16- logger := a.AppLogger
17- eventsChan <- "Starting Traefik installation..."
18- // Retrieve the logger from the context
19- // Reject POST requests if they are not allowed with the RejectPOSTPolicy function
20- if err := RejectPOSTPolicy(c); err != nil {
21- logger.Error().Err(err).Msg("POST request rejected")
22- return err
23- }
24-
25- // Get data from the form install-traefik-form (input fields : topdomain, adminemail) and sanitize it
26- topDomain, err := SanitizeUserInput(c.FormValue("topdomain"))
27- if err != nil {
28- logger.Error().Err(err).Msg("Failed to sanitize user input")
29- return err
30- }
31- adminEmail, err := SanitizeUserInput(c.FormValue("adminemail"))
32- if err != nil {
33- logger.Error().Err(err).Msg("Failed to sanitize user input")
34- return err
35- }
36-
37- // Call the InstallTraefik function from pkg/scripts/install-traefik.go
38- if success, err := scripts.InstallTraefik(topDomain, adminEmail, a); err != nil {
39- logger.Error().Err(err).Msg("Failed to install Traefik")
40- return err
41- } else if !success {
42- logger.Error().Msg("Failed to install Traefik")
43- return err
44- } else {
45- eventsChan <- "Traefik installation completed!"
46- return c.String(200, "Traefik installed successfully")
47- }
48-}
49-
50-func TraefikInstallerSSEHandler(c echo.Context, a *app.App) error {
51- w := c.Response().Writer
52- flusher, ok := w.(http.Flusher)
53- if !ok {
54- return c.String(http.StatusInternalServerError, "Streaming unsupported!")
55- }
56-
57- c.Response().Header().Set("Content-Type", "text/event-stream")
58- c.Response().Header().Set("Cache-Control", "no-cache")
59- c.Response().Header().Set("Connection", "keep-alive")
60-
61- for {
62- // Listen for a new message to be sent to the client
63- msg, open := <-eventsChan
64- if !open {
65- fmt.Println("Connection closed")
66- break
67- }
68- _, err := fmt.Fprintf(w, "data: %s\n\n", msg)
69- if err != nil {
70- return err
71- }
72- flusher.Flush()
73- }
74- return nil
75-}
D internal/http/handlers/post.go +0 -38
@@ -1,38 +0,0 @@
1-package handlers
2-
3-import (
4- "fmt"
5- "net/http"
6-
7- "github.com/labstack/echo/v4"
8- "gogs.bnema.dev/gordon-echo/pkg/templating"
9-)
10-
11-func RejectPOSTPolicy(c echo.Context) error {
12- // Maximum POST size in bytes
13- const maxPOSTSize = 1024 * 1024 // = 1 MB
14- // Check the request method
15- if c.Request().Method != http.MethodPost {
16- return fmt.Errorf("invalid request method")
17- }
18- // Check for unusually large POST bodies, which might indicate an attempt at a DoS attack
19- if c.Request().ContentLength > maxPOSTSize {
20- return fmt.Errorf("request too large: %d bytes", c.Request().ContentLength)
21- }
22-
23- // Check for other suspicious behaviors, e.g., missing user agent, too many request headers, etc.
24- if c.Request().UserAgent() == "" {
25- return fmt.Errorf("missing user agent")
26- }
27-
28- // If all checks pass
29- return nil
30-}
31-
32-func SanitizeUserInput(input string) (string, error) {
33- sanitized, err := templating.SanitizeHTML(input)
34- if err != nil {
35- return "", fmt.Errorf("Failed to sanitize user input: %w", err)
36- }
37- return sanitized, nil
38-}
@@ -1,38 +0,0 @@
1-package handlers
2-
3-import (
4- "fmt"
5- "net/http"
6-
7- "github.com/labstack/echo/v4"
8- "gogs.bnema.dev/gordon-echo/pkg/templating"
9-)
10-
11-func RejectPOSTPolicy(c echo.Context) error {
12- // Maximum POST size in bytes
13- const maxPOSTSize = 1024 * 1024 // = 1 MB
14- // Check the request method
15- if c.Request().Method != http.MethodPost {
16- return fmt.Errorf("invalid request method")
17- }
18- // Check for unusually large POST bodies, which might indicate an attempt at a DoS attack
19- if c.Request().ContentLength > maxPOSTSize {
20- return fmt.Errorf("request too large: %d bytes", c.Request().ContentLength)
21- }
22-
23- // Check for other suspicious behaviors, e.g., missing user agent, too many request headers, etc.
24- if c.Request().UserAgent() == "" {
25- return fmt.Errorf("missing user agent")
26- }
27-
28- // If all checks pass
29- return nil
30-}
31-
32-func SanitizeUserInput(input string) (string, error) {
33- sanitized, err := templating.SanitizeHTML(input)
34- if err != nil {
35- return "", fmt.Errorf("Failed to sanitize user input: %w", err)
36- }
37- return sanitized, nil
38-}
D internal/http/middlewares/detect-color-scheme.go +0 -38
@@ -1,38 +0,0 @@
1-package middlewares
2-
3-import (
4- "github.com/labstack/echo-contrib/session"
5- "github.com/labstack/echo/v4"
6-)
7-
8-// ColorSchemeKey is a key for setting and getting the color scheme from the context
9-const ColorSchemeKey = "CurrentColorScheme"
10-
11-// ColorSchemeDetection detects the current color scheme and sets it in the context
12-func ColorSchemeDetection(next echo.HandlerFunc) echo.HandlerFunc {
13- return func(c echo.Context) error {
14- colorScheme := detectCurrentColorScheme(c)
15- c.Set(ColorSchemeKey, colorScheme)
16- return next(c)
17- }
18-}
19-
20-func detectCurrentColorScheme(c echo.Context) string {
21- // Check if the color scheme key is set in the session storage
22- sess, err := session.Get("session", c)
23- if err != nil {
24- // If session storage is not available, proceed with "Sec-CH-Prefers-Color-Scheme" header detection
25- return "light"
26- }
27-
28- if colorSchemeValue, ok := sess.Values["colorScheme"]; ok && colorSchemeValue != nil {
29- return colorSchemeValue.(string) // Return the color scheme value from the session storage
30- }
31-
32- // If color scheme key is not set, proceed with "Sec-CH-Prefers-Color-Scheme" header detection
33- header := c.Request().Header.Get("Sec-CH-Prefers-Color-Scheme")
34- if header == "dark" {
35- return "dark"
36- }
37- return "light" // Default to light mode
38-}
@@ -1,38 +0,0 @@
1-package middlewares
2-
3-import (
4- "github.com/labstack/echo-contrib/session"
5- "github.com/labstack/echo/v4"
6-)
7-
8-// ColorSchemeKey is a key for setting and getting the color scheme from the context
9-const ColorSchemeKey = "CurrentColorScheme"
10-
11-// ColorSchemeDetection detects the current color scheme and sets it in the context
12-func ColorSchemeDetection(next echo.HandlerFunc) echo.HandlerFunc {
13- return func(c echo.Context) error {
14- colorScheme := detectCurrentColorScheme(c)
15- c.Set(ColorSchemeKey, colorScheme)
16- return next(c)
17- }
18-}
19-
20-func detectCurrentColorScheme(c echo.Context) string {
21- // Check if the color scheme key is set in the session storage
22- sess, err := session.Get("session", c)
23- if err != nil {
24- // If session storage is not available, proceed with "Sec-CH-Prefers-Color-Scheme" header detection
25- return "light"
26- }
27-
28- if colorSchemeValue, ok := sess.Values["colorScheme"]; ok && colorSchemeValue != nil {
29- return colorSchemeValue.(string) // Return the color scheme value from the session storage
30- }
31-
32- // If color scheme key is not set, proceed with "Sec-CH-Prefers-Color-Scheme" header detection
33- header := c.Request().Header.Get("Sec-CH-Prefers-Color-Scheme")
34- if header == "dark" {
35- return "dark"
36- }
37- return "light" // Default to light mode
38-}
D internal/http/middlewares/detect-lang.go +0 -40
@@ -1,40 +0,0 @@
1-package middlewares
2-
3-import (
4- "strings"
5-
6- "github.com/labstack/echo-contrib/session"
7- "github.com/labstack/echo/v4"
8-)
9-
10-// LangKey is a key for setting and getting the language from the context
11-const LangKey = "CurrentLang"
12-
13-// LanguageDetectiondetects the current language and sets it in the context
14-func LanguageDetection(next echo.HandlerFunc) echo.HandlerFunc {
15- return func(c echo.Context) error {
16- lang := detectCurrentLanguage(c)
17- c.Set(LangKey, lang)
18- return next(c)
19- }
20-}
21-
22-func detectCurrentLanguage(c echo.Context) string {
23- // Check if the lang key is set in the session storage
24- sess, err := session.Get("session", c)
25- if err != nil {
26- // If session storage is not available, proceed with "Accept-Language" header detection
27- return "en"
28- }
29-
30- if langValue, ok := sess.Values["lang"]; ok && langValue != nil {
31- return langValue.(string) // Return the lang value from the session storage
32- }
33-
34- // If lang key is not set, proceed with "Accept-Language" header detection
35- header := c.Request().Header.Get("Accept-Language")
36- if strings.Contains(header, "fr") {
37- return "fr"
38- }
39- return "en" // Default to English
40-}
@@ -1,40 +0,0 @@
1-package middlewares
2-
3-import (
4- "strings"
5-
6- "github.com/labstack/echo-contrib/session"
7- "github.com/labstack/echo/v4"
8-)
9-
10-// LangKey is a key for setting and getting the language from the context
11-const LangKey = "CurrentLang"
12-
13-// LanguageDetectiondetects the current language and sets it in the context
14-func LanguageDetection(next echo.HandlerFunc) echo.HandlerFunc {
15- return func(c echo.Context) error {
16- lang := detectCurrentLanguage(c)
17- c.Set(LangKey, lang)
18- return next(c)
19- }
20-}
21-
22-func detectCurrentLanguage(c echo.Context) string {
23- // Check if the lang key is set in the session storage
24- sess, err := session.Get("session", c)
25- if err != nil {
26- // If session storage is not available, proceed with "Accept-Language" header detection
27- return "en"
28- }
29-
30- if langValue, ok := sess.Values["lang"]; ok && langValue != nil {
31- return langValue.(string) // Return the lang value from the session storage
32- }
33-
34- // If lang key is not set, proceed with "Accept-Language" header detection
35- header := c.Request().Header.Get("Accept-Language")
36- if strings.Contains(header, "fr") {
37- return "fr"
38- }
39- return "en" // Default to English
40-}
D internal/http/middlewares/logger.go +0 -40
@@ -1,40 +0,0 @@
1-package middlewares
2-
3-import (
4- "strings"
5-
6- "github.com/labstack/echo/v4"
7- "github.com/labstack/echo/v4/middleware"
8- "gogs.bnema.dev/gordon-echo/pkg/utils"
9-)
10-
11-// HTTPAccessLogger creates a new middleware to log HTTP requests using the provided HttpLogger.
12-func HTTPAccessLogger(logger *utils.Logger) echo.MiddlewareFunc {
13- return middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
14- LogURI: true,
15- LogStatus: true,
16- LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
17- // Filter out any requests that contain the substring "sse" in the URI
18- if strings.Contains(v.URI, "sse") {
19- return nil
20- }
21- logger.Info().
22- Str("type", "http").
23- Str("remote_ip", c.RealIP()).
24- Str("method", c.Request().Method).
25- Str("URI", v.URI).
26- Int("status", v.Status).
27- Msg("request")
28- return nil
29- },
30- })
31-}
32-
33-func SetLogger(logger *utils.Logger) echo.MiddlewareFunc {
34- return func(next echo.HandlerFunc) echo.HandlerFunc {
35- return func(c echo.Context) error {
36- c.Set("logger", logger)
37- return next(c)
38- }
39- }
40-}
@@ -1,40 +0,0 @@
1-package middlewares
2-
3-import (
4- "strings"
5-
6- "github.com/labstack/echo/v4"
7- "github.com/labstack/echo/v4/middleware"
8- "gogs.bnema.dev/gordon-echo/pkg/utils"
9-)
10-
11-// HTTPAccessLogger creates a new middleware to log HTTP requests using the provided HttpLogger.
12-func HTTPAccessLogger(logger *utils.Logger) echo.MiddlewareFunc {
13- return middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
14- LogURI: true,
15- LogStatus: true,
16- LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
17- // Filter out any requests that contain the substring "sse" in the URI
18- if strings.Contains(v.URI, "sse") {
19- return nil
20- }
21- logger.Info().
22- Str("type", "http").
23- Str("remote_ip", c.RealIP()).
24- Str("method", c.Request().Method).
25- Str("URI", v.URI).
26- Int("status", v.Status).
27- Msg("request")
28- return nil
29- },
30- })
31-}
32-
33-func SetLogger(logger *utils.Logger) echo.MiddlewareFunc {
34- return func(next echo.HandlerFunc) echo.HandlerFunc {
35- return func(c echo.Context) error {
36- c.Set("logger", logger)
37- return next(c)
38- }
39- }
40-}
D internal/http/middlewares/secure.go +0 -18
@@ -1,18 +0,0 @@
1-package middlewares
2-
3-import (
4- "github.com/labstack/echo/v4"
5- "github.com/labstack/echo/v4/middleware"
6-)
7-
8-func SecureRoutes() echo.MiddlewareFunc {
9- return middleware.SecureWithConfig(middleware.SecureConfig{
10- XSSProtection: "1; mode=block",
11- ContentTypeNosniff: "nosniff",
12- XFrameOptions: "SAMEORIGIN",
13- HSTSMaxAge: 3600,
14- HSTSExcludeSubdomains: false,
15- ContentSecurityPolicy: "default-src 'self'",
16- // Skipper: YourCustomSkipperFunction, // If you have a custom skipper
17- })
18-}
@@ -1,18 +0,0 @@
1-package middlewares
2-
3-import (
4- "github.com/labstack/echo/v4"
5- "github.com/labstack/echo/v4/middleware"
6-)
7-
8-func SecureRoutes() echo.MiddlewareFunc {
9- return middleware.SecureWithConfig(middleware.SecureConfig{
10- XSSProtection: "1; mode=block",
11- ContentTypeNosniff: "nosniff",
12- XFrameOptions: "SAMEORIGIN",
13- HSTSMaxAge: 3600,
14- HSTSExcludeSubdomains: false,
15- ContentSecurityPolicy: "default-src 'self'",
16- // Skipper: YourCustomSkipperFunction, // If you have a custom skipper
17- })
18-}
D internal/http/routes/admin-routes.go +0 -76
@@ -1,76 +0,0 @@
1-package routes
2-
3-import (
4- "fmt"
5- "net/http"
6-
7- "github.com/labstack/echo/v4"
8- "gogs.bnema.dev/gordon-echo/internal/app"
9- "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
10- "gogs.bnema.dev/gordon-echo/pkg/templating"
11- "gogs.bnema.dev/gordon-echo/pkg/utils"
12-)
13-
14-func AdminRoute(c echo.Context, ac *app.Config, a *app.App) error {
15- // Check if it's the first launch of the app
16- if utils.IsConfigFilePresent() {
17- fmt.Println("Fresh installation detected, redirecting to /admin/install")
18- return c.Redirect(http.StatusFound, "/admin/install")
19- }
20-
21- // Retrieve the current language from the context
22- lang := c.Get(middlewares.LangKey).(string)
23-
24- // Assuming the filename is "strings.yml"
25- var data utils.StringsYamlData
26- err := utils.PopulateDataFromYAML(lang, ac.GetTemplateFS(), "strings.yml", &data)
27- if err != nil {
28- return err
29- }
30-
31- renderer, err := templating.GetHTMLRenderer("index.gohtml", ac.GetPublicFS(), a)
32- if err != nil {
33- return err
34- }
35-
36- html, err := renderer.Render(data.CurrentLang, a)
37- if err != nil {
38- // Return the error into NewLogger() to log it
39- utils.NewLogger().Error()
40- return err
41- }
42-
43- return c.HTML(http.StatusOK, html)
44-}
45-
46-func InstallRoute(c echo.Context, ac *app.Config, a *app.App) error {
47- // Check if it's the first launch of the app
48- if !utils.IsConfigFilePresent() {
49- fmt.Println("Config file already present, redirecting to /admin")
50- return c.Redirect(http.StatusFound, "/admin")
51- }
52-
53- // Retrieve the current language from the context
54- lang := c.Get(middlewares.LangKey).(string)
55-
56- var data utils.StringsYamlData
57- err := utils.PopulateDataFromYAML(lang, ac.GetTemplateFS(), "strings.yml", &data)
58- if err != nil {
59- return fmt.Errorf("failed to populate data from YAML: %w", err)
60- }
61-
62- renderer, err := templating.GetHTMLRenderer("install.gohtml", ac.GetTemplateFS(), a)
63- fmt.Println(renderer)
64- if err != nil {
65- return err
66- }
67-
68- html, err := renderer.Render(data.CurrentLang, a)
69- if err != nil {
70- // Return the error into NewLogger() to log it
71- utils.NewLogger().Error()
72- return err
73- }
74-
75- return c.HTML(http.StatusOK, html)
76-}
@@ -1,76 +0,0 @@
1-package routes
2-
3-import (
4- "fmt"
5- "net/http"
6-
7- "github.com/labstack/echo/v4"
8- "gogs.bnema.dev/gordon-echo/internal/app"
9- "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
10- "gogs.bnema.dev/gordon-echo/pkg/templating"
11- "gogs.bnema.dev/gordon-echo/pkg/utils"
12-)
13-
14-func AdminRoute(c echo.Context, ac *app.Config, a *app.App) error {
15- // Check if it's the first launch of the app
16- if utils.IsConfigFilePresent() {
17- fmt.Println("Fresh installation detected, redirecting to /admin/install")
18- return c.Redirect(http.StatusFound, "/admin/install")
19- }
20-
21- // Retrieve the current language from the context
22- lang := c.Get(middlewares.LangKey).(string)
23-
24- // Assuming the filename is "strings.yml"
25- var data utils.StringsYamlData
26- err := utils.PopulateDataFromYAML(lang, ac.GetTemplateFS(), "strings.yml", &data)
27- if err != nil {
28- return err
29- }
30-
31- renderer, err := templating.GetHTMLRenderer("index.gohtml", ac.GetPublicFS(), a)
32- if err != nil {
33- return err
34- }
35-
36- html, err := renderer.Render(data.CurrentLang, a)
37- if err != nil {
38- // Return the error into NewLogger() to log it
39- utils.NewLogger().Error()
40- return err
41- }
42-
43- return c.HTML(http.StatusOK, html)
44-}
45-
46-func InstallRoute(c echo.Context, ac *app.Config, a *app.App) error {
47- // Check if it's the first launch of the app
48- if !utils.IsConfigFilePresent() {
49- fmt.Println("Config file already present, redirecting to /admin")
50- return c.Redirect(http.StatusFound, "/admin")
51- }
52-
53- // Retrieve the current language from the context
54- lang := c.Get(middlewares.LangKey).(string)
55-
56- var data utils.StringsYamlData
57- err := utils.PopulateDataFromYAML(lang, ac.GetTemplateFS(), "strings.yml", &data)
58- if err != nil {
59- return fmt.Errorf("failed to populate data from YAML: %w", err)
60- }
61-
62- renderer, err := templating.GetHTMLRenderer("install.gohtml", ac.GetTemplateFS(), a)
63- fmt.Println(renderer)
64- if err != nil {
65- return err
66- }
67-
68- html, err := renderer.Render(data.CurrentLang, a)
69- if err != nil {
70- // Return the error into NewLogger() to log it
71- utils.NewLogger().Error()
72- return err
73- }
74-
75- return c.HTML(http.StatusOK, html)
76-}
D internal/http/routes/router.go +0 -66
@@ -1,66 +0,0 @@
1-package routes
2-
3-import (
4- "github.com/labstack/echo/v4"
5- "gogs.bnema.dev/gordon-echo/internal/app"
6- "gogs.bnema.dev/gordon-echo/internal/http/handlers"
7- "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
8-)
9-
10-func ConfigureRouter(e *echo.Echo, a *app.App, ac *app.Config) *echo.Echo {
11-
12- // SetLogger is a middleware that sets the logger in the context
13- e.Use(middlewares.SetLogger(a.HttpLogger))
14- // HTTPAccessLogger is a middleware that logs HTTP requests
15- e.Use(middlewares.HTTPAccessLogger(a.HttpLogger))
16- e.Use(middlewares.LanguageDetection)
17- e.Use(middlewares.ColorSchemeDetection)
18- //e.Use(middlewares.SecureMiddleware()) // Need testing
19-
20- // Register routes
21- e = bindAdminRoutes(e, a, ac)
22- e = bindHtmxRoutes(e, a, ac)
23- e = bindStaticRoutes(e, a.Config)
24- e.HTTPErrorHandler = handlers.ErrorNumberHandler
25- return e
26-}
27-func bindAdminRoutes(e *echo.Echo, a *app.App, ac *app.Config) *echo.Echo {
28- // We pass the embed config to the routes so we can parse the strings.yml file
29- e.GET("/admin", func(c echo.Context) error {
30- return AdminRoute(c, ac, a)
31- })
32- e.GET("/admin/install", func(c echo.Context) error {
33- return InstallRoute(c, ac, a)
34- })
35-
36- e.POST("/admin/install/traefik", func(c echo.Context) error {
37- return handlers.TraefikInstallerHandler(c, a)
38- })
39- e.GET("/admin/install/traefik/sse", func(c echo.Context) error {
40- return handlers.TraefikInstallerSSEHandler(c, a)
41- })
42- return e
43-}
44-
45-func bindHtmxRoutes(e *echo.Echo, a *app.App, ac *app.Config) *echo.Echo {
46- e.GET("/htmx", func(c echo.Context) error {
47- return handlers.HTMXHandler(c, a, ac)
48- })
49- e.GET("/htmx/fragment", func(c echo.Context) error {
50- return handlers.HTMXHandler(c, a, ac)
51- })
52- e.POST("/htmx", func(c echo.Context) error {
53- return handlers.HTMXHandler(c, a, ac)
54- })
55- e.POST("/htmx/fragment", func(c echo.Context) error {
56- return handlers.HTMXHandler(c, a, ac)
57- })
58- return e
59-}
60-
61-func bindStaticRoutes(e *echo.Echo, config *app.Config) *echo.Echo {
62- e.GET("/*", func(c echo.Context) error {
63- return StaticRoute(c, config)
64- })
65- return e
66-}
@@ -1,66 +0,0 @@
1-package routes
2-
3-import (
4- "github.com/labstack/echo/v4"
5- "gogs.bnema.dev/gordon-echo/internal/app"
6- "gogs.bnema.dev/gordon-echo/internal/http/handlers"
7- "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
8-)
9-
10-func ConfigureRouter(e *echo.Echo, a *app.App, ac *app.Config) *echo.Echo {
11-
12- // SetLogger is a middleware that sets the logger in the context
13- e.Use(middlewares.SetLogger(a.HttpLogger))
14- // HTTPAccessLogger is a middleware that logs HTTP requests
15- e.Use(middlewares.HTTPAccessLogger(a.HttpLogger))
16- e.Use(middlewares.LanguageDetection)
17- e.Use(middlewares.ColorSchemeDetection)
18- //e.Use(middlewares.SecureMiddleware()) // Need testing
19-
20- // Register routes
21- e = bindAdminRoutes(e, a, ac)
22- e = bindHtmxRoutes(e, a, ac)
23- e = bindStaticRoutes(e, a.Config)
24- e.HTTPErrorHandler = handlers.ErrorNumberHandler
25- return e
26-}
27-func bindAdminRoutes(e *echo.Echo, a *app.App, ac *app.Config) *echo.Echo {
28- // We pass the embed config to the routes so we can parse the strings.yml file
29- e.GET("/admin", func(c echo.Context) error {
30- return AdminRoute(c, ac, a)
31- })
32- e.GET("/admin/install", func(c echo.Context) error {
33- return InstallRoute(c, ac, a)
34- })
35-
36- e.POST("/admin/install/traefik", func(c echo.Context) error {
37- return handlers.TraefikInstallerHandler(c, a)
38- })
39- e.GET("/admin/install/traefik/sse", func(c echo.Context) error {
40- return handlers.TraefikInstallerSSEHandler(c, a)
41- })
42- return e
43-}
44-
45-func bindHtmxRoutes(e *echo.Echo, a *app.App, ac *app.Config) *echo.Echo {
46- e.GET("/htmx", func(c echo.Context) error {
47- return handlers.HTMXHandler(c, a, ac)
48- })
49- e.GET("/htmx/fragment", func(c echo.Context) error {
50- return handlers.HTMXHandler(c, a, ac)
51- })
52- e.POST("/htmx", func(c echo.Context) error {
53- return handlers.HTMXHandler(c, a, ac)
54- })
55- e.POST("/htmx/fragment", func(c echo.Context) error {
56- return handlers.HTMXHandler(c, a, ac)
57- })
58- return e
59-}
60-
61-func bindStaticRoutes(e *echo.Echo, config *app.Config) *echo.Echo {
62- e.GET("/*", func(c echo.Context) error {
63- return StaticRoute(c, config)
64- })
65- return e
66-}
D internal/http/routes/static-routes.go +0 -25
@@ -1,25 +0,0 @@
1-package routes
2-
3-import (
4- "io/fs"
5- "net/http"
6- "os"
7-
8- "github.com/labstack/echo/v4"
9- "gogs.bnema.dev/gordon-echo/internal/app"
10-)
11-
12-func StaticRoute(c echo.Context, config *app.Config) error {
13-
14- // Set the cache-control header based on PROD environment variable
15- if os.Getenv("PROD") == "true" {
16- c.Response().Header().Set("Cache-Control", "public, max-age=86400")
17- } else {
18- c.Response().Header().Set("Cache-Control", "no-cache")
19- }
20-
21- // Serve the file from the embedded filesystem
22- httpFs := http.FileServer(http.FS(fs.FS(config.GetPublicFS())))
23- httpFs.ServeHTTP(c.Response(), c.Request())
24- return nil
25-}
@@ -1,25 +0,0 @@
1-package routes
2-
3-import (
4- "io/fs"
5- "net/http"
6- "os"
7-
8- "github.com/labstack/echo/v4"
9- "gogs.bnema.dev/gordon-echo/internal/app"
10-)
11-
12-func StaticRoute(c echo.Context, config *app.Config) error {
13-
14- // Set the cache-control header based on PROD environment variable
15- if os.Getenv("PROD") == "true" {
16- c.Response().Header().Set("Cache-Control", "public, max-age=86400")
17- } else {
18- c.Response().Header().Set("Cache-Control", "no-cache")
19- }
20-
21- // Serve the file from the embedded filesystem
22- httpFs := http.FileServer(http.FS(fs.FS(config.GetPublicFS())))
23- httpFs.ServeHTTP(c.Response(), c.Request())
24- return nil
25-}
D internal/ui/public/404.gohtml +0 -12
@@ -1,12 +0,0 @@
1-<!DOCTYPE html>
2-<html lang="en">
3- <head>
4- <meta charset="UTF-8" />
5- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6- <title>Error 404</title>
7- </head>
8- <body>
9- <h1>Error 404</h1>
10- <p>Page not found</p>
11- </body>
12-</html>
@@ -1,12 +0,0 @@
1-<!DOCTYPE html>
2-<html lang="en">
3- <head>
4- <meta charset="UTF-8" />
5- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6- <title>Error 404</title>
7- </head>
8- <body>
9- <h1>Error 404</h1>
10- <p>Page not found</p>
11- </body>
12-</html>
D internal/ui/public/500.gohtml +0 -12
@@ -1,12 +0,0 @@
1-<!DOCTYPE html>
2-<html lang="en">
3- <head>
4- <meta charset="UTF-8" />
5- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6- <title>Error 500</title>
7- </head>
8- <body>
9- <h1>500 Internal Server Error</h1>
10- <p>Something went really wrong o/</p>
11- </body>
12-</html>
@@ -1,12 +0,0 @@
1-<!DOCTYPE html>
2-<html lang="en">
3- <head>
4- <meta charset="UTF-8" />
5- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6- <title>Error 500</title>
7- </head>
8- <body>
9- <h1>500 Internal Server Error</h1>
10- <p>Something went really wrong o/</p>
11- </body>
12-</html>
D internal/ui/public/android-chrome-192x192.png +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/android-chrome-512x512.png +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/apple-touch-icon.png +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/assets/css/custom.css +0 -116
@@ -1,116 +0,0 @@
1-/**
2- * Path: internal/ui/public/assets/css/custom.css
3- *
4- * @format
5- */
6-
7-/* Embeded path : assets/css/cutsom.css */
8-
9-@tailwind base;
10-@tailwind components;
11-@tailwind utilities;
12-
13-/* Import IBM Plex Fonts */
14-@font-face {
15- font-family: "IBMPlexSans-Bold";
16- src: url("/assets/fonts/IBMPlex/IBMPlexSans-Bold.woff2") format("woff2");
17- font-weight: bold;
18- font-style: normal;
19-}
20-
21-@font-face {
22- font-family: "IBMPlexSans-Regular";
23- src: url("/assets/fonts/IBMPlex/IBMPlexSans-Regular.woff2") format("woff2");
24- font-weight: normal;
25- font-style: normal;
26-}
27-
28-/* Import Jetbrains Mono Nerd Font */
29-@font-face {
30- font-family: "JetBrainsMono-Nerd-Font-Regular";
31- src: url("/assets/fonts/JetBrainsMonoNerd/JetBrainsMonoNerdFontMono-Regular.woff2")
32- format("woff2");
33- font-weight: normal;
34- font-style: normal;
35-}
36-
37-@font-face {
38- font-family: "LilitaOne-Regular";
39- src: url("/assets/fonts/LilitaOne/LilitaOne-Regular.ttf") format("truetype");
40- font-weight: normal;
41- font-style: normal;
42-}
43-
44-.fade-me-out.htmx-swapping {
45- opacity: 0;
46- transition: opacity 1s ease-out;
47-}
48-
49-/* View transition */
50-@keyframes fade-in {
51- from {
52- opacity: 0;
53- }
54-}
55-
56-@keyframes fade-out {
57- to {
58- opacity: 0;
59- }
60-}
61-
62-@keyframes slide-from-right {
63- from {
64- transform: translateX(90px);
65- }
66-}
67-
68-@keyframes slide-to-left {
69- to {
70- transform: translateX(-90px);
71- }
72-}
73-
74-.slide-it {
75- view-transition-name: slide-it;
76-}
77-
78-::view-transition-old(slide-it) {
79- animation: 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
80- 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
81-}
82-::view-transition-new(slide-it) {
83- animation: 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
84- 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
85-}
86-
87-.gordon-title {
88- font-family: "LilitaOne-Regular", sans-serif;
89-
90-}
91-
92-.IBMPlexSans {
93- font-family: "IBMPlexSans-Regular", sans-serif;
94-}
95-
96-.circle-shadow {
97- width: 100%;
98- position: relative;
99-}
100-
101-.circle-shadow::after {
102- position: absolute;
103- width: 50%;
104- height: 0.4rem;
105- margin: auto;
106- background: rgba(0, 0, 0, 0.1);
107- border-radius: 50%;
108- z-index: -1;
109- bottom: 2%;
110- content: "";
111- box-shadow: 0 0 1px 0.3rem rgba(0, 0, 0, 0.1);
112-}
113-
114-.iconoir {
115- margin-right: 8px;
116-}
@@ -1,116 +0,0 @@
1-/**
2- * Path: internal/ui/public/assets/css/custom.css
3- *
4- * @format
5- */
6-
7-/* Embeded path : assets/css/cutsom.css */
8-
9-@tailwind base;
10-@tailwind components;
11-@tailwind utilities;
12-
13-/* Import IBM Plex Fonts */
14-@font-face {
15- font-family: "IBMPlexSans-Bold";
16- src: url("/assets/fonts/IBMPlex/IBMPlexSans-Bold.woff2") format("woff2");
17- font-weight: bold;
18- font-style: normal;
19-}
20-
21-@font-face {
22- font-family: "IBMPlexSans-Regular";
23- src: url("/assets/fonts/IBMPlex/IBMPlexSans-Regular.woff2") format("woff2");
24- font-weight: normal;
25- font-style: normal;
26-}
27-
28-/* Import Jetbrains Mono Nerd Font */
29-@font-face {
30- font-family: "JetBrainsMono-Nerd-Font-Regular";
31- src: url("/assets/fonts/JetBrainsMonoNerd/JetBrainsMonoNerdFontMono-Regular.woff2")
32- format("woff2");
33- font-weight: normal;
34- font-style: normal;
35-}
36-
37-@font-face {
38- font-family: "LilitaOne-Regular";
39- src: url("/assets/fonts/LilitaOne/LilitaOne-Regular.ttf") format("truetype");
40- font-weight: normal;
41- font-style: normal;
42-}
43-
44-.fade-me-out.htmx-swapping {
45- opacity: 0;
46- transition: opacity 1s ease-out;
47-}
48-
49-/* View transition */
50-@keyframes fade-in {
51- from {
52- opacity: 0;
53- }
54-}
55-
56-@keyframes fade-out {
57- to {
58- opacity: 0;
59- }
60-}
61-
62-@keyframes slide-from-right {
63- from {
64- transform: translateX(90px);
65- }
66-}
67-
68-@keyframes slide-to-left {
69- to {
70- transform: translateX(-90px);
71- }
72-}
73-
74-.slide-it {
75- view-transition-name: slide-it;
76-}
77-
78-::view-transition-old(slide-it) {
79- animation: 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
80- 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
81-}
82-::view-transition-new(slide-it) {
83- animation: 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
84- 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
85-}
86-
87-.gordon-title {
88- font-family: "LilitaOne-Regular", sans-serif;
89-
90-}
91-
92-.IBMPlexSans {
93- font-family: "IBMPlexSans-Regular", sans-serif;
94-}
95-
96-.circle-shadow {
97- width: 100%;
98- position: relative;
99-}
100-
101-.circle-shadow::after {
102- position: absolute;
103- width: 50%;
104- height: 0.4rem;
105- margin: auto;
106- background: rgba(0, 0, 0, 0.1);
107- border-radius: 50%;
108- z-index: -1;
109- bottom: 2%;
110- content: "";
111- box-shadow: 0 0 1px 0.3rem rgba(0, 0, 0, 0.1);
112-}
113-
114-.iconoir {
115- margin-right: 8px;
116-}
D internal/ui/public/assets/css/tailwind.css +0 -1046
@@ -1,1046 +0,0 @@
1-/**
2- * Path: internal/ui/public/assets/css/custom.css
3- *
4- * @format
5- */
6-
7-/* Embeded path : assets/css/cutsom.css */
8-
9-/*! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com
10- */
11-
12-/*
13-1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
14-2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
15-*/
16-
17-*,
18-::before,
19-::after {
20- box-sizing: border-box;
21- /* 1 */
22- border-width: 0;
23- /* 2 */
24- border-style: solid;
25- /* 2 */
26- border-color: #e5e7eb;
27- /* 2 */
28-}
29-
30-::before,
31-::after {
32- --tw-content: '';
33-}
34-
35-/*
36-1. Use a consistent sensible line-height in all browsers.
37-2. Prevent adjustments of font size after orientation changes in iOS.
38-3. Use a more readable tab size.
39-4. Use the user's configured `sans` font-family by default.
40-5. Use the user's configured `sans` font-feature-settings by default.
41-6. Use the user's configured `sans` font-variation-settings by default.
42-*/
43-
44-html {
45- line-height: 1.5;
46- /* 1 */
47- -webkit-text-size-adjust: 100%;
48- /* 2 */
49- -moz-tab-size: 4;
50- /* 3 */
51- -o-tab-size: 4;
52- tab-size: 4;
53- /* 3 */
54- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
55- /* 4 */
56- font-feature-settings: normal;
57- /* 5 */
58- font-variation-settings: normal;
59- /* 6 */
60-}
61-
62-/*
63-1. Remove the margin in all browsers.
64-2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
65-*/
66-
67-body {
68- margin: 0;
69- /* 1 */
70- line-height: inherit;
71- /* 2 */
72-}
73-
74-/*
75-1. Add the correct height in Firefox.
76-2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
77-3. Ensure horizontal rules are visible by default.
78-*/
79-
80-hr {
81- height: 0;
82- /* 1 */
83- color: inherit;
84- /* 2 */
85- border-top-width: 1px;
86- /* 3 */
87-}
88-
89-/*
90-Add the correct text decoration in Chrome, Edge, and Safari.
91-*/
92-
93-abbr:where([title]) {
94- -webkit-text-decoration: underline dotted;
95- text-decoration: underline dotted;
96-}
97-
98-/*
99-Remove the default font size and weight for headings.
100-*/
101-
102-h1,
103-h2,
104-h3,
105-h4,
106-h5,
107-h6 {
108- font-size: inherit;
109- font-weight: inherit;
110-}
111-
112-/*
113-Reset links to optimize for opt-in styling instead of opt-out.
114-*/
115-
116-a {
117- color: inherit;
118- text-decoration: inherit;
119-}
120-
121-/*
122-Add the correct font weight in Edge and Safari.
123-*/
124-
125-b,
126-strong {
127- font-weight: bolder;
128-}
129-
130-/*
131-1. Use the user's configured `mono` font family by default.
132-2. Correct the odd `em` font sizing in all browsers.
133-*/
134-
135-code,
136-kbd,
137-samp,
138-pre {
139- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
140- /* 1 */
141- font-size: 1em;
142- /* 2 */
143-}
144-
145-/*
146-Add the correct font size in all browsers.
147-*/
148-
149-small {
150- font-size: 80%;
151-}
152-
153-/*
154-Prevent `sub` and `sup` elements from affecting the line height in all browsers.
155-*/
156-
157-sub,
158-sup {
159- font-size: 75%;
160- line-height: 0;
161- position: relative;
162- vertical-align: baseline;
163-}
164-
165-sub {
166- bottom: -0.25em;
167-}
168-
169-sup {
170- top: -0.5em;
171-}
172-
173-/*
174-1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
175-2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
176-3. Remove gaps between table borders by default.
177-*/
178-
179-table {
180- text-indent: 0;
181- /* 1 */
182- border-color: inherit;
183- /* 2 */
184- border-collapse: collapse;
185- /* 3 */
186-}
187-
188-/*
189-1. Change the font styles in all browsers.
190-2. Remove the margin in Firefox and Safari.
191-3. Remove default padding in all browsers.
192-*/
193-
194-button,
195-input,
196-optgroup,
197-select,
198-textarea {
199- font-family: inherit;
200- /* 1 */
201- font-feature-settings: inherit;
202- /* 1 */
203- font-variation-settings: inherit;
204- /* 1 */
205- font-size: 100%;
206- /* 1 */
207- font-weight: inherit;
208- /* 1 */
209- line-height: inherit;
210- /* 1 */
211- color: inherit;
212- /* 1 */
213- margin: 0;
214- /* 2 */
215- padding: 0;
216- /* 3 */
217-}
218-
219-/*
220-Remove the inheritance of text transform in Edge and Firefox.
221-*/
222-
223-button,
224-select {
225- text-transform: none;
226-}
227-
228-/*
229-1. Correct the inability to style clickable types in iOS and Safari.
230-2. Remove default button styles.
231-*/
232-
233-button,
234-[type='button'],
235-[type='reset'],
236-[type='submit'] {
237- -webkit-appearance: button;
238- /* 1 */
239- background-color: transparent;
240- /* 2 */
241- background-image: none;
242- /* 2 */
243-}
244-
245-/*
246-Use the modern Firefox focus style for all focusable elements.
247-*/
248-
249-:-moz-focusring {
250- outline: auto;
251-}
252-
253-/*
254-Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
255-*/
256-
257-:-moz-ui-invalid {
258- box-shadow: none;
259-}
260-
261-/*
262-Add the correct vertical alignment in Chrome and Firefox.
263-*/
264-
265-progress {
266- vertical-align: baseline;
267-}
268-
269-/*
270-Correct the cursor style of increment and decrement buttons in Safari.
271-*/
272-
273-::-webkit-inner-spin-button,
274-::-webkit-outer-spin-button {
275- height: auto;
276-}
277-
278-/*
279-1. Correct the odd appearance in Chrome and Safari.
280-2. Correct the outline style in Safari.
281-*/
282-
283-[type='search'] {
284- -webkit-appearance: textfield;
285- /* 1 */
286- outline-offset: -2px;
287- /* 2 */
288-}
289-
290-/*
291-Remove the inner padding in Chrome and Safari on macOS.
292-*/
293-
294-::-webkit-search-decoration {
295- -webkit-appearance: none;
296-}
297-
298-/*
299-1. Correct the inability to style clickable types in iOS and Safari.
300-2. Change font properties to `inherit` in Safari.
301-*/
302-
303-::-webkit-file-upload-button {
304- -webkit-appearance: button;
305- /* 1 */
306- font: inherit;
307- /* 2 */
308-}
309-
310-/*
311-Add the correct display in Chrome and Safari.
312-*/
313-
314-summary {
315- display: list-item;
316-}
317-
318-/*
319-Removes the default spacing and border for appropriate elements.
320-*/
321-
322-blockquote,
323-dl,
324-dd,
325-h1,
326-h2,
327-h3,
328-h4,
329-h5,
330-h6,
331-hr,
332-figure,
333-p,
334-pre {
335- margin: 0;
336-}
337-
338-fieldset {
339- margin: 0;
340- padding: 0;
341-}
342-
343-legend {
344- padding: 0;
345-}
346-
347-ol,
348-ul,
349-menu {
350- list-style: none;
351- margin: 0;
352- padding: 0;
353-}
354-
355-/*
356-Reset default styling for dialogs.
357-*/
358-
359-dialog {
360- padding: 0;
361-}
362-
363-/*
364-Prevent resizing textareas horizontally by default.
365-*/
366-
367-textarea {
368- resize: vertical;
369-}
370-
371-/*
372-1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
373-2. Set the default placeholder color to the user's configured gray 400 color.
374-*/
375-
376-input::-moz-placeholder, textarea::-moz-placeholder {
377- opacity: 1;
378- /* 1 */
379- color: #9ca3af;
380- /* 2 */
381-}
382-
383-input::placeholder,
384-textarea::placeholder {
385- opacity: 1;
386- /* 1 */
387- color: #9ca3af;
388- /* 2 */
389-}
390-
391-/*
392-Set the default cursor for buttons.
393-*/
394-
395-button,
396-[role="button"] {
397- cursor: pointer;
398-}
399-
400-/*
401-Make sure disabled buttons don't get the pointer cursor.
402-*/
403-
404-:disabled {
405- cursor: default;
406-}
407-
408-/*
409-1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
410-2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
411- This can trigger a poorly considered lint error in some tools but is included by design.
412-*/
413-
414-img,
415-svg,
416-video,
417-canvas,
418-audio,
419-iframe,
420-embed,
421-object {
422- display: block;
423- /* 1 */
424- vertical-align: middle;
425- /* 2 */
426-}
427-
428-/*
429-Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
430-*/
431-
432-img,
433-video {
434- max-width: 100%;
435- height: auto;
436-}
437-
438-/* Make elements with the HTML hidden attribute stay hidden by default */
439-
440-[hidden] {
441- display: none;
442-}
443-
444-*, ::before, ::after {
445- --tw-border-spacing-x: 0;
446- --tw-border-spacing-y: 0;
447- --tw-translate-x: 0;
448- --tw-translate-y: 0;
449- --tw-rotate: 0;
450- --tw-skew-x: 0;
451- --tw-skew-y: 0;
452- --tw-scale-x: 1;
453- --tw-scale-y: 1;
454- --tw-pan-x: ;
455- --tw-pan-y: ;
456- --tw-pinch-zoom: ;
457- --tw-scroll-snap-strictness: proximity;
458- --tw-gradient-from-position: ;
459- --tw-gradient-via-position: ;
460- --tw-gradient-to-position: ;
461- --tw-ordinal: ;
462- --tw-slashed-zero: ;
463- --tw-numeric-figure: ;
464- --tw-numeric-spacing: ;
465- --tw-numeric-fraction: ;
466- --tw-ring-inset: ;
467- --tw-ring-offset-width: 0px;
468- --tw-ring-offset-color: #fff;
469- --tw-ring-color: rgb(59 130 246 / 0.5);
470- --tw-ring-offset-shadow: 0 0 #0000;
471- --tw-ring-shadow: 0 0 #0000;
472- --tw-shadow: 0 0 #0000;
473- --tw-shadow-colored: 0 0 #0000;
474- --tw-blur: ;
475- --tw-brightness: ;
476- --tw-contrast: ;
477- --tw-grayscale: ;
478- --tw-hue-rotate: ;
479- --tw-invert: ;
480- --tw-saturate: ;
481- --tw-sepia: ;
482- --tw-drop-shadow: ;
483- --tw-backdrop-blur: ;
484- --tw-backdrop-brightness: ;
485- --tw-backdrop-contrast: ;
486- --tw-backdrop-grayscale: ;
487- --tw-backdrop-hue-rotate: ;
488- --tw-backdrop-invert: ;
489- --tw-backdrop-opacity: ;
490- --tw-backdrop-saturate: ;
491- --tw-backdrop-sepia: ;
492-}
493-
494-::backdrop {
495- --tw-border-spacing-x: 0;
496- --tw-border-spacing-y: 0;
497- --tw-translate-x: 0;
498- --tw-translate-y: 0;
499- --tw-rotate: 0;
500- --tw-skew-x: 0;
501- --tw-skew-y: 0;
502- --tw-scale-x: 1;
503- --tw-scale-y: 1;
504- --tw-pan-x: ;
505- --tw-pan-y: ;
506- --tw-pinch-zoom: ;
507- --tw-scroll-snap-strictness: proximity;
508- --tw-gradient-from-position: ;
509- --tw-gradient-via-position: ;
510- --tw-gradient-to-position: ;
511- --tw-ordinal: ;
512- --tw-slashed-zero: ;
513- --tw-numeric-figure: ;
514- --tw-numeric-spacing: ;
515- --tw-numeric-fraction: ;
516- --tw-ring-inset: ;
517- --tw-ring-offset-width: 0px;
518- --tw-ring-offset-color: #fff;
519- --tw-ring-color: rgb(59 130 246 / 0.5);
520- --tw-ring-offset-shadow: 0 0 #0000;
521- --tw-ring-shadow: 0 0 #0000;
522- --tw-shadow: 0 0 #0000;
523- --tw-shadow-colored: 0 0 #0000;
524- --tw-blur: ;
525- --tw-brightness: ;
526- --tw-contrast: ;
527- --tw-grayscale: ;
528- --tw-hue-rotate: ;
529- --tw-invert: ;
530- --tw-saturate: ;
531- --tw-sepia: ;
532- --tw-drop-shadow: ;
533- --tw-backdrop-blur: ;
534- --tw-backdrop-brightness: ;
535- --tw-backdrop-contrast: ;
536- --tw-backdrop-grayscale: ;
537- --tw-backdrop-hue-rotate: ;
538- --tw-backdrop-invert: ;
539- --tw-backdrop-opacity: ;
540- --tw-backdrop-saturate: ;
541- --tw-backdrop-sepia: ;
542-}
543-
544-.my-2 {
545- margin-top: 0.5rem;
546- margin-bottom: 0.5rem;
547-}
548-
549-.mb-4 {
550- margin-bottom: 1rem;
551-}
552-
553-.ml-2 {
554- margin-left: 0.5rem;
555-}
556-
557-.ml-4 {
558- margin-left: 1rem;
559-}
560-
561-.mr-2 {
562- margin-right: 0.5rem;
563-}
564-
565-.mt-4 {
566- margin-top: 1rem;
567-}
568-
569-.flex {
570- display: flex;
571-}
572-
573-.h-10 {
574- height: 2.5rem;
575-}
576-
577-.h-24 {
578- height: 6rem;
579-}
580-
581-.h-32 {
582- height: 8rem;
583-}
584-
585-.h-44 {
586- height: 11rem;
587-}
588-
589-.h-64 {
590- height: 16rem;
591-}
592-
593-.h-full {
594- height: 100%;
595-}
596-
597-.w-1\/5 {
598- width: 20%;
599-}
600-
601-.w-2\/6 {
602- width: 33.333333%;
603-}
604-
605-.w-4\/6 {
606- width: 66.666667%;
607-}
608-
609-.w-72 {
610- width: 18rem;
611-}
612-
613-.w-full {
614- width: 100%;
615-}
616-
617-.w-2\/3 {
618- width: 66.666667%;
619-}
620-
621-.w-screen {
622- width: 100vw;
623-}
624-
625-.w-96 {
626- width: 24rem;
627-}
628-
629-.flex-1 {
630- flex: 1 1 0%;
631-}
632-
633-.flex-grow {
634- flex-grow: 1;
635-}
636-
637-.flex-row {
638- flex-direction: row;
639-}
640-
641-.flex-col {
642- flex-direction: column;
643-}
644-
645-.items-center {
646- align-items: center;
647-}
648-
649-.justify-end {
650- justify-content: flex-end;
651-}
652-
653-.justify-center {
654- justify-content: center;
655-}
656-
657-.justify-between {
658- justify-content: space-between;
659-}
660-
661-.justify-items-center {
662- justify-items: center;
663-}
664-
665-.space-x-4 > :not([hidden]) ~ :not([hidden]) {
666- --tw-space-x-reverse: 0;
667- margin-right: calc(1rem * var(--tw-space-x-reverse));
668- margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
669-}
670-
671-.space-y-2 > :not([hidden]) ~ :not([hidden]) {
672- --tw-space-y-reverse: 0;
673- margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
674- margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
675-}
676-
677-.space-y-4 > :not([hidden]) ~ :not([hidden]) {
678- --tw-space-y-reverse: 0;
679- margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
680- margin-bottom: calc(1rem * var(--tw-space-y-reverse));
681-}
682-
683-.space-x-3 > :not([hidden]) ~ :not([hidden]) {
684- --tw-space-x-reverse: 0;
685- margin-right: calc(0.75rem * var(--tw-space-x-reverse));
686- margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
687-}
688-
689-.space-y-3 > :not([hidden]) ~ :not([hidden]) {
690- --tw-space-y-reverse: 0;
691- margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse)));
692- margin-bottom: calc(0.75rem * var(--tw-space-y-reverse));
693-}
694-
695-.overflow-y-scroll {
696- overflow-y: scroll;
697-}
698-
699-.rounded-lg {
700- border-radius: 0.5rem;
701-}
702-
703-.rounded-md {
704- border-radius: 0.375rem;
705-}
706-
707-.rounded-sm {
708- border-radius: 0.125rem;
709-}
710-
711-.border {
712- border-width: 1px;
713-}
714-
715-.border-gray-300 {
716- --tw-border-opacity: 1;
717- border-color: rgb(209 213 219 / var(--tw-border-opacity));
718-}
719-
720-.bg-gordonmascot-lblue {
721- --tw-bg-opacity: 1;
722- background-color: rgb(0 123 192 / var(--tw-bg-opacity));
723-}
724-
725-.bg-gordonmascot-mblue {
726- --tw-bg-opacity: 1;
727- background-color: rgb(0 82 147 / var(--tw-bg-opacity));
728-}
729-
730-.bg-gray-300 {
731- --tw-bg-opacity: 1;
732- background-color: rgb(209 213 219 / var(--tw-bg-opacity));
733-}
734-
735-.bg-transparent {
736- background-color: transparent;
737-}
738-
739-.bg-white {
740- --tw-bg-opacity: 1;
741- background-color: rgb(255 255 255 / var(--tw-bg-opacity));
742-}
743-
744-.bg-zinc-200 {
745- --tw-bg-opacity: 1;
746- background-color: rgb(228 228 231 / var(--tw-bg-opacity));
747-}
748-
749-.bg-gordonmascot-hblue {
750- --tw-bg-opacity: 1;
751- background-color: rgb(1 30 92 / var(--tw-bg-opacity));
752-}
753-
754-.bg-zinc-400 {
755- --tw-bg-opacity: 1;
756- background-color: rgb(161 161 170 / var(--tw-bg-opacity));
757-}
758-
759-.p-4 {
760- padding: 1rem;
761-}
762-
763-.p-2 {
764- padding: 0.5rem;
765-}
766-
767-.px-2 {
768- padding-left: 0.5rem;
769- padding-right: 0.5rem;
770-}
771-
772-.px-6 {
773- padding-left: 1.5rem;
774- padding-right: 1.5rem;
775-}
776-
777-.py-2 {
778- padding-top: 0.5rem;
779- padding-bottom: 0.5rem;
780-}
781-
782-.px-4 {
783- padding-left: 1rem;
784- padding-right: 1rem;
785-}
786-
787-.text-2xl {
788- font-size: 1.5rem;
789- line-height: 2rem;
790-}
791-
792-.text-5xl {
793- font-size: 3rem;
794- line-height: 1;
795-}
796-
797-.text-lg {
798- font-size: 1.125rem;
799- line-height: 1.75rem;
800-}
801-
802-.text-sm {
803- font-size: 0.875rem;
804- line-height: 1.25rem;
805-}
806-
807-.text-xl {
808- font-size: 1.25rem;
809- line-height: 1.75rem;
810-}
811-
812-.font-semibold {
813- font-weight: 600;
814-}
815-
816-.tracking-tighter {
817- letter-spacing: -0.05em;
818-}
819-
820-.text-gordonmascot-hblue {
821- --tw-text-opacity: 1;
822- color: rgb(1 30 92 / var(--tw-text-opacity));
823-}
824-
825-.text-gordonmascot-mblue {
826- --tw-text-opacity: 1;
827- color: rgb(0 82 147 / var(--tw-text-opacity));
828-}
829-
830-.text-gray-100 {
831- --tw-text-opacity: 1;
832- color: rgb(243 244 246 / var(--tw-text-opacity));
833-}
834-
835-.text-gray-800 {
836- --tw-text-opacity: 1;
837- color: rgb(31 41 55 / var(--tw-text-opacity));
838-}
839-
840-.text-slate-400 {
841- --tw-text-opacity: 1;
842- color: rgb(148 163 184 / var(--tw-text-opacity));
843-}
844-
845-.text-slate-100 {
846- --tw-text-opacity: 1;
847- color: rgb(241 245 249 / var(--tw-text-opacity));
848-}
849-
850-.shadow {
851- --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
852- --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
853- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
854-}
855-
856-.outline-none {
857- outline: 2px solid transparent;
858- outline-offset: 2px;
859-}
860-
861-/* Import IBM Plex Fonts */
862-
863-@font-face {
864- font-family: "IBMPlexSans-Bold";
865-
866- src: url("/assets/fonts/IBMPlex/IBMPlexSans-Bold.woff2") format("woff2");
867-
868- font-weight: bold;
869-
870- font-style: normal;
871-}
872-
873-@font-face {
874- font-family: "IBMPlexSans-Regular";
875-
876- src: url("/assets/fonts/IBMPlex/IBMPlexSans-Regular.woff2") format("woff2");
877-
878- font-weight: normal;
879-
880- font-style: normal;
881-}
882-
883-/* Import Jetbrains Mono Nerd Font */
884-
885-@font-face {
886- font-family: "JetBrainsMono-Nerd-Font-Regular";
887-
888- src: url("/assets/fonts/JetBrainsMonoNerd/JetBrainsMonoNerdFontMono-Regular.woff2")
889- format("woff2");
890-
891- font-weight: normal;
892-
893- font-style: normal;
894-}
895-
896-@font-face {
897- font-family: "LilitaOne-Regular";
898-
899- src: url("/assets/fonts/LilitaOne/LilitaOne-Regular.ttf") format("truetype");
900-
901- font-weight: normal;
902-
903- font-style: normal;
904-}
905-
906-.fade-me-out.htmx-swapping {
907- opacity: 0;
908- transition: opacity 1s ease-out;
909-}
910-
911-/* View transition */
912-
913-@keyframes fade-in {
914- from {
915- opacity: 0;
916- }
917-}
918-
919-@keyframes fade-out {
920- to {
921- opacity: 0;
922- }
923-}
924-
925-@keyframes slide-from-right {
926- from {
927- transform: translateX(90px);
928- }
929-}
930-
931-@keyframes slide-to-left {
932- to {
933- transform: translateX(-90px);
934- }
935-}
936-
937-.slide-it {
938- view-transition-name: slide-it;
939-}
940-
941-::view-transition-old(slide-it) {
942- animation: 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
943- 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
944-}
945-
946-::view-transition-new(slide-it) {
947- animation: 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
948- 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
949-}
950-
951-.gordon-title {
952- font-family: "LilitaOne-Regular", sans-serif;
953-}
954-
955-.IBMPlexSans {
956- font-family: "IBMPlexSans-Regular", sans-serif;
957-}
958-
959-.circle-shadow {
960- width: 100%;
961- position: relative;
962-}
963-
964-.circle-shadow::after {
965- position: absolute;
966- width: 50%;
967- height: 0.4rem;
968- margin: auto;
969- background: rgba(0, 0, 0, 0.1);
970- border-radius: 50%;
971- z-index: -1;
972- bottom: 2%;
973- content: "";
974- box-shadow: 0 0 1px 0.3rem rgba(0, 0, 0, 0.1);
975-}
976-
977-.iconoir {
978- margin-right: 8px;
979-}
980-
981-@media (min-width: 640px) {
982- .sm\:h-24 {
983- height: 6rem;
984- }
985-}
986-
987-@media (min-width: 768px) {
988- .md\:w-1\/2 {
989- width: 50%;
990- }
991-
992- .md\:flex-row {
993- flex-direction: row;
994- }
995-
996- .md\:space-y-6 > :not([hidden]) ~ :not([hidden]) {
997- --tw-space-y-reverse: 0;
998- margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
999- margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
1000- }
1001-
1002- .md\:p-8 {
1003- padding: 2rem;
1004- }
1005-
1006- .md\:py-8 {
1007- padding-top: 2rem;
1008- padding-bottom: 2rem;
1009- }
1010-
1011- .md\:pl-4 {
1012- padding-left: 1rem;
1013- }
1014-
1015- .md\:pt-10 {
1016- padding-top: 2.5rem;
1017- }
1018-
1019- .md\:text-2xl {
1020- font-size: 1.5rem;
1021- line-height: 2rem;
1022- }
1023-
1024- .md\:text-3xl {
1025- font-size: 1.875rem;
1026- line-height: 2.25rem;
1027- }
1028-}
1029-
1030-@media (min-width: 1024px) {
1031- .lg\:text-4xl {
1032- font-size: 2.25rem;
1033- line-height: 2.5rem;
1034- }
1035-}
1036-
1037-@media (min-width: 1280px) {
1038- .xl\:h-48 {
1039- height: 12rem;
1040- }
1041-
1042- .xl\:text-5xl {
1043- font-size: 3rem;
1044- line-height: 1;
1045- }
1046-}
@@ -1,1046 +0,0 @@
1-/**
2- * Path: internal/ui/public/assets/css/custom.css
3- *
4- * @format
5- */
6-
7-/* Embeded path : assets/css/cutsom.css */
8-
9-/*! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com
10- */
11-
12-/*
13-1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
14-2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
15-*/
16-
17-*,
18-::before,
19-::after {
20- box-sizing: border-box;
21- /* 1 */
22- border-width: 0;
23- /* 2 */
24- border-style: solid;
25- /* 2 */
26- border-color: #e5e7eb;
27- /* 2 */
28-}
29-
30-::before,
31-::after {
32- --tw-content: '';
33-}
34-
35-/*
36-1. Use a consistent sensible line-height in all browsers.
37-2. Prevent adjustments of font size after orientation changes in iOS.
38-3. Use a more readable tab size.
39-4. Use the user's configured `sans` font-family by default.
40-5. Use the user's configured `sans` font-feature-settings by default.
41-6. Use the user's configured `sans` font-variation-settings by default.
42-*/
43-
44-html {
45- line-height: 1.5;
46- /* 1 */
47- -webkit-text-size-adjust: 100%;
48- /* 2 */
49- -moz-tab-size: 4;
50- /* 3 */
51- -o-tab-size: 4;
52- tab-size: 4;
53- /* 3 */
54- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
55- /* 4 */
56- font-feature-settings: normal;
57- /* 5 */
58- font-variation-settings: normal;
59- /* 6 */
60-}
61-
62-/*
63-1. Remove the margin in all browsers.
64-2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
65-*/
66-
67-body {
68- margin: 0;
69- /* 1 */
70- line-height: inherit;
71- /* 2 */
72-}
73-
74-/*
75-1. Add the correct height in Firefox.
76-2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
77-3. Ensure horizontal rules are visible by default.
78-*/
79-
80-hr {
81- height: 0;
82- /* 1 */
83- color: inherit;
84- /* 2 */
85- border-top-width: 1px;
86- /* 3 */
87-}
88-
89-/*
90-Add the correct text decoration in Chrome, Edge, and Safari.
91-*/
92-
93-abbr:where([title]) {
94- -webkit-text-decoration: underline dotted;
95- text-decoration: underline dotted;
96-}
97-
98-/*
99-Remove the default font size and weight for headings.
100-*/
101-
102-h1,
103-h2,
104-h3,
105-h4,
106-h5,
107-h6 {
108- font-size: inherit;
109- font-weight: inherit;
110-}
111-
112-/*
113-Reset links to optimize for opt-in styling instead of opt-out.
114-*/
115-
116-a {
117- color: inherit;
118- text-decoration: inherit;
119-}
120-
121-/*
122-Add the correct font weight in Edge and Safari.
123-*/
124-
125-b,
126-strong {
127- font-weight: bolder;
128-}
129-
130-/*
131-1. Use the user's configured `mono` font family by default.
132-2. Correct the odd `em` font sizing in all browsers.
133-*/
134-
135-code,
136-kbd,
137-samp,
138-pre {
139- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
140- /* 1 */
141- font-size: 1em;
142- /* 2 */
143-}
144-
145-/*
146-Add the correct font size in all browsers.
147-*/
148-
149-small {
150- font-size: 80%;
151-}
152-
153-/*
154-Prevent `sub` and `sup` elements from affecting the line height in all browsers.
155-*/
156-
157-sub,
158-sup {
159- font-size: 75%;
160- line-height: 0;
161- position: relative;
162- vertical-align: baseline;
163-}
164-
165-sub {
166- bottom: -0.25em;
167-}
168-
169-sup {
170- top: -0.5em;
171-}
172-
173-/*
174-1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
175-2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
176-3. Remove gaps between table borders by default.
177-*/
178-
179-table {
180- text-indent: 0;
181- /* 1 */
182- border-color: inherit;
183- /* 2 */
184- border-collapse: collapse;
185- /* 3 */
186-}
187-
188-/*
189-1. Change the font styles in all browsers.
190-2. Remove the margin in Firefox and Safari.
191-3. Remove default padding in all browsers.
192-*/
193-
194-button,
195-input,
196-optgroup,
197-select,
198-textarea {
199- font-family: inherit;
200- /* 1 */
201- font-feature-settings: inherit;
202- /* 1 */
203- font-variation-settings: inherit;
204- /* 1 */
205- font-size: 100%;
206- /* 1 */
207- font-weight: inherit;
208- /* 1 */
209- line-height: inherit;
210- /* 1 */
211- color: inherit;
212- /* 1 */
213- margin: 0;
214- /* 2 */
215- padding: 0;
216- /* 3 */
217-}
218-
219-/*
220-Remove the inheritance of text transform in Edge and Firefox.
221-*/
222-
223-button,
224-select {
225- text-transform: none;
226-}
227-
228-/*
229-1. Correct the inability to style clickable types in iOS and Safari.
230-2. Remove default button styles.
231-*/
232-
233-button,
234-[type='button'],
235-[type='reset'],
236-[type='submit'] {
237- -webkit-appearance: button;
238- /* 1 */
239- background-color: transparent;
240- /* 2 */
241- background-image: none;
242- /* 2 */
243-}
244-
245-/*
246-Use the modern Firefox focus style for all focusable elements.
247-*/
248-
249-:-moz-focusring {
250- outline: auto;
251-}
252-
253-/*
254-Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
255-*/
256-
257-:-moz-ui-invalid {
258- box-shadow: none;
259-}
260-
261-/*
262-Add the correct vertical alignment in Chrome and Firefox.
263-*/
264-
265-progress {
266- vertical-align: baseline;
267-}
268-
269-/*
270-Correct the cursor style of increment and decrement buttons in Safari.
271-*/
272-
273-::-webkit-inner-spin-button,
274-::-webkit-outer-spin-button {
275- height: auto;
276-}
277-
278-/*
279-1. Correct the odd appearance in Chrome and Safari.
280-2. Correct the outline style in Safari.
281-*/
282-
283-[type='search'] {
284- -webkit-appearance: textfield;
285- /* 1 */
286- outline-offset: -2px;
287- /* 2 */
288-}
289-
290-/*
291-Remove the inner padding in Chrome and Safari on macOS.
292-*/
293-
294-::-webkit-search-decoration {
295- -webkit-appearance: none;
296-}
297-
298-/*
299-1. Correct the inability to style clickable types in iOS and Safari.
300-2. Change font properties to `inherit` in Safari.
301-*/
302-
303-::-webkit-file-upload-button {
304- -webkit-appearance: button;
305- /* 1 */
306- font: inherit;
307- /* 2 */
308-}
309-
310-/*
311-Add the correct display in Chrome and Safari.
312-*/
313-
314-summary {
315- display: list-item;
316-}
317-
318-/*
319-Removes the default spacing and border for appropriate elements.
320-*/
321-
322-blockquote,
323-dl,
324-dd,
325-h1,
326-h2,
327-h3,
328-h4,
329-h5,
330-h6,
331-hr,
332-figure,
333-p,
334-pre {
335- margin: 0;
336-}
337-
338-fieldset {
339- margin: 0;
340- padding: 0;
341-}
342-
343-legend {
344- padding: 0;
345-}
346-
347-ol,
348-ul,
349-menu {
350- list-style: none;
351- margin: 0;
352- padding: 0;
353-}
354-
355-/*
356-Reset default styling for dialogs.
357-*/
358-
359-dialog {
360- padding: 0;
361-}
362-
363-/*
364-Prevent resizing textareas horizontally by default.
365-*/
366-
367-textarea {
368- resize: vertical;
369-}
370-
371-/*
372-1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
373-2. Set the default placeholder color to the user's configured gray 400 color.
374-*/
375-
376-input::-moz-placeholder, textarea::-moz-placeholder {
377- opacity: 1;
378- /* 1 */
379- color: #9ca3af;
380- /* 2 */
381-}
382-
383-input::placeholder,
384-textarea::placeholder {
385- opacity: 1;
386- /* 1 */
387- color: #9ca3af;
388- /* 2 */
389-}
390-
391-/*
392-Set the default cursor for buttons.
393-*/
394-
395-button,
396-[role="button"] {
397- cursor: pointer;
398-}
399-
400-/*
401-Make sure disabled buttons don't get the pointer cursor.
402-*/
403-
404-:disabled {
405- cursor: default;
406-}
407-
408-/*
409-1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
410-2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
411- This can trigger a poorly considered lint error in some tools but is included by design.
412-*/
413-
414-img,
415-svg,
416-video,
417-canvas,
418-audio,
419-iframe,
420-embed,
421-object {
422- display: block;
423- /* 1 */
424- vertical-align: middle;
425- /* 2 */
426-}
427-
428-/*
429-Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
430-*/
431-
432-img,
433-video {
434- max-width: 100%;
435- height: auto;
436-}
437-
438-/* Make elements with the HTML hidden attribute stay hidden by default */
439-
440-[hidden] {
441- display: none;
442-}
443-
444-*, ::before, ::after {
445- --tw-border-spacing-x: 0;
446- --tw-border-spacing-y: 0;
447- --tw-translate-x: 0;
448- --tw-translate-y: 0;
449- --tw-rotate: 0;
450- --tw-skew-x: 0;
451- --tw-skew-y: 0;
452- --tw-scale-x: 1;
453- --tw-scale-y: 1;
454- --tw-pan-x: ;
455- --tw-pan-y: ;
456- --tw-pinch-zoom: ;
457- --tw-scroll-snap-strictness: proximity;
458- --tw-gradient-from-position: ;
459- --tw-gradient-via-position: ;
460- --tw-gradient-to-position: ;
461- --tw-ordinal: ;
462- --tw-slashed-zero: ;
463- --tw-numeric-figure: ;
464- --tw-numeric-spacing: ;
465- --tw-numeric-fraction: ;
466- --tw-ring-inset: ;
467- --tw-ring-offset-width: 0px;
468- --tw-ring-offset-color: #fff;
469- --tw-ring-color: rgb(59 130 246 / 0.5);
470- --tw-ring-offset-shadow: 0 0 #0000;
471- --tw-ring-shadow: 0 0 #0000;
472- --tw-shadow: 0 0 #0000;
473- --tw-shadow-colored: 0 0 #0000;
474- --tw-blur: ;
475- --tw-brightness: ;
476- --tw-contrast: ;
477- --tw-grayscale: ;
478- --tw-hue-rotate: ;
479- --tw-invert: ;
480- --tw-saturate: ;
481- --tw-sepia: ;
482- --tw-drop-shadow: ;
483- --tw-backdrop-blur: ;
484- --tw-backdrop-brightness: ;
485- --tw-backdrop-contrast: ;
486- --tw-backdrop-grayscale: ;
487- --tw-backdrop-hue-rotate: ;
488- --tw-backdrop-invert: ;
489- --tw-backdrop-opacity: ;
490- --tw-backdrop-saturate: ;
491- --tw-backdrop-sepia: ;
492-}
493-
494-::backdrop {
495- --tw-border-spacing-x: 0;
496- --tw-border-spacing-y: 0;
497- --tw-translate-x: 0;
498- --tw-translate-y: 0;
499- --tw-rotate: 0;
500- --tw-skew-x: 0;
501- --tw-skew-y: 0;
502- --tw-scale-x: 1;
503- --tw-scale-y: 1;
504- --tw-pan-x: ;
505- --tw-pan-y: ;
506- --tw-pinch-zoom: ;
507- --tw-scroll-snap-strictness: proximity;
508- --tw-gradient-from-position: ;
509- --tw-gradient-via-position: ;
510- --tw-gradient-to-position: ;
511- --tw-ordinal: ;
512- --tw-slashed-zero: ;
513- --tw-numeric-figure: ;
514- --tw-numeric-spacing: ;
515- --tw-numeric-fraction: ;
516- --tw-ring-inset: ;
517- --tw-ring-offset-width: 0px;
518- --tw-ring-offset-color: #fff;
519- --tw-ring-color: rgb(59 130 246 / 0.5);
520- --tw-ring-offset-shadow: 0 0 #0000;
521- --tw-ring-shadow: 0 0 #0000;
522- --tw-shadow: 0 0 #0000;
523- --tw-shadow-colored: 0 0 #0000;
524- --tw-blur: ;
525- --tw-brightness: ;
526- --tw-contrast: ;
527- --tw-grayscale: ;
528- --tw-hue-rotate: ;
529- --tw-invert: ;
530- --tw-saturate: ;
531- --tw-sepia: ;
532- --tw-drop-shadow: ;
533- --tw-backdrop-blur: ;
534- --tw-backdrop-brightness: ;
535- --tw-backdrop-contrast: ;
536- --tw-backdrop-grayscale: ;
537- --tw-backdrop-hue-rotate: ;
538- --tw-backdrop-invert: ;
539- --tw-backdrop-opacity: ;
540- --tw-backdrop-saturate: ;
541- --tw-backdrop-sepia: ;
542-}
543-
544-.my-2 {
545- margin-top: 0.5rem;
546- margin-bottom: 0.5rem;
547-}
548-
549-.mb-4 {
550- margin-bottom: 1rem;
551-}
552-
553-.ml-2 {
554- margin-left: 0.5rem;
555-}
556-
557-.ml-4 {
558- margin-left: 1rem;
559-}
560-
561-.mr-2 {
562- margin-right: 0.5rem;
563-}
564-
565-.mt-4 {
566- margin-top: 1rem;
567-}
568-
569-.flex {
570- display: flex;
571-}
572-
573-.h-10 {
574- height: 2.5rem;
575-}
576-
577-.h-24 {
578- height: 6rem;
579-}
580-
581-.h-32 {
582- height: 8rem;
583-}
584-
585-.h-44 {
586- height: 11rem;
587-}
588-
589-.h-64 {
590- height: 16rem;
591-}
592-
593-.h-full {
594- height: 100%;
595-}
596-
597-.w-1\/5 {
598- width: 20%;
599-}
600-
601-.w-2\/6 {
602- width: 33.333333%;
603-}
604-
605-.w-4\/6 {
606- width: 66.666667%;
607-}
608-
609-.w-72 {
610- width: 18rem;
611-}
612-
613-.w-full {
614- width: 100%;
615-}
616-
617-.w-2\/3 {
618- width: 66.666667%;
619-}
620-
621-.w-screen {
622- width: 100vw;
623-}
624-
625-.w-96 {
626- width: 24rem;
627-}
628-
629-.flex-1 {
630- flex: 1 1 0%;
631-}
632-
633-.flex-grow {
634- flex-grow: 1;
635-}
636-
637-.flex-row {
638- flex-direction: row;
639-}
640-
641-.flex-col {
642- flex-direction: column;
643-}
644-
645-.items-center {
646- align-items: center;
647-}
648-
649-.justify-end {
650- justify-content: flex-end;
651-}
652-
653-.justify-center {
654- justify-content: center;
655-}
656-
657-.justify-between {
658- justify-content: space-between;
659-}
660-
661-.justify-items-center {
662- justify-items: center;
663-}
664-
665-.space-x-4 > :not([hidden]) ~ :not([hidden]) {
666- --tw-space-x-reverse: 0;
667- margin-right: calc(1rem * var(--tw-space-x-reverse));
668- margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
669-}
670-
671-.space-y-2 > :not([hidden]) ~ :not([hidden]) {
672- --tw-space-y-reverse: 0;
673- margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
674- margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
675-}
676-
677-.space-y-4 > :not([hidden]) ~ :not([hidden]) {
678- --tw-space-y-reverse: 0;
679- margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
680- margin-bottom: calc(1rem * var(--tw-space-y-reverse));
681-}
682-
683-.space-x-3 > :not([hidden]) ~ :not([hidden]) {
684- --tw-space-x-reverse: 0;
685- margin-right: calc(0.75rem * var(--tw-space-x-reverse));
686- margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
687-}
688-
689-.space-y-3 > :not([hidden]) ~ :not([hidden]) {
690- --tw-space-y-reverse: 0;
691- margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse)));
692- margin-bottom: calc(0.75rem * var(--tw-space-y-reverse));
693-}
694-
695-.overflow-y-scroll {
696- overflow-y: scroll;
697-}
698-
699-.rounded-lg {
700- border-radius: 0.5rem;
701-}
702-
703-.rounded-md {
704- border-radius: 0.375rem;
705-}
706-
707-.rounded-sm {
708- border-radius: 0.125rem;
709-}
710-
711-.border {
712- border-width: 1px;
713-}
714-
715-.border-gray-300 {
716- --tw-border-opacity: 1;
717- border-color: rgb(209 213 219 / var(--tw-border-opacity));
718-}
719-
720-.bg-gordonmascot-lblue {
721- --tw-bg-opacity: 1;
722- background-color: rgb(0 123 192 / var(--tw-bg-opacity));
723-}
724-
725-.bg-gordonmascot-mblue {
726- --tw-bg-opacity: 1;
727- background-color: rgb(0 82 147 / var(--tw-bg-opacity));
728-}
729-
730-.bg-gray-300 {
731- --tw-bg-opacity: 1;
732- background-color: rgb(209 213 219 / var(--tw-bg-opacity));
733-}
734-
735-.bg-transparent {
736- background-color: transparent;
737-}
738-
739-.bg-white {
740- --tw-bg-opacity: 1;
741- background-color: rgb(255 255 255 / var(--tw-bg-opacity));
742-}
743-
744-.bg-zinc-200 {
745- --tw-bg-opacity: 1;
746- background-color: rgb(228 228 231 / var(--tw-bg-opacity));
747-}
748-
749-.bg-gordonmascot-hblue {
750- --tw-bg-opacity: 1;
751- background-color: rgb(1 30 92 / var(--tw-bg-opacity));
752-}
753-
754-.bg-zinc-400 {
755- --tw-bg-opacity: 1;
756- background-color: rgb(161 161 170 / var(--tw-bg-opacity));
757-}
758-
759-.p-4 {
760- padding: 1rem;
761-}
762-
763-.p-2 {
764- padding: 0.5rem;
765-}
766-
767-.px-2 {
768- padding-left: 0.5rem;
769- padding-right: 0.5rem;
770-}
771-
772-.px-6 {
773- padding-left: 1.5rem;
774- padding-right: 1.5rem;
775-}
776-
777-.py-2 {
778- padding-top: 0.5rem;
779- padding-bottom: 0.5rem;
780-}
781-
782-.px-4 {
783- padding-left: 1rem;
784- padding-right: 1rem;
785-}
786-
787-.text-2xl {
788- font-size: 1.5rem;
789- line-height: 2rem;
790-}
791-
792-.text-5xl {
793- font-size: 3rem;
794- line-height: 1;
795-}
796-
797-.text-lg {
798- font-size: 1.125rem;
799- line-height: 1.75rem;
800-}
801-
802-.text-sm {
803- font-size: 0.875rem;
804- line-height: 1.25rem;
805-}
806-
807-.text-xl {
808- font-size: 1.25rem;
809- line-height: 1.75rem;
810-}
811-
812-.font-semibold {
813- font-weight: 600;
814-}
815-
816-.tracking-tighter {
817- letter-spacing: -0.05em;
818-}
819-
820-.text-gordonmascot-hblue {
821- --tw-text-opacity: 1;
822- color: rgb(1 30 92 / var(--tw-text-opacity));
823-}
824-
825-.text-gordonmascot-mblue {
826- --tw-text-opacity: 1;
827- color: rgb(0 82 147 / var(--tw-text-opacity));
828-}
829-
830-.text-gray-100 {
831- --tw-text-opacity: 1;
832- color: rgb(243 244 246 / var(--tw-text-opacity));
833-}
834-
835-.text-gray-800 {
836- --tw-text-opacity: 1;
837- color: rgb(31 41 55 / var(--tw-text-opacity));
838-}
839-
840-.text-slate-400 {
841- --tw-text-opacity: 1;
842- color: rgb(148 163 184 / var(--tw-text-opacity));
843-}
844-
845-.text-slate-100 {
846- --tw-text-opacity: 1;
847- color: rgb(241 245 249 / var(--tw-text-opacity));
848-}
849-
850-.shadow {
851- --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
852- --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
853- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
854-}
855-
856-.outline-none {
857- outline: 2px solid transparent;
858- outline-offset: 2px;
859-}
860-
861-/* Import IBM Plex Fonts */
862-
863-@font-face {
864- font-family: "IBMPlexSans-Bold";
865-
866- src: url("/assets/fonts/IBMPlex/IBMPlexSans-Bold.woff2") format("woff2");
867-
868- font-weight: bold;
869-
870- font-style: normal;
871-}
872-
873-@font-face {
874- font-family: "IBMPlexSans-Regular";
875-
876- src: url("/assets/fonts/IBMPlex/IBMPlexSans-Regular.woff2") format("woff2");
877-
878- font-weight: normal;
879-
880- font-style: normal;
881-}
882-
883-/* Import Jetbrains Mono Nerd Font */
884-
885-@font-face {
886- font-family: "JetBrainsMono-Nerd-Font-Regular";
887-
888- src: url("/assets/fonts/JetBrainsMonoNerd/JetBrainsMonoNerdFontMono-Regular.woff2")
889- format("woff2");
890-
891- font-weight: normal;
892-
893- font-style: normal;
894-}
895-
896-@font-face {
897- font-family: "LilitaOne-Regular";
898-
899- src: url("/assets/fonts/LilitaOne/LilitaOne-Regular.ttf") format("truetype");
900-
901- font-weight: normal;
902-
903- font-style: normal;
904-}
905-
906-.fade-me-out.htmx-swapping {
907- opacity: 0;
908- transition: opacity 1s ease-out;
909-}
910-
911-/* View transition */
912-
913-@keyframes fade-in {
914- from {
915- opacity: 0;
916- }
917-}
918-
919-@keyframes fade-out {
920- to {
921- opacity: 0;
922- }
923-}
924-
925-@keyframes slide-from-right {
926- from {
927- transform: translateX(90px);
928- }
929-}
930-
931-@keyframes slide-to-left {
932- to {
933- transform: translateX(-90px);
934- }
935-}
936-
937-.slide-it {
938- view-transition-name: slide-it;
939-}
940-
941-::view-transition-old(slide-it) {
942- animation: 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
943- 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
944-}
945-
946-::view-transition-new(slide-it) {
947- animation: 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
948- 600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
949-}
950-
951-.gordon-title {
952- font-family: "LilitaOne-Regular", sans-serif;
953-}
954-
955-.IBMPlexSans {
956- font-family: "IBMPlexSans-Regular", sans-serif;
957-}
958-
959-.circle-shadow {
960- width: 100%;
961- position: relative;
962-}
963-
964-.circle-shadow::after {
965- position: absolute;
966- width: 50%;
967- height: 0.4rem;
968- margin: auto;
969- background: rgba(0, 0, 0, 0.1);
970- border-radius: 50%;
971- z-index: -1;
972- bottom: 2%;
973- content: "";
974- box-shadow: 0 0 1px 0.3rem rgba(0, 0, 0, 0.1);
975-}
976-
977-.iconoir {
978- margin-right: 8px;
979-}
980-
981-@media (min-width: 640px) {
982- .sm\:h-24 {
983- height: 6rem;
984- }
985-}
986-
987-@media (min-width: 768px) {
988- .md\:w-1\/2 {
989- width: 50%;
990- }
991-
992- .md\:flex-row {
993- flex-direction: row;
994- }
995-
996- .md\:space-y-6 > :not([hidden]) ~ :not([hidden]) {
997- --tw-space-y-reverse: 0;
998- margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
999- margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
1000- }
1001-
1002- .md\:p-8 {
1003- padding: 2rem;
1004- }
1005-
1006- .md\:py-8 {
1007- padding-top: 2rem;
1008- padding-bottom: 2rem;
1009- }
1010-
1011- .md\:pl-4 {
1012- padding-left: 1rem;
1013- }
1014-
1015- .md\:pt-10 {
1016- padding-top: 2.5rem;
1017- }
1018-
1019- .md\:text-2xl {
1020- font-size: 1.5rem;
1021- line-height: 2rem;
1022- }
1023-
1024- .md\:text-3xl {
1025- font-size: 1.875rem;
1026- line-height: 2.25rem;
1027- }
1028-}
1029-
1030-@media (min-width: 1024px) {
1031- .lg\:text-4xl {
1032- font-size: 2.25rem;
1033- line-height: 2.5rem;
1034- }
1035-}
1036-
1037-@media (min-width: 1280px) {
1038- .xl\:h-48 {
1039- height: 12rem;
1040- }
1041-
1042- .xl\:text-5xl {
1043- font-size: 3rem;
1044- line-height: 1;
1045- }
1046-}
D internal/ui/public/assets/fonts/IBMPlex/IBMPlexSans-Bold.woff2 +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/assets/fonts/IBMPlex/IBMPlexSans-Light.woff2 +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/assets/fonts/IBMPlex/IBMPlexSans-Medium.woff2 +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/assets/fonts/IBMPlex/IBMPlexSans-Regular.woff2 +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/assets/fonts/IBMPlex/IBMPlexSans-Thin.woff2 +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/assets/fonts/JetBrainsMonoNerd/JetBrainsMonoNerdFont-Bold.woff2 +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/assets/fonts/JetBrainsMonoNerd/JetBrainsMonoNerdFont-Regular.woff2 +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/assets/fonts/JetBrainsMonoNerd/JetBrainsMonoNerdFontMono-Regular.ttf +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/assets/fonts/LilitaOne/LilitaOne-Regular.ttf +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/assets/fonts/LilitaOne/LilitaOne-Regular.woff2 +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/assets/imgs/gordon-mascot-hq-trsp.png +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/assets/imgs/gordon-mascot-mq-trsp.png +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/assets/imgs/gordon-mascot.png +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/favicon-16x16.png +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/favicon-32x32.png +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/favicon.ico +0 -0 binary file: no text diff

binary file: no text diff

D internal/ui/public/index.gohtml +0 -124
@@ -1,124 +0,0 @@
1-<!-- @format -->
2-
3-<!DOCTYPE html>
4-<html lang="en">
5- <head>
6- <meta charset="UTF-8" />
7- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8- <title>{{ .header.meta.title }}</title>
9- <meta name="description" content="{{ .Header.Meta.Description }}" />
10- <meta name="keywords" content="{{ .Header.Meta.Keywords }}" />
11- <!-- Custom CSS + Tailwind -->
12- <link rel="stylesheet" href="/assets/css/tailwind.css" />
13- <!-- Iconoir -->
14- <link rel="stylesheet" href="/libs/iconoir.css" />
15- <!-- Alpine -->
16- <script src="/libs/alpine.min.js"></script>
17- <!-- HTMX min js -->
18- <script src="/libs/htmx.min.js"></script>
19- <!-- HTMX SSE -->
20- <script src="/libs/htmx-sse.js"></script>
21- <!-- Custom JS from assets/js -->
22- <script src="assets/js/custom.js"></script>
23- </head>
24- <body class="IBMPlexSans bg-zinc-200 md:p-8 flex">
25- <!-- Left Menu -->
26- <div class="w-1/5 flex flex-col space-evenly">
27- <!-- Logo -->
28- <div id="logo-mascot-leftnav" class="flex justify-center p-4">
29- <div class="circle-shadow flex justify-center w-100">
30- <img
31- src="/assets/imgs/gordon-mascot-hq-trsp.png"
32- alt="logo"
33- class="sm:h-24 h-32 xl:h-48"
34- />
35- </div>
36- </div>
37- <!-- Site Name + Version -->
38- <div
39- class="p-4 flex flex-col bg-gordonmascot-lblue rounded-lg shadow space-y-4"
40- >
41- <div class="w-full flex justify-center">
42- <h1
43- class="gordon-title text-2xl lg:text-4xl xl:text-5xl text-gordonmascot-hblue tracking-tighter"
44- >
45- Gordon
46- </h1>
47- <span class="gordon-v-subtitle text-gordonmascot-mblue text-sm"
48- >v0.0.1</span
49- >
50- </div>
51- <!--(Navigation) -->
52- <div id="menu-leftnav" class="w-full">
53- <ul
54- class="flex flex-col md:space-y-6 md:py-8 font-semibold text-gray-100 text-lg md:text-2xl md:pl-4"
55- >
56- <li class="flex items-center">
57- <i class="iconoir-dashboard-speed mr-2 md:text-3xl"></i>
58- <a href="index.html">Dashboard</a>
59- </li>
60- <li class="flex items-center">
61- <i class="iconoir-server mr-2 md:text-3xl"></i>
62- <a href="#manager">Manager</a>
63- </li>
64- <li class="flex items-center">
65- <i class="iconoir-community mr-2 md:text-3xl"></i>
66- <a href="#users">Users</a>
67- </li>
68- <li class="flex items-center">
69- <i class="iconoir-database-settings mr-2 md:text-3xl"></i>
70- <a href="#gordon-settings">Settings</a>
71- </li>
72- </ul>
73- </div>
74-
75- <div class="w-full h-24 bg-gordonmascot-mblue rounded-lg"></div>
76- <!-- Mock menu item -->
77- </div>
78- </div>
79-
80- <!-- Main Content including the navbar -->
81- <div class="flex-1 flex flex-col md:pt-10 space-y-4 ml-4">
82- <!-- Search Nav within the main content -->
83- <div class="mb-4 flex justify-end">
84- <div class="w-72 h-10 bg-gray-300 rounded-lg">
85- <!-- Search input with iconir search -->
86- <div class="flex justify-between items-center h-full px-2">
87- <input
88- type="text"
89- class="w-full h-full bg-transparent outline-none"
90- placeholder="Search"
91- />
92- <i class="iconoir-search"></i>
93- </div>
94- </div>
95- <!-- Mock search or other element -->
96- </div>
97-
98- <!-- Cards section -->
99- <div class="flex space-x-4 flex-grow">
100- <!-- Main Card on the Left -->
101- <div class="w-4/6 bg-white p-4 rounded-md shadow mb-4">
102- <div class="w-full rounded-sm bg-gray-300"></div>
103- <!-- Mock main card -->
104- </div>
105-
106- <!-- Two Cards on the Right -->
107- <div class="w-2/6 flex flex-col flex-justify-start">
108- <div class="bg-white p-4 rounded-md shadow mb-4">
109- <div class="w-full h-44 rounded-sm bg-gray-300"></div>
110- <!-- Mock card -->
111- </div>
112- <div class="bg-white p-4 rounded-md shadow mb-4">
113- <div class="w-full h-44 rounded-sm bg-gray-300"></div>
114- <!-- Mock card -->
115- </div>
116- <div class="bg-white p-4 rounded-md shadow mb-4">
117- <div class="w-full h-44 rounded-sm bg-gray-300"></div>
118- <!-- Mock card -->
119- </div>
120- </div>
121- </div>
122- </div>
123- </body>
124-</html>
@@ -1,124 +0,0 @@
1-<!-- @format -->
2-
3-<!DOCTYPE html>
4-<html lang="en">
5- <head>
6- <meta charset="UTF-8" />
7- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8- <title>{{ .header.meta.title }}</title>
9- <meta name="description" content="{{ .Header.Meta.Description }}" />
10- <meta name="keywords" content="{{ .Header.Meta.Keywords }}" />
11- <!-- Custom CSS + Tailwind -->
12- <link rel="stylesheet" href="/assets/css/tailwind.css" />
13- <!-- Iconoir -->
14- <link rel="stylesheet" href="/libs/iconoir.css" />
15- <!-- Alpine -->
16- <script src="/libs/alpine.min.js"></script>
17- <!-- HTMX min js -->
18- <script src="/libs/htmx.min.js"></script>
19- <!-- HTMX SSE -->
20- <script src="/libs/htmx-sse.js"></script>
21- <!-- Custom JS from assets/js -->
22- <script src="assets/js/custom.js"></script>
23- </head>
24- <body class="IBMPlexSans bg-zinc-200 md:p-8 flex">
25- <!-- Left Menu -->
26- <div class="w-1/5 flex flex-col space-evenly">
27- <!-- Logo -->
28- <div id="logo-mascot-leftnav" class="flex justify-center p-4">
29- <div class="circle-shadow flex justify-center w-100">
30- <img
31- src="/assets/imgs/gordon-mascot-hq-trsp.png"
32- alt="logo"
33- class="sm:h-24 h-32 xl:h-48"
34- />
35- </div>
36- </div>
37- <!-- Site Name + Version -->
38- <div
39- class="p-4 flex flex-col bg-gordonmascot-lblue rounded-lg shadow space-y-4"
40- >
41- <div class="w-full flex justify-center">
42- <h1
43- class="gordon-title text-2xl lg:text-4xl xl:text-5xl text-gordonmascot-hblue tracking-tighter"
44- >
45- Gordon
46- </h1>
47- <span class="gordon-v-subtitle text-gordonmascot-mblue text-sm"
48- >v0.0.1</span
49- >
50- </div>
51- <!--(Navigation) -->
52- <div id="menu-leftnav" class="w-full">
53- <ul
54- class="flex flex-col md:space-y-6 md:py-8 font-semibold text-gray-100 text-lg md:text-2xl md:pl-4"
55- >
56- <li class="flex items-center">
57- <i class="iconoir-dashboard-speed mr-2 md:text-3xl"></i>
58- <a href="index.html">Dashboard</a>
59- </li>
60- <li class="flex items-center">
61- <i class="iconoir-server mr-2 md:text-3xl"></i>
62- <a href="#manager">Manager</a>
63- </li>
64- <li class="flex items-center">
65- <i class="iconoir-community mr-2 md:text-3xl"></i>
66- <a href="#users">Users</a>
67- </li>
68- <li class="flex items-center">
69- <i class="iconoir-database-settings mr-2 md:text-3xl"></i>
70- <a href="#gordon-settings">Settings</a>
71- </li>
72- </ul>
73- </div>
74-
75- <div class="w-full h-24 bg-gordonmascot-mblue rounded-lg"></div>
76- <!-- Mock menu item -->
77- </div>
78- </div>
79-
80- <!-- Main Content including the navbar -->
81- <div class="flex-1 flex flex-col md:pt-10 space-y-4 ml-4">
82- <!-- Search Nav within the main content -->
83- <div class="mb-4 flex justify-end">
84- <div class="w-72 h-10 bg-gray-300 rounded-lg">
85- <!-- Search input with iconir search -->
86- <div class="flex justify-between items-center h-full px-2">
87- <input
88- type="text"
89- class="w-full h-full bg-transparent outline-none"
90- placeholder="Search"
91- />
92- <i class="iconoir-search"></i>
93- </div>
94- </div>
95- <!-- Mock search or other element -->
96- </div>
97-
98- <!-- Cards section -->
99- <div class="flex space-x-4 flex-grow">
100- <!-- Main Card on the Left -->
101- <div class="w-4/6 bg-white p-4 rounded-md shadow mb-4">
102- <div class="w-full rounded-sm bg-gray-300"></div>
103- <!-- Mock main card -->
104- </div>
105-
106- <!-- Two Cards on the Right -->
107- <div class="w-2/6 flex flex-col flex-justify-start">
108- <div class="bg-white p-4 rounded-md shadow mb-4">
109- <div class="w-full h-44 rounded-sm bg-gray-300"></div>
110- <!-- Mock card -->
111- </div>
112- <div class="bg-white p-4 rounded-md shadow mb-4">
113- <div class="w-full h-44 rounded-sm bg-gray-300"></div>
114- <!-- Mock card -->
115- </div>
116- <div class="bg-white p-4 rounded-md shadow mb-4">
117- <div class="w-full h-44 rounded-sm bg-gray-300"></div>
118- <!-- Mock card -->
119- </div>
120- </div>
121- </div>
122- </div>
123- </body>
124-</html>
D internal/ui/public/libs/alpine.min.js +0 -5
@@ -1,5 +0,0 @@
1-(()=>{var Ze=!1,Qe=!1,V=[],et=-1;function Kt(e){bn(e)}function bn(e){V.includes(e)||V.push(e),wn()}function we(e){let t=V.indexOf(e);t!==-1&&t>et&&V.splice(t,1)}function wn(){!Qe&&!Ze&&(Ze=!0,queueMicrotask(En))}function En(){Ze=!1,Qe=!0;for(let e=0;e<V.length;e++)V[e](),et=e;V.length=0,et=-1,Qe=!1}var T,D,L,rt,tt=!0;function zt(e){tt=!1,e(),tt=!0}function Ht(e){T=e.reactive,L=e.release,D=t=>e.effect(t,{scheduler:r=>{tt?Kt(r):r()}}),rt=e.raw}function nt(e){D=e}function Vt(e){let t=()=>{};return[n=>{let i=D(n);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(o=>o())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),L(i))},i},()=>{t()}]}function q(e,t,r={}){e.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0,cancelable:!0}))}function O(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(i=>O(i,t));return}let r=!1;if(t(e,()=>r=!0),r)return;let n=e.firstElementChild;for(;n;)O(n,t,!1),n=n.nextElementSibling}function S(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}var qt=!1;function Ut(){qt&&S("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."),qt=!0,document.body||S("Unable to initialize. Trying to load Alpine before `<body>` is available. Did you forget to add `defer` in Alpine's `<script>` tag?"),q(document,"alpine:init"),q(document,"alpine:initializing"),ce(),Qt(t=>v(t,O)),Q(t=>ae(t)),Se((t,r)=>{le(t,r).forEach(n=>n())});let e=t=>!U(t.parentElement,!0);Array.from(document.querySelectorAll(Jt())).filter(e).forEach(t=>{v(t)}),q(document,"alpine:initialized")}var it=[],Wt=[];function Gt(){return it.map(e=>e())}function Jt(){return it.concat(Wt).map(e=>e())}function Ee(e){it.push(e)}function ve(e){Wt.push(e)}function U(e,t=!1){return Z(e,r=>{if((t?Jt():Gt()).some(i=>r.matches(i)))return!0})}function Z(e,t){if(e){if(t(e))return e;if(e._x_teleportBack&&(e=e._x_teleportBack),!!e.parentElement)return Z(e.parentElement,t)}}function Yt(e){return Gt().some(t=>e.matches(t))}var Xt=[];function Zt(e){Xt.push(e)}function v(e,t=O,r=()=>{}){tr(()=>{t(e,(n,i)=>{r(n,i),Xt.forEach(o=>o(n,i)),le(n,n.attributes).forEach(o=>o()),n._x_ignore&&i()})})}function ae(e){O(e,t=>{ot(t),er(t)})}var rr=[],nr=[],ir=[];function Qt(e){ir.push(e)}function Q(e,t){typeof t=="function"?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,nr.push(t))}function Se(e){rr.push(e)}function Oe(e,t,r){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(r)}function ot(e,t){e._x_attributeCleanups&&Object.entries(e._x_attributeCleanups).forEach(([r,n])=>{(t===void 0||t.includes(r))&&(n.forEach(i=>i()),delete e._x_attributeCleanups[r])})}function er(e){if(e._x_cleanups)for(;e._x_cleanups.length;)e._x_cleanups.pop()()}var at=new MutationObserver(ft),ct=!1;function ce(){at.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),ct=!0}function lt(){vn(),at.disconnect(),ct=!1}var ue=[],st=!1;function vn(){ue=ue.concat(at.takeRecords()),ue.length&&!st&&(st=!0,queueMicrotask(()=>{Sn(),st=!1}))}function Sn(){ft(ue),ue.length=0}function h(e){if(!ct)return e();lt();let t=e();return ce(),t}var ut=!1,Ae=[];function or(){ut=!0}function sr(){ut=!1,ft(Ae),Ae=[]}function ft(e){if(ut){Ae=Ae.concat(e);return}let t=[],r=[],n=new Map,i=new Map;for(let o=0;o<e.length;o++)if(!e[o].target._x_ignoreMutationObserver&&(e[o].type==="childList"&&(e[o].addedNodes.forEach(s=>s.nodeType===1&&t.push(s)),e[o].removedNodes.forEach(s=>s.nodeType===1&&r.push(s))),e[o].type==="attributes")){let s=e[o].target,a=e[o].attributeName,c=e[o].oldValue,l=()=>{n.has(s)||n.set(s,[]),n.get(s).push({name:a,value:s.getAttribute(a)})},u=()=>{i.has(s)||i.set(s,[]),i.get(s).push(a)};s.hasAttribute(a)&&c===null?l():s.hasAttribute(a)?(u(),l()):u()}i.forEach((o,s)=>{ot(s,o)}),n.forEach((o,s)=>{rr.forEach(a=>a(s,o))});for(let o of r)t.includes(o)||(nr.forEach(s=>s(o)),ae(o));t.forEach(o=>{o._x_ignoreSelf=!0,o._x_ignore=!0});for(let o of t)r.includes(o)||o.isConnected&&(delete o._x_ignoreSelf,delete o._x_ignore,ir.forEach(s=>s(o)),o._x_ignore=!0,o._x_ignoreSelf=!0);t.forEach(o=>{delete o._x_ignoreSelf,delete o._x_ignore}),t=null,r=null,n=null,i=null}function Ce(e){return F($(e))}function N(e,t,r){return e._x_dataStack=[t,...$(r||e)],()=>{e._x_dataStack=e._x_dataStack.filter(n=>n!==t)}}function $(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?$(e.host):e.parentNode?$(e.parentNode):[]}function F(e){let t=new Proxy({},{ownKeys:()=>Array.from(new Set(e.flatMap(r=>Object.keys(r)))),has:(r,n)=>e.some(i=>i.hasOwnProperty(n)),get:(r,n)=>(e.find(i=>{if(i.hasOwnProperty(n)){let o=Object.getOwnPropertyDescriptor(i,n);if(o.get&&o.get._x_alreadyBound||o.set&&o.set._x_alreadyBound)return!0;if((o.get||o.set)&&o.enumerable){let s=o.get,a=o.set,c=o;s=s&&s.bind(t),a=a&&a.bind(t),s&&(s._x_alreadyBound=!0),a&&(a._x_alreadyBound=!0),Object.defineProperty(i,n,{...c,get:s,set:a})}return!0}return!1})||{})[n],set:(r,n,i)=>{let o=e.find(s=>s.hasOwnProperty(n));return o?o[n]=i:e[e.length-1][n]=i,!0}});return t}function Te(e){let t=n=>typeof n=="object"&&!Array.isArray(n)&&n!==null,r=(n,i="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach(([o,{value:s,enumerable:a}])=>{if(a===!1||s===void 0)return;let c=i===""?o:`${i}.${o}`;typeof s=="object"&&s!==null&&s._x_interceptor?n[o]=s.initialize(e,c,o):t(s)&&s!==n&&!(s instanceof Element)&&r(s,c)})};return r(e)}function Re(e,t=()=>{}){let r={initialValue:void 0,_x_interceptor:!0,initialize(n,i,o){return e(this.initialValue,()=>An(n,i),s=>dt(n,i,s),i,o)}};return t(r),n=>{if(typeof n=="object"&&n!==null&&n._x_interceptor){let i=r.initialize.bind(r);r.initialize=(o,s,a)=>{let c=n.initialize(o,s,a);return r.initialValue=c,i(o,s,a)}}else r.initialValue=n;return r}}function An(e,t){return t.split(".").reduce((r,n)=>r[n],e)}function dt(e,t,r){if(typeof t=="string"&&(t=t.split(".")),t.length===1)e[t[0]]=r;else{if(t.length===0)throw error;return e[t[0]]||(e[t[0]]={}),dt(e[t[0]],t.slice(1),r)}}var ar={};function y(e,t){ar[e]=t}function fe(e,t){return Object.entries(ar).forEach(([r,n])=>{let i=null;function o(){if(i)return i;{let[s,a]=pt(t);return i={interceptor:Re,...s},Q(t,a),i}}Object.defineProperty(e,`$${r}`,{get(){return n(t,o())},enumerable:!1})}),e}function cr(e,t,r,...n){try{return r(...n)}catch(i){ee(i,e,t)}}function ee(e,t,r=void 0){Object.assign(e,{el:t,expression:r}),console.warn(`Alpine Expression Error: ${e.message}
2-
3-${r?'Expression: "'+r+`"
4-
5-`:""}`,t),setTimeout(()=>{throw e},0)}var Me=!0;function Pe(e){let t=Me;Me=!1;let r=e();return Me=t,r}function R(e,t,r={}){let n;return x(e,t)(i=>n=i,r),n}function x(...e){return lr(...e)}var lr=ht;function ur(e){lr=e}function ht(e,t){let r={};fe(r,e);let n=[r,...$(e)],i=typeof t=="function"?On(n,t):Tn(n,t,e);return cr.bind(null,e,t,i)}function On(e,t){return(r=()=>{},{scope:n={},params:i=[]}={})=>{let o=t.apply(F([n,...e]),i);Ne(r,o)}}var mt={};function Cn(e,t){if(mt[e])return mt[e];let r=Object.getPrototypeOf(async function(){}).constructor,n=/^[\n\s]*if.*\(.*\)/.test(e.trim())||/^(let|const)\s/.test(e.trim())?`(async()=>{ ${e} })()`:e,o=(()=>{try{return new r(["__self","scope"],`with (scope) { __self.result = ${n} }; __self.finished = true; return __self.result;`)}catch(s){return ee(s,t,e),Promise.resolve()}})();return mt[e]=o,o}function Tn(e,t,r){let n=Cn(t,r);return(i=()=>{},{scope:o={},params:s=[]}={})=>{n.result=void 0,n.finished=!1;let a=F([o,...e]);if(typeof n=="function"){let c=n(n,a).catch(l=>ee(l,r,t));n.finished?(Ne(i,n.result,a,s,r),n.result=void 0):c.then(l=>{Ne(i,l,a,s,r)}).catch(l=>ee(l,r,t)).finally(()=>n.result=void 0)}}}function Ne(e,t,r,n,i){if(Me&&typeof t=="function"){let o=t.apply(r,n);o instanceof Promise?o.then(s=>Ne(e,s,r,n)).catch(s=>ee(s,i,t)):e(o)}else typeof t=="object"&&t instanceof Promise?t.then(o=>e(o)):e(t)}var yt="x-";function C(e=""){return yt+e}function fr(e){yt=e}var _t={};function p(e,t){return _t[e]=t,{before(r){if(!_t[r]){console.warn("Cannot find directive `${directive}`. `${name}` will use the default order of execution");return}let n=W.indexOf(r);W.splice(n>=0?n:W.indexOf("DEFAULT"),0,e)}}}function le(e,t,r){if(t=Array.from(t),e._x_virtualDirectives){let o=Object.entries(e._x_virtualDirectives).map(([a,c])=>({name:a,value:c})),s=bt(o);o=o.map(a=>s.find(c=>c.name===a.name)?{name:`x-bind:${a.name}`,value:`"${a.value}"`}:a),t=t.concat(o)}let n={};return t.map(pr((o,s)=>n[o]=s)).filter(hr).map(Mn(n,r)).sort(Nn).map(o=>Rn(e,o))}function bt(e){return Array.from(e).map(pr()).filter(t=>!hr(t))}var gt=!1,de=new Map,dr=Symbol();function tr(e){gt=!0;let t=Symbol();dr=t,de.set(t,[]);let r=()=>{for(;de.get(t).length;)de.get(t).shift()();de.delete(t)},n=()=>{gt=!1,r()};e(r),n()}function pt(e){let t=[],r=a=>t.push(a),[n,i]=Vt(e);return t.push(i),[{Alpine:j,effect:n,cleanup:r,evaluateLater:x.bind(x,e),evaluate:R.bind(R,e)},()=>t.forEach(a=>a())]}function Rn(e,t){let r=()=>{},n=_t[t.type]||r,[i,o]=pt(e);Oe(e,t.original,o);let s=()=>{e._x_ignore||e._x_ignoreSelf||(n.inline&&n.inline(e,t,i),n=n.bind(n,e,t,i),gt?de.get(dr).push(n):n())};return s.runCleanups=o,s}var De=(e,t)=>({name:r,value:n})=>(r.startsWith(e)&&(r=r.replace(e,t)),{name:r,value:n}),Ie=e=>e;function pr(e=()=>{}){return({name:t,value:r})=>{let{name:n,value:i}=mr.reduce((o,s)=>s(o),{name:t,value:r});return n!==t&&e(n,t),{name:n,value:i}}}var mr=[];function te(e){mr.push(e)}function hr({name:e}){return _r().test(e)}var _r=()=>new RegExp(`^${yt}([^:^.]+)\\b`);function Mn(e,t){return({name:r,value:n})=>{let i=r.match(_r()),o=r.match(/:([a-zA-Z0-9\-:]+)/),s=r.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],a=t||e[r]||r;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:s.map(c=>c.replace(".","")),expression:n,original:a}}}var xt="DEFAULT",W=["ignore","ref","data","id","bind","init","for","model","modelable","transition","show","if",xt,"teleport"];function Nn(e,t){let r=W.indexOf(e.type)===-1?xt:e.type,n=W.indexOf(t.type)===-1?xt:t.type;return W.indexOf(r)-W.indexOf(n)}var wt=[],Et=!1;function re(e=()=>{}){return queueMicrotask(()=>{Et||setTimeout(()=>{ke()})}),new Promise(t=>{wt.push(()=>{e(),t()})})}function ke(){for(Et=!1;wt.length;)wt.shift()()}function gr(){Et=!0}function pe(e,t){return Array.isArray(t)?xr(e,t.join(" ")):typeof t=="object"&&t!==null?Pn(e,t):typeof t=="function"?pe(e,t()):xr(e,t)}function xr(e,t){let r=o=>o.split(" ").filter(Boolean),n=o=>o.split(" ").filter(s=>!e.classList.contains(s)).filter(Boolean),i=o=>(e.classList.add(...o),()=>{e.classList.remove(...o)});return t=t===!0?t="":t||"",i(n(t))}function Pn(e,t){let r=a=>a.split(" ").filter(Boolean),n=Object.entries(t).flatMap(([a,c])=>c?r(a):!1).filter(Boolean),i=Object.entries(t).flatMap(([a,c])=>c?!1:r(a)).filter(Boolean),o=[],s=[];return i.forEach(a=>{e.classList.contains(a)&&(e.classList.remove(a),s.push(a))}),n.forEach(a=>{e.classList.contains(a)||(e.classList.add(a),o.push(a))}),()=>{s.forEach(a=>e.classList.add(a)),o.forEach(a=>e.classList.remove(a))}}function G(e,t){return typeof t=="object"&&t!==null?Dn(e,t):In(e,t)}function Dn(e,t){let r={};return Object.entries(t).forEach(([n,i])=>{r[n]=e.style[n],n.startsWith("--")||(n=kn(n)),e.style.setProperty(n,i)}),setTimeout(()=>{e.style.length===0&&e.removeAttribute("style")}),()=>{G(e,r)}}function In(e,t){let r=e.getAttribute("style",t);return e.setAttribute("style",t),()=>{e.setAttribute("style",r||"")}}function kn(e){return e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}function me(e,t=()=>{}){let r=!1;return function(){r?t.apply(this,arguments):(r=!0,e.apply(this,arguments))}}p("transition",(e,{value:t,modifiers:r,expression:n},{evaluate:i})=>{typeof n=="function"&&(n=i(n)),n!==!1&&(!n||typeof n=="boolean"?$n(e,r,t):Ln(e,n,t))});function Ln(e,t,r){yr(e,pe,""),{enter:i=>{e._x_transition.enter.during=i},"enter-start":i=>{e._x_transition.enter.start=i},"enter-end":i=>{e._x_transition.enter.end=i},leave:i=>{e._x_transition.leave.during=i},"leave-start":i=>{e._x_transition.leave.start=i},"leave-end":i=>{e._x_transition.leave.end=i}}[r](t)}function $n(e,t,r){yr(e,G);let n=!t.includes("in")&&!t.includes("out")&&!r,i=n||t.includes("in")||["enter"].includes(r),o=n||t.includes("out")||["leave"].includes(r);t.includes("in")&&!n&&(t=t.filter((g,b)=>b<t.indexOf("out"))),t.includes("out")&&!n&&(t=t.filter((g,b)=>b>t.indexOf("out")));let s=!t.includes("opacity")&&!t.includes("scale"),a=s||t.includes("opacity"),c=s||t.includes("scale"),l=a?0:1,u=c?he(t,"scale",95)/100:1,d=he(t,"delay",0)/1e3,m=he(t,"origin","center"),w="opacity, transform",k=he(t,"duration",150)/1e3,be=he(t,"duration",75)/1e3,f="cubic-bezier(0.4, 0.0, 0.2, 1)";i&&(e._x_transition.enter.during={transformOrigin:m,transitionDelay:`${d}s`,transitionProperty:w,transitionDuration:`${k}s`,transitionTimingFunction:f},e._x_transition.enter.start={opacity:l,transform:`scale(${u})`},e._x_transition.enter.end={opacity:1,transform:"scale(1)"}),o&&(e._x_transition.leave.during={transformOrigin:m,transitionDelay:`${d}s`,transitionProperty:w,transitionDuration:`${be}s`,transitionTimingFunction:f},e._x_transition.leave.start={opacity:1,transform:"scale(1)"},e._x_transition.leave.end={opacity:l,transform:`scale(${u})`})}function yr(e,t,r={}){e._x_transition||(e._x_transition={enter:{during:r,start:r,end:r},leave:{during:r,start:r,end:r},in(n=()=>{},i=()=>{}){Le(e,t,{during:this.enter.during,start:this.enter.start,end:this.enter.end},n,i)},out(n=()=>{},i=()=>{}){Le(e,t,{during:this.leave.during,start:this.leave.start,end:this.leave.end},n,i)}})}window.Element.prototype._x_toggleAndCascadeWithTransitions=function(e,t,r,n){let i=document.visibilityState==="visible"?requestAnimationFrame:setTimeout,o=()=>i(r);if(t){e._x_transition&&(e._x_transition.enter||e._x_transition.leave)?e._x_transition.enter&&(Object.entries(e._x_transition.enter.during).length||Object.entries(e._x_transition.enter.start).length||Object.entries(e._x_transition.enter.end).length)?e._x_transition.in(r):o():e._x_transition?e._x_transition.in(r):o();return}e._x_hidePromise=e._x_transition?new Promise((s,a)=>{e._x_transition.out(()=>{},()=>s(n)),e._x_transitioning.beforeCancel(()=>a({isFromCancelledTransition:!0}))}):Promise.resolve(n),queueMicrotask(()=>{let s=br(e);s?(s._x_hideChildren||(s._x_hideChildren=[]),s._x_hideChildren.push(e)):i(()=>{let a=c=>{let l=Promise.all([c._x_hidePromise,...(c._x_hideChildren||[]).map(a)]).then(([u])=>u());return delete c._x_hidePromise,delete c._x_hideChildren,l};a(e).catch(c=>{if(!c.isFromCancelledTransition)throw c})})})};function br(e){let t=e.parentNode;if(t)return t._x_hidePromise?t:br(t)}function Le(e,t,{during:r,start:n,end:i}={},o=()=>{},s=()=>{}){if(e._x_transitioning&&e._x_transitioning.cancel(),Object.keys(r).length===0&&Object.keys(n).length===0&&Object.keys(i).length===0){o(),s();return}let a,c,l;Fn(e,{start(){a=t(e,n)},during(){c=t(e,r)},before:o,end(){a(),l=t(e,i)},after:s,cleanup(){c(),l()}})}function Fn(e,t){let r,n,i,o=me(()=>{h(()=>{r=!0,n||t.before(),i||(t.end(),ke()),t.after(),e.isConnected&&t.cleanup(),delete e._x_transitioning})});e._x_transitioning={beforeCancels:[],beforeCancel(s){this.beforeCancels.push(s)},cancel:me(function(){for(;this.beforeCancels.length;)this.beforeCancels.shift()();o()}),finish:o},h(()=>{t.start(),t.during()}),gr(),requestAnimationFrame(()=>{if(r)return;let s=Number(getComputedStyle(e).transitionDuration.replace(/,.*/,"").replace("s",""))*1e3,a=Number(getComputedStyle(e).transitionDelay.replace(/,.*/,"").replace("s",""))*1e3;s===0&&(s=Number(getComputedStyle(e).animationDuration.replace("s",""))*1e3),h(()=>{t.before()}),n=!0,requestAnimationFrame(()=>{r||(h(()=>{t.end()}),ke(),setTimeout(e._x_transitioning.finish,s+a),i=!0)})})}function he(e,t,r){if(e.indexOf(t)===-1)return r;let n=e[e.indexOf(t)+1];if(!n||t==="scale"&&isNaN(n))return r;if(t==="duration"||t==="delay"){let i=n.match(/([0-9]+)ms/);if(i)return i[1]}return t==="origin"&&["top","right","left","center","bottom"].includes(e[e.indexOf(t)+2])?[n,e[e.indexOf(t)+2]].join(" "):n}var I=!1;function B(e,t=()=>{}){return(...r)=>I?t(...r):e(...r)}function wr(e){return(...t)=>I&&e(...t)}function Er(e,t){e._x_dataStack&&(t._x_dataStack=e._x_dataStack,t.setAttribute("data-has-alpine-state",!0)),I=!0,Sr(()=>{v(t,(r,n)=>{n(r,()=>{})})}),I=!1}var vt=!1;function vr(e,t){t._x_dataStack||(t._x_dataStack=e._x_dataStack),I=!0,vt=!0,Sr(()=>{jn(t)}),I=!1,vt=!1}function jn(e){let t=!1;v(e,(n,i)=>{O(n,(o,s)=>{if(t&&Yt(o))return s();t=!0,i(o,s)})})}function Sr(e){let t=D;nt((r,n)=>{let i=t(r);return L(i),()=>{}}),e(),nt(t)}function Ar(e){return I?vt?!0:e.hasAttribute("data-has-alpine-state"):!1}function _e(e,t,r,n=[]){switch(e._x_bindings||(e._x_bindings=T({})),e._x_bindings[t]=r,t=n.includes("camel")?Wn(t):t,t){case"value":Bn(e,r);break;case"style":zn(e,r);break;case"class":Kn(e,r);break;case"selected":case"checked":Hn(e,t,r);break;default:Cr(e,t,r);break}}function Bn(e,t){if(e.type==="radio")e.attributes.value===void 0&&(e.value=t),window.fromModel&&(e.checked=Or(e.value,t));else if(e.type==="checkbox")Number.isInteger(t)?e.value=t:!Array.isArray(t)&&typeof t!="boolean"&&![null,void 0].includes(t)?e.value=String(t):Array.isArray(t)?e.checked=t.some(r=>Or(r,e.value)):e.checked=!!t;else if(e.tagName==="SELECT")Un(e,t);else{if(e.value===t)return;e.value=t===void 0?"":t}}function Kn(e,t){e._x_undoAddedClasses&&e._x_undoAddedClasses(),e._x_undoAddedClasses=pe(e,t)}function zn(e,t){e._x_undoAddedStyles&&e._x_undoAddedStyles(),e._x_undoAddedStyles=G(e,t)}function Hn(e,t,r){Cr(e,t,r),qn(e,t,r)}function Cr(e,t,r){[null,void 0,!1].includes(r)&&Gn(t)?e.removeAttribute(t):(Tr(t)&&(r=t),Vn(e,t,r))}function Vn(e,t,r){e.getAttribute(t)!=r&&e.setAttribute(t,r)}function qn(e,t,r){e[t]!==r&&(e[t]=r)}function Un(e,t){let r=[].concat(t).map(n=>n+"");Array.from(e.options).forEach(n=>{n.selected=r.includes(n.value)})}function Wn(e){return e.toLowerCase().replace(/-(\w)/g,(t,r)=>r.toUpperCase())}function Or(e,t){return e==t}function Tr(e){return["disabled","checked","required","readonly","hidden","open","selected","autofocus","itemscope","multiple","novalidate","allowfullscreen","allowpaymentrequest","formnovalidate","autoplay","controls","loop","muted","playsinline","default","ismap","reversed","async","defer","nomodule"].includes(e)}function Gn(e){return!["aria-pressed","aria-checked","aria-expanded","aria-selected"].includes(e)}function Rr(e,t,r){return e._x_bindings&&e._x_bindings[t]!==void 0?e._x_bindings[t]:Nr(e,t,r)}function Mr(e,t,r,n=!0){if(e._x_bindings&&e._x_bindings[t]!==void 0)return e._x_bindings[t];if(e._x_inlineBindings&&e._x_inlineBindings[t]!==void 0){let i=e._x_inlineBindings[t];return i.extract=n,Pe(()=>R(e,i.expression))}return Nr(e,t,r)}function Nr(e,t,r){let n=e.getAttribute(t);return n===null?typeof r=="function"?r():r:n===""?!0:Tr(t)?!![t,"true"].includes(n):n}function $e(e,t){var r;return function(){var n=this,i=arguments,o=function(){r=null,e.apply(n,i)};clearTimeout(r),r=setTimeout(o,t)}}function Fe(e,t){let r;return function(){let n=this,i=arguments;r||(e.apply(n,i),r=!0,setTimeout(()=>r=!1,t))}}function je({get:e,set:t},{get:r,set:n}){let i=!0,o,s,a,c,l=D(()=>{let u,d;i?(u=e(),n(JSON.parse(JSON.stringify(u))),d=r(),i=!1):(u=e(),d=r(),a=JSON.stringify(u),c=JSON.stringify(d),a!==o?(d=r(),n(u),d=u):(t(JSON.parse(c??null)),u=d)),o=JSON.stringify(u),s=JSON.stringify(d)});return()=>{L(l)}}function Pr(e){(Array.isArray(e)?e:[e]).forEach(r=>r(j))}var J={},Dr=!1;function Ir(e,t){if(Dr||(J=T(J),Dr=!0),t===void 0)return J[e];J[e]=t,typeof t=="object"&&t!==null&&t.hasOwnProperty("init")&&typeof t.init=="function"&&J[e].init(),Te(J[e])}function kr(){return J}var Lr={};function $r(e,t){let r=typeof t!="function"?()=>t:t;return e instanceof Element?St(e,r()):(Lr[e]=r,()=>{})}function Fr(e){return Object.entries(Lr).forEach(([t,r])=>{Object.defineProperty(e,t,{get(){return(...n)=>r(...n)}})}),e}function St(e,t,r){let n=[];for(;n.length;)n.pop()();let i=Object.entries(t).map(([s,a])=>({name:s,value:a})),o=bt(i);return i=i.map(s=>o.find(a=>a.name===s.name)?{name:`x-bind:${s.name}`,value:`"${s.value}"`}:s),le(e,i,r).map(s=>{n.push(s.runCleanups),s()}),()=>{for(;n.length;)n.pop()()}}var jr={};function Br(e,t){jr[e]=t}function Kr(e,t){return Object.entries(jr).forEach(([r,n])=>{Object.defineProperty(e,r,{get(){return(...i)=>n.bind(t)(...i)},enumerable:!1})}),e}var Jn={get reactive(){return T},get release(){return L},get effect(){return D},get raw(){return rt},version:"3.13.0",flushAndStopDeferringMutations:sr,dontAutoEvaluateFunctions:Pe,disableEffectScheduling:zt,startObservingMutations:ce,stopObservingMutations:lt,setReactivityEngine:Ht,onAttributeRemoved:Oe,onAttributesAdded:Se,closestDataStack:$,skipDuringClone:B,onlyDuringClone:wr,addRootSelector:Ee,addInitSelector:ve,addScopeToNode:N,deferMutations:or,mapAttributes:te,evaluateLater:x,interceptInit:Zt,setEvaluator:ur,mergeProxies:F,extractProp:Mr,findClosest:Z,onElRemoved:Q,closestRoot:U,destroyTree:ae,interceptor:Re,transition:Le,setStyles:G,mutateDom:h,directive:p,entangle:je,throttle:Fe,debounce:$e,evaluate:R,initTree:v,nextTick:re,prefixed:C,prefix:fr,plugin:Pr,magic:y,store:Ir,start:Ut,clone:vr,cloneNode:Er,bound:Rr,$data:Ce,walk:O,data:Br,bind:$r},j=Jn;function At(e,t){let r=Object.create(null),n=e.split(",");for(let i=0;i<n.length;i++)r[n[i]]=!0;return t?i=>!!r[i.toLowerCase()]:i=>!!r[i]}var Yn="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly";var As=At(Yn+",async,autofocus,autoplay,controls,default,defer,disabled,hidden,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected");var zr=Object.freeze({}),Os=Object.freeze([]);var Xn=Object.prototype.hasOwnProperty,ge=(e,t)=>Xn.call(e,t),K=Array.isArray,ne=e=>Hr(e)==="[object Map]";var Zn=e=>typeof e=="string",Be=e=>typeof e=="symbol",xe=e=>e!==null&&typeof e=="object";var Qn=Object.prototype.toString,Hr=e=>Qn.call(e),Ot=e=>Hr(e).slice(8,-1);var Ke=e=>Zn(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e;var ze=e=>{let t=Object.create(null);return r=>t[r]||(t[r]=e(r))},ei=/-(\w)/g,Cs=ze(e=>e.replace(ei,(t,r)=>r?r.toUpperCase():"")),ti=/\B([A-Z])/g,Ts=ze(e=>e.replace(ti,"-$1").toLowerCase()),Ct=ze(e=>e.charAt(0).toUpperCase()+e.slice(1)),Rs=ze(e=>e?`on${Ct(e)}`:""),Tt=(e,t)=>e!==t&&(e===e||t===t);var Rt=new WeakMap,ye=[],P,Y=Symbol("iterate"),Mt=Symbol("Map key iterate");function ri(e){return e&&e._isEffect===!0}function Jr(e,t=zr){ri(e)&&(e=e.raw);let r=ii(e,t);return t.lazy||r(),r}function Yr(e){e.active&&(Xr(e),e.options.onStop&&e.options.onStop(),e.active=!1)}var ni=0;function ii(e,t){let r=function(){if(!r.active)return e();if(!ye.includes(r)){Xr(r);try{return si(),ye.push(r),P=r,e()}finally{ye.pop(),Zr(),P=ye[ye.length-1]}}};return r.id=ni++,r.allowRecurse=!!t.allowRecurse,r._isEffect=!0,r.active=!0,r.raw=e,r.deps=[],r.options=t,r}function Xr(e){let{deps:t}=e;if(t.length){for(let r=0;r<t.length;r++)t[r].delete(e);t.length=0}}var ie=!0,Pt=[];function oi(){Pt.push(ie),ie=!1}function si(){Pt.push(ie),ie=!0}function Zr(){let e=Pt.pop();ie=e===void 0?!0:e}function M(e,t,r){if(!ie||P===void 0)return;let n=Rt.get(e);n||Rt.set(e,n=new Map);let i=n.get(r);i||n.set(r,i=new Set),i.has(P)||(i.add(P),P.deps.push(i),P.options.onTrack&&P.options.onTrack({effect:P,target:e,type:t,key:r}))}function H(e,t,r,n,i,o){let s=Rt.get(e);if(!s)return;let a=new Set,c=u=>{u&&u.forEach(d=>{(d!==P||d.allowRecurse)&&a.add(d)})};if(t==="clear")s.forEach(c);else if(r==="length"&&K(e))s.forEach((u,d)=>{(d==="length"||d>=n)&&c(u)});else switch(r!==void 0&&c(s.get(r)),t){case"add":K(e)?Ke(r)&&c(s.get("length")):(c(s.get(Y)),ne(e)&&c(s.get(Mt)));break;case"delete":K(e)||(c(s.get(Y)),ne(e)&&c(s.get(Mt)));break;case"set":ne(e)&&c(s.get(Y));break}let l=u=>{u.options.onTrigger&&u.options.onTrigger({effect:u,target:e,key:r,type:t,newValue:n,oldValue:i,oldTarget:o}),u.options.scheduler?u.options.scheduler(u):u()};a.forEach(l)}var ai=At("__proto__,__v_isRef,__isVue"),Qr=new Set(Object.getOwnPropertyNames(Symbol).map(e=>Symbol[e]).filter(Be)),ci=en();var li=en(!0);var Vr=ui();function ui(){let e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...r){let n=_(this);for(let o=0,s=this.length;o<s;o++)M(n,"get",o+"");let i=n[t](...r);return i===-1||i===!1?n[t](...r.map(_)):i}}),["push","pop","shift","unshift","splice"].forEach(t=>{e[t]=function(...r){oi();let n=_(this)[t].apply(this,r);return Zr(),n}}),e}function en(e=!1,t=!1){return function(n,i,o){if(i==="__v_isReactive")return!e;if(i==="__v_isReadonly")return e;if(i==="__v_raw"&&o===(e?t?Oi:on:t?Ai:nn).get(n))return n;let s=K(n);if(!e&&s&&ge(Vr,i))return Reflect.get(Vr,i,o);let a=Reflect.get(n,i,o);return(Be(i)?Qr.has(i):ai(i))||(e||M(n,"get",i),t)?a:Nt(a)?!s||!Ke(i)?a.value:a:xe(a)?e?sn(a):Je(a):a}}var fi=di();function di(e=!1){return function(r,n,i,o){let s=r[n];if(!e&&(i=_(i),s=_(s),!K(r)&&Nt(s)&&!Nt(i)))return s.value=i,!0;let a=K(r)&&Ke(n)?Number(n)<r.length:ge(r,n),c=Reflect.set(r,n,i,o);return r===_(o)&&(a?Tt(i,s)&&H(r,"set",n,i,s):H(r,"add",n,i)),c}}function pi(e,t){let r=ge(e,t),n=e[t],i=Reflect.deleteProperty(e,t);return i&&r&&H(e,"delete",t,void 0,n),i}function mi(e,t){let r=Reflect.has(e,t);return(!Be(t)||!Qr.has(t))&&M(e,"has",t),r}function hi(e){return M(e,"iterate",K(e)?"length":Y),Reflect.ownKeys(e)}var _i={get:ci,set:fi,deleteProperty:pi,has:mi,ownKeys:hi},gi={get:li,set(e,t){return console.warn(`Set operation on key "${String(t)}" failed: target is readonly.`,e),!0},deleteProperty(e,t){return console.warn(`Delete operation on key "${String(t)}" failed: target is readonly.`,e),!0}};var Dt=e=>xe(e)?Je(e):e,It=e=>xe(e)?sn(e):e,kt=e=>e,Ge=e=>Reflect.getPrototypeOf(e);function He(e,t,r=!1,n=!1){e=e.__v_raw;let i=_(e),o=_(t);t!==o&&!r&&M(i,"get",t),!r&&M(i,"get",o);let{has:s}=Ge(i),a=n?kt:r?It:Dt;if(s.call(i,t))return a(e.get(t));if(s.call(i,o))return a(e.get(o));e!==i&&e.get(t)}function Ve(e,t=!1){let r=this.__v_raw,n=_(r),i=_(e);return e!==i&&!t&&M(n,"has",e),!t&&M(n,"has",i),e===i?r.has(e):r.has(e)||r.has(i)}function qe(e,t=!1){return e=e.__v_raw,!t&&M(_(e),"iterate",Y),Reflect.get(e,"size",e)}function qr(e){e=_(e);let t=_(this);return Ge(t).has.call(t,e)||(t.add(e),H(t,"add",e,e)),this}function Ur(e,t){t=_(t);let r=_(this),{has:n,get:i}=Ge(r),o=n.call(r,e);o?rn(r,n,e):(e=_(e),o=n.call(r,e));let s=i.call(r,e);return r.set(e,t),o?Tt(t,s)&&H(r,"set",e,t,s):H(r,"add",e,t),this}function Wr(e){let t=_(this),{has:r,get:n}=Ge(t),i=r.call(t,e);i?rn(t,r,e):(e=_(e),i=r.call(t,e));let o=n?n.call(t,e):void 0,s=t.delete(e);return i&&H(t,"delete",e,void 0,o),s}function Gr(){let e=_(this),t=e.size!==0,r=ne(e)?new Map(e):new Set(e),n=e.clear();return t&&H(e,"clear",void 0,void 0,r),n}function Ue(e,t){return function(n,i){let o=this,s=o.__v_raw,a=_(s),c=t?kt:e?It:Dt;return!e&&M(a,"iterate",Y),s.forEach((l,u)=>n.call(i,c(l),c(u),o))}}function We(e,t,r){return function(...n){let i=this.__v_raw,o=_(i),s=ne(o),a=e==="entries"||e===Symbol.iterator&&s,c=e==="keys"&&s,l=i[e](...n),u=r?kt:t?It:Dt;return!t&&M(o,"iterate",c?Mt:Y),{next(){let{value:d,done:m}=l.next();return m?{value:d,done:m}:{value:a?[u(d[0]),u(d[1])]:u(d),done:m}},[Symbol.iterator](){return this}}}}function z(e){return function(...t){{let r=t[0]?`on key "${t[0]}" `:"";console.warn(`${Ct(e)} operation ${r}failed: target is readonly.`,_(this))}return e==="delete"?!1:this}}function xi(){let e={get(o){return He(this,o)},get size(){return qe(this)},has:Ve,add:qr,set:Ur,delete:Wr,clear:Gr,forEach:Ue(!1,!1)},t={get(o){return He(this,o,!1,!0)},get size(){return qe(this)},has:Ve,add:qr,set:Ur,delete:Wr,clear:Gr,forEach:Ue(!1,!0)},r={get(o){return He(this,o,!0)},get size(){return qe(this,!0)},has(o){return Ve.call(this,o,!0)},add:z("add"),set:z("set"),delete:z("delete"),clear:z("clear"),forEach:Ue(!0,!1)},n={get(o){return He(this,o,!0,!0)},get size(){return qe(this,!0)},has(o){return Ve.call(this,o,!0)},add:z("add"),set:z("set"),delete:z("delete"),clear:z("clear"),forEach:Ue(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(o=>{e[o]=We(o,!1,!1),r[o]=We(o,!0,!1),t[o]=We(o,!1,!0),n[o]=We(o,!0,!0)}),[e,r,t,n]}var[yi,bi,wi,Ei]=xi();function tn(e,t){let r=t?e?Ei:wi:e?bi:yi;return(n,i,o)=>i==="__v_isReactive"?!e:i==="__v_isReadonly"?e:i==="__v_raw"?n:Reflect.get(ge(r,i)&&i in n?r:n,i,o)}var vi={get:tn(!1,!1)};var Si={get:tn(!0,!1)};function rn(e,t,r){let n=_(r);if(n!==r&&t.call(e,n)){let i=Ot(e);console.warn(`Reactive ${i} contains both the raw and reactive versions of the same object${i==="Map"?" as keys":""}, which can lead to inconsistencies. Avoid differentiating between the raw and reactive versions of an object and only use the reactive version if possible.`)}}var nn=new WeakMap,Ai=new WeakMap,on=new WeakMap,Oi=new WeakMap;function Ci(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Ti(e){return e.__v_skip||!Object.isExtensible(e)?0:Ci(Ot(e))}function Je(e){return e&&e.__v_isReadonly?e:an(e,!1,_i,vi,nn)}function sn(e){return an(e,!0,gi,Si,on)}function an(e,t,r,n,i){if(!xe(e))return console.warn(`value cannot be made reactive: ${String(e)}`),e;if(e.__v_raw&&!(t&&e.__v_isReactive))return e;let o=i.get(e);if(o)return o;let s=Ti(e);if(s===0)return e;let a=new Proxy(e,s===2?n:r);return i.set(e,a),a}function _(e){return e&&_(e.__v_raw)||e}function Nt(e){return Boolean(e&&e.__v_isRef===!0)}y("nextTick",()=>re);y("dispatch",e=>q.bind(q,e));y("watch",(e,{evaluateLater:t,effect:r})=>(n,i)=>{let o=t(n),s=!0,a,c=r(()=>o(l=>{JSON.stringify(l),s?a=l:queueMicrotask(()=>{i(l,a),a=l}),s=!1}));e._x_effects.delete(c)});y("store",kr);y("data",e=>Ce(e));y("root",e=>U(e));y("refs",e=>(e._x_refs_proxy||(e._x_refs_proxy=F(Ri(e))),e._x_refs_proxy));function Ri(e){let t=[],r=e;for(;r;)r._x_refs&&t.push(r._x_refs),r=r.parentNode;return t}var Lt={};function $t(e){return Lt[e]||(Lt[e]=0),++Lt[e]}function cn(e,t){return Z(e,r=>{if(r._x_ids&&r._x_ids[t])return!0})}function ln(e,t){e._x_ids||(e._x_ids={}),e._x_ids[t]||(e._x_ids[t]=$t(t))}y("id",e=>(t,r=null)=>{let n=cn(e,t),i=n?n._x_ids[t]:$t(t);return r?`${t}-${i}-${r}`:`${t}-${i}`});y("el",e=>e);un("Focus","focus","focus");un("Persist","persist","persist");function un(e,t,r){y(t,n=>S(`You can't use [$${directiveName}] without first installing the "${e}" plugin here: https://alpinejs.dev/plugins/${r}`,n))}p("modelable",(e,{expression:t},{effect:r,evaluateLater:n,cleanup:i})=>{let o=n(t),s=()=>{let u;return o(d=>u=d),u},a=n(`${t} = __placeholder`),c=u=>a(()=>{},{scope:{__placeholder:u}}),l=s();c(l),queueMicrotask(()=>{if(!e._x_model)return;e._x_removeModelListeners.default();let u=e._x_model.get,d=e._x_model.set,m=je({get(){return u()},set(w){d(w)}},{get(){return s()},set(w){c(w)}});i(m)})});var Mi=document.createElement("div");p("teleport",(e,{modifiers:t,expression:r},{cleanup:n})=>{e.tagName.toLowerCase()!=="template"&&S("x-teleport can only be used on a <template> tag",e);let i=B(()=>document.querySelector(r),()=>Mi)();i||S(`Cannot find x-teleport element for selector: "${r}"`);let o=e.content.cloneNode(!0).firstElementChild;e._x_teleport=o,o._x_teleportBack=e,e._x_forwardEvents&&e._x_forwardEvents.forEach(s=>{o.addEventListener(s,a=>{a.stopPropagation(),e.dispatchEvent(new a.constructor(a.type,a))})}),N(o,{},e),h(()=>{t.includes("prepend")?i.parentNode.insertBefore(o,i):t.includes("append")?i.parentNode.insertBefore(o,i.nextSibling):i.appendChild(o),v(o),o._x_ignore=!0}),n(()=>o.remove())});var fn=()=>{};fn.inline=(e,{modifiers:t},{cleanup:r})=>{t.includes("self")?e._x_ignoreSelf=!0:e._x_ignore=!0,r(()=>{t.includes("self")?delete e._x_ignoreSelf:delete e._x_ignore})};p("ignore",fn);p("effect",(e,{expression:t},{effect:r})=>r(x(e,t)));function oe(e,t,r,n){let i=e,o=c=>n(c),s={},a=(c,l)=>u=>l(c,u);if(r.includes("dot")&&(t=Ni(t)),r.includes("camel")&&(t=Pi(t)),r.includes("passive")&&(s.passive=!0),r.includes("capture")&&(s.capture=!0),r.includes("window")&&(i=window),r.includes("document")&&(i=document),r.includes("debounce")){let c=r[r.indexOf("debounce")+1]||"invalid-wait",l=Ye(c.split("ms")[0])?Number(c.split("ms")[0]):250;o=$e(o,l)}if(r.includes("throttle")){let c=r[r.indexOf("throttle")+1]||"invalid-wait",l=Ye(c.split("ms")[0])?Number(c.split("ms")[0]):250;o=Fe(o,l)}return r.includes("prevent")&&(o=a(o,(c,l)=>{l.preventDefault(),c(l)})),r.includes("stop")&&(o=a(o,(c,l)=>{l.stopPropagation(),c(l)})),r.includes("self")&&(o=a(o,(c,l)=>{l.target===e&&c(l)})),(r.includes("away")||r.includes("outside"))&&(i=document,o=a(o,(c,l)=>{e.contains(l.target)||l.target.isConnected!==!1&&(e.offsetWidth<1&&e.offsetHeight<1||e._x_isShown!==!1&&c(l))})),r.includes("once")&&(o=a(o,(c,l)=>{c(l),i.removeEventListener(t,o,s)})),o=a(o,(c,l)=>{Ii(t)&&ki(l,r)||c(l)}),i.addEventListener(t,o,s),()=>{i.removeEventListener(t,o,s)}}function Ni(e){return e.replace(/-/g,".")}function Pi(e){return e.toLowerCase().replace(/-(\w)/g,(t,r)=>r.toUpperCase())}function Ye(e){return!Array.isArray(e)&&!isNaN(e)}function Di(e){return[" ","_"].includes(e)?e:e.replace(/([a-z])([A-Z])/g,"$1-$2").replace(/[_\s]/,"-").toLowerCase()}function Ii(e){return["keydown","keyup"].includes(e)}function ki(e,t){let r=t.filter(o=>!["window","document","prevent","stop","once","capture"].includes(o));if(r.includes("debounce")){let o=r.indexOf("debounce");r.splice(o,Ye((r[o+1]||"invalid-wait").split("ms")[0])?2:1)}if(r.includes("throttle")){let o=r.indexOf("throttle");r.splice(o,Ye((r[o+1]||"invalid-wait").split("ms")[0])?2:1)}if(r.length===0||r.length===1&&dn(e.key).includes(r[0]))return!1;let i=["ctrl","shift","alt","meta","cmd","super"].filter(o=>r.includes(o));return r=r.filter(o=>!i.includes(o)),!(i.length>0&&i.filter(s=>((s==="cmd"||s==="super")&&(s="meta"),e[`${s}Key`])).length===i.length&&dn(e.key).includes(r[0]))}function dn(e){if(!e)return[];e=Di(e);let t={ctrl:"control",slash:"/",space:" ",spacebar:" ",cmd:"meta",esc:"escape",up:"arrow-up",down:"arrow-down",left:"arrow-left",right:"arrow-right",period:".",equal:"=",minus:"-",underscore:"_"};return t[e]=e,Object.keys(t).map(r=>{if(t[r]===e)return r}).filter(r=>r)}p("model",(e,{modifiers:t,expression:r},{effect:n,cleanup:i})=>{let o=e;t.includes("parent")&&(o=e.parentNode);let s=x(o,r),a;typeof r=="string"?a=x(o,`${r} = __placeholder`):typeof r=="function"&&typeof r()=="string"?a=x(o,`${r()} = __placeholder`):a=()=>{};let c=()=>{let m;return s(w=>m=w),pn(m)?m.get():m},l=m=>{let w;s(k=>w=k),pn(w)?w.set(m):a(()=>{},{scope:{__placeholder:m}})};typeof r=="string"&&e.type==="radio"&&h(()=>{e.hasAttribute("name")||e.setAttribute("name",r)});var u=e.tagName.toLowerCase()==="select"||["checkbox","radio"].includes(e.type)||t.includes("lazy")?"change":"input";let d=I?()=>{}:oe(e,u,t,m=>{l(Li(e,t,m,c()))});if(t.includes("fill")&&([null,""].includes(c())||e.type==="checkbox"&&Array.isArray(c()))&&e.dispatchEvent(new Event(u,{})),e._x_removeModelListeners||(e._x_removeModelListeners={}),e._x_removeModelListeners.default=d,i(()=>e._x_removeModelListeners.default()),e.form){let m=oe(e.form,"reset",[],w=>{re(()=>e._x_model&&e._x_model.set(e.value))});i(()=>m())}e._x_model={get(){return c()},set(m){l(m)}},e._x_forceModelUpdate=m=>{m===void 0&&typeof r=="string"&&r.match(/\./)&&(m=""),window.fromModel=!0,h(()=>_e(e,"value",m)),delete window.fromModel},n(()=>{let m=c();t.includes("unintrusive")&&document.activeElement.isSameNode(e)||e._x_forceModelUpdate(m)})});function Li(e,t,r,n){return h(()=>{if(r instanceof CustomEvent&&r.detail!==void 0)return r.detail??r.target.value;if(e.type==="checkbox")if(Array.isArray(n)){let i=t.includes("number")?Ft(r.target.value):r.target.value;return r.target.checked?n.concat([i]):n.filter(o=>!$i(o,i))}else return r.target.checked;else{if(e.tagName.toLowerCase()==="select"&&e.multiple)return t.includes("number")?Array.from(r.target.selectedOptions).map(i=>{let o=i.value||i.text;return Ft(o)}):Array.from(r.target.selectedOptions).map(i=>i.value||i.text);{let i=r.target.value;return t.includes("number")?Ft(i):t.includes("trim")?i.trim():i}}})}function Ft(e){let t=e?parseFloat(e):null;return Fi(t)?t:e}function $i(e,t){return e==t}function Fi(e){return!Array.isArray(e)&&!isNaN(e)}function pn(e){return e!==null&&typeof e=="object"&&typeof e.get=="function"&&typeof e.set=="function"}p("cloak",e=>queueMicrotask(()=>h(()=>e.removeAttribute(C("cloak")))));ve(()=>`[${C("init")}]`);p("init",B((e,{expression:t},{evaluate:r})=>typeof t=="string"?!!t.trim()&&r(t,{},!1):r(t,{},!1)));p("text",(e,{expression:t},{effect:r,evaluateLater:n})=>{let i=n(t);r(()=>{i(o=>{h(()=>{e.textContent=o})})})});p("html",(e,{expression:t},{effect:r,evaluateLater:n})=>{let i=n(t);r(()=>{i(o=>{h(()=>{e.innerHTML=o,e._x_ignoreSelf=!0,v(e),delete e._x_ignoreSelf})})})});te(De(":",Ie(C("bind:"))));var mn=(e,{value:t,modifiers:r,expression:n,original:i},{effect:o})=>{if(!t){let a={};Fr(a),x(e,n)(l=>{St(e,l,i)},{scope:a});return}if(t==="key")return ji(e,n);if(e._x_inlineBindings&&e._x_inlineBindings[t]&&e._x_inlineBindings[t].extract)return;let s=x(e,n);o(()=>s(a=>{a===void 0&&typeof n=="string"&&n.match(/\./)&&(a=""),h(()=>_e(e,t,a,r))}))};mn.inline=(e,{value:t,modifiers:r,expression:n})=>{t&&(e._x_inlineBindings||(e._x_inlineBindings={}),e._x_inlineBindings[t]={expression:n,extract:!1})};p("bind",mn);function ji(e,t){e._x_keyExpression=t}Ee(()=>`[${C("data")}]`);p("data",(e,{expression:t},{cleanup:r})=>{if(Ar(e))return;t=t===""?"{}":t;let n={};fe(n,e);let i={};Kr(i,n);let o=R(e,t,{scope:i});(o===void 0||o===!0)&&(o={}),fe(o,e);let s=T(o);Te(s);let a=N(e,s);s.init&&R(e,s.init),r(()=>{s.destroy&&R(e,s.destroy),a()})});p("show",(e,{modifiers:t,expression:r},{effect:n})=>{let i=x(e,r);e._x_doHide||(e._x_doHide=()=>{h(()=>{e.style.setProperty("display","none",t.includes("important")?"important":void 0)})}),e._x_doShow||(e._x_doShow=()=>{h(()=>{e.style.length===1&&e.style.display==="none"?e.removeAttribute("style"):e.style.removeProperty("display")})});let o=()=>{e._x_doHide(),e._x_isShown=!1},s=()=>{e._x_doShow(),e._x_isShown=!0},a=()=>setTimeout(s),c=me(d=>d?s():o(),d=>{typeof e._x_toggleAndCascadeWithTransitions=="function"?e._x_toggleAndCascadeWithTransitions(e,d,s,o):d?a():o()}),l,u=!0;n(()=>i(d=>{!u&&d===l||(t.includes("immediate")&&(d?a():o()),c(d),l=d,u=!1)}))});p("for",(e,{expression:t},{effect:r,cleanup:n})=>{let i=Ki(t),o=x(e,i.items),s=x(e,e._x_keyExpression||"index");e._x_prevKeys=[],e._x_lookup={},r(()=>Bi(e,i,o,s)),n(()=>{Object.values(e._x_lookup).forEach(a=>a.remove()),delete e._x_prevKeys,delete e._x_lookup})});function Bi(e,t,r,n){let i=s=>typeof s=="object"&&!Array.isArray(s),o=e;r(s=>{zi(s)&&s>=0&&(s=Array.from(Array(s).keys(),f=>f+1)),s===void 0&&(s=[]);let a=e._x_lookup,c=e._x_prevKeys,l=[],u=[];if(i(s))s=Object.entries(s).map(([f,g])=>{let b=hn(t,g,f,s);n(E=>u.push(E),{scope:{index:f,...b}}),l.push(b)});else for(let f=0;f<s.length;f++){let g=hn(t,s[f],f,s);n(b=>u.push(b),{scope:{index:f,...g}}),l.push(g)}let d=[],m=[],w=[],k=[];for(let f=0;f<c.length;f++){let g=c[f];u.indexOf(g)===-1&&w.push(g)}c=c.filter(f=>!w.includes(f));let be="template";for(let f=0;f<u.length;f++){let g=u[f],b=c.indexOf(g);if(b===-1)c.splice(f,0,g),d.push([be,f]);else if(b!==f){let E=c.splice(f,1)[0],A=c.splice(b-1,1)[0];c.splice(f,0,A),c.splice(b,0,E),m.push([E,A])}else k.push(g);be=g}for(let f=0;f<w.length;f++){let g=w[f];a[g]._x_effects&&a[g]._x_effects.forEach(we),a[g].remove(),a[g]=null,delete a[g]}for(let f=0;f<m.length;f++){let[g,b]=m[f],E=a[g],A=a[b],X=document.createElement("div");h(()=>{A||S('x-for ":key" is undefined or invalid',o),A.after(X),E.after(A),A._x_currentIfEl&&A.after(A._x_currentIfEl),X.before(E),E._x_currentIfEl&&E.after(E._x_currentIfEl),X.remove()}),A._x_refreshXForScope(l[u.indexOf(b)])}for(let f=0;f<d.length;f++){let[g,b]=d[f],E=g==="template"?o:a[g];E._x_currentIfEl&&(E=E._x_currentIfEl);let A=l[b],X=u[b],se=document.importNode(o.content,!0).firstElementChild,Bt=T(A);N(se,Bt,o),se._x_refreshXForScope=gn=>{Object.entries(gn).forEach(([xn,yn])=>{Bt[xn]=yn})},h(()=>{E.after(se),v(se)}),typeof X=="object"&&S("x-for key cannot be an object, it must be a string or an integer",o),a[X]=se}for(let f=0;f<k.length;f++)a[k[f]]._x_refreshXForScope(l[u.indexOf(k[f])]);o._x_prevKeys=u})}function Ki(e){let t=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,r=/^\s*\(|\)\s*$/g,n=/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/,i=e.match(n);if(!i)return;let o={};o.items=i[2].trim();let s=i[1].replace(r,"").trim(),a=s.match(t);return a?(o.item=s.replace(t,"").trim(),o.index=a[1].trim(),a[2]&&(o.collection=a[2].trim())):o.item=s,o}function hn(e,t,r,n){let i={};return/^\[.*\]$/.test(e.item)&&Array.isArray(t)?e.item.replace("[","").replace("]","").split(",").map(s=>s.trim()).forEach((s,a)=>{i[s]=t[a]}):/^\{.*\}$/.test(e.item)&&!Array.isArray(t)&&typeof t=="object"?e.item.replace("{","").replace("}","").split(",").map(s=>s.trim()).forEach(s=>{i[s]=t[s]}):i[e.item]=t,e.index&&(i[e.index]=r),e.collection&&(i[e.collection]=n),i}function zi(e){return!Array.isArray(e)&&!isNaN(e)}function _n(){}_n.inline=(e,{expression:t},{cleanup:r})=>{let n=U(e);n._x_refs||(n._x_refs={}),n._x_refs[t]=e,r(()=>delete n._x_refs[t])};p("ref",_n);p("if",(e,{expression:t},{effect:r,cleanup:n})=>{let i=x(e,t),o=()=>{if(e._x_currentIfEl)return e._x_currentIfEl;let a=e.content.cloneNode(!0).firstElementChild;return N(a,{},e),h(()=>{e.after(a),v(a)}),e._x_currentIfEl=a,e._x_undoIf=()=>{O(a,c=>{c._x_effects&&c._x_effects.forEach(we)}),a.remove(),delete e._x_currentIfEl},a},s=()=>{e._x_undoIf&&(e._x_undoIf(),delete e._x_undoIf)};r(()=>i(a=>{a?o():s()})),n(()=>e._x_undoIf&&e._x_undoIf())});p("id",(e,{expression:t},{evaluate:r})=>{r(t).forEach(i=>ln(e,i))});te(De("@",Ie(C("on:"))));p("on",B((e,{value:t,modifiers:r,expression:n},{cleanup:i})=>{let o=n?x(e,n):()=>{};e.tagName.toLowerCase()==="template"&&(e._x_forwardEvents||(e._x_forwardEvents=[]),e._x_forwardEvents.includes(t)||e._x_forwardEvents.push(t));let s=oe(e,t,r,a=>{o(()=>{},{scope:{$event:a},params:[a]})});i(()=>s())}));Xe("Collapse","collapse","collapse");Xe("Intersect","intersect","intersect");Xe("Focus","trap","focus");Xe("Mask","mask","mask");function Xe(e,t,r){p(t,n=>S(`You can't use [x-${t}] without first installing the "${e}" plugin here: https://alpinejs.dev/plugins/${r}`,n))}j.setEvaluator(ht);j.setReactivityEngine({reactive:Je,effect:Jr,release:Yr,raw:_});var jt=j;window.Alpine=jt;queueMicrotask(()=>{jt.start()});})();
@@ -1,5 +0,0 @@
1-(()=>{var Ze=!1,Qe=!1,V=[],et=-1;function Kt(e){bn(e)}function bn(e){V.includes(e)||V.push(e),wn()}function we(e){let t=V.indexOf(e);t!==-1&&t>et&&V.splice(t,1)}function wn(){!Qe&&!Ze&&(Ze=!0,queueMicrotask(En))}function En(){Ze=!1,Qe=!0;for(let e=0;e<V.length;e++)V[e](),et=e;V.length=0,et=-1,Qe=!1}var T,D,L,rt,tt=!0;function zt(e){tt=!1,e(),tt=!0}function Ht(e){T=e.reactive,L=e.release,D=t=>e.effect(t,{scheduler:r=>{tt?Kt(r):r()}}),rt=e.raw}function nt(e){D=e}function Vt(e){let t=()=>{};return[n=>{let i=D(n);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(o=>o())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),L(i))},i},()=>{t()}]}function q(e,t,r={}){e.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0,cancelable:!0}))}function O(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(i=>O(i,t));return}let r=!1;if(t(e,()=>r=!0),r)return;let n=e.firstElementChild;for(;n;)O(n,t,!1),n=n.nextElementSibling}function S(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}var qt=!1;function Ut(){qt&&S("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."),qt=!0,document.body||S("Unable to initialize. Trying to load Alpine before `<body>` is available. Did you forget to add `defer` in Alpine's `<script>` tag?"),q(document,"alpine:init"),q(document,"alpine:initializing"),ce(),Qt(t=>v(t,O)),Q(t=>ae(t)),Se((t,r)=>{le(t,r).forEach(n=>n())});let e=t=>!U(t.parentElement,!0);Array.from(document.querySelectorAll(Jt())).filter(e).forEach(t=>{v(t)}),q(document,"alpine:initialized")}var it=[],Wt=[];function Gt(){return it.map(e=>e())}function Jt(){return it.concat(Wt).map(e=>e())}function Ee(e){it.push(e)}function ve(e){Wt.push(e)}function U(e,t=!1){return Z(e,r=>{if((t?Jt():Gt()).some(i=>r.matches(i)))return!0})}function Z(e,t){if(e){if(t(e))return e;if(e._x_teleportBack&&(e=e._x_teleportBack),!!e.parentElement)return Z(e.parentElement,t)}}function Yt(e){return Gt().some(t=>e.matches(t))}var Xt=[];function Zt(e){Xt.push(e)}function v(e,t=O,r=()=>{}){tr(()=>{t(e,(n,i)=>{r(n,i),Xt.forEach(o=>o(n,i)),le(n,n.attributes).forEach(o=>o()),n._x_ignore&&i()})})}function ae(e){O(e,t=>{ot(t),er(t)})}var rr=[],nr=[],ir=[];function Qt(e){ir.push(e)}function Q(e,t){typeof t=="function"?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,nr.push(t))}function Se(e){rr.push(e)}function Oe(e,t,r){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(r)}function ot(e,t){e._x_attributeCleanups&&Object.entries(e._x_attributeCleanups).forEach(([r,n])=>{(t===void 0||t.includes(r))&&(n.forEach(i=>i()),delete e._x_attributeCleanups[r])})}function er(e){if(e._x_cleanups)for(;e._x_cleanups.length;)e._x_cleanups.pop()()}var at=new MutationObserver(ft),ct=!1;function ce(){at.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),ct=!0}function lt(){vn(),at.disconnect(),ct=!1}var ue=[],st=!1;function vn(){ue=ue.concat(at.takeRecords()),ue.length&&!st&&(st=!0,queueMicrotask(()=>{Sn(),st=!1}))}function Sn(){ft(ue),ue.length=0}function h(e){if(!ct)return e();lt();let t=e();return ce(),t}var ut=!1,Ae=[];function or(){ut=!0}function sr(){ut=!1,ft(Ae),Ae=[]}function ft(e){if(ut){Ae=Ae.concat(e);return}let t=[],r=[],n=new Map,i=new Map;for(let o=0;o<e.length;o++)if(!e[o].target._x_ignoreMutationObserver&&(e[o].type==="childList"&&(e[o].addedNodes.forEach(s=>s.nodeType===1&&t.push(s)),e[o].removedNodes.forEach(s=>s.nodeType===1&&r.push(s))),e[o].type==="attributes")){let s=e[o].target,a=e[o].attributeName,c=e[o].oldValue,l=()=>{n.has(s)||n.set(s,[]),n.get(s).push({name:a,value:s.getAttribute(a)})},u=()=>{i.has(s)||i.set(s,[]),i.get(s).push(a)};s.hasAttribute(a)&&c===null?l():s.hasAttribute(a)?(u(),l()):u()}i.forEach((o,s)=>{ot(s,o)}),n.forEach((o,s)=>{rr.forEach(a=>a(s,o))});for(let o of r)t.includes(o)||(nr.forEach(s=>s(o)),ae(o));t.forEach(o=>{o._x_ignoreSelf=!0,o._x_ignore=!0});for(let o of t)r.includes(o)||o.isConnected&&(delete o._x_ignoreSelf,delete o._x_ignore,ir.forEach(s=>s(o)),o._x_ignore=!0,o._x_ignoreSelf=!0);t.forEach(o=>{delete o._x_ignoreSelf,delete o._x_ignore}),t=null,r=null,n=null,i=null}function Ce(e){return F($(e))}function N(e,t,r){return e._x_dataStack=[t,...$(r||e)],()=>{e._x_dataStack=e._x_dataStack.filter(n=>n!==t)}}function $(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?$(e.host):e.parentNode?$(e.parentNode):[]}function F(e){let t=new Proxy({},{ownKeys:()=>Array.from(new Set(e.flatMap(r=>Object.keys(r)))),has:(r,n)=>e.some(i=>i.hasOwnProperty(n)),get:(r,n)=>(e.find(i=>{if(i.hasOwnProperty(n)){let o=Object.getOwnPropertyDescriptor(i,n);if(o.get&&o.get._x_alreadyBound||o.set&&o.set._x_alreadyBound)return!0;if((o.get||o.set)&&o.enumerable){let s=o.get,a=o.set,c=o;s=s&&s.bind(t),a=a&&a.bind(t),s&&(s._x_alreadyBound=!0),a&&(a._x_alreadyBound=!0),Object.defineProperty(i,n,{...c,get:s,set:a})}return!0}return!1})||{})[n],set:(r,n,i)=>{let o=e.find(s=>s.hasOwnProperty(n));return o?o[n]=i:e[e.length-1][n]=i,!0}});return t}function Te(e){let t=n=>typeof n=="object"&&!Array.isArray(n)&&n!==null,r=(n,i="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach(([o,{value:s,enumerable:a}])=>{if(a===!1||s===void 0)return;let c=i===""?o:`${i}.${o}`;typeof s=="object"&&s!==null&&s._x_interceptor?n[o]=s.initialize(e,c,o):t(s)&&s!==n&&!(s instanceof Element)&&r(s,c)})};return r(e)}function Re(e,t=()=>{}){let r={initialValue:void 0,_x_interceptor:!0,initialize(n,i,o){return e(this.initialValue,()=>An(n,i),s=>dt(n,i,s),i,o)}};return t(r),n=>{if(typeof n=="object"&&n!==null&&n._x_interceptor){let i=r.initialize.bind(r);r.initialize=(o,s,a)=>{let c=n.initialize(o,s,a);return r.initialValue=c,i(o,s,a)}}else r.initialValue=n;return r}}function An(e,t){return t.split(".").reduce((r,n)=>r[n],e)}function dt(e,t,r){if(typeof t=="string"&&(t=t.split(".")),t.length===1)e[t[0]]=r;else{if(t.length===0)throw error;return e[t[0]]||(e[t[0]]={}),dt(e[t[0]],t.slice(1),r)}}var ar={};function y(e,t){ar[e]=t}function fe(e,t){return Object.entries(ar).forEach(([r,n])=>{let i=null;function o(){if(i)return i;{let[s,a]=pt(t);return i={interceptor:Re,...s},Q(t,a),i}}Object.defineProperty(e,`$${r}`,{get(){return n(t,o())},enumerable:!1})}),e}function cr(e,t,r,...n){try{return r(...n)}catch(i){ee(i,e,t)}}function ee(e,t,r=void 0){Object.assign(e,{el:t,expression:r}),console.warn(`Alpine Expression Error: ${e.message}
2-
3-${r?'Expression: "'+r+`"
4-
5-`:""}`,t),setTimeout(()=>{throw e},0)}var Me=!0;function Pe(e){let t=Me;Me=!1;let r=e();return Me=t,r}function R(e,t,r={}){let n;return x(e,t)(i=>n=i,r),n}function x(...e){return lr(...e)}var lr=ht;function ur(e){lr=e}function ht(e,t){let r={};fe(r,e);let n=[r,...$(e)],i=typeof t=="function"?On(n,t):Tn(n,t,e);return cr.bind(null,e,t,i)}function On(e,t){return(r=()=>{},{scope:n={},params:i=[]}={})=>{let o=t.apply(F([n,...e]),i);Ne(r,o)}}var mt={};function Cn(e,t){if(mt[e])return mt[e];let r=Object.getPrototypeOf(async function(){}).constructor,n=/^[\n\s]*if.*\(.*\)/.test(e.trim())||/^(let|const)\s/.test(e.trim())?`(async()=>{ ${e} })()`:e,o=(()=>{try{return new r(["__self","scope"],`with (scope) { __self.result = ${n} }; __self.finished = true; return __self.result;`)}catch(s){return ee(s,t,e),Promise.resolve()}})();return mt[e]=o,o}function Tn(e,t,r){let n=Cn(t,r);return(i=()=>{},{scope:o={},params:s=[]}={})=>{n.result=void 0,n.finished=!1;let a=F([o,...e]);if(typeof n=="function"){let c=n(n,a).catch(l=>ee(l,r,t));n.finished?(Ne(i,n.result,a,s,r),n.result=void 0):c.then(l=>{Ne(i,l,a,s,r)}).catch(l=>ee(l,r,t)).finally(()=>n.result=void 0)}}}function Ne(e,t,r,n,i){if(Me&&typeof t=="function"){let o=t.apply(r,n);o instanceof Promise?o.then(s=>Ne(e,s,r,n)).catch(s=>ee(s,i,t)):e(o)}else typeof t=="object"&&t instanceof Promise?t.then(o=>e(o)):e(t)}var yt="x-";function C(e=""){return yt+e}function fr(e){yt=e}var _t={};function p(e,t){return _t[e]=t,{before(r){if(!_t[r]){console.warn("Cannot find directive `${directive}`. `${name}` will use the default order of execution");return}let n=W.indexOf(r);W.splice(n>=0?n:W.indexOf("DEFAULT"),0,e)}}}function le(e,t,r){if(t=Array.from(t),e._x_virtualDirectives){let o=Object.entries(e._x_virtualDirectives).map(([a,c])=>({name:a,value:c})),s=bt(o);o=o.map(a=>s.find(c=>c.name===a.name)?{name:`x-bind:${a.name}`,value:`"${a.value}"`}:a),t=t.concat(o)}let n={};return t.map(pr((o,s)=>n[o]=s)).filter(hr).map(Mn(n,r)).sort(Nn).map(o=>Rn(e,o))}function bt(e){return Array.from(e).map(pr()).filter(t=>!hr(t))}var gt=!1,de=new Map,dr=Symbol();function tr(e){gt=!0;let t=Symbol();dr=t,de.set(t,[]);let r=()=>{for(;de.get(t).length;)de.get(t).shift()();de.delete(t)},n=()=>{gt=!1,r()};e(r),n()}function pt(e){let t=[],r=a=>t.push(a),[n,i]=Vt(e);return t.push(i),[{Alpine:j,effect:n,cleanup:r,evaluateLater:x.bind(x,e),evaluate:R.bind(R,e)},()=>t.forEach(a=>a())]}function Rn(e,t){let r=()=>{},n=_t[t.type]||r,[i,o]=pt(e);Oe(e,t.original,o);let s=()=>{e._x_ignore||e._x_ignoreSelf||(n.inline&&n.inline(e,t,i),n=n.bind(n,e,t,i),gt?de.get(dr).push(n):n())};return s.runCleanups=o,s}var De=(e,t)=>({name:r,value:n})=>(r.startsWith(e)&&(r=r.replace(e,t)),{name:r,value:n}),Ie=e=>e;function pr(e=()=>{}){return({name:t,value:r})=>{let{name:n,value:i}=mr.reduce((o,s)=>s(o),{name:t,value:r});return n!==t&&e(n,t),{name:n,value:i}}}var mr=[];function te(e){mr.push(e)}function hr({name:e}){return _r().test(e)}var _r=()=>new RegExp(`^${yt}([^:^.]+)\\b`);function Mn(e,t){return({name:r,value:n})=>{let i=r.match(_r()),o=r.match(/:([a-zA-Z0-9\-:]+)/),s=r.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],a=t||e[r]||r;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:s.map(c=>c.replace(".","")),expression:n,original:a}}}var xt="DEFAULT",W=["ignore","ref","data","id","bind","init","for","model","modelable","transition","show","if",xt,"teleport"];function Nn(e,t){let r=W.indexOf(e.type)===-1?xt:e.type,n=W.indexOf(t.type)===-1?xt:t.type;return W.indexOf(r)-W.indexOf(n)}var wt=[],Et=!1;function re(e=()=>{}){return queueMicrotask(()=>{Et||setTimeout(()=>{ke()})}),new Promise(t=>{wt.push(()=>{e(),t()})})}function ke(){for(Et=!1;wt.length;)wt.shift()()}function gr(){Et=!0}function pe(e,t){return Array.isArray(t)?xr(e,t.join(" ")):typeof t=="object"&&t!==null?Pn(e,t):typeof t=="function"?pe(e,t()):xr(e,t)}function xr(e,t){let r=o=>o.split(" ").filter(Boolean),n=o=>o.split(" ").filter(s=>!e.classList.contains(s)).filter(Boolean),i=o=>(e.classList.add(...o),()=>{e.classList.remove(...o)});return t=t===!0?t="":t||"",i(n(t))}function Pn(e,t){let r=a=>a.split(" ").filter(Boolean),n=Object.entries(t).flatMap(([a,c])=>c?r(a):!1).filter(Boolean),i=Object.entries(t).flatMap(([a,c])=>c?!1:r(a)).filter(Boolean),o=[],s=[];return i.forEach(a=>{e.classList.contains(a)&&(e.classList.remove(a),s.push(a))}),n.forEach(a=>{e.classList.contains(a)||(e.classList.add(a),o.push(a))}),()=>{s.forEach(a=>e.classList.add(a)),o.forEach(a=>e.classList.remove(a))}}function G(e,t){return typeof t=="object"&&t!==null?Dn(e,t):In(e,t)}function Dn(e,t){let r={};return Object.entries(t).forEach(([n,i])=>{r[n]=e.style[n],n.startsWith("--")||(n=kn(n)),e.style.setProperty(n,i)}),setTimeout(()=>{e.style.length===0&&e.removeAttribute("style")}),()=>{G(e,r)}}function In(e,t){let r=e.getAttribute("style",t);return e.setAttribute("style",t),()=>{e.setAttribute("style",r||"")}}function kn(e){return e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}function me(e,t=()=>{}){let r=!1;return function(){r?t.apply(this,arguments):(r=!0,e.apply(this,arguments))}}p("transition",(e,{value:t,modifiers:r,expression:n},{evaluate:i})=>{typeof n=="function"&&(n=i(n)),n!==!1&&(!n||typeof n=="boolean"?$n(e,r,t):Ln(e,n,t))});function Ln(e,t,r){yr(e,pe,""),{enter:i=>{e._x_transition.enter.during=i},"enter-start":i=>{e._x_transition.enter.start=i},"enter-end":i=>{e._x_transition.enter.end=i},leave:i=>{e._x_transition.leave.during=i},"leave-start":i=>{e._x_transition.leave.start=i},"leave-end":i=>{e._x_transition.leave.end=i}}[r](t)}function $n(e,t,r){yr(e,G);let n=!t.includes("in")&&!t.includes("out")&&!r,i=n||t.includes("in")||["enter"].includes(r),o=n||t.includes("out")||["leave"].includes(r);t.includes("in")&&!n&&(t=t.filter((g,b)=>b<t.indexOf("out"))),t.includes("out")&&!n&&(t=t.filter((g,b)=>b>t.indexOf("out")));let s=!t.includes("opacity")&&!t.includes("scale"),a=s||t.includes("opacity"),c=s||t.includes("scale"),l=a?0:1,u=c?he(t,"scale",95)/100:1,d=he(t,"delay",0)/1e3,m=he(t,"origin","center"),w="opacity, transform",k=he(t,"duration",150)/1e3,be=he(t,"duration",75)/1e3,f="cubic-bezier(0.4, 0.0, 0.2, 1)";i&&(e._x_transition.enter.during={transformOrigin:m,transitionDelay:`${d}s`,transitionProperty:w,transitionDuration:`${k}s`,transitionTimingFunction:f},e._x_transition.enter.start={opacity:l,transform:`scale(${u})`},e._x_transition.enter.end={opacity:1,transform:"scale(1)"}),o&&(e._x_transition.leave.during={transformOrigin:m,transitionDelay:`${d}s`,transitionProperty:w,transitionDuration:`${be}s`,transitionTimingFunction:f},e._x_transition.leave.start={opacity:1,transform:"scale(1)"},e._x_transition.leave.end={opacity:l,transform:`scale(${u})`})}function yr(e,t,r={}){e._x_transition||(e._x_transition={enter:{during:r,start:r,end:r},leave:{during:r,start:r,end:r},in(n=()=>{},i=()=>{}){Le(e,t,{during:this.enter.during,start:this.enter.start,end:this.enter.end},n,i)},out(n=()=>{},i=()=>{}){Le(e,t,{during:this.leave.during,start:this.leave.start,end:this.leave.end},n,i)}})}window.Element.prototype._x_toggleAndCascadeWithTransitions=function(e,t,r,n){let i=document.visibilityState==="visible"?requestAnimationFrame:setTimeout,o=()=>i(r);if(t){e._x_transition&&(e._x_transition.enter||e._x_transition.leave)?e._x_transition.enter&&(Object.entries(e._x_transition.enter.during).length||Object.entries(e._x_transition.enter.start).length||Object.entries(e._x_transition.enter.end).length)?e._x_transition.in(r):o():e._x_transition?e._x_transition.in(r):o();return}e._x_hidePromise=e._x_transition?new Promise((s,a)=>{e._x_transition.out(()=>{},()=>s(n)),e._x_transitioning.beforeCancel(()=>a({isFromCancelledTransition:!0}))}):Promise.resolve(n),queueMicrotask(()=>{let s=br(e);s?(s._x_hideChildren||(s._x_hideChildren=[]),s._x_hideChildren.push(e)):i(()=>{let a=c=>{let l=Promise.all([c._x_hidePromise,...(c._x_hideChildren||[]).map(a)]).then(([u])=>u());return delete c._x_hidePromise,delete c._x_hideChildren,l};a(e).catch(c=>{if(!c.isFromCancelledTransition)throw c})})})};function br(e){let t=e.parentNode;if(t)return t._x_hidePromise?t:br(t)}function Le(e,t,{during:r,start:n,end:i}={},o=()=>{},s=()=>{}){if(e._x_transitioning&&e._x_transitioning.cancel(),Object.keys(r).length===0&&Object.keys(n).length===0&&Object.keys(i).length===0){o(),s();return}let a,c,l;Fn(e,{start(){a=t(e,n)},during(){c=t(e,r)},before:o,end(){a(),l=t(e,i)},after:s,cleanup(){c(),l()}})}function Fn(e,t){let r,n,i,o=me(()=>{h(()=>{r=!0,n||t.before(),i||(t.end(),ke()),t.after(),e.isConnected&&t.cleanup(),delete e._x_transitioning})});e._x_transitioning={beforeCancels:[],beforeCancel(s){this.beforeCancels.push(s)},cancel:me(function(){for(;this.beforeCancels.length;)this.beforeCancels.shift()();o()}),finish:o},h(()=>{t.start(),t.during()}),gr(),requestAnimationFrame(()=>{if(r)return;let s=Number(getComputedStyle(e).transitionDuration.replace(/,.*/,"").replace("s",""))*1e3,a=Number(getComputedStyle(e).transitionDelay.replace(/,.*/,"").replace("s",""))*1e3;s===0&&(s=Number(getComputedStyle(e).animationDuration.replace("s",""))*1e3),h(()=>{t.before()}),n=!0,requestAnimationFrame(()=>{r||(h(()=>{t.end()}),ke(),setTimeout(e._x_transitioning.finish,s+a),i=!0)})})}function he(e,t,r){if(e.indexOf(t)===-1)return r;let n=e[e.indexOf(t)+1];if(!n||t==="scale"&&isNaN(n))return r;if(t==="duration"||t==="delay"){let i=n.match(/([0-9]+)ms/);if(i)return i[1]}return t==="origin"&&["top","right","left","center","bottom"].includes(e[e.indexOf(t)+2])?[n,e[e.indexOf(t)+2]].join(" "):n}var I=!1;function B(e,t=()=>{}){return(...r)=>I?t(...r):e(...r)}function wr(e){return(...t)=>I&&e(...t)}function Er(e,t){e._x_dataStack&&(t._x_dataStack=e._x_dataStack,t.setAttribute("data-has-alpine-state",!0)),I=!0,Sr(()=>{v(t,(r,n)=>{n(r,()=>{})})}),I=!1}var vt=!1;function vr(e,t){t._x_dataStack||(t._x_dataStack=e._x_dataStack),I=!0,vt=!0,Sr(()=>{jn(t)}),I=!1,vt=!1}function jn(e){let t=!1;v(e,(n,i)=>{O(n,(o,s)=>{if(t&&Yt(o))return s();t=!0,i(o,s)})})}function Sr(e){let t=D;nt((r,n)=>{let i=t(r);return L(i),()=>{}}),e(),nt(t)}function Ar(e){return I?vt?!0:e.hasAttribute("data-has-alpine-state"):!1}function _e(e,t,r,n=[]){switch(e._x_bindings||(e._x_bindings=T({})),e._x_bindings[t]=r,t=n.includes("camel")?Wn(t):t,t){case"value":Bn(e,r);break;case"style":zn(e,r);break;case"class":Kn(e,r);break;case"selected":case"checked":Hn(e,t,r);break;default:Cr(e,t,r);break}}function Bn(e,t){if(e.type==="radio")e.attributes.value===void 0&&(e.value=t),window.fromModel&&(e.checked=Or(e.value,t));else if(e.type==="checkbox")Number.isInteger(t)?e.value=t:!Array.isArray(t)&&typeof t!="boolean"&&![null,void 0].includes(t)?e.value=String(t):Array.isArray(t)?e.checked=t.some(r=>Or(r,e.value)):e.checked=!!t;else if(e.tagName==="SELECT")Un(e,t);else{if(e.value===t)return;e.value=t===void 0?"":t}}function Kn(e,t){e._x_undoAddedClasses&&e._x_undoAddedClasses(),e._x_undoAddedClasses=pe(e,t)}function zn(e,t){e._x_undoAddedStyles&&e._x_undoAddedStyles(),e._x_undoAddedStyles=G(e,t)}function Hn(e,t,r){Cr(e,t,r),qn(e,t,r)}function Cr(e,t,r){[null,void 0,!1].includes(r)&&Gn(t)?e.removeAttribute(t):(Tr(t)&&(r=t),Vn(e,t,r))}function Vn(e,t,r){e.getAttribute(t)!=r&&e.setAttribute(t,r)}function qn(e,t,r){e[t]!==r&&(e[t]=r)}function Un(e,t){let r=[].concat(t).map(n=>n+"");Array.from(e.options).forEach(n=>{n.selected=r.includes(n.value)})}function Wn(e){return e.toLowerCase().replace(/-(\w)/g,(t,r)=>r.toUpperCase())}function Or(e,t){return e==t}function Tr(e){return["disabled","checked","required","readonly","hidden","open","selected","autofocus","itemscope","multiple","novalidate","allowfullscreen","allowpaymentrequest","formnovalidate","autoplay","controls","loop","muted","playsinline","default","ismap","reversed","async","defer","nomodule"].includes(e)}function Gn(e){return!["aria-pressed","aria-checked","aria-expanded","aria-selected"].includes(e)}function Rr(e,t,r){return e._x_bindings&&e._x_bindings[t]!==void 0?e._x_bindings[t]:Nr(e,t,r)}function Mr(e,t,r,n=!0){if(e._x_bindings&&e._x_bindings[t]!==void 0)return e._x_bindings[t];if(e._x_inlineBindings&&e._x_inlineBindings[t]!==void 0){let i=e._x_inlineBindings[t];return i.extract=n,Pe(()=>R(e,i.expression))}return Nr(e,t,r)}function Nr(e,t,r){let n=e.getAttribute(t);return n===null?typeof r=="function"?r():r:n===""?!0:Tr(t)?!![t,"true"].includes(n):n}function $e(e,t){var r;return function(){var n=this,i=arguments,o=function(){r=null,e.apply(n,i)};clearTimeout(r),r=setTimeout(o,t)}}function Fe(e,t){let r;return function(){let n=this,i=arguments;r||(e.apply(n,i),r=!0,setTimeout(()=>r=!1,t))}}function je({get:e,set:t},{get:r,set:n}){let i=!0,o,s,a,c,l=D(()=>{let u,d;i?(u=e(),n(JSON.parse(JSON.stringify(u))),d=r(),i=!1):(u=e(),d=r(),a=JSON.stringify(u),c=JSON.stringify(d),a!==o?(d=r(),n(u),d=u):(t(JSON.parse(c??null)),u=d)),o=JSON.stringify(u),s=JSON.stringify(d)});return()=>{L(l)}}function Pr(e){(Array.isArray(e)?e:[e]).forEach(r=>r(j))}var J={},Dr=!1;function Ir(e,t){if(Dr||(J=T(J),Dr=!0),t===void 0)return J[e];J[e]=t,typeof t=="object"&&t!==null&&t.hasOwnProperty("init")&&typeof t.init=="function"&&J[e].init(),Te(J[e])}function kr(){return J}var Lr={};function $r(e,t){let r=typeof t!="function"?()=>t:t;return e instanceof Element?St(e,r()):(Lr[e]=r,()=>{})}function Fr(e){return Object.entries(Lr).forEach(([t,r])=>{Object.defineProperty(e,t,{get(){return(...n)=>r(...n)}})}),e}function St(e,t,r){let n=[];for(;n.length;)n.pop()();let i=Object.entries(t).map(([s,a])=>({name:s,value:a})),o=bt(i);return i=i.map(s=>o.find(a=>a.name===s.name)?{name:`x-bind:${s.name}`,value:`"${s.value}"`}:s),le(e,i,r).map(s=>{n.push(s.runCleanups),s()}),()=>{for(;n.length;)n.pop()()}}var jr={};function Br(e,t){jr[e]=t}function Kr(e,t){return Object.entries(jr).forEach(([r,n])=>{Object.defineProperty(e,r,{get(){return(...i)=>n.bind(t)(...i)},enumerable:!1})}),e}var Jn={get reactive(){return T},get release(){return L},get effect(){return D},get raw(){return rt},version:"3.13.0",flushAndStopDeferringMutations:sr,dontAutoEvaluateFunctions:Pe,disableEffectScheduling:zt,startObservingMutations:ce,stopObservingMutations:lt,setReactivityEngine:Ht,onAttributeRemoved:Oe,onAttributesAdded:Se,closestDataStack:$,skipDuringClone:B,onlyDuringClone:wr,addRootSelector:Ee,addInitSelector:ve,addScopeToNode:N,deferMutations:or,mapAttributes:te,evaluateLater:x,interceptInit:Zt,setEvaluator:ur,mergeProxies:F,extractProp:Mr,findClosest:Z,onElRemoved:Q,closestRoot:U,destroyTree:ae,interceptor:Re,transition:Le,setStyles:G,mutateDom:h,directive:p,entangle:je,throttle:Fe,debounce:$e,evaluate:R,initTree:v,nextTick:re,prefixed:C,prefix:fr,plugin:Pr,magic:y,store:Ir,start:Ut,clone:vr,cloneNode:Er,bound:Rr,$data:Ce,walk:O,data:Br,bind:$r},j=Jn;function At(e,t){let r=Object.create(null),n=e.split(",");for(let i=0;i<n.length;i++)r[n[i]]=!0;return t?i=>!!r[i.toLowerCase()]:i=>!!r[i]}var Yn="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly";var As=At(Yn+",async,autofocus,autoplay,controls,default,defer,disabled,hidden,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected");var zr=Object.freeze({}),Os=Object.freeze([]);var Xn=Object.prototype.hasOwnProperty,ge=(e,t)=>Xn.call(e,t),K=Array.isArray,ne=e=>Hr(e)==="[object Map]";var Zn=e=>typeof e=="string",Be=e=>typeof e=="symbol",xe=e=>e!==null&&typeof e=="object";var Qn=Object.prototype.toString,Hr=e=>Qn.call(e),Ot=e=>Hr(e).slice(8,-1);var Ke=e=>Zn(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e;var ze=e=>{let t=Object.create(null);return r=>t[r]||(t[r]=e(r))},ei=/-(\w)/g,Cs=ze(e=>e.replace(ei,(t,r)=>r?r.toUpperCase():"")),ti=/\B([A-Z])/g,Ts=ze(e=>e.replace(ti,"-$1").toLowerCase()),Ct=ze(e=>e.charAt(0).toUpperCase()+e.slice(1)),Rs=ze(e=>e?`on${Ct(e)}`:""),Tt=(e,t)=>e!==t&&(e===e||t===t);var Rt=new WeakMap,ye=[],P,Y=Symbol("iterate"),Mt=Symbol("Map key iterate");function ri(e){return e&&e._isEffect===!0}function Jr(e,t=zr){ri(e)&&(e=e.raw);let r=ii(e,t);return t.lazy||r(),r}function Yr(e){e.active&&(Xr(e),e.options.onStop&&e.options.onStop(),e.active=!1)}var ni=0;function ii(e,t){let r=function(){if(!r.active)return e();if(!ye.includes(r)){Xr(r);try{return si(),ye.push(r),P=r,e()}finally{ye.pop(),Zr(),P=ye[ye.length-1]}}};return r.id=ni++,r.allowRecurse=!!t.allowRecurse,r._isEffect=!0,r.active=!0,r.raw=e,r.deps=[],r.options=t,r}function Xr(e){let{deps:t}=e;if(t.length){for(let r=0;r<t.length;r++)t[r].delete(e);t.length=0}}var ie=!0,Pt=[];function oi(){Pt.push(ie),ie=!1}function si(){Pt.push(ie),ie=!0}function Zr(){let e=Pt.pop();ie=e===void 0?!0:e}function M(e,t,r){if(!ie||P===void 0)return;let n=Rt.get(e);n||Rt.set(e,n=new Map);let i=n.get(r);i||n.set(r,i=new Set),i.has(P)||(i.add(P),P.deps.push(i),P.options.onTrack&&P.options.onTrack({effect:P,target:e,type:t,key:r}))}function H(e,t,r,n,i,o){let s=Rt.get(e);if(!s)return;let a=new Set,c=u=>{u&&u.forEach(d=>{(d!==P||d.allowRecurse)&&a.add(d)})};if(t==="clear")s.forEach(c);else if(r==="length"&&K(e))s.forEach((u,d)=>{(d==="length"||d>=n)&&c(u)});else switch(r!==void 0&&c(s.get(r)),t){case"add":K(e)?Ke(r)&&c(s.get("length")):(c(s.get(Y)),ne(e)&&c(s.get(Mt)));break;case"delete":K(e)||(c(s.get(Y)),ne(e)&&c(s.get(Mt)));break;case"set":ne(e)&&c(s.get(Y));break}let l=u=>{u.options.onTrigger&&u.options.onTrigger({effect:u,target:e,key:r,type:t,newValue:n,oldValue:i,oldTarget:o}),u.options.scheduler?u.options.scheduler(u):u()};a.forEach(l)}var ai=At("__proto__,__v_isRef,__isVue"),Qr=new Set(Object.getOwnPropertyNames(Symbol).map(e=>Symbol[e]).filter(Be)),ci=en();var li=en(!0);var Vr=ui();function ui(){let e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...r){let n=_(this);for(let o=0,s=this.length;o<s;o++)M(n,"get",o+"");let i=n[t](...r);return i===-1||i===!1?n[t](...r.map(_)):i}}),["push","pop","shift","unshift","splice"].forEach(t=>{e[t]=function(...r){oi();let n=_(this)[t].apply(this,r);return Zr(),n}}),e}function en(e=!1,t=!1){return function(n,i,o){if(i==="__v_isReactive")return!e;if(i==="__v_isReadonly")return e;if(i==="__v_raw"&&o===(e?t?Oi:on:t?Ai:nn).get(n))return n;let s=K(n);if(!e&&s&&ge(Vr,i))return Reflect.get(Vr,i,o);let a=Reflect.get(n,i,o);return(Be(i)?Qr.has(i):ai(i))||(e||M(n,"get",i),t)?a:Nt(a)?!s||!Ke(i)?a.value:a:xe(a)?e?sn(a):Je(a):a}}var fi=di();function di(e=!1){return function(r,n,i,o){let s=r[n];if(!e&&(i=_(i),s=_(s),!K(r)&&Nt(s)&&!Nt(i)))return s.value=i,!0;let a=K(r)&&Ke(n)?Number(n)<r.length:ge(r,n),c=Reflect.set(r,n,i,o);return r===_(o)&&(a?Tt(i,s)&&H(r,"set",n,i,s):H(r,"add",n,i)),c}}function pi(e,t){let r=ge(e,t),n=e[t],i=Reflect.deleteProperty(e,t);return i&&r&&H(e,"delete",t,void 0,n),i}function mi(e,t){let r=Reflect.has(e,t);return(!Be(t)||!Qr.has(t))&&M(e,"has",t),r}function hi(e){return M(e,"iterate",K(e)?"length":Y),Reflect.ownKeys(e)}var _i={get:ci,set:fi,deleteProperty:pi,has:mi,ownKeys:hi},gi={get:li,set(e,t){return console.warn(`Set operation on key "${String(t)}" failed: target is readonly.`,e),!0},deleteProperty(e,t){return console.warn(`Delete operation on key "${String(t)}" failed: target is readonly.`,e),!0}};var Dt=e=>xe(e)?Je(e):e,It=e=>xe(e)?sn(e):e,kt=e=>e,Ge=e=>Reflect.getPrototypeOf(e);function He(e,t,r=!1,n=!1){e=e.__v_raw;let i=_(e),o=_(t);t!==o&&!r&&M(i,"get",t),!r&&M(i,"get",o);let{has:s}=Ge(i),a=n?kt:r?It:Dt;if(s.call(i,t))return a(e.get(t));if(s.call(i,o))return a(e.get(o));e!==i&&e.get(t)}function Ve(e,t=!1){let r=this.__v_raw,n=_(r),i=_(e);return e!==i&&!t&&M(n,"has",e),!t&&M(n,"has",i),e===i?r.has(e):r.has(e)||r.has(i)}function qe(e,t=!1){return e=e.__v_raw,!t&&M(_(e),"iterate",Y),Reflect.get(e,"size",e)}function qr(e){e=_(e);let t=_(this);return Ge(t).has.call(t,e)||(t.add(e),H(t,"add",e,e)),this}function Ur(e,t){t=_(t);let r=_(this),{has:n,get:i}=Ge(r),o=n.call(r,e);o?rn(r,n,e):(e=_(e),o=n.call(r,e));let s=i.call(r,e);return r.set(e,t),o?Tt(t,s)&&H(r,"set",e,t,s):H(r,"add",e,t),this}function Wr(e){let t=_(this),{has:r,get:n}=Ge(t),i=r.call(t,e);i?rn(t,r,e):(e=_(e),i=r.call(t,e));let o=n?n.call(t,e):void 0,s=t.delete(e);return i&&H(t,"delete",e,void 0,o),s}function Gr(){let e=_(this),t=e.size!==0,r=ne(e)?new Map(e):new Set(e),n=e.clear();return t&&H(e,"clear",void 0,void 0,r),n}function Ue(e,t){return function(n,i){let o=this,s=o.__v_raw,a=_(s),c=t?kt:e?It:Dt;return!e&&M(a,"iterate",Y),s.forEach((l,u)=>n.call(i,c(l),c(u),o))}}function We(e,t,r){return function(...n){let i=this.__v_raw,o=_(i),s=ne(o),a=e==="entries"||e===Symbol.iterator&&s,c=e==="keys"&&s,l=i[e](...n),u=r?kt:t?It:Dt;return!t&&M(o,"iterate",c?Mt:Y),{next(){let{value:d,done:m}=l.next();return m?{value:d,done:m}:{value:a?[u(d[0]),u(d[1])]:u(d),done:m}},[Symbol.iterator](){return this}}}}function z(e){return function(...t){{let r=t[0]?`on key "${t[0]}" `:"";console.warn(`${Ct(e)} operation ${r}failed: target is readonly.`,_(this))}return e==="delete"?!1:this}}function xi(){let e={get(o){return He(this,o)},get size(){return qe(this)},has:Ve,add:qr,set:Ur,delete:Wr,clear:Gr,forEach:Ue(!1,!1)},t={get(o){return He(this,o,!1,!0)},get size(){return qe(this)},has:Ve,add:qr,set:Ur,delete:Wr,clear:Gr,forEach:Ue(!1,!0)},r={get(o){return He(this,o,!0)},get size(){return qe(this,!0)},has(o){return Ve.call(this,o,!0)},add:z("add"),set:z("set"),delete:z("delete"),clear:z("clear"),forEach:Ue(!0,!1)},n={get(o){return He(this,o,!0,!0)},get size(){return qe(this,!0)},has(o){return Ve.call(this,o,!0)},add:z("add"),set:z("set"),delete:z("delete"),clear:z("clear"),forEach:Ue(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(o=>{e[o]=We(o,!1,!1),r[o]=We(o,!0,!1),t[o]=We(o,!1,!0),n[o]=We(o,!0,!0)}),[e,r,t,n]}var[yi,bi,wi,Ei]=xi();function tn(e,t){let r=t?e?Ei:wi:e?bi:yi;return(n,i,o)=>i==="__v_isReactive"?!e:i==="__v_isReadonly"?e:i==="__v_raw"?n:Reflect.get(ge(r,i)&&i in n?r:n,i,o)}var vi={get:tn(!1,!1)};var Si={get:tn(!0,!1)};function rn(e,t,r){let n=_(r);if(n!==r&&t.call(e,n)){let i=Ot(e);console.warn(`Reactive ${i} contains both the raw and reactive versions of the same object${i==="Map"?" as keys":""}, which can lead to inconsistencies. Avoid differentiating between the raw and reactive versions of an object and only use the reactive version if possible.`)}}var nn=new WeakMap,Ai=new WeakMap,on=new WeakMap,Oi=new WeakMap;function Ci(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Ti(e){return e.__v_skip||!Object.isExtensible(e)?0:Ci(Ot(e))}function Je(e){return e&&e.__v_isReadonly?e:an(e,!1,_i,vi,nn)}function sn(e){return an(e,!0,gi,Si,on)}function an(e,t,r,n,i){if(!xe(e))return console.warn(`value cannot be made reactive: ${String(e)}`),e;if(e.__v_raw&&!(t&&e.__v_isReactive))return e;let o=i.get(e);if(o)return o;let s=Ti(e);if(s===0)return e;let a=new Proxy(e,s===2?n:r);return i.set(e,a),a}function _(e){return e&&_(e.__v_raw)||e}function Nt(e){return Boolean(e&&e.__v_isRef===!0)}y("nextTick",()=>re);y("dispatch",e=>q.bind(q,e));y("watch",(e,{evaluateLater:t,effect:r})=>(n,i)=>{let o=t(n),s=!0,a,c=r(()=>o(l=>{JSON.stringify(l),s?a=l:queueMicrotask(()=>{i(l,a),a=l}),s=!1}));e._x_effects.delete(c)});y("store",kr);y("data",e=>Ce(e));y("root",e=>U(e));y("refs",e=>(e._x_refs_proxy||(e._x_refs_proxy=F(Ri(e))),e._x_refs_proxy));function Ri(e){let t=[],r=e;for(;r;)r._x_refs&&t.push(r._x_refs),r=r.parentNode;return t}var Lt={};function $t(e){return Lt[e]||(Lt[e]=0),++Lt[e]}function cn(e,t){return Z(e,r=>{if(r._x_ids&&r._x_ids[t])return!0})}function ln(e,t){e._x_ids||(e._x_ids={}),e._x_ids[t]||(e._x_ids[t]=$t(t))}y("id",e=>(t,r=null)=>{let n=cn(e,t),i=n?n._x_ids[t]:$t(t);return r?`${t}-${i}-${r}`:`${t}-${i}`});y("el",e=>e);un("Focus","focus","focus");un("Persist","persist","persist");function un(e,t,r){y(t,n=>S(`You can't use [$${directiveName}] without first installing the "${e}" plugin here: https://alpinejs.dev/plugins/${r}`,n))}p("modelable",(e,{expression:t},{effect:r,evaluateLater:n,cleanup:i})=>{let o=n(t),s=()=>{let u;return o(d=>u=d),u},a=n(`${t} = __placeholder`),c=u=>a(()=>{},{scope:{__placeholder:u}}),l=s();c(l),queueMicrotask(()=>{if(!e._x_model)return;e._x_removeModelListeners.default();let u=e._x_model.get,d=e._x_model.set,m=je({get(){return u()},set(w){d(w)}},{get(){return s()},set(w){c(w)}});i(m)})});var Mi=document.createElement("div");p("teleport",(e,{modifiers:t,expression:r},{cleanup:n})=>{e.tagName.toLowerCase()!=="template"&&S("x-teleport can only be used on a <template> tag",e);let i=B(()=>document.querySelector(r),()=>Mi)();i||S(`Cannot find x-teleport element for selector: "${r}"`);let o=e.content.cloneNode(!0).firstElementChild;e._x_teleport=o,o._x_teleportBack=e,e._x_forwardEvents&&e._x_forwardEvents.forEach(s=>{o.addEventListener(s,a=>{a.stopPropagation(),e.dispatchEvent(new a.constructor(a.type,a))})}),N(o,{},e),h(()=>{t.includes("prepend")?i.parentNode.insertBefore(o,i):t.includes("append")?i.parentNode.insertBefore(o,i.nextSibling):i.appendChild(o),v(o),o._x_ignore=!0}),n(()=>o.remove())});var fn=()=>{};fn.inline=(e,{modifiers:t},{cleanup:r})=>{t.includes("self")?e._x_ignoreSelf=!0:e._x_ignore=!0,r(()=>{t.includes("self")?delete e._x_ignoreSelf:delete e._x_ignore})};p("ignore",fn);p("effect",(e,{expression:t},{effect:r})=>r(x(e,t)));function oe(e,t,r,n){let i=e,o=c=>n(c),s={},a=(c,l)=>u=>l(c,u);if(r.includes("dot")&&(t=Ni(t)),r.includes("camel")&&(t=Pi(t)),r.includes("passive")&&(s.passive=!0),r.includes("capture")&&(s.capture=!0),r.includes("window")&&(i=window),r.includes("document")&&(i=document),r.includes("debounce")){let c=r[r.indexOf("debounce")+1]||"invalid-wait",l=Ye(c.split("ms")[0])?Number(c.split("ms")[0]):250;o=$e(o,l)}if(r.includes("throttle")){let c=r[r.indexOf("throttle")+1]||"invalid-wait",l=Ye(c.split("ms")[0])?Number(c.split("ms")[0]):250;o=Fe(o,l)}return r.includes("prevent")&&(o=a(o,(c,l)=>{l.preventDefault(),c(l)})),r.includes("stop")&&(o=a(o,(c,l)=>{l.stopPropagation(),c(l)})),r.includes("self")&&(o=a(o,(c,l)=>{l.target===e&&c(l)})),(r.includes("away")||r.includes("outside"))&&(i=document,o=a(o,(c,l)=>{e.contains(l.target)||l.target.isConnected!==!1&&(e.offsetWidth<1&&e.offsetHeight<1||e._x_isShown!==!1&&c(l))})),r.includes("once")&&(o=a(o,(c,l)=>{c(l),i.removeEventListener(t,o,s)})),o=a(o,(c,l)=>{Ii(t)&&ki(l,r)||c(l)}),i.addEventListener(t,o,s),()=>{i.removeEventListener(t,o,s)}}function Ni(e){return e.replace(/-/g,".")}function Pi(e){return e.toLowerCase().replace(/-(\w)/g,(t,r)=>r.toUpperCase())}function Ye(e){return!Array.isArray(e)&&!isNaN(e)}function Di(e){return[" ","_"].includes(e)?e:e.replace(/([a-z])([A-Z])/g,"$1-$2").replace(/[_\s]/,"-").toLowerCase()}function Ii(e){return["keydown","keyup"].includes(e)}function ki(e,t){let r=t.filter(o=>!["window","document","prevent","stop","once","capture"].includes(o));if(r.includes("debounce")){let o=r.indexOf("debounce");r.splice(o,Ye((r[o+1]||"invalid-wait").split("ms")[0])?2:1)}if(r.includes("throttle")){let o=r.indexOf("throttle");r.splice(o,Ye((r[o+1]||"invalid-wait").split("ms")[0])?2:1)}if(r.length===0||r.length===1&&dn(e.key).includes(r[0]))return!1;let i=["ctrl","shift","alt","meta","cmd","super"].filter(o=>r.includes(o));return r=r.filter(o=>!i.includes(o)),!(i.length>0&&i.filter(s=>((s==="cmd"||s==="super")&&(s="meta"),e[`${s}Key`])).length===i.length&&dn(e.key).includes(r[0]))}function dn(e){if(!e)return[];e=Di(e);let t={ctrl:"control",slash:"/",space:" ",spacebar:" ",cmd:"meta",esc:"escape",up:"arrow-up",down:"arrow-down",left:"arrow-left",right:"arrow-right",period:".",equal:"=",minus:"-",underscore:"_"};return t[e]=e,Object.keys(t).map(r=>{if(t[r]===e)return r}).filter(r=>r)}p("model",(e,{modifiers:t,expression:r},{effect:n,cleanup:i})=>{let o=e;t.includes("parent")&&(o=e.parentNode);let s=x(o,r),a;typeof r=="string"?a=x(o,`${r} = __placeholder`):typeof r=="function"&&typeof r()=="string"?a=x(o,`${r()} = __placeholder`):a=()=>{};let c=()=>{let m;return s(w=>m=w),pn(m)?m.get():m},l=m=>{let w;s(k=>w=k),pn(w)?w.set(m):a(()=>{},{scope:{__placeholder:m}})};typeof r=="string"&&e.type==="radio"&&h(()=>{e.hasAttribute("name")||e.setAttribute("name",r)});var u=e.tagName.toLowerCase()==="select"||["checkbox","radio"].includes(e.type)||t.includes("lazy")?"change":"input";let d=I?()=>{}:oe(e,u,t,m=>{l(Li(e,t,m,c()))});if(t.includes("fill")&&([null,""].includes(c())||e.type==="checkbox"&&Array.isArray(c()))&&e.dispatchEvent(new Event(u,{})),e._x_removeModelListeners||(e._x_removeModelListeners={}),e._x_removeModelListeners.default=d,i(()=>e._x_removeModelListeners.default()),e.form){let m=oe(e.form,"reset",[],w=>{re(()=>e._x_model&&e._x_model.set(e.value))});i(()=>m())}e._x_model={get(){return c()},set(m){l(m)}},e._x_forceModelUpdate=m=>{m===void 0&&typeof r=="string"&&r.match(/\./)&&(m=""),window.fromModel=!0,h(()=>_e(e,"value",m)),delete window.fromModel},n(()=>{let m=c();t.includes("unintrusive")&&document.activeElement.isSameNode(e)||e._x_forceModelUpdate(m)})});function Li(e,t,r,n){return h(()=>{if(r instanceof CustomEvent&&r.detail!==void 0)return r.detail??r.target.value;if(e.type==="checkbox")if(Array.isArray(n)){let i=t.includes("number")?Ft(r.target.value):r.target.value;return r.target.checked?n.concat([i]):n.filter(o=>!$i(o,i))}else return r.target.checked;else{if(e.tagName.toLowerCase()==="select"&&e.multiple)return t.includes("number")?Array.from(r.target.selectedOptions).map(i=>{let o=i.value||i.text;return Ft(o)}):Array.from(r.target.selectedOptions).map(i=>i.value||i.text);{let i=r.target.value;return t.includes("number")?Ft(i):t.includes("trim")?i.trim():i}}})}function Ft(e){let t=e?parseFloat(e):null;return Fi(t)?t:e}function $i(e,t){return e==t}function Fi(e){return!Array.isArray(e)&&!isNaN(e)}function pn(e){return e!==null&&typeof e=="object"&&typeof e.get=="function"&&typeof e.set=="function"}p("cloak",e=>queueMicrotask(()=>h(()=>e.removeAttribute(C("cloak")))));ve(()=>`[${C("init")}]`);p("init",B((e,{expression:t},{evaluate:r})=>typeof t=="string"?!!t.trim()&&r(t,{},!1):r(t,{},!1)));p("text",(e,{expression:t},{effect:r,evaluateLater:n})=>{let i=n(t);r(()=>{i(o=>{h(()=>{e.textContent=o})})})});p("html",(e,{expression:t},{effect:r,evaluateLater:n})=>{let i=n(t);r(()=>{i(o=>{h(()=>{e.innerHTML=o,e._x_ignoreSelf=!0,v(e),delete e._x_ignoreSelf})})})});te(De(":",Ie(C("bind:"))));var mn=(e,{value:t,modifiers:r,expression:n,original:i},{effect:o})=>{if(!t){let a={};Fr(a),x(e,n)(l=>{St(e,l,i)},{scope:a});return}if(t==="key")return ji(e,n);if(e._x_inlineBindings&&e._x_inlineBindings[t]&&e._x_inlineBindings[t].extract)return;let s=x(e,n);o(()=>s(a=>{a===void 0&&typeof n=="string"&&n.match(/\./)&&(a=""),h(()=>_e(e,t,a,r))}))};mn.inline=(e,{value:t,modifiers:r,expression:n})=>{t&&(e._x_inlineBindings||(e._x_inlineBindings={}),e._x_inlineBindings[t]={expression:n,extract:!1})};p("bind",mn);function ji(e,t){e._x_keyExpression=t}Ee(()=>`[${C("data")}]`);p("data",(e,{expression:t},{cleanup:r})=>{if(Ar(e))return;t=t===""?"{}":t;let n={};fe(n,e);let i={};Kr(i,n);let o=R(e,t,{scope:i});(o===void 0||o===!0)&&(o={}),fe(o,e);let s=T(o);Te(s);let a=N(e,s);s.init&&R(e,s.init),r(()=>{s.destroy&&R(e,s.destroy),a()})});p("show",(e,{modifiers:t,expression:r},{effect:n})=>{let i=x(e,r);e._x_doHide||(e._x_doHide=()=>{h(()=>{e.style.setProperty("display","none",t.includes("important")?"important":void 0)})}),e._x_doShow||(e._x_doShow=()=>{h(()=>{e.style.length===1&&e.style.display==="none"?e.removeAttribute("style"):e.style.removeProperty("display")})});let o=()=>{e._x_doHide(),e._x_isShown=!1},s=()=>{e._x_doShow(),e._x_isShown=!0},a=()=>setTimeout(s),c=me(d=>d?s():o(),d=>{typeof e._x_toggleAndCascadeWithTransitions=="function"?e._x_toggleAndCascadeWithTransitions(e,d,s,o):d?a():o()}),l,u=!0;n(()=>i(d=>{!u&&d===l||(t.includes("immediate")&&(d?a():o()),c(d),l=d,u=!1)}))});p("for",(e,{expression:t},{effect:r,cleanup:n})=>{let i=Ki(t),o=x(e,i.items),s=x(e,e._x_keyExpression||"index");e._x_prevKeys=[],e._x_lookup={},r(()=>Bi(e,i,o,s)),n(()=>{Object.values(e._x_lookup).forEach(a=>a.remove()),delete e._x_prevKeys,delete e._x_lookup})});function Bi(e,t,r,n){let i=s=>typeof s=="object"&&!Array.isArray(s),o=e;r(s=>{zi(s)&&s>=0&&(s=Array.from(Array(s).keys(),f=>f+1)),s===void 0&&(s=[]);let a=e._x_lookup,c=e._x_prevKeys,l=[],u=[];if(i(s))s=Object.entries(s).map(([f,g])=>{let b=hn(t,g,f,s);n(E=>u.push(E),{scope:{index:f,...b}}),l.push(b)});else for(let f=0;f<s.length;f++){let g=hn(t,s[f],f,s);n(b=>u.push(b),{scope:{index:f,...g}}),l.push(g)}let d=[],m=[],w=[],k=[];for(let f=0;f<c.length;f++){let g=c[f];u.indexOf(g)===-1&&w.push(g)}c=c.filter(f=>!w.includes(f));let be="template";for(let f=0;f<u.length;f++){let g=u[f],b=c.indexOf(g);if(b===-1)c.splice(f,0,g),d.push([be,f]);else if(b!==f){let E=c.splice(f,1)[0],A=c.splice(b-1,1)[0];c.splice(f,0,A),c.splice(b,0,E),m.push([E,A])}else k.push(g);be=g}for(let f=0;f<w.length;f++){let g=w[f];a[g]._x_effects&&a[g]._x_effects.forEach(we),a[g].remove(),a[g]=null,delete a[g]}for(let f=0;f<m.length;f++){let[g,b]=m[f],E=a[g],A=a[b],X=document.createElement("div");h(()=>{A||S('x-for ":key" is undefined or invalid',o),A.after(X),E.after(A),A._x_currentIfEl&&A.after(A._x_currentIfEl),X.before(E),E._x_currentIfEl&&E.after(E._x_currentIfEl),X.remove()}),A._x_refreshXForScope(l[u.indexOf(b)])}for(let f=0;f<d.length;f++){let[g,b]=d[f],E=g==="template"?o:a[g];E._x_currentIfEl&&(E=E._x_currentIfEl);let A=l[b],X=u[b],se=document.importNode(o.content,!0).firstElementChild,Bt=T(A);N(se,Bt,o),se._x_refreshXForScope=gn=>{Object.entries(gn).forEach(([xn,yn])=>{Bt[xn]=yn})},h(()=>{E.after(se),v(se)}),typeof X=="object"&&S("x-for key cannot be an object, it must be a string or an integer",o),a[X]=se}for(let f=0;f<k.length;f++)a[k[f]]._x_refreshXForScope(l[u.indexOf(k[f])]);o._x_prevKeys=u})}function Ki(e){let t=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,r=/^\s*\(|\)\s*$/g,n=/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/,i=e.match(n);if(!i)return;let o={};o.items=i[2].trim();let s=i[1].replace(r,"").trim(),a=s.match(t);return a?(o.item=s.replace(t,"").trim(),o.index=a[1].trim(),a[2]&&(o.collection=a[2].trim())):o.item=s,o}function hn(e,t,r,n){let i={};return/^\[.*\]$/.test(e.item)&&Array.isArray(t)?e.item.replace("[","").replace("]","").split(",").map(s=>s.trim()).forEach((s,a)=>{i[s]=t[a]}):/^\{.*\}$/.test(e.item)&&!Array.isArray(t)&&typeof t=="object"?e.item.replace("{","").replace("}","").split(",").map(s=>s.trim()).forEach(s=>{i[s]=t[s]}):i[e.item]=t,e.index&&(i[e.index]=r),e.collection&&(i[e.collection]=n),i}function zi(e){return!Array.isArray(e)&&!isNaN(e)}function _n(){}_n.inline=(e,{expression:t},{cleanup:r})=>{let n=U(e);n._x_refs||(n._x_refs={}),n._x_refs[t]=e,r(()=>delete n._x_refs[t])};p("ref",_n);p("if",(e,{expression:t},{effect:r,cleanup:n})=>{let i=x(e,t),o=()=>{if(e._x_currentIfEl)return e._x_currentIfEl;let a=e.content.cloneNode(!0).firstElementChild;return N(a,{},e),h(()=>{e.after(a),v(a)}),e._x_currentIfEl=a,e._x_undoIf=()=>{O(a,c=>{c._x_effects&&c._x_effects.forEach(we)}),a.remove(),delete e._x_currentIfEl},a},s=()=>{e._x_undoIf&&(e._x_undoIf(),delete e._x_undoIf)};r(()=>i(a=>{a?o():s()})),n(()=>e._x_undoIf&&e._x_undoIf())});p("id",(e,{expression:t},{evaluate:r})=>{r(t).forEach(i=>ln(e,i))});te(De("@",Ie(C("on:"))));p("on",B((e,{value:t,modifiers:r,expression:n},{cleanup:i})=>{let o=n?x(e,n):()=>{};e.tagName.toLowerCase()==="template"&&(e._x_forwardEvents||(e._x_forwardEvents=[]),e._x_forwardEvents.includes(t)||e._x_forwardEvents.push(t));let s=oe(e,t,r,a=>{o(()=>{},{scope:{$event:a},params:[a]})});i(()=>s())}));Xe("Collapse","collapse","collapse");Xe("Intersect","intersect","intersect");Xe("Focus","trap","focus");Xe("Mask","mask","mask");function Xe(e,t,r){p(t,n=>S(`You can't use [x-${t}] without first installing the "${e}" plugin here: https://alpinejs.dev/plugins/${r}`,n))}j.setEvaluator(ht);j.setReactivityEngine({reactive:Je,effect:Jr,release:Yr,raw:_});var jt=j;window.Alpine=jt;queueMicrotask(()=>{jt.start()});})();
D internal/ui/public/libs/chart.js +0 -20 too large to show in the browser

too large to show in the browser

D internal/ui/public/libs/htmx-sse.js +0 -322
@@ -1,322 +0,0 @@
1-/*
2-Server Sent Events Extension
3-============================
4-This extension adds support for Server Sent Events to htmx. See /www/extensions/sse.md for usage instructions.
5-
6-*/
7-
8-(function(){
9-
10- /** @type {import("../htmx").HtmxInternalApi} */
11- var api;
12-
13- htmx.defineExtension("sse", {
14-
15- /**
16- * Init saves the provided reference to the internal HTMX API.
17- *
18- * @param {import("../htmx").HtmxInternalApi} api
19- * @returns void
20- */
21- init: function(apiRef) {
22- // store a reference to the internal API.
23- api = apiRef;
24-
25- // set a function in the public API for creating new EventSource objects
26- if (htmx.createEventSource == undefined) {
27- htmx.createEventSource = createEventSource;
28- }
29- },
30-
31- /**
32- * onEvent handles all events passed to this extension.
33- *
34- * @param {string} name
35- * @param {Event} evt
36- * @returns void
37- */
38- onEvent: function(name, evt) {
39-
40- switch (name) {
41-
42- // Try to remove remove an EventSource when elements are removed
43- case "htmx:beforeCleanupElement":
44- var internalData = api.getInternalData(evt.target)
45- if (internalData.sseEventSource) {
46- internalData.sseEventSource.close();
47- }
48- return;
49-
50- // Try to create EventSources when elements are processed
51- case "htmx:afterProcessNode":
52- createEventSourceOnElement(evt.target);
53- }
54- }
55- });
56-
57- ///////////////////////////////////////////////
58- // HELPER FUNCTIONS
59- ///////////////////////////////////////////////
60-
61-
62- /**
63- * createEventSource is the default method for creating new EventSource objects.
64- * it is hoisted into htmx.config.createEventSource to be overridden by the user, if needed.
65- *
66- * @param {string} url
67- * @returns EventSource
68- */
69- function createEventSource(url) {
70- return new EventSource(url, {withCredentials:true});
71- }
72-
73- function splitOnWhitespace(trigger) {
74- return trigger.trim().split(/\s+/);
75- }
76-
77- function getLegacySSEURL(elt) {
78- var legacySSEValue = api.getAttributeValue(elt, "hx-sse");
79- if (legacySSEValue) {
80- var values = splitOnWhitespace(legacySSEValue);
81- for (var i = 0; i < values.length; i++) {
82- var value = values[i].split(/:(.+)/);
83- if (value[0] === "connect") {
84- return value[1];
85- }
86- }
87- }
88- }
89-
90- function getLegacySSESwaps(elt) {
91- var legacySSEValue = api.getAttributeValue(elt, "hx-sse");
92- var returnArr = [];
93- if (legacySSEValue) {
94- var values = splitOnWhitespace(legacySSEValue);
95- for (var i = 0; i < values.length; i++) {
96- var value = values[i].split(/:(.+)/);
97- if (value[0] === "swap") {
98- returnArr.push(value[1]);
99- }
100- }
101- }
102- return returnArr;
103- }
104-
105- /**
106- * createEventSourceOnElement creates a new EventSource connection on the provided element.
107- * If a usable EventSource already exists, then it is returned. If not, then a new EventSource
108- * is created and stored in the element's internalData.
109- * @param {HTMLElement} elt
110- * @param {number} retryCount
111- * @returns {EventSource | null}
112- */
113- function createEventSourceOnElement(elt, retryCount) {
114-
115- if (elt == null) {
116- return null;
117- }
118-
119- var internalData = api.getInternalData(elt);
120-
121- // get URL from element's attribute
122- var sseURL = api.getAttributeValue(elt, "sse-connect");
123-
124-
125- if (sseURL == undefined) {
126- var legacyURL = getLegacySSEURL(elt)
127- if (legacyURL) {
128- sseURL = legacyURL;
129- } else {
130- return null;
131- }
132- }
133-
134- // Connect to the EventSource
135- var source = htmx.createEventSource(sseURL);
136- internalData.sseEventSource = source;
137-
138- // Create event handlers
139- source.onerror = function (err) {
140-
141- // Log an error event
142- api.triggerErrorEvent(elt, "htmx:sseError", {error:err, source:source});
143-
144- // If parent no longer exists in the document, then clean up this EventSource
145- if (maybeCloseSSESource(elt)) {
146- return;
147- }
148-
149- // Otherwise, try to reconnect the EventSource
150- if (source.readyState === EventSource.CLOSED) {
151- retryCount = retryCount || 0;
152- var timeout = Math.random() * (2 ^ retryCount) * 500;
153- window.setTimeout(function() {
154- createEventSourceOnElement(elt, Math.min(7, retryCount+1));
155- }, timeout);
156- }
157- };
158-
159- source.onopen = function (evt) {
160- api.triggerEvent(elt, "htmx:sseOpen", {source: source});
161- }
162-
163- // Add message handlers for every `sse-swap` attribute
164- queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function(child) {
165-
166- var sseSwapAttr = api.getAttributeValue(child, "sse-swap");
167- if (sseSwapAttr) {
168- var sseEventNames = sseSwapAttr.split(",");
169- } else {
170- var sseEventNames = getLegacySSESwaps(child);
171- }
172-
173- for (var i = 0 ; i < sseEventNames.length ; i++) {
174- var sseEventName = sseEventNames[i].trim();
175- var listener = function(event) {
176-
177- // If the parent is missing then close SSE and remove listener
178- if (maybeCloseSSESource(elt)) {
179- source.removeEventListener(sseEventName, listener);
180- return;
181- }
182-
183- // swap the response into the DOM and trigger a notification
184- swap(child, event.data);
185- api.triggerEvent(elt, "htmx:sseMessage", event);
186- };
187-
188- // Register the new listener
189- api.getInternalData(elt).sseEventListener = listener;
190- source.addEventListener(sseEventName, listener);
191- }
192- });
193-
194- // Add message handlers for every `hx-trigger="sse:*"` attribute
195- queryAttributeOnThisOrChildren(elt, "hx-trigger").forEach(function(child) {
196-
197- var sseEventName = api.getAttributeValue(child, "hx-trigger");
198- if (sseEventName == null) {
199- return;
200- }
201-
202- // Only process hx-triggers for events with the "sse:" prefix
203- if (sseEventName.slice(0, 4) != "sse:") {
204- return;
205- }
206-
207- var listener = function(event) {
208-
209- // If parent is missing, then close SSE and remove listener
210- if (maybeCloseSSESource(elt)) {
211- source.removeEventListener(sseEventName, listener);
212- return;
213- }
214-
215- // Trigger events to be handled by the rest of htmx
216- htmx.trigger(child, sseEventName, event);
217- htmx.trigger(child, "htmx:sseMessage", event);
218- }
219-
220- // Register the new listener
221- api.getInternalData(elt).sseEventListener = listener;
222- source.addEventListener(sseEventName.slice(4), listener);
223- });
224- }
225-
226- /**
227- * maybeCloseSSESource confirms that the parent element still exists.
228- * If not, then any associated SSE source is closed and the function returns true.
229- *
230- * @param {HTMLElement} elt
231- * @returns boolean
232- */
233- function maybeCloseSSESource(elt) {
234- if (!api.bodyContains(elt)) {
235- var source = api.getInternalData(elt).sseEventSource;
236- if (source != undefined) {
237- source.close();
238- // source = null
239- return true;
240- }
241- }
242- return false;
243- }
244-
245- /**
246- * queryAttributeOnThisOrChildren returns all nodes that contain the requested attributeName, INCLUDING THE PROVIDED ROOT ELEMENT.
247- *
248- * @param {HTMLElement} elt
249- * @param {string} attributeName
250- */
251- function queryAttributeOnThisOrChildren(elt, attributeName) {
252-
253- var result = [];
254-
255- // If the parent element also contains the requested attribute, then add it to the results too.
256- if (api.hasAttribute(elt, attributeName) || api.hasAttribute(elt, "hx-sse")) {
257- result.push(elt);
258- }
259-
260- // Search all child nodes that match the requested attribute
261- elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "], [hx-sse], [data-hx-sse]").forEach(function(node) {
262- result.push(node);
263- });
264-
265- return result;
266- }
267-
268- /**
269- * @param {HTMLElement} elt
270- * @param {string} content
271- */
272- function swap(elt, content) {
273-
274- api.withExtensions(elt, function(extension) {
275- content = extension.transformResponse(content, null, elt);
276- });
277-
278- var swapSpec = api.getSwapSpecification(elt);
279- var target = api.getTarget(elt);
280- var settleInfo = api.makeSettleInfo(elt);
281-
282- api.selectAndSwap(swapSpec.swapStyle, target, elt, content, settleInfo);
283-
284- settleInfo.elts.forEach(function (elt) {
285- if (elt.classList) {
286- elt.classList.add(htmx.config.settlingClass);
287- }
288- api.triggerEvent(elt, 'htmx:beforeSettle');
289- });
290-
291- // Handle settle tasks (with delay if requested)
292- if (swapSpec.settleDelay > 0) {
293- setTimeout(doSettle(settleInfo), swapSpec.settleDelay);
294- } else {
295- doSettle(settleInfo)();
296- }
297- }
298-
299- /**
300- * doSettle mirrors much of the functionality in htmx that
301- * settles elements after their content has been swapped.
302- * TODO: this should be published by htmx, and not duplicated here
303- * @param {import("../htmx").HtmxSettleInfo} settleInfo
304- * @returns () => void
305- */
306- function doSettle(settleInfo) {
307-
308- return function() {
309- settleInfo.tasks.forEach(function (task) {
310- task.call();
311- });
312-
313- settleInfo.elts.forEach(function (elt) {
314- if (elt.classList) {
315- elt.classList.remove(htmx.config.settlingClass);
316- }
317- api.triggerEvent(elt, 'htmx:afterSettle');
318- });
319- }
320- }
321-
322-})();⏎̸
@@ -1,322 +0,0 @@
1-/*
2-Server Sent Events Extension
3-============================
4-This extension adds support for Server Sent Events to htmx. See /www/extensions/sse.md for usage instructions.
5-
6-*/
7-
8-(function(){
9-
10- /** @type {import("../htmx").HtmxInternalApi} */
11- var api;
12-
13- htmx.defineExtension("sse", {
14-
15- /**
16- * Init saves the provided reference to the internal HTMX API.
17- *
18- * @param {import("../htmx").HtmxInternalApi} api
19- * @returns void
20- */
21- init: function(apiRef) {
22- // store a reference to the internal API.
23- api = apiRef;
24-
25- // set a function in the public API for creating new EventSource objects
26- if (htmx.createEventSource == undefined) {
27- htmx.createEventSource = createEventSource;
28- }
29- },
30-
31- /**
32- * onEvent handles all events passed to this extension.
33- *
34- * @param {string} name
35- * @param {Event} evt
36- * @returns void
37- */
38- onEvent: function(name, evt) {
39-
40- switch (name) {
41-
42- // Try to remove remove an EventSource when elements are removed
43- case "htmx:beforeCleanupElement":
44- var internalData = api.getInternalData(evt.target)
45- if (internalData.sseEventSource) {
46- internalData.sseEventSource.close();
47- }
48- return;
49-
50- // Try to create EventSources when elements are processed
51- case "htmx:afterProcessNode":
52- createEventSourceOnElement(evt.target);
53- }
54- }
55- });
56-
57- ///////////////////////////////////////////////
58- // HELPER FUNCTIONS
59- ///////////////////////////////////////////////
60-
61-
62- /**
63- * createEventSource is the default method for creating new EventSource objects.
64- * it is hoisted into htmx.config.createEventSource to be overridden by the user, if needed.
65- *
66- * @param {string} url
67- * @returns EventSource
68- */
69- function createEventSource(url) {
70- return new EventSource(url, {withCredentials:true});
71- }
72-
73- function splitOnWhitespace(trigger) {
74- return trigger.trim().split(/\s+/);
75- }
76-
77- function getLegacySSEURL(elt) {
78- var legacySSEValue = api.getAttributeValue(elt, "hx-sse");
79- if (legacySSEValue) {
80- var values = splitOnWhitespace(legacySSEValue);
81- for (var i = 0; i < values.length; i++) {
82- var value = values[i].split(/:(.+)/);
83- if (value[0] === "connect") {
84- return value[1];
85- }
86- }
87- }
88- }
89-
90- function getLegacySSESwaps(elt) {
91- var legacySSEValue = api.getAttributeValue(elt, "hx-sse");
92- var returnArr = [];
93- if (legacySSEValue) {
94- var values = splitOnWhitespace(legacySSEValue);
95- for (var i = 0; i < values.length; i++) {
96- var value = values[i].split(/:(.+)/);
97- if (value[0] === "swap") {
98- returnArr.push(value[1]);
99- }
100- }
101- }
102- return returnArr;
103- }
104-
105- /**
106- * createEventSourceOnElement creates a new EventSource connection on the provided element.
107- * If a usable EventSource already exists, then it is returned. If not, then a new EventSource
108- * is created and stored in the element's internalData.
109- * @param {HTMLElement} elt
110- * @param {number} retryCount
111- * @returns {EventSource | null}
112- */
113- function createEventSourceOnElement(elt, retryCount) {
114-
115- if (elt == null) {
116- return null;
117- }
118-
119- var internalData = api.getInternalData(elt);
120-
121- // get URL from element's attribute
122- var sseURL = api.getAttributeValue(elt, "sse-connect");
123-
124-
125- if (sseURL == undefined) {
126- var legacyURL = getLegacySSEURL(elt)
127- if (legacyURL) {
128- sseURL = legacyURL;
129- } else {
130- return null;
131- }
132- }
133-
134- // Connect to the EventSource
135- var source = htmx.createEventSource(sseURL);
136- internalData.sseEventSource = source;
137-
138- // Create event handlers
139- source.onerror = function (err) {
140-
141- // Log an error event
142- api.triggerErrorEvent(elt, "htmx:sseError", {error:err, source:source});
143-
144- // If parent no longer exists in the document, then clean up this EventSource
145- if (maybeCloseSSESource(elt)) {
146- return;
147- }
148-
149- // Otherwise, try to reconnect the EventSource
150- if (source.readyState === EventSource.CLOSED) {
151- retryCount = retryCount || 0;
152- var timeout = Math.random() * (2 ^ retryCount) * 500;
153- window.setTimeout(function() {
154- createEventSourceOnElement(elt, Math.min(7, retryCount+1));
155- }, timeout);
156- }
157- };
158-
159- source.onopen = function (evt) {
160- api.triggerEvent(elt, "htmx:sseOpen", {source: source});
161- }
162-
163- // Add message handlers for every `sse-swap` attribute
164- queryAttributeOnThisOrChildren(elt, "sse-swap").forEach(function(child) {
165-
166- var sseSwapAttr = api.getAttributeValue(child, "sse-swap");
167- if (sseSwapAttr) {
168- var sseEventNames = sseSwapAttr.split(",");
169- } else {
170- var sseEventNames = getLegacySSESwaps(child);
171- }
172-
173- for (var i = 0 ; i < sseEventNames.length ; i++) {
174- var sseEventName = sseEventNames[i].trim();
175- var listener = function(event) {
176-
177- // If the parent is missing then close SSE and remove listener
178- if (maybeCloseSSESource(elt)) {
179- source.removeEventListener(sseEventName, listener);
180- return;
181- }
182-
183- // swap the response into the DOM and trigger a notification
184- swap(child, event.data);
185- api.triggerEvent(elt, "htmx:sseMessage", event);
186- };
187-
188- // Register the new listener
189- api.getInternalData(elt).sseEventListener = listener;
190- source.addEventListener(sseEventName, listener);
191- }
192- });
193-
194- // Add message handlers for every `hx-trigger="sse:*"` attribute
195- queryAttributeOnThisOrChildren(elt, "hx-trigger").forEach(function(child) {
196-
197- var sseEventName = api.getAttributeValue(child, "hx-trigger");
198- if (sseEventName == null) {
199- return;
200- }
201-
202- // Only process hx-triggers for events with the "sse:" prefix
203- if (sseEventName.slice(0, 4) != "sse:") {
204- return;
205- }
206-
207- var listener = function(event) {
208-
209- // If parent is missing, then close SSE and remove listener
210- if (maybeCloseSSESource(elt)) {
211- source.removeEventListener(sseEventName, listener);
212- return;
213- }
214-
215- // Trigger events to be handled by the rest of htmx
216- htmx.trigger(child, sseEventName, event);
217- htmx.trigger(child, "htmx:sseMessage", event);
218- }
219-
220- // Register the new listener
221- api.getInternalData(elt).sseEventListener = listener;
222- source.addEventListener(sseEventName.slice(4), listener);
223- });
224- }
225-
226- /**
227- * maybeCloseSSESource confirms that the parent element still exists.
228- * If not, then any associated SSE source is closed and the function returns true.
229- *
230- * @param {HTMLElement} elt
231- * @returns boolean
232- */
233- function maybeCloseSSESource(elt) {
234- if (!api.bodyContains(elt)) {
235- var source = api.getInternalData(elt).sseEventSource;
236- if (source != undefined) {
237- source.close();
238- // source = null
239- return true;
240- }
241- }
242- return false;
243- }
244-
245- /**
246- * queryAttributeOnThisOrChildren returns all nodes that contain the requested attributeName, INCLUDING THE PROVIDED ROOT ELEMENT.
247- *
248- * @param {HTMLElement} elt
249- * @param {string} attributeName
250- */
251- function queryAttributeOnThisOrChildren(elt, attributeName) {
252-
253- var result = [];
254-
255- // If the parent element also contains the requested attribute, then add it to the results too.
256- if (api.hasAttribute(elt, attributeName) || api.hasAttribute(elt, "hx-sse")) {
257- result.push(elt);
258- }
259-
260- // Search all child nodes that match the requested attribute
261- elt.querySelectorAll("[" + attributeName + "], [data-" + attributeName + "], [hx-sse], [data-hx-sse]").forEach(function(node) {
262- result.push(node);
263- });
264-
265- return result;
266- }
267-
268- /**
269- * @param {HTMLElement} elt
270- * @param {string} content
271- */
272- function swap(elt, content) {
273-
274- api.withExtensions(elt, function(extension) {
275- content = extension.transformResponse(content, null, elt);
276- });
277-
278- var swapSpec = api.getSwapSpecification(elt);
279- var target = api.getTarget(elt);
280- var settleInfo = api.makeSettleInfo(elt);
281-
282- api.selectAndSwap(swapSpec.swapStyle, target, elt, content, settleInfo);
283-
284- settleInfo.elts.forEach(function (elt) {
285- if (elt.classList) {
286- elt.classList.add(htmx.config.settlingClass);
287- }
288- api.triggerEvent(elt, 'htmx:beforeSettle');
289- });
290-
291- // Handle settle tasks (with delay if requested)
292- if (swapSpec.settleDelay > 0) {
293- setTimeout(doSettle(settleInfo), swapSpec.settleDelay);
294- } else {
295- doSettle(settleInfo)();
296- }
297- }
298-
299- /**
300- * doSettle mirrors much of the functionality in htmx that
301- * settles elements after their content has been swapped.
302- * TODO: this should be published by htmx, and not duplicated here
303- * @param {import("../htmx").HtmxSettleInfo} settleInfo
304- * @returns () => void
305- */
306- function doSettle(settleInfo) {
307-
308- return function() {
309- settleInfo.tasks.forEach(function (task) {
310- task.call();
311- });
312-
313- settleInfo.elts.forEach(function (elt) {
314- if (elt.classList) {
315- elt.classList.remove(htmx.config.settlingClass);
316- }
317- api.triggerEvent(elt, 'htmx:afterSettle');
318- });
319- }
320- }
321-
322-})();⏎̸
D internal/ui/public/libs/htmx.min.js +0 -1 generated file: collapsed
D internal/ui/public/libs/iconoir.css +0 -22 too large to show in the browser

too large to show in the browser

D internal/ui/public/site.webmanifest +0 -1
@@ -1,1 +0,0 @@
1-{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}⏎̸
@@ -1,1 +0,0 @@
1-{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}⏎̸
D internal/ui/templates/components.gohtml +0 -33
@@ -1,33 +0,0 @@
1-<!-- @format -->
2-
3-<div id="header">
4- <h1
5- class="text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s"
6- >
7- {{ .body.header.helloworld }}
8- </h1>
9-</div>
10-
11-<div id="hello">
12- <h1
13- class="text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s"
14- >
15- {{ .body.div.hello }}
16- </h1>
17-</div>
18-<div
19- id="header"
20- hx-get="/htmx"
21- hx-headers='{"HX-Fragment": "header"}'
22- hx-trigger="load"
23- hx-swap="outerHTML"
24-></div>
25-
26-<!-- App Fragment -->
27-<div
28- id="app"
29- hx-get="/htmx"
30- hx-headers='{"HX-Fragment": "hello"}'
31- hx-trigger="load"
32- hx-swap="outerHTML"
33-></div>
@@ -1,33 +0,0 @@
1-<!-- @format -->
2-
3-<div id="header">
4- <h1
5- class="text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s"
6- >
7- {{ .body.header.helloworld }}
8- </h1>
9-</div>
10-
11-<div id="hello">
12- <h1
13- class="text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s"
14- >
15- {{ .body.div.hello }}
16- </h1>
17-</div>
18-<div
19- id="header"
20- hx-get="/htmx"
21- hx-headers='{"HX-Fragment": "header"}'
22- hx-trigger="load"
23- hx-swap="outerHTML"
24-></div>
25-
26-<!-- App Fragment -->
27-<div
28- id="app"
29- hx-get="/htmx"
30- hx-headers='{"HX-Fragment": "hello"}'
31- hx-trigger="load"
32- hx-swap="outerHTML"
33-></div>
D internal/ui/templates/dashboard.gohtml +0 -9
@@ -1,9 +0,0 @@
1-<div id="dashboard">
2- <h1
3- class="text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s"
4- >
5- Hello World from dashboard.html
6- </h1>
7- <!-- We load Chart.js here to avoid loading it in every page end when all the rest is loaded -->
8- <script src="libs/chart.js"></script>
9-</div>
@@ -1,9 +0,0 @@
1-<div id="dashboard">
2- <h1
3- class="text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s"
4- >
5- Hello World from dashboard.html
6- </h1>
7- <!-- We load Chart.js here to avoid loading it in every page end when all the rest is loaded -->
8- <script src="libs/chart.js"></script>
9-</div>
D internal/ui/templates/install.gohtml +0 -137
@@ -1,137 +0,0 @@
1-<!DOCTYPE html>
2-<html lang="en">
3- <head>
4- <meta charset="UTF-8" />
5- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6- <title>{{ .header.meta.title }}</title>
7- <meta name="description" content="{{ .Header.Meta.Description }}" />
8- <meta name="keywords" content="{{ .Header.Meta.Keywords }}" />
9- <!-- Custom CSS + Tailwind -->
10- <link rel="stylesheet" href="/assets/css/tailwind.css" />
11- <!-- Iconoir -->
12- <link rel="stylesheet" href="/libs/iconoir.css" />
13- <!-- Alpine -->
14- <script src="/libs/alpine.min.js"></script>
15- <!-- HTMX min js -->
16- <script src="/libs/htmx.min.js"></script>
17- <!-- HTMX SSE -->
18- <script src="/libs/htmx-sse.js"></script>
19- <!-- Custom JS from assets/js -->
20- <script src="/assets/js/custom.js"></script>
21- </head>
22-
23- <body
24- class="IBMPlexSans bg-zinc-200 md:p-8 flex flex-col items-center"
25- hx-boost="true"
26- >
27- <!-- Logo and Title -->
28- <div
29- id="logo-mascot"
30- class="flex flex-col justify-center items-center p-4 mb-4"
31- >
32- <div class="circle-shadow flex justify-center w-100">
33- <img
34- src="/assets/imgs/gordon-mascot-hq-trsp.png"
35- alt="logo"
36- class="sm:h-24 h-32 xl:h-48"
37- />
38- </div>
39- <div class="flex justify-center mt-4">
40- <h1
41- class="gordon-title text-2xl lg:text-4xl xl:text-5xl text-gordonmascot-hblue tracking-tighter"
42- >
43- Gordon
44- </h1>
45- <span class="gordon-v-subtitle text-gordonmascot-mblue text-sm ml-2"
46- >v0.0.1</span
47- >
48- </div>
49- </div>
50-
51- <!-- Main Content -->
52- <div class="bg-white p-4 rounded-md shadow mb-4 w-2/3">
53- <!-- Install Traefik -->
54- <div id="install-traefik">
55- <div class="flex flex-col items-center">
56- <h2 class="text-2xl font-semibold text-gray-800">
57- 1 - Install Traefik
58- </h2>
59- <!-- Dual view left : install procedure / right : SSE log installation -->
60- <div class="flex flex-col md:flex-row justify-items-center w-full">
61- <!-- Left -->
62- <div class="w-full md:w-1/2 p-4">
63- <form
64- id="install-traefik-form"
65- action="/admin/install/traefik"
66- method="POST"
67- hx-post="/admin/install/traefik"
68- hx-swap="outerHTML"
69- >
70- <h2 class="">1.1 - Set your top domain name</h2>
71- <input
72- type="text"
73- name="topdomain"
74- id="topdomain"
75- class="w-full py-2 px-4 my-2 border border-gray-300 rounded-md"
76- placeholder="example.com"
77- />
78- <h2>1.2 - Set your admin email (a reachable one)</h2>
79- <input
80- type="text"
81- name="adminemail"
82- id="adminemail"
83- class="w-full p-2 border border-gray-300 rounded-md"
84- placeholder="admin@example.com"
85- />
86- <button
87- type="submit"
88- class="w-full p-2 bg-gordonmascot-hblue text-slate-100 rounded-md mt-4"
89- >
90- Save & Install Traefik
91- </button>
92- </form>
93- </div>
94- <!-- Right -->
95- <div class="w-full md:w-1/2 p-4 text-slate-100">
96- <div
97- class="flex flex-col items-center bg-gordonmascot-hblue rounded-md"
98- >
99- <div class="w-full h-64 overflow-y-scroll-auto p-4">
100- <div
101- class="flex flex-col space-y-2"
102- hx-ext="sse"
103- sse-connect="/admin/install/traefik/sse"
104- >
105- <!-- One item -->
106- <div class="flex flex-row items-center">
107- <span class="">[INFO]</span>
108- <span class="">Traefik installed</span>
109- </div>
110- <!-- One item -->
111- <div class="flex flex-row items-center">
112- <span class="">[INFO]</span>
113- <span class="ml-2">Traefik started</span>
114- </div>
115- <!-- One item -->
116- <div class="flex flex-row items-center">
117- <span class="">[INFO]</span>
118- <span class="ml-2"
119- >Traefik configuration file created</span
120- >
121- </div>
122- <!-- One item -->
123- <div class="flex flex-row items-center">
124- <span class="">[INFO]</span>
125- <span class="">Traefik configuration file updated</span>
126- </div>
127- </div>
128- </div>
129- </div>
130- </div>
131- </div>
132- </div>
133- </div>
134- <!-- End Install Traefik -->
135- </div>
136- </body>
137-</html>
@@ -1,137 +0,0 @@
1-<!DOCTYPE html>
2-<html lang="en">
3- <head>
4- <meta charset="UTF-8" />
5- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6- <title>{{ .header.meta.title }}</title>
7- <meta name="description" content="{{ .Header.Meta.Description }}" />
8- <meta name="keywords" content="{{ .Header.Meta.Keywords }}" />
9- <!-- Custom CSS + Tailwind -->
10- <link rel="stylesheet" href="/assets/css/tailwind.css" />
11- <!-- Iconoir -->
12- <link rel="stylesheet" href="/libs/iconoir.css" />
13- <!-- Alpine -->
14- <script src="/libs/alpine.min.js"></script>
15- <!-- HTMX min js -->
16- <script src="/libs/htmx.min.js"></script>
17- <!-- HTMX SSE -->
18- <script src="/libs/htmx-sse.js"></script>
19- <!-- Custom JS from assets/js -->
20- <script src="/assets/js/custom.js"></script>
21- </head>
22-
23- <body
24- class="IBMPlexSans bg-zinc-200 md:p-8 flex flex-col items-center"
25- hx-boost="true"
26- >
27- <!-- Logo and Title -->
28- <div
29- id="logo-mascot"
30- class="flex flex-col justify-center items-center p-4 mb-4"
31- >
32- <div class="circle-shadow flex justify-center w-100">
33- <img
34- src="/assets/imgs/gordon-mascot-hq-trsp.png"
35- alt="logo"
36- class="sm:h-24 h-32 xl:h-48"
37- />
38- </div>
39- <div class="flex justify-center mt-4">
40- <h1
41- class="gordon-title text-2xl lg:text-4xl xl:text-5xl text-gordonmascot-hblue tracking-tighter"
42- >
43- Gordon
44- </h1>
45- <span class="gordon-v-subtitle text-gordonmascot-mblue text-sm ml-2"
46- >v0.0.1</span
47- >
48- </div>
49- </div>
50-
51- <!-- Main Content -->
52- <div class="bg-white p-4 rounded-md shadow mb-4 w-2/3">
53- <!-- Install Traefik -->
54- <div id="install-traefik">
55- <div class="flex flex-col items-center">
56- <h2 class="text-2xl font-semibold text-gray-800">
57- 1 - Install Traefik
58- </h2>
59- <!-- Dual view left : install procedure / right : SSE log installation -->
60- <div class="flex flex-col md:flex-row justify-items-center w-full">
61- <!-- Left -->
62- <div class="w-full md:w-1/2 p-4">
63- <form
64- id="install-traefik-form"
65- action="/admin/install/traefik"
66- method="POST"
67- hx-post="/admin/install/traefik"
68- hx-swap="outerHTML"
69- >
70- <h2 class="">1.1 - Set your top domain name</h2>
71- <input
72- type="text"
73- name="topdomain"
74- id="topdomain"
75- class="w-full py-2 px-4 my-2 border border-gray-300 rounded-md"
76- placeholder="example.com"
77- />
78- <h2>1.2 - Set your admin email (a reachable one)</h2>
79- <input
80- type="text"
81- name="adminemail"
82- id="adminemail"
83- class="w-full p-2 border border-gray-300 rounded-md"
84- placeholder="admin@example.com"
85- />
86- <button
87- type="submit"
88- class="w-full p-2 bg-gordonmascot-hblue text-slate-100 rounded-md mt-4"
89- >
90- Save & Install Traefik
91- </button>
92- </form>
93- </div>
94- <!-- Right -->
95- <div class="w-full md:w-1/2 p-4 text-slate-100">
96- <div
97- class="flex flex-col items-center bg-gordonmascot-hblue rounded-md"
98- >
99- <div class="w-full h-64 overflow-y-scroll-auto p-4">
100- <div
101- class="flex flex-col space-y-2"
102- hx-ext="sse"
103- sse-connect="/admin/install/traefik/sse"
104- >
105- <!-- One item -->
106- <div class="flex flex-row items-center">
107- <span class="">[INFO]</span>
108- <span class="">Traefik installed</span>
109- </div>
110- <!-- One item -->
111- <div class="flex flex-row items-center">
112- <span class="">[INFO]</span>
113- <span class="ml-2">Traefik started</span>
114- </div>
115- <!-- One item -->
116- <div class="flex flex-row items-center">
117- <span class="">[INFO]</span>
118- <span class="ml-2"
119- >Traefik configuration file created</span
120- >
121- </div>
122- <!-- One item -->
123- <div class="flex flex-row items-center">
124- <span class="">[INFO]</span>
125- <span class="">Traefik configuration file updated</span>
126- </div>
127- </div>
128- </div>
129- </div>
130- </div>
131- </div>
132- </div>
133- </div>
134- <!-- End Install Traefik -->
135- </div>
136- </body>
137-</html>
D internal/ui/templates/manager.gohtml +0 -0

no textual change.

D internal/ui/templates/strings.yml +0 -23
@@ -1,23 +0,0 @@
1-en:
2- header:
3- meta:
4- title: Gordon - Admin Dashboard
5- description: Admin dashboard for Gordon
6- keywords: gordon, admin, dashboard
7- body:
8- header:
9- helloworld: Hello World!
10- div:
11- hello: Hello component loaded from a template!
12-
13-fr:
14- header:
15- meta:
16- title: Gordon - Tableau de bord administrateur
17- description: Tableau de bord administrateur pour Gordon
18- keywords: gordon, admin, dashboard
19- body:
20- header:
21- helloworld: Bonjour le monde!
22- div:
23- hello: Composant de salut chargé à partir d'un modèle!
@@ -1,23 +0,0 @@
1-en:
2- header:
3- meta:
4- title: Gordon - Admin Dashboard
5- description: Admin dashboard for Gordon
6- keywords: gordon, admin, dashboard
7- body:
8- header:
9- helloworld: Hello World!
10- div:
11- hello: Hello component loaded from a template!
12-
13-fr:
14- header:
15- meta:
16- title: Gordon - Tableau de bord administrateur
17- description: Tableau de bord administrateur pour Gordon
18- keywords: gordon, admin, dashboard
19- body:
20- header:
21- helloworld: Bonjour le monde!
22- div:
23- hello: Composant de salut chargé à partir d'un modèle!
D internal/ui/templates/user.gohtml +0 -0

no textual change.

D loop +0 -0

no textual change.

D pkg/docker/commands.go +0 -165
@@ -1,165 +0,0 @@
1-package docker
2-
3-import (
4- "bufio"
5- "bytes"
6- "context"
7- "errors"
8- "fmt"
9- "io"
10- "os"
11- "os/exec"
12- "path/filepath"
13-
14- "github.com/docker/docker/api/types"
15- "github.com/docker/docker/client"
16- "github.com/docker/docker/pkg/archive"
17- "gogs.bnema.dev/gordon-echo/internal/app"
18- "gogs.bnema.dev/gordon-echo/pkg/utils"
19-)
20-
21-// Use the App struct to access the logger
22-
23-func CreateImageFromDockerfile(a *app.App) error {
24- ctx := context.Background()
25- cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
26- if err != nil {
27- a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to initialize Docker client: %v", err))
28- defer cli.Close()
29- }
30-
31- a.AppLogger.Log(utils.INFO, "Building image...")
32- buildCtx, err := archive.TarWithOptions("tmp/docker-test/", &archive.TarOptions{})
33- if err != nil {
34- a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to create build context: %v", err))
35- }
36- defer buildCtx.Close()
37-
38- a.AppLogger.Log(utils.INFO, "Building image...")
39- imageBuildResponse, err := cli.ImageBuild(ctx, buildCtx, types.ImageBuildOptions{
40- Tags: []string{"gordon-echo:latest"},
41- })
42- if err != nil {
43- a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to build image: %v", err))
44- }
45- defer imageBuildResponse.Body.Close()
46-
47- a.AppLogger.Log(utils.INFO, "Saving image to file...")
48-
49- imageTar, err := cli.ImageSave(ctx, []string{"gordon-echo:latest"})
50- if err != nil {
51- a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to save image: %v", err))
52- }
53-
54- outputFile, err := os.Create("tmp/docker-test/gordon-echo.tar")
55- if err != nil {
56- a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to create output file: %v", err))
57- }
58- defer outputFile.Close()
59-
60- _, err = io.Copy(outputFile, imageTar)
61- if err != nil {
62- a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to save image to file: %v", err))
63- }
64-
65- return nil
66-}
67-
68-func CreateNetwork(name string, a *app.App) error {
69- a.AppLogger.Log(utils.INFO, "Entering CreateNetwork function with name: "+name)
70-
71- ctx := context.Background()
72- cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
73- if err != nil {
74- errMsg := fmt.Sprintf("Failed to initialize Docker client: %v", err)
75- a.AppLogger.Log(utils.ERROR, errMsg)
76- return errors.New(errMsg)
77- }
78- defer cli.Close()
79-
80- a.AppLogger.Log(utils.INFO, "Attempting to create network...")
81- _, err = cli.NetworkCreate(ctx, name, types.NetworkCreate{
82- CheckDuplicate: true,
83- Driver: "bridge",
84- })
85- if err != nil {
86- errMsg := fmt.Sprintf("Failed to create network: %v", err)
87- a.AppLogger.Log(utils.ERROR, errMsg)
88- return errors.New(errMsg)
89- }
90-
91- a.AppLogger.Log(utils.INFO, "Successfully created network: "+name)
92- return nil
93-}
94-
95-// Check if a container exists
96-func ContainerExists(name string, a *app.App) error {
97- ctx := context.Background()
98- cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
99- if err != nil {
100- return err
101- }
102- defer cli.Close()
103-
104- fmt.Println("Checking if container exists...")
105- containers, err := cli.ContainerList(ctx, types.ContainerListOptions{})
106- if err != nil {
107- return err
108- }
109-
110- for _, container := range containers {
111- for _, containerName := range container.Names {
112- if containerName == "/"+name {
113- return errors.New("container already exists")
114- }
115- }
116- }
117-
118- return nil
119-}
120-
121-func CheckIfDockerIsRunning(a *app.App) error {
122- cmd := exec.Command("docker", "info")
123- if err := cmd.Run(); err != nil {
124- return errors.New("docker is not running")
125- }
126- return nil
127-}
128-
129-// CreateContainerFromComposeFile creates containers using docker compose based on the provided compose file.
130-func CreateContainerFromComposeFile(composeFilePath string, a *app.App) error {
131- a.AppLogger.Log(utils.INFO, "Executing CreateContainerFromComposeFile()")
132-
133- // Extract directory from the composeFilePath
134- dir := filepath.Dir(composeFilePath)
135-
136- // Change directory to the compose file directory
137- if err := os.Chdir(dir); err != nil {
138- return errors.New("failed to change directory: " + err.Error())
139- }
140-
141- // Create the command
142- cmd := exec.Command("docker", "compose", "up", "-d")
143-
144- // Capture standard output and error
145- var stdout, stderr bytes.Buffer
146- cmd.Stdout = &stdout
147- cmd.Stderr = &stderr
148-
149- // Execute the command
150- if err := cmd.Run(); err != nil {
151- // Log the captured standard output and error for better diagnostics
152- a.AppLogger.Log(utils.ERROR, "Docker Compose Output: "+stdout.String())
153- a.AppLogger.Log(utils.ERROR, "Docker Compose Error: "+stderr.String())
154- return errors.New("failed to execute docker compose command: " + err.Error())
155- }
156-
157- return nil
158-}
159-
160-func streamToLogger(r io.Reader, logger *utils.Logger, level utils.LogLevel) {
161- scanner := bufio.NewScanner(r)
162- for scanner.Scan() {
163- logger.Log(level, scanner.Text())
164- }
165-}
@@ -1,165 +0,0 @@
1-package docker
2-
3-import (
4- "bufio"
5- "bytes"
6- "context"
7- "errors"
8- "fmt"
9- "io"
10- "os"
11- "os/exec"
12- "path/filepath"
13-
14- "github.com/docker/docker/api/types"
15- "github.com/docker/docker/client"
16- "github.com/docker/docker/pkg/archive"
17- "gogs.bnema.dev/gordon-echo/internal/app"
18- "gogs.bnema.dev/gordon-echo/pkg/utils"
19-)
20-
21-// Use the App struct to access the logger
22-
23-func CreateImageFromDockerfile(a *app.App) error {
24- ctx := context.Background()
25- cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
26- if err != nil {
27- a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to initialize Docker client: %v", err))
28- defer cli.Close()
29- }
30-
31- a.AppLogger.Log(utils.INFO, "Building image...")
32- buildCtx, err := archive.TarWithOptions("tmp/docker-test/", &archive.TarOptions{})
33- if err != nil {
34- a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to create build context: %v", err))
35- }
36- defer buildCtx.Close()
37-
38- a.AppLogger.Log(utils.INFO, "Building image...")
39- imageBuildResponse, err := cli.ImageBuild(ctx, buildCtx, types.ImageBuildOptions{
40- Tags: []string{"gordon-echo:latest"},
41- })
42- if err != nil {
43- a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to build image: %v", err))
44- }
45- defer imageBuildResponse.Body.Close()
46-
47- a.AppLogger.Log(utils.INFO, "Saving image to file...")
48-
49- imageTar, err := cli.ImageSave(ctx, []string{"gordon-echo:latest"})
50- if err != nil {
51- a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to save image: %v", err))
52- }
53-
54- outputFile, err := os.Create("tmp/docker-test/gordon-echo.tar")
55- if err != nil {
56- a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to create output file: %v", err))
57- }
58- defer outputFile.Close()
59-
60- _, err = io.Copy(outputFile, imageTar)
61- if err != nil {
62- a.AppLogger.Log(utils.ERROR, fmt.Sprintf("Failed to save image to file: %v", err))
63- }
64-
65- return nil
66-}
67-
68-func CreateNetwork(name string, a *app.App) error {
69- a.AppLogger.Log(utils.INFO, "Entering CreateNetwork function with name: "+name)
70-
71- ctx := context.Background()
72- cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
73- if err != nil {
74- errMsg := fmt.Sprintf("Failed to initialize Docker client: %v", err)
75- a.AppLogger.Log(utils.ERROR, errMsg)
76- return errors.New(errMsg)
77- }
78- defer cli.Close()
79-
80- a.AppLogger.Log(utils.INFO, "Attempting to create network...")
81- _, err = cli.NetworkCreate(ctx, name, types.NetworkCreate{
82- CheckDuplicate: true,
83- Driver: "bridge",
84- })
85- if err != nil {
86- errMsg := fmt.Sprintf("Failed to create network: %v", err)
87- a.AppLogger.Log(utils.ERROR, errMsg)
88- return errors.New(errMsg)
89- }
90-
91- a.AppLogger.Log(utils.INFO, "Successfully created network: "+name)
92- return nil
93-}
94-
95-// Check if a container exists
96-func ContainerExists(name string, a *app.App) error {
97- ctx := context.Background()
98- cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
99- if err != nil {
100- return err
101- }
102- defer cli.Close()
103-
104- fmt.Println("Checking if container exists...")
105- containers, err := cli.ContainerList(ctx, types.ContainerListOptions{})
106- if err != nil {
107- return err
108- }
109-
110- for _, container := range containers {
111- for _, containerName := range container.Names {
112- if containerName == "/"+name {
113- return errors.New("container already exists")
114- }
115- }
116- }
117-
118- return nil
119-}
120-
121-func CheckIfDockerIsRunning(a *app.App) error {
122- cmd := exec.Command("docker", "info")
123- if err := cmd.Run(); err != nil {
124- return errors.New("docker is not running")
125- }
126- return nil
127-}
128-
129-// CreateContainerFromComposeFile creates containers using docker compose based on the provided compose file.
130-func CreateContainerFromComposeFile(composeFilePath string, a *app.App) error {
131- a.AppLogger.Log(utils.INFO, "Executing CreateContainerFromComposeFile()")
132-
133- // Extract directory from the composeFilePath
134- dir := filepath.Dir(composeFilePath)
135-
136- // Change directory to the compose file directory
137- if err := os.Chdir(dir); err != nil {
138- return errors.New("failed to change directory: " + err.Error())
139- }
140-
141- // Create the command
142- cmd := exec.Command("docker", "compose", "up", "-d")
143-
144- // Capture standard output and error
145- var stdout, stderr bytes.Buffer
146- cmd.Stdout = &stdout
147- cmd.Stderr = &stderr
148-
149- // Execute the command
150- if err := cmd.Run(); err != nil {
151- // Log the captured standard output and error for better diagnostics
152- a.AppLogger.Log(utils.ERROR, "Docker Compose Output: "+stdout.String())
153- a.AppLogger.Log(utils.ERROR, "Docker Compose Error: "+stderr.String())
154- return errors.New("failed to execute docker compose command: " + err.Error())
155- }
156-
157- return nil
158-}
159-
160-func streamToLogger(r io.Reader, logger *utils.Logger, level utils.LogLevel) {
161- scanner := bufio.NewScanner(r)
162- for scanner.Scan() {
163- logger.Log(level, scanner.Text())
164- }
165-}
D pkg/docker/daemon.json +0 -11
@@ -1,11 +0,0 @@
1-{
2- "log-driver": "json-file",
3- "log-opts": {
4- "max-size": "100m",
5- "max-file": "5"
6- },
7- "features": {
8- "buildkit": true
9- },
10- "live-restore": true
11-}
@@ -1,11 +0,0 @@
1-{
2- "log-driver": "json-file",
3- "log-opts": {
4- "max-size": "100m",
5- "max-file": "5"
6- },
7- "features": {
8- "buildkit": true
9- },
10- "live-restore": true
11-}
D pkg/htmx/html-fragments.go +0 -41
@@ -1,41 +0,0 @@
1-package htmx
2-
3-import (
4- "bytes"
5-
6- "github.com/PuerkitoBio/goquery"
7- "gogs.bnema.dev/gordon-echo/internal/app"
8- "gogs.bnema.dev/gordon-echo/pkg/templating"
9-)
10-
11-// GetHTMLFragmentByID returns the HTML fragment with the specified id
12-func GetHTMLFragmentByID(id string, data interface{}, a *app.App, ac *app.Config) (string, error) {
13- // 1. Render the template
14- renderer, err := templating.GetHTMLRenderer("components.gohtml", ac.GetTemplateFS(), a)
15- if err != nil {
16- return "", err
17- }
18-
19- renderedHTML, err := renderer.Render(data, a)
20- if err != nil {
21- return "", err
22- }
23-
24- // 2. Parse the rendered HTML with goquery
25- doc, err := goquery.NewDocumentFromReader(bytes.NewReader([]byte(renderedHTML)))
26- if err != nil {
27- return "", err
28- }
29-
30- // 3. Find the div with the specified id
31- fragmentContent, err := doc.Find("#" + id).Html()
32- if err != nil {
33- return "", err
34- }
35-
36- if fragmentContent == "" {
37- return "", err
38- }
39-
40- return fragmentContent, nil
41-}
@@ -1,41 +0,0 @@
1-package htmx
2-
3-import (
4- "bytes"
5-
6- "github.com/PuerkitoBio/goquery"
7- "gogs.bnema.dev/gordon-echo/internal/app"
8- "gogs.bnema.dev/gordon-echo/pkg/templating"
9-)
10-
11-// GetHTMLFragmentByID returns the HTML fragment with the specified id
12-func GetHTMLFragmentByID(id string, data interface{}, a *app.App, ac *app.Config) (string, error) {
13- // 1. Render the template
14- renderer, err := templating.GetHTMLRenderer("components.gohtml", ac.GetTemplateFS(), a)
15- if err != nil {
16- return "", err
17- }
18-
19- renderedHTML, err := renderer.Render(data, a)
20- if err != nil {
21- return "", err
22- }
23-
24- // 2. Parse the rendered HTML with goquery
25- doc, err := goquery.NewDocumentFromReader(bytes.NewReader([]byte(renderedHTML)))
26- if err != nil {
27- return "", err
28- }
29-
30- // 3. Find the div with the specified id
31- fragmentContent, err := doc.Find("#" + id).Html()
32- if err != nil {
33- return "", err
34- }
35-
36- if fragmentContent == "" {
37- return "", err
38- }
39-
40- return fragmentContent, nil
41-}
D pkg/htmx/htmx.go +0 -88
@@ -1,88 +0,0 @@
1-package htmx
2-
3-import (
4- "net/http"
5-
6- "github.com/labstack/echo/v4"
7-)
8-
9-// Headers (https://htmx.org/docs/#requests)
10-const (
11- HeaderRequest = "HX-Request"
12- HeaderBoosted = "HX-Boosted"
13- HeaderTrigger = "HX-Trigger"
14- HeaderTriggerName = "HX-Trigger-Name"
15- HeaderTriggerAfterSwap = "HX-Trigger-After-Swap"
16- HeaderTriggerAfterSettle = "HX-Trigger-After-Settle"
17- HeaderTarget = "HX-Target"
18- HeaderPrompt = "HX-Prompt"
19- HeaderPush = "HX-Push"
20- HeaderRedirect = "HX-Redirect"
21- HeaderRefresh = "HX-Refresh"
22- // Custom header
23- HeaderFragment = "HX-Fragment"
24-)
25-
26-type (
27- // Request contains data that HTMX provides during requests
28- Request struct {
29- Enabled bool
30- Boosted bool
31- Trigger string
32- TriggerName string
33- Target string
34- Prompt string
35- }
36-
37- // Response contain data that the server can communicate back to HTMX
38- Response struct {
39- Push string
40- Redirect string
41- Refresh bool
42- Trigger string
43- TriggerAfterSwap string
44- TriggerAfterSettle string
45- NoContent bool
46- }
47-)
48-
49-// GetRequest extracts HTMX data from the request
50-func GetRequest(ctx echo.Context) (Request, error) {
51- request := Request{
52- Enabled: ctx.Request().Header.Get(HeaderRequest) == "true",
53- Boosted: ctx.Request().Header.Get(HeaderBoosted) == "true",
54- Trigger: ctx.Request().Header.Get(HeaderTrigger),
55- TriggerName: ctx.Request().Header.Get(HeaderTriggerName),
56- Target: ctx.Request().Header.Get(HeaderTarget),
57- Prompt: ctx.Request().Header.Get(HeaderPrompt),
58- }
59-
60- return request, nil
61-}
62-
63-// Apply applies data from a Response to a server response
64-func (r Response) Apply(ctx echo.Context) error {
65- if r.Push != "" {
66- ctx.Response().Header().Set(HeaderPush, r.Push)
67- }
68- if r.Redirect != "" {
69- ctx.Response().Header().Set(HeaderRedirect, r.Redirect)
70- }
71- if r.Refresh {
72- ctx.Response().Header().Set(HeaderRefresh, "true")
73- }
74- if r.Trigger != "" {
75- ctx.Response().Header().Set(HeaderTrigger, r.Trigger)
76- }
77- if r.TriggerAfterSwap != "" {
78- ctx.Response().Header().Set(HeaderTriggerAfterSwap, r.TriggerAfterSwap)
79- }
80- if r.TriggerAfterSettle != "" {
81- ctx.Response().Header().Set(HeaderTriggerAfterSettle, r.TriggerAfterSettle)
82- }
83- if r.NoContent {
84- ctx.Response().Status = http.StatusNoContent
85- }
86-
87- return nil
88-}
@@ -1,88 +0,0 @@
1-package htmx
2-
3-import (
4- "net/http"
5-
6- "github.com/labstack/echo/v4"
7-)
8-
9-// Headers (https://htmx.org/docs/#requests)
10-const (
11- HeaderRequest = "HX-Request"
12- HeaderBoosted = "HX-Boosted"
13- HeaderTrigger = "HX-Trigger"
14- HeaderTriggerName = "HX-Trigger-Name"
15- HeaderTriggerAfterSwap = "HX-Trigger-After-Swap"
16- HeaderTriggerAfterSettle = "HX-Trigger-After-Settle"
17- HeaderTarget = "HX-Target"
18- HeaderPrompt = "HX-Prompt"
19- HeaderPush = "HX-Push"
20- HeaderRedirect = "HX-Redirect"
21- HeaderRefresh = "HX-Refresh"
22- // Custom header
23- HeaderFragment = "HX-Fragment"
24-)
25-
26-type (
27- // Request contains data that HTMX provides during requests
28- Request struct {
29- Enabled bool
30- Boosted bool
31- Trigger string
32- TriggerName string
33- Target string
34- Prompt string
35- }
36-
37- // Response contain data that the server can communicate back to HTMX
38- Response struct {
39- Push string
40- Redirect string
41- Refresh bool
42- Trigger string
43- TriggerAfterSwap string
44- TriggerAfterSettle string
45- NoContent bool
46- }
47-)
48-
49-// GetRequest extracts HTMX data from the request
50-func GetRequest(ctx echo.Context) (Request, error) {
51- request := Request{
52- Enabled: ctx.Request().Header.Get(HeaderRequest) == "true",
53- Boosted: ctx.Request().Header.Get(HeaderBoosted) == "true",
54- Trigger: ctx.Request().Header.Get(HeaderTrigger),
55- TriggerName: ctx.Request().Header.Get(HeaderTriggerName),
56- Target: ctx.Request().Header.Get(HeaderTarget),
57- Prompt: ctx.Request().Header.Get(HeaderPrompt),
58- }
59-
60- return request, nil
61-}
62-
63-// Apply applies data from a Response to a server response
64-func (r Response) Apply(ctx echo.Context) error {
65- if r.Push != "" {
66- ctx.Response().Header().Set(HeaderPush, r.Push)
67- }
68- if r.Redirect != "" {
69- ctx.Response().Header().Set(HeaderRedirect, r.Redirect)
70- }
71- if r.Refresh {
72- ctx.Response().Header().Set(HeaderRefresh, "true")
73- }
74- if r.Trigger != "" {
75- ctx.Response().Header().Set(HeaderTrigger, r.Trigger)
76- }
77- if r.TriggerAfterSwap != "" {
78- ctx.Response().Header().Set(HeaderTriggerAfterSwap, r.TriggerAfterSwap)
79- }
80- if r.TriggerAfterSettle != "" {
81- ctx.Response().Header().Set(HeaderTriggerAfterSettle, r.TriggerAfterSettle)
82- }
83- if r.NoContent {
84- ctx.Response().Status = http.StatusNoContent
85- }
86-
87- return nil
88-}
D pkg/scripts/install-traefik.go +0 -226
@@ -1,226 +0,0 @@
1-package scripts
2-
3-import (
4- "errors"
5- "fmt"
6- "os"
7-
8- root "gogs.bnema.dev/gordon-echo"
9- "gogs.bnema.dev/gordon-echo/internal/app"
10- "gogs.bnema.dev/gordon-echo/pkg/docker"
11- "gogs.bnema.dev/gordon-echo/pkg/templating"
12- "gogs.bnema.dev/gordon-echo/pkg/utils"
13-)
14-
15-var path string
16-
17-const (
18- traefikPath = "traefik"
19- traefikVolumePath = "traefik/volume"
20- traefikLog = "logs/traefik.log"
21- acmeJSON = "traefik/volume/acme.json"
22- networkName = "totototo"
23-)
24-
25-func init() {
26- path = app.GetBuildDir() + "/"
27-}
28-
29-type InstallationResult struct {
30- Success bool
31- Message string
32- Err error
33-}
34-
35-func InstallTraefik(topDomain, adminEmail string, a *app.App) (bool, error) {
36- // 1 - Files/directories creation
37- // 1.1 - Check if directory traefik exists, if not create it
38- if err := createDirectory(path + traefikPath); err != nil {
39- a.AppLogger.Log(utils.ERROR, err)
40- return false, err
41- } else {
42- fmt.Println("traefik directory created")
43- }
44-
45- // 1.2 - Check if directory traefik/volume exists, if not create it
46- if err := createDirectory(path + traefikVolumePath); err != nil {
47- a.AppLogger.Log(utils.ERROR, err)
48- return false, err
49- } else {
50- fmt.Println("traefik/volume directory created")
51- }
52-
53- // 1.3 - touch traefik/traefik.log
54- if err := createTraefikLogFile(path + traefikLog); err != nil {
55- a.AppLogger.Log(utils.ERROR, err)
56- return false, err
57- } else {
58- a.AppLogger.Log(utils.INFO, "Traefik log file created")
59- }
60-
61- // 1.4 - touch traefik/volume/acme.json
62- if err := createACMEfile(path + acmeJSON); err != nil {
63- a.AppLogger.Log(utils.ERROR, err)
64- return false, err
65- }
66-
67- a.AppLogger.Log(utils.INFO, "Traefik acme.json file created")
68-
69- // 2 - Render the yml files
70- // 2.1 - Render traefik-compose.yml
71- traefikComposeContent, err := renderTemplate("traefik-compose.goyml", topDomain, adminEmail, a.AppLogger)
72- if err != nil {
73- a.AppLogger.Log(utils.ERROR, err)
74- return false, err
75- }
76-
77- a.AppLogger.Log(utils.INFO, "Traefik compose file rendered")
78-
79- // 2.2 - Save traefik-compose.yml
80- if err := saveFile(path+"docker-compose.yml", traefikComposeContent); err != nil {
81- a.AppLogger.Log(utils.ERROR, err)
82- return false, err
83- } else {
84- fmt.Println("docker-compose.yml saved")
85- }
86-
87- // 2.3 - Render traefik.yml
88- traefikContent, err := renderTemplate("traefik.goyml", topDomain, adminEmail, a.AppLogger)
89- if err != nil {
90- a.AppLogger.Log(utils.ERROR, err)
91- return false, err
92- }
93-
94- a.AppLogger.Log(utils.INFO, "Traefik file rendered")
95-
96- // 2.4 - Save traefik.yml
97- if err := saveFile(path+"traefik.yml", traefikContent); err != nil {
98- a.AppLogger.Log(utils.ERROR, err)
99- return false, err
100- }
101-
102- a.AppLogger.Log(utils.INFO, "Traefik file saved")
103-
104- // 3 - Check if traefik is running
105- if err := checkIfTraefikIsRunning(a); err != nil {
106- a.AppLogger.Log(utils.ERROR, err)
107- return false, err
108- }
109-
110- // // 4 - Create network
111- // if err := docker.CreateNetwork(networkName, a); err != nil {
112- // a.AppLogger.Log(utils.ERROR, err)
113- // return false, err
114- // }
115-
116- // 5 - Create traefik container
117- if err := createTraefikContainer(path+"docker-compose.yml", a); err != nil {
118- a.AppLogger.Log(utils.ERROR, err)
119- return false, err
120- }
121-
122- // 6 - Ping traefik
123- if err := pingWithCURL(topDomain); err != nil {
124- a.AppLogger.Log(utils.ERROR, err)
125- return false, err
126- }
127-
128- return true, nil
129-}
130-
131-func renderTemplate(filename, topDomain, adminEmail string, logger *utils.Logger) (string, error) {
132- modelFS := root.ModelFS
133- if modelFS == nil {
134- return "", errors.New("modelFS is nil")
135- }
136-
137- renderer, err := templating.GetTXTRenderer(filename, modelFS, logger)
138- if err != nil {
139- return "", err
140- }
141-
142- var contentData map[string]interface{}
143- switch filename {
144- case "traefik-compose.goyml":
145- contentData = map[string]interface{}{"topdomain": topDomain}
146- case "traefik.goyml":
147- contentData = map[string]interface{}{
148- "admin": map[string]string{
149- "email": adminEmail,
150- },
151- }
152- default:
153- return "", errors.New("invalid template filename")
154- }
155-
156- return renderer.TXTRender(contentData)
157-}
158-
159-func createDirectory(dir string) error {
160- if _, err := os.Stat(dir); os.IsNotExist(err) {
161- return os.Mkdir(dir, 0755)
162- }
163- return nil
164-}
165-func saveFile(filePath, content string) error {
166- f, err := os.Create(filePath)
167- if err != nil {
168- return err
169- }
170- defer f.Close()
171- _, err = f.WriteString(content)
172- if err != nil {
173- fmt.Println("Error while writing to file: ", err)
174- return err
175- }
176- return err
177-}
178-
179-func closeFile(f *os.File) error {
180- // defer f.Close()
181- return f.Close()
182-}
183-
184-func createACMEfile(filePath string) error {
185- f, err := os.Create(filePath)
186- if err != nil {
187- return err
188- }
189- defer f.Close()
190- return f.Chmod(0600)
191-}
192-
193-func createTraefikLogFile(filePath string) error {
194- f, err := os.Create(filePath)
195- if err != nil {
196- return err
197- }
198- defer f.Close()
199- return f.Chmod(0600)
200-}
201-
202-func checkIfTraefikIsRunning(a *app.App) error {
203- // Check if docker is running
204- if err := docker.CheckIfDockerIsRunning(a); err != nil {
205- return err
206- }
207- // Check if a container named traefik already exists
208- if err := docker.ContainerExists("traefik", a); err != nil {
209- return err
210- }
211-
212- return nil
213-}
214-
215-func createTraefikContainer(filePath string, a *app.App) error {
216- err := docker.CreateContainerFromComposeFile(filePath, a)
217- if err != nil {
218- return err
219- }
220-
221- return nil
222-}
223-
224-func pingWithCURL(topDomain string) error {
225- return nil
226-}
@@ -1,226 +0,0 @@
1-package scripts
2-
3-import (
4- "errors"
5- "fmt"
6- "os"
7-
8- root "gogs.bnema.dev/gordon-echo"
9- "gogs.bnema.dev/gordon-echo/internal/app"
10- "gogs.bnema.dev/gordon-echo/pkg/docker"
11- "gogs.bnema.dev/gordon-echo/pkg/templating"
12- "gogs.bnema.dev/gordon-echo/pkg/utils"
13-)
14-
15-var path string
16-
17-const (
18- traefikPath = "traefik"
19- traefikVolumePath = "traefik/volume"
20- traefikLog = "logs/traefik.log"
21- acmeJSON = "traefik/volume/acme.json"
22- networkName = "totototo"
23-)
24-
25-func init() {
26- path = app.GetBuildDir() + "/"
27-}
28-
29-type InstallationResult struct {
30- Success bool
31- Message string
32- Err error
33-}
34-
35-func InstallTraefik(topDomain, adminEmail string, a *app.App) (bool, error) {
36- // 1 - Files/directories creation
37- // 1.1 - Check if directory traefik exists, if not create it
38- if err := createDirectory(path + traefikPath); err != nil {
39- a.AppLogger.Log(utils.ERROR, err)
40- return false, err
41- } else {
42- fmt.Println("traefik directory created")
43- }
44-
45- // 1.2 - Check if directory traefik/volume exists, if not create it
46- if err := createDirectory(path + traefikVolumePath); err != nil {
47- a.AppLogger.Log(utils.ERROR, err)
48- return false, err
49- } else {
50- fmt.Println("traefik/volume directory created")
51- }
52-
53- // 1.3 - touch traefik/traefik.log
54- if err := createTraefikLogFile(path + traefikLog); err != nil {
55- a.AppLogger.Log(utils.ERROR, err)
56- return false, err
57- } else {
58- a.AppLogger.Log(utils.INFO, "Traefik log file created")
59- }
60-
61- // 1.4 - touch traefik/volume/acme.json
62- if err := createACMEfile(path + acmeJSON); err != nil {
63- a.AppLogger.Log(utils.ERROR, err)
64- return false, err
65- }
66-
67- a.AppLogger.Log(utils.INFO, "Traefik acme.json file created")
68-
69- // 2 - Render the yml files
70- // 2.1 - Render traefik-compose.yml
71- traefikComposeContent, err := renderTemplate("traefik-compose.goyml", topDomain, adminEmail, a.AppLogger)
72- if err != nil {
73- a.AppLogger.Log(utils.ERROR, err)
74- return false, err
75- }
76-
77- a.AppLogger.Log(utils.INFO, "Traefik compose file rendered")
78-
79- // 2.2 - Save traefik-compose.yml
80- if err := saveFile(path+"docker-compose.yml", traefikComposeContent); err != nil {
81- a.AppLogger.Log(utils.ERROR, err)
82- return false, err
83- } else {
84- fmt.Println("docker-compose.yml saved")
85- }
86-
87- // 2.3 - Render traefik.yml
88- traefikContent, err := renderTemplate("traefik.goyml", topDomain, adminEmail, a.AppLogger)
89- if err != nil {
90- a.AppLogger.Log(utils.ERROR, err)
91- return false, err
92- }
93-
94- a.AppLogger.Log(utils.INFO, "Traefik file rendered")
95-
96- // 2.4 - Save traefik.yml
97- if err := saveFile(path+"traefik.yml", traefikContent); err != nil {
98- a.AppLogger.Log(utils.ERROR, err)
99- return false, err
100- }
101-
102- a.AppLogger.Log(utils.INFO, "Traefik file saved")
103-
104- // 3 - Check if traefik is running
105- if err := checkIfTraefikIsRunning(a); err != nil {
106- a.AppLogger.Log(utils.ERROR, err)
107- return false, err
108- }
109-
110- // // 4 - Create network
111- // if err := docker.CreateNetwork(networkName, a); err != nil {
112- // a.AppLogger.Log(utils.ERROR, err)
113- // return false, err
114- // }
115-
116- // 5 - Create traefik container
117- if err := createTraefikContainer(path+"docker-compose.yml", a); err != nil {
118- a.AppLogger.Log(utils.ERROR, err)
119- return false, err
120- }
121-
122- // 6 - Ping traefik
123- if err := pingWithCURL(topDomain); err != nil {
124- a.AppLogger.Log(utils.ERROR, err)
125- return false, err
126- }
127-
128- return true, nil
129-}
130-
131-func renderTemplate(filename, topDomain, adminEmail string, logger *utils.Logger) (string, error) {
132- modelFS := root.ModelFS
133- if modelFS == nil {
134- return "", errors.New("modelFS is nil")
135- }
136-
137- renderer, err := templating.GetTXTRenderer(filename, modelFS, logger)
138- if err != nil {
139- return "", err
140- }
141-
142- var contentData map[string]interface{}
143- switch filename {
144- case "traefik-compose.goyml":
145- contentData = map[string]interface{}{"topdomain": topDomain}
146- case "traefik.goyml":
147- contentData = map[string]interface{}{
148- "admin": map[string]string{
149- "email": adminEmail,
150- },
151- }
152- default:
153- return "", errors.New("invalid template filename")
154- }
155-
156- return renderer.TXTRender(contentData)
157-}
158-
159-func createDirectory(dir string) error {
160- if _, err := os.Stat(dir); os.IsNotExist(err) {
161- return os.Mkdir(dir, 0755)
162- }
163- return nil
164-}
165-func saveFile(filePath, content string) error {
166- f, err := os.Create(filePath)
167- if err != nil {
168- return err
169- }
170- defer f.Close()
171- _, err = f.WriteString(content)
172- if err != nil {
173- fmt.Println("Error while writing to file: ", err)
174- return err
175- }
176- return err
177-}
178-
179-func closeFile(f *os.File) error {
180- // defer f.Close()
181- return f.Close()
182-}
183-
184-func createACMEfile(filePath string) error {
185- f, err := os.Create(filePath)
186- if err != nil {
187- return err
188- }
189- defer f.Close()
190- return f.Chmod(0600)
191-}
192-
193-func createTraefikLogFile(filePath string) error {
194- f, err := os.Create(filePath)
195- if err != nil {
196- return err
197- }
198- defer f.Close()
199- return f.Chmod(0600)
200-}
201-
202-func checkIfTraefikIsRunning(a *app.App) error {
203- // Check if docker is running
204- if err := docker.CheckIfDockerIsRunning(a); err != nil {
205- return err
206- }
207- // Check if a container named traefik already exists
208- if err := docker.ContainerExists("traefik", a); err != nil {
209- return err
210- }
211-
212- return nil
213-}
214-
215-func createTraefikContainer(filePath string, a *app.App) error {
216- err := docker.CreateContainerFromComposeFile(filePath, a)
217- if err != nil {
218- return err
219- }
220-
221- return nil
222-}
223-
224-func pingWithCURL(topDomain string) error {
225- return nil
226-}
D pkg/services/sqlite.go +0 -1
@@ -1,1 +0,0 @@
1-package services
@@ -1,1 +0,0 @@
1-package services
D pkg/templating/html-render.go +0 -54
@@ -1,54 +0,0 @@
1-package templating
2-
3-import (
4- "bytes"
5- "html/template"
6- "io/fs"
7-
8- "gogs.bnema.dev/gordon-echo/internal/app"
9-)
10-
11-type Renderer struct {
12- Template *template.Template
13- ParseError error
14-}
15-
16-// Render function renders the template with the given data
17-func (r *Renderer) Render(data interface{}, a *app.App) (string, error) {
18- if r.ParseError != nil {
19- a.AppLogger.Error().Err(r.ParseError).Msg("Failed to parse template")
20- return "", r.ParseError
21- }
22- if r.Template == nil {
23- a.AppLogger.Error().Msg("Template is nil")
24- return "", r.ParseError
25- }
26-
27- buf := new(bytes.Buffer)
28- err := r.Template.Execute(buf, data)
29- if err != nil {
30- a.AppLogger.Error().Err(err).Msg("Failed to execute template")
31- return "", err
32- }
33-
34- return buf.String(), nil
35-}
36-
37-// GetRenderer function returns a new Renderer instance
38-func GetHTMLRenderer(filename string, fs fs.FS, a *app.App) (*Renderer, error) {
39- // Check if the file exists in the provided fs.FS using fs.Open
40- file, err := fs.Open(filename)
41- if err != nil {
42- a.AppLogger.Error().Err(err).Msg("Failed to open file")
43- return nil, err
44- }
45- file.Close() // Close the file after checking
46- tmpl, err := template.New(filename).ParseFS(fs, filename)
47- if err != nil {
48- a.AppLogger.Error().Err(err).Msg("Failed to parse template")
49- return nil, err
50- }
51- return &Renderer{
52- Template: tmpl,
53- }, nil
54-}
@@ -1,54 +0,0 @@
1-package templating
2-
3-import (
4- "bytes"
5- "html/template"
6- "io/fs"
7-
8- "gogs.bnema.dev/gordon-echo/internal/app"
9-)
10-
11-type Renderer struct {
12- Template *template.Template
13- ParseError error
14-}
15-
16-// Render function renders the template with the given data
17-func (r *Renderer) Render(data interface{}, a *app.App) (string, error) {
18- if r.ParseError != nil {
19- a.AppLogger.Error().Err(r.ParseError).Msg("Failed to parse template")
20- return "", r.ParseError
21- }
22- if r.Template == nil {
23- a.AppLogger.Error().Msg("Template is nil")
24- return "", r.ParseError
25- }
26-
27- buf := new(bytes.Buffer)
28- err := r.Template.Execute(buf, data)
29- if err != nil {
30- a.AppLogger.Error().Err(err).Msg("Failed to execute template")
31- return "", err
32- }
33-
34- return buf.String(), nil
35-}
36-
37-// GetRenderer function returns a new Renderer instance
38-func GetHTMLRenderer(filename string, fs fs.FS, a *app.App) (*Renderer, error) {
39- // Check if the file exists in the provided fs.FS using fs.Open
40- file, err := fs.Open(filename)
41- if err != nil {
42- a.AppLogger.Error().Err(err).Msg("Failed to open file")
43- return nil, err
44- }
45- file.Close() // Close the file after checking
46- tmpl, err := template.New(filename).ParseFS(fs, filename)
47- if err != nil {
48- a.AppLogger.Error().Err(err).Msg("Failed to parse template")
49- return nil, err
50- }
51- return &Renderer{
52- Template: tmpl,
53- }, nil
54-}
D pkg/templating/models/registry-compose.goyml +0 -15
@@ -1,15 +0,0 @@
1-registry:
2- restart: always
3- image: registry:2
4- ports:
5- - 5000:5000
6- environment:
7- REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
8- REGISTRY_HTTP_TLS_KEY: /certs/domain.key
9- REGISTRY_AUTH: htpasswd
10- REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
11- REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
12- volumes:
13- - /path/data:/var/lib/registry
14- - /path/certs:/certs
15- - /path/auth:/auth⏎̸
@@ -1,15 +0,0 @@
1-registry:
2- restart: always
3- image: registry:2
4- ports:
5- - 5000:5000
6- environment:
7- REGISTRY_HTTP_TLS_CERTIFICATE: /certs/domain.crt
8- REGISTRY_HTTP_TLS_KEY: /certs/domain.key
9- REGISTRY_AUTH: htpasswd
10- REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
11- REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
12- volumes:
13- - /path/data:/var/lib/registry
14- - /path/certs:/certs
15- - /path/auth:/auth⏎̸
D pkg/templating/models/traefik-compose.goyml +0 -29
@@ -1,29 +0,0 @@
1-version: "3"
2-
3-services:
4- traefik:
5- image: traefik:v2.10
6- container_name: traefik
7- environment:
8- - COMPOSE_PROJECT_NAME=traefik
9- restart: unless-stopped
10- ports:
11- - "80:80"
12- - "443:443"
13- volumes:
14- - /var/run/docker.sock:/var/run/docker.sock:ro
15- - ./traefik.yml:/etc/traefik/traefik.yml
16- - ./volume/acme.json:/acme.json
17- - ./logs/traefik.log:/var/log/traefik/traefik.log
18- labels:
19- - "traefik.enable=true"
20- - "traefik.http.routers.api.rule=Host(`traefik.{{ .topdomain }}`)"
21- - "traefik.http.routers.api.service=api@internal"
22- - "traefik.http.routers.api.entrypoints=websecure"
23- - "traefik.http.routers.api.tls.certresolver=myresolver"
24- - "traefik.http.services.api.loadbalancer.server.port=8080"
25- networks:
26- - exposed
27-
28-networks:
29- exposed:⏎̸
@@ -1,29 +0,0 @@
1-version: "3"
2-
3-services:
4- traefik:
5- image: traefik:v2.10
6- container_name: traefik
7- environment:
8- - COMPOSE_PROJECT_NAME=traefik
9- restart: unless-stopped
10- ports:
11- - "80:80"
12- - "443:443"
13- volumes:
14- - /var/run/docker.sock:/var/run/docker.sock:ro
15- - ./traefik.yml:/etc/traefik/traefik.yml
16- - ./volume/acme.json:/acme.json
17- - ./logs/traefik.log:/var/log/traefik/traefik.log
18- labels:
19- - "traefik.enable=true"
20- - "traefik.http.routers.api.rule=Host(`traefik.{{ .topdomain }}`)"
21- - "traefik.http.routers.api.service=api@internal"
22- - "traefik.http.routers.api.entrypoints=websecure"
23- - "traefik.http.routers.api.tls.certresolver=myresolver"
24- - "traefik.http.services.api.loadbalancer.server.port=8080"
25- networks:
26- - exposed
27-
28-networks:
29- exposed:⏎̸
D pkg/templating/models/traefik.goyml +0 -30
@@ -1,30 +0,0 @@
1-api:
2- dashboard: false
3-
4-entryPoints:
5- web:
6- address: ":80"
7- websecure:
8- address: ":443"
9-
10-providers:
11- docker:
12- endpoint: "unix:///var/run/docker.sock"
13- exposedByDefault: false
14-
15-certificatesResolvers:
16- myresolver:
17- acme:
18- email: {{ .admin.email }}
19- storage: acme.json
20- httpChallenge:
21- entryPoint: web
22-
23-log:
24- level: "DEBUG" # Set the desired log level: DEBUG, INFO, WARN, ERROR, FATAL, PANIC
25- filePath: "/var/log/traefik/traefik.log"
26-
27-accessLog: {}
28-
29-ping:
30- entryPoint: web ⏎̸
@@ -1,30 +0,0 @@
1-api:
2- dashboard: false
3-
4-entryPoints:
5- web:
6- address: ":80"
7- websecure:
8- address: ":443"
9-
10-providers:
11- docker:
12- endpoint: "unix:///var/run/docker.sock"
13- exposedByDefault: false
14-
15-certificatesResolvers:
16- myresolver:
17- acme:
18- email: {{ .admin.email }}
19- storage: acme.json
20- httpChallenge:
21- entryPoint: web
22-
23-log:
24- level: "DEBUG" # Set the desired log level: DEBUG, INFO, WARN, ERROR, FATAL, PANIC
25- filePath: "/var/log/traefik/traefik.log"
26-
27-accessLog: {}
28-
29-ping:
30- entryPoint: web ⏎̸
D pkg/templating/sanitizer.go +0 -22
@@ -1,22 +0,0 @@
1-package templating
2-
3-import (
4- "fmt"
5-
6- "github.com/microcosm-cc/bluemonday"
7-)
8-
9-// SanitizeHTML sanitizes HTML input using bluemonday package and returns an error if sanitization altered the input.
10-func SanitizeHTML(htmlInput string) (string, error) {
11- // Create a new UGC policy
12- p := bluemonday.UGCPolicy()
13-
14- // Sanitize the input HTML
15- sanitized := p.Sanitize(htmlInput)
16-
17- if sanitized != htmlInput {
18- return sanitized, fmt.Errorf("HTML input was altered during sanitization")
19- }
20-
21- return sanitized, nil
22-}
@@ -1,22 +0,0 @@
1-package templating
2-
3-import (
4- "fmt"
5-
6- "github.com/microcosm-cc/bluemonday"
7-)
8-
9-// SanitizeHTML sanitizes HTML input using bluemonday package and returns an error if sanitization altered the input.
10-func SanitizeHTML(htmlInput string) (string, error) {
11- // Create a new UGC policy
12- p := bluemonday.UGCPolicy()
13-
14- // Sanitize the input HTML
15- sanitized := p.Sanitize(htmlInput)
16-
17- if sanitized != htmlInput {
18- return sanitized, fmt.Errorf("HTML input was altered during sanitization")
19- }
20-
21- return sanitized, nil
22-}
D pkg/templating/txt-render.go +0 -63
@@ -1,63 +0,0 @@
1-package templating
2-
3-import (
4- "bytes"
5- "errors"
6- "fmt"
7- "io/fs"
8- "text/template"
9-
10- "gogs.bnema.dev/gordon-echo/pkg/utils"
11-)
12-
13-type TXTRenderer struct {
14- Models *template.Template
15- Format string
16- ParseError error
17- Logger *utils.Logger
18-}
19-
20-// Render function renders the template with the given data
21-func (r *TXTRenderer) TXTRender(data interface{}) (string, error) {
22- if r.ParseError != nil {
23- r.Logger.Error().Err(r.ParseError).Msg("Parse error")
24- return "", r.ParseError
25- }
26-
27- if r.Models == nil {
28- err := errors.New("invalid or nil model")
29- r.Logger.Error().Err(r.ParseError).Msg("Parse error")
30-
31- return "", err
32- }
33-
34- buf := new(bytes.Buffer)
35- err := r.Models.Execute(buf, data)
36- if err != nil {
37- r.Logger.Error().Err(err).Msg("Render error")
38- return "", err
39- }
40-
41- return buf.String(), nil
42-}
43-
44-// GetRenderer function returns a new Renderer instance
45-func GetTXTRenderer(filename string, fs fs.FS, logger *utils.Logger) (*TXTRenderer, error) {
46- fmt.Println(filename)
47- // Check if the file exists in the provided fs.FS using fs.Open
48- file, err := fs.Open(filename)
49- if err != nil {
50- logger.Error().Err(err).Msg("Template or model '%s' not found" + filename)
51- return nil, err
52- }
53- file.Close() // Close the file after checking
54- mdls, err := template.New(filename).ParseFS(fs, filename)
55- if err != nil {
56- logger.Error().Err(err).Msg("Failed to parse template")
57- return nil, err
58- }
59- return &TXTRenderer{
60- Models: mdls,
61- Logger: logger,
62- }, nil
63-}
@@ -1,63 +0,0 @@
1-package templating
2-
3-import (
4- "bytes"
5- "errors"
6- "fmt"
7- "io/fs"
8- "text/template"
9-
10- "gogs.bnema.dev/gordon-echo/pkg/utils"
11-)
12-
13-type TXTRenderer struct {
14- Models *template.Template
15- Format string
16- ParseError error
17- Logger *utils.Logger
18-}
19-
20-// Render function renders the template with the given data
21-func (r *TXTRenderer) TXTRender(data interface{}) (string, error) {
22- if r.ParseError != nil {
23- r.Logger.Error().Err(r.ParseError).Msg("Parse error")
24- return "", r.ParseError
25- }
26-
27- if r.Models == nil {
28- err := errors.New("invalid or nil model")
29- r.Logger.Error().Err(r.ParseError).Msg("Parse error")
30-
31- return "", err
32- }
33-
34- buf := new(bytes.Buffer)
35- err := r.Models.Execute(buf, data)
36- if err != nil {
37- r.Logger.Error().Err(err).Msg("Render error")
38- return "", err
39- }
40-
41- return buf.String(), nil
42-}
43-
44-// GetRenderer function returns a new Renderer instance
45-func GetTXTRenderer(filename string, fs fs.FS, logger *utils.Logger) (*TXTRenderer, error) {
46- fmt.Println(filename)
47- // Check if the file exists in the provided fs.FS using fs.Open
48- file, err := fs.Open(filename)
49- if err != nil {
50- logger.Error().Err(err).Msg("Template or model '%s' not found" + filename)
51- return nil, err
52- }
53- file.Close() // Close the file after checking
54- mdls, err := template.New(filename).ParseFS(fs, filename)
55- if err != nil {
56- logger.Error().Err(err).Msg("Failed to parse template")
57- return nil, err
58- }
59- return &TXTRenderer{
60- Models: mdls,
61- Logger: logger,
62- }, nil
63-}
D pkg/utils/detect-first-launch.go +0 -14
@@ -1,14 +0,0 @@
1-package utils
2-
3-import (
4- "os"
5-)
6-
7-// IsConfigFilePresent checks if it's the first launch of the app based on the existence of gordon.yml
8-func IsConfigFilePresent() bool {
9- // Check if gordon.yml exists at the root of the project
10- if _, err := os.Stat("gordon.yml"); os.IsNotExist(err) {
11- return true
12- }
13- return false
14-}
@@ -1,14 +0,0 @@
1-package utils
2-
3-import (
4- "os"
5-)
6-
7-// IsConfigFilePresent checks if it's the first launch of the app based on the existence of gordon.yml
8-func IsConfigFilePresent() bool {
9- // Check if gordon.yml exists at the root of the project
10- if _, err := os.Stat("gordon.yml"); os.IsNotExist(err) {
11- return true
12- }
13- return false
14-}
D pkg/utils/logger.go +0 -282
@@ -1,282 +0,0 @@
1-package utils
2-
3-import (
4- "bufio"
5- "fmt"
6- "io"
7- "os"
8-
9- "github.com/labstack/gommon/log"
10- "github.com/rs/zerolog"
11-)
12-
13-const (
14- DEBUG LogLevel = "debug"
15- INFO LogLevel = "info"
16- WARN LogLevel = "warn"
17- ERROR LogLevel = "error"
18- FATAL LogLevel = "fatal"
19- PANIC LogLevel = "panic"
20-)
21-
22-type LogLevel string
23-
24-type Logger struct {
25- zerolog.Logger
26-}
27-
28-type stdoutCapture struct {
29- logger *Logger
30-}
31-
32-type EchoLoggerWrapper struct {
33- *Logger
34- output io.Writer
35-}
36-
37-// LogFunc is a function that logs a message with optional context.
38-type LogFunc func() *zerolog.Event
39-
40-// Map log levels to zerolog log functions
41-func (l *Logger) getLogFunc(level LogLevel) LogFunc {
42- switch level {
43- case DEBUG:
44- return func() *zerolog.Event { return l.Logger.Debug() }
45- case INFO:
46- return func() *zerolog.Event { return l.Logger.Info() }
47- case WARN:
48- return func() *zerolog.Event { return l.Logger.Warn() }
49- case ERROR:
50- return func() *zerolog.Event { return l.Logger.Error() }
51- case FATAL:
52- return func() *zerolog.Event { return l.Logger.Fatal() }
53- case PANIC:
54- return func() *zerolog.Event { return l.Logger.Panic() }
55- default:
56- return func() *zerolog.Event { return l.Logger.Info() } // default to INFO if unknown level
57- }
58-}
59-
60-func (l *Logger) Log(level LogLevel, i ...interface{}) {
61- logFunc := l.getLogFunc(level)
62- logFunc().Msg(fmt.Sprint(i...))
63-}
64-
65-func NewLogger() *Logger {
66- writer := zerolog.ConsoleWriter{Out: os.Stdout}
67- zl := zerolog.New(writer).With().Timestamp().Logger()
68- return &Logger{zl}
69-}
70-
71-func (s *stdoutCapture) Write(p []byte) (n int, err error) {
72- s.logger.Info().Str("type", "app").Msg(string(p))
73- return len(p), nil
74-}
75-
76-func CaptureSTDOUT(logger *Logger) {
77- // Create a reading and writing pipe
78- reader, writer, _ := os.Pipe()
79-
80- // Replace os.Stdout with the writer end of the pipe
81- os.Stdout = writer
82-
83- // Start a goroutine to read from the pipe
84- go func() {
85- scanner := bufio.NewScanner(reader)
86- for scanner.Scan() {
87- text := scanner.Text()
88- logger.Debug().Str("type", "app").Msg(text)
89- }
90- }()
91-}
92-
93-func NewEchoLoggerWrapper(l *Logger) *EchoLoggerWrapper {
94- return &EchoLoggerWrapper{Logger: l, output: os.Stdout}
95-}
96-
97-func (l *Logger) SetOutput(w io.Writer) *Logger {
98- l.Logger = l.Logger.Output(w)
99- return l
100-}
101-
102-func (l *Logger) SetLevel(level LogLevel) *Logger {
103- l.Logger = l.Logger.Level(getZerologLevel(level))
104- return l
105-}
106-
107-func getZerologLevel(level LogLevel) zerolog.Level {
108- switch level {
109- case DEBUG:
110- return zerolog.DebugLevel
111- case INFO:
112- return zerolog.InfoLevel
113- case WARN:
114- return zerolog.WarnLevel
115- case ERROR:
116- return zerolog.ErrorLevel
117- case FATAL:
118- return zerolog.FatalLevel
119- case PANIC:
120- return zerolog.PanicLevel
121- default:
122- return zerolog.InfoLevel // default to INFO if unknown level
123- }
124-}
125-
126-func CreateLogsDir(logDir string) error {
127- if _, err := os.Stat(logDir); os.IsNotExist(err) {
128- err := os.MkdirAll(logDir, 0755)
129- if err != nil {
130- return err
131- }
132- }
133- return nil
134-}
135-
136-func (l *EchoLoggerWrapper) Output() io.Writer {
137- return l.output
138-}
139-
140-func (l *EchoLoggerWrapper) SetOutput(w io.Writer) {
141- l.output = w
142-}
143-
144-func (l *EchoLoggerWrapper) Prefix() string {
145- return ""
146-}
147-
148-func (l *EchoLoggerWrapper) SetPrefix(p string) {
149- // do nothing
150-}
151-
152-func (l *EchoLoggerWrapper) Level() log.Lvl {
153- return log.Lvl(l.Logger.GetLevel())
154-}
155-
156-// SetHeader
157-func (l *EchoLoggerWrapper) SetHeader(h string) {
158- // do nothing
159-}
160-
161-func (l *EchoLoggerWrapper) SetLevel(log.Lvl) {
162- // do nothing
163-}
164-
165-// SetEvent creates a new event with the given level and message.
166-func (l *EchoLoggerWrapper) SetEvent(level zerolog.Level, msg string) *zerolog.Event {
167- //cannot use utils.NewEchoLoggerWrapper(app.HTTPLogger) (value of type *utils.EchoLoggerWrapper) as echo.Logger value in assignment: *utils.EchoLoggerWrapper does not implement echo.Logger (wrong type for method Error)
168- return l.Logger.WithLevel(level).Str("message", msg)
169-}
170-
171-func (l *EchoLoggerWrapper) OutputStdout() bool {
172- return true
173-}
174-
175-// Helper function for methods that accept variadic slice of interfaces
176-func (l *EchoLoggerWrapper) LogWithArgs(level LogLevel, i ...interface{}) {
177- l.Logger.Log(level, i...)
178-}
179-
180-// Helper function for methods that accept a format string and arguments
181-func (l *EchoLoggerWrapper) LogWithFormat(level LogLevel, format string, args ...interface{}) {
182- l.Logger.Log(level, fmt.Sprintf(format, args...))
183-}
184-
185-// Helper function for methods that accept a log.JSON object
186-func (l *EchoLoggerWrapper) LogWithJSON(level LogLevel, j log.JSON) {
187- l.Logger.Log(level, j)
188-}
189-
190-// Debug
191-func (l *EchoLoggerWrapper) Debug(i ...interface{}) {
192- l.LogWithArgs(DEBUG, i...)
193-}
194-
195-func (l *EchoLoggerWrapper) Debugf(format string, args ...interface{}) {
196- l.LogWithFormat(DEBUG, format, args...)
197-}
198-
199-func (l *EchoLoggerWrapper) Debugj(j log.JSON) {
200- l.LogWithJSON(DEBUG, j)
201-}
202-
203-// Info
204-func (l *EchoLoggerWrapper) Info(i ...interface{}) {
205- l.LogWithArgs(INFO, i...)
206-}
207-
208-func (l *EchoLoggerWrapper) Infof(format string, args ...interface{}) {
209- l.LogWithFormat(INFO, format, args...)
210-}
211-
212-func (l *EchoLoggerWrapper) Infoj(j log.JSON) {
213- l.LogWithJSON(INFO, j)
214-}
215-
216-// Warn
217-
218-func (l *EchoLoggerWrapper) Warn(i ...interface{}) {
219- l.LogWithArgs(WARN, i...)
220-}
221-
222-func (l *EchoLoggerWrapper) Warnf(format string, args ...interface{}) {
223- l.LogWithFormat(WARN, format, args...)
224-}
225-
226-func (l *EchoLoggerWrapper) Warnj(j log.JSON) {
227- l.LogWithJSON(WARN, j)
228-}
229-
230-// Error
231-func (l *EchoLoggerWrapper) Error(i ...interface{}) {
232- l.LogWithArgs(ERROR, i...)
233-}
234-
235-func (l *EchoLoggerWrapper) Errorf(format string, args ...interface{}) {
236- l.LogWithFormat(ERROR, format, args...)
237-}
238-
239-func (l *EchoLoggerWrapper) Errorj(j log.JSON) {
240- l.LogWithJSON(ERROR, j)
241-}
242-
243-// Fatal
244-func (l *EchoLoggerWrapper) Fatal(i ...interface{}) {
245- l.LogWithArgs(FATAL, i...)
246-}
247-
248-func (l *EchoLoggerWrapper) Fatalf(format string, args ...interface{}) {
249- l.LogWithFormat(FATAL, format, args...)
250-}
251-
252-func (l *EchoLoggerWrapper) Fatalj(j log.JSON) {
253- l.LogWithJSON(FATAL, j)
254-}
255-
256-// Panic
257-
258-func (l *EchoLoggerWrapper) Panic(i ...interface{}) {
259- l.LogWithArgs(PANIC, i...)
260-}
261-
262-func (l *EchoLoggerWrapper) Panicf(format string, args ...interface{}) {
263- l.LogWithFormat(PANIC, format, args...)
264-}
265-
266-func (l *EchoLoggerWrapper) Panicj(j log.JSON) {
267- l.LogWithJSON(PANIC, j)
268-}
269-
270-// Print
271-
272-func (l *EchoLoggerWrapper) Print(i ...interface{}) {
273- l.LogWithArgs(INFO, i...)
274-}
275-
276-func (l *EchoLoggerWrapper) Printf(format string, args ...interface{}) {
277- l.LogWithFormat(INFO, format, args...)
278-}
279-
280-func (l *EchoLoggerWrapper) Printj(j log.JSON) {
281- l.LogWithJSON(INFO, j)
282-}
@@ -1,282 +0,0 @@
1-package utils
2-
3-import (
4- "bufio"
5- "fmt"
6- "io"
7- "os"
8-
9- "github.com/labstack/gommon/log"
10- "github.com/rs/zerolog"
11-)
12-
13-const (
14- DEBUG LogLevel = "debug"
15- INFO LogLevel = "info"
16- WARN LogLevel = "warn"
17- ERROR LogLevel = "error"
18- FATAL LogLevel = "fatal"
19- PANIC LogLevel = "panic"
20-)
21-
22-type LogLevel string
23-
24-type Logger struct {
25- zerolog.Logger
26-}
27-
28-type stdoutCapture struct {
29- logger *Logger
30-}
31-
32-type EchoLoggerWrapper struct {
33- *Logger
34- output io.Writer
35-}
36-
37-// LogFunc is a function that logs a message with optional context.
38-type LogFunc func() *zerolog.Event
39-
40-// Map log levels to zerolog log functions
41-func (l *Logger) getLogFunc(level LogLevel) LogFunc {
42- switch level {
43- case DEBUG:
44- return func() *zerolog.Event { return l.Logger.Debug() }
45- case INFO:
46- return func() *zerolog.Event { return l.Logger.Info() }
47- case WARN:
48- return func() *zerolog.Event { return l.Logger.Warn() }
49- case ERROR:
50- return func() *zerolog.Event { return l.Logger.Error() }
51- case FATAL:
52- return func() *zerolog.Event { return l.Logger.Fatal() }
53- case PANIC:
54- return func() *zerolog.Event { return l.Logger.Panic() }
55- default:
56- return func() *zerolog.Event { return l.Logger.Info() } // default to INFO if unknown level
57- }
58-}
59-
60-func (l *Logger) Log(level LogLevel, i ...interface{}) {
61- logFunc := l.getLogFunc(level)
62- logFunc().Msg(fmt.Sprint(i...))
63-}
64-
65-func NewLogger() *Logger {
66- writer := zerolog.ConsoleWriter{Out: os.Stdout}
67- zl := zerolog.New(writer).With().Timestamp().Logger()
68- return &Logger{zl}
69-}
70-
71-func (s *stdoutCapture) Write(p []byte) (n int, err error) {
72- s.logger.Info().Str("type", "app").Msg(string(p))
73- return len(p), nil
74-}
75-
76-func CaptureSTDOUT(logger *Logger) {
77- // Create a reading and writing pipe
78- reader, writer, _ := os.Pipe()
79-
80- // Replace os.Stdout with the writer end of the pipe
81- os.Stdout = writer
82-
83- // Start a goroutine to read from the pipe
84- go func() {
85- scanner := bufio.NewScanner(reader)
86- for scanner.Scan() {
87- text := scanner.Text()
88- logger.Debug().Str("type", "app").Msg(text)
89- }
90- }()
91-}
92-
93-func NewEchoLoggerWrapper(l *Logger) *EchoLoggerWrapper {
94- return &EchoLoggerWrapper{Logger: l, output: os.Stdout}
95-}
96-
97-func (l *Logger) SetOutput(w io.Writer) *Logger {
98- l.Logger = l.Logger.Output(w)
99- return l
100-}
101-
102-func (l *Logger) SetLevel(level LogLevel) *Logger {
103- l.Logger = l.Logger.Level(getZerologLevel(level))
104- return l
105-}
106-
107-func getZerologLevel(level LogLevel) zerolog.Level {
108- switch level {
109- case DEBUG:
110- return zerolog.DebugLevel
111- case INFO:
112- return zerolog.InfoLevel
113- case WARN:
114- return zerolog.WarnLevel
115- case ERROR:
116- return zerolog.ErrorLevel
117- case FATAL:
118- return zerolog.FatalLevel
119- case PANIC:
120- return zerolog.PanicLevel
121- default:
122- return zerolog.InfoLevel // default to INFO if unknown level
123- }
124-}
125-
126-func CreateLogsDir(logDir string) error {
127- if _, err := os.Stat(logDir); os.IsNotExist(err) {
128- err := os.MkdirAll(logDir, 0755)
129- if err != nil {
130- return err
131- }
132- }
133- return nil
134-}
135-
136-func (l *EchoLoggerWrapper) Output() io.Writer {
137- return l.output
138-}
139-
140-func (l *EchoLoggerWrapper) SetOutput(w io.Writer) {
141- l.output = w
142-}
143-
144-func (l *EchoLoggerWrapper) Prefix() string {
145- return ""
146-}
147-
148-func (l *EchoLoggerWrapper) SetPrefix(p string) {
149- // do nothing
150-}
151-
152-func (l *EchoLoggerWrapper) Level() log.Lvl {
153- return log.Lvl(l.Logger.GetLevel())
154-}
155-
156-// SetHeader
157-func (l *EchoLoggerWrapper) SetHeader(h string) {
158- // do nothing
159-}
160-
161-func (l *EchoLoggerWrapper) SetLevel(log.Lvl) {
162- // do nothing
163-}
164-
165-// SetEvent creates a new event with the given level and message.
166-func (l *EchoLoggerWrapper) SetEvent(level zerolog.Level, msg string) *zerolog.Event {
167- //cannot use utils.NewEchoLoggerWrapper(app.HTTPLogger) (value of type *utils.EchoLoggerWrapper) as echo.Logger value in assignment: *utils.EchoLoggerWrapper does not implement echo.Logger (wrong type for method Error)
168- return l.Logger.WithLevel(level).Str("message", msg)
169-}
170-
171-func (l *EchoLoggerWrapper) OutputStdout() bool {
172- return true
173-}
174-
175-// Helper function for methods that accept variadic slice of interfaces
176-func (l *EchoLoggerWrapper) LogWithArgs(level LogLevel, i ...interface{}) {
177- l.Logger.Log(level, i...)
178-}
179-
180-// Helper function for methods that accept a format string and arguments
181-func (l *EchoLoggerWrapper) LogWithFormat(level LogLevel, format string, args ...interface{}) {
182- l.Logger.Log(level, fmt.Sprintf(format, args...))
183-}
184-
185-// Helper function for methods that accept a log.JSON object
186-func (l *EchoLoggerWrapper) LogWithJSON(level LogLevel, j log.JSON) {
187- l.Logger.Log(level, j)
188-}
189-
190-// Debug
191-func (l *EchoLoggerWrapper) Debug(i ...interface{}) {
192- l.LogWithArgs(DEBUG, i...)
193-}
194-
195-func (l *EchoLoggerWrapper) Debugf(format string, args ...interface{}) {
196- l.LogWithFormat(DEBUG, format, args...)
197-}
198-
199-func (l *EchoLoggerWrapper) Debugj(j log.JSON) {
200- l.LogWithJSON(DEBUG, j)
201-}
202-
203-// Info
204-func (l *EchoLoggerWrapper) Info(i ...interface{}) {
205- l.LogWithArgs(INFO, i...)
206-}
207-
208-func (l *EchoLoggerWrapper) Infof(format string, args ...interface{}) {
209- l.LogWithFormat(INFO, format, args...)
210-}
211-
212-func (l *EchoLoggerWrapper) Infoj(j log.JSON) {
213- l.LogWithJSON(INFO, j)
214-}
215-
216-// Warn
217-
218-func (l *EchoLoggerWrapper) Warn(i ...interface{}) {
219- l.LogWithArgs(WARN, i...)
220-}
221-
222-func (l *EchoLoggerWrapper) Warnf(format string, args ...interface{}) {
223- l.LogWithFormat(WARN, format, args...)
224-}
225-
226-func (l *EchoLoggerWrapper) Warnj(j log.JSON) {
227- l.LogWithJSON(WARN, j)
228-}
229-
230-// Error
231-func (l *EchoLoggerWrapper) Error(i ...interface{}) {
232- l.LogWithArgs(ERROR, i...)
233-}
234-
235-func (l *EchoLoggerWrapper) Errorf(format string, args ...interface{}) {
236- l.LogWithFormat(ERROR, format, args...)
237-}
238-
239-func (l *EchoLoggerWrapper) Errorj(j log.JSON) {
240- l.LogWithJSON(ERROR, j)
241-}
242-
243-// Fatal
244-func (l *EchoLoggerWrapper) Fatal(i ...interface{}) {
245- l.LogWithArgs(FATAL, i...)
246-}
247-
248-func (l *EchoLoggerWrapper) Fatalf(format string, args ...interface{}) {
249- l.LogWithFormat(FATAL, format, args...)
250-}
251-
252-func (l *EchoLoggerWrapper) Fatalj(j log.JSON) {
253- l.LogWithJSON(FATAL, j)
254-}
255-
256-// Panic
257-
258-func (l *EchoLoggerWrapper) Panic(i ...interface{}) {
259- l.LogWithArgs(PANIC, i...)
260-}
261-
262-func (l *EchoLoggerWrapper) Panicf(format string, args ...interface{}) {
263- l.LogWithFormat(PANIC, format, args...)
264-}
265-
266-func (l *EchoLoggerWrapper) Panicj(j log.JSON) {
267- l.LogWithJSON(PANIC, j)
268-}
269-
270-// Print
271-
272-func (l *EchoLoggerWrapper) Print(i ...interface{}) {
273- l.LogWithArgs(INFO, i...)
274-}
275-
276-func (l *EchoLoggerWrapper) Printf(format string, args ...interface{}) {
277- l.LogWithFormat(INFO, format, args...)
278-}
279-
280-func (l *EchoLoggerWrapper) Printj(j log.JSON) {
281- l.LogWithJSON(INFO, j)
282-}
D pkg/utils/parser.go +0 -101
@@ -1,101 +0,0 @@
1-package utils
2-
3-import (
4- "fmt"
5- "io"
6- "io/fs"
7- "os"
8-
9- "github.com/tidwall/gjson"
10- "gopkg.in/yaml.v3"
11-)
12-
13-// Yaml Parser
14-type LangData map[string]interface{}
15-
16-type StringsYamlData struct {
17- StringsModel StringsModel `yaml:"strings_model"`
18- EN LangData `yaml:"en"`
19- FR LangData `yaml:"fr"`
20- CurrentLang LangData
21-}
22-
23-type ComposeModelData struct {
24- ComposeModel ComposeModel `yaml:"compose_model"`
25-}
26-
27-type StringsModel struct {
28- Header string `yaml:"header"`
29- Subheader string `yaml:"subheader"`
30-}
31-
32-type ComposeModel struct {
33- Header string `yaml:"header"`
34- Subheader string `yaml:"subheader"`
35-}
36-
37-// ReadDataFromYAML loads the data from the given YAML file from any filesystem
38-func ReadDataFromYAML(fs fs.FS, filename string, out interface{}) error {
39- // Open the YAML file
40- file, err := fs.Open(filename)
41- if err != nil {
42- return fmt.Errorf("failed to open %s: %w", filename, err)
43- }
44- defer file.Close()
45-
46- // Read the content of the file
47- content, err := io.ReadAll(file)
48- if err != nil {
49- return fmt.Errorf("failed to read %s: %w", filename, err)
50- }
51-
52- // Unmarshal the YAML content into the out interface
53- if err := yaml.Unmarshal(content, out); err != nil {
54- return fmt.Errorf("failed to unmarshal %s: %w", filename, err)
55- }
56-
57- return nil
58-}
59-
60-// PopulateDataFromYAML retrieves the data from the YAML file and sets the appropriate language data
61-func PopulateDataFromYAML(lang string, fs fs.FS, filename string, out interface{}) error {
62- err := ReadDataFromYAML(fs, filename, out)
63- if err != nil {
64- return fmt.Errorf("failed to read data from YAML: %w", err)
65- }
66-
67- // If the type assertion succeeds, we set the appropriate language data
68- if data, ok := out.(*StringsYamlData); ok {
69- switch lang {
70- case "en":
71- data.CurrentLang = data.EN
72- case "fr":
73- data.CurrentLang = data.FR
74- default: // default to English if no match
75- data.CurrentLang = data.EN
76- }
77- }
78-
79- return nil
80-}
81-
82-// Json Parser
83-
84-// ReadDataFromJSONFile loads the data from the given JSON file
85-func ReadDataFromJSONFile(filePath string) (string, error) {
86- bytes, err := os.ReadFile(filePath)
87- if err != nil {
88- return "", err
89- }
90- return string(bytes), nil
91-}
92-
93-// QueryJSON queries the provided JSON content based on the given gjson query and returns the results as a slice of strings
94-func QueryJSON(jsonContent, query string) []string {
95- results := gjson.GetMany(jsonContent, query)
96- var values []string
97- for _, result := range results {
98- values = append(values, result.String())
99- }
100- return values
101-}
@@ -1,101 +0,0 @@
1-package utils
2-
3-import (
4- "fmt"
5- "io"
6- "io/fs"
7- "os"
8-
9- "github.com/tidwall/gjson"
10- "gopkg.in/yaml.v3"
11-)
12-
13-// Yaml Parser
14-type LangData map[string]interface{}
15-
16-type StringsYamlData struct {
17- StringsModel StringsModel `yaml:"strings_model"`
18- EN LangData `yaml:"en"`
19- FR LangData `yaml:"fr"`
20- CurrentLang LangData
21-}
22-
23-type ComposeModelData struct {
24- ComposeModel ComposeModel `yaml:"compose_model"`
25-}
26-
27-type StringsModel struct {
28- Header string `yaml:"header"`
29- Subheader string `yaml:"subheader"`
30-}
31-
32-type ComposeModel struct {
33- Header string `yaml:"header"`
34- Subheader string `yaml:"subheader"`
35-}
36-
37-// ReadDataFromYAML loads the data from the given YAML file from any filesystem
38-func ReadDataFromYAML(fs fs.FS, filename string, out interface{}) error {
39- // Open the YAML file
40- file, err := fs.Open(filename)
41- if err != nil {
42- return fmt.Errorf("failed to open %s: %w", filename, err)
43- }
44- defer file.Close()
45-
46- // Read the content of the file
47- content, err := io.ReadAll(file)
48- if err != nil {
49- return fmt.Errorf("failed to read %s: %w", filename, err)
50- }
51-
52- // Unmarshal the YAML content into the out interface
53- if err := yaml.Unmarshal(content, out); err != nil {
54- return fmt.Errorf("failed to unmarshal %s: %w", filename, err)
55- }
56-
57- return nil
58-}
59-
60-// PopulateDataFromYAML retrieves the data from the YAML file and sets the appropriate language data
61-func PopulateDataFromYAML(lang string, fs fs.FS, filename string, out interface{}) error {
62- err := ReadDataFromYAML(fs, filename, out)
63- if err != nil {
64- return fmt.Errorf("failed to read data from YAML: %w", err)
65- }
66-
67- // If the type assertion succeeds, we set the appropriate language data
68- if data, ok := out.(*StringsYamlData); ok {
69- switch lang {
70- case "en":
71- data.CurrentLang = data.EN
72- case "fr":
73- data.CurrentLang = data.FR
74- default: // default to English if no match
75- data.CurrentLang = data.EN
76- }
77- }
78-
79- return nil
80-}
81-
82-// Json Parser
83-
84-// ReadDataFromJSONFile loads the data from the given JSON file
85-func ReadDataFromJSONFile(filePath string) (string, error) {
86- bytes, err := os.ReadFile(filePath)
87- if err != nil {
88- return "", err
89- }
90- return string(bytes), nil
91-}
92-
93-// QueryJSON queries the provided JSON content based on the given gjson query and returns the results as a slice of strings
94-func QueryJSON(jsonContent, query string) []string {
95- results := gjson.GetMany(jsonContent, query)
96- var values []string
97- for _, result := range results {
98- values = append(values, result.String())
99- }
100- return values
101-}
D pkg/utils/tty.go +0 -65
@@ -1,65 +0,0 @@
1-package utils
2-
3-import (
4- "net/http"
5- "os"
6- "os/exec"
7- "os/signal"
8- "syscall"
9- "time"
10-
11- "github.com/rs/zerolog"
12-)
13-
14-// SetRawMode sets the terminal to raw mode.
15-func SetRawMode() {
16- cmd := exec.Command("stty", "raw", "-echo")
17- cmd.Stdin = os.Stdin
18- _ = cmd.Run()
19-}
20-
21-// RestoreTerminal restores the terminal settings.
22-func RestoreTerminal() {
23- cmd := exec.Command("stty", "-raw", "echo")
24- cmd.Stdin = os.Stdin
25- _ = cmd.Run()
26-}
27-
28-func RunAppCatchSIGINT(startServerFunc func() error, logger *zerolog.Logger) {
29- // Start the server in a separate goroutine
30- go func() {
31- if err := startServerFunc(); err != nil && err != http.ErrServerClosed {
32- logger.Fatal().Err(err).Msg("Failed to start the server")
33- }
34- }()
35-
36- // Catch SIGINT and SIGTERM signals without raw mode initially
37- c := make(chan os.Signal, 1)
38- signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
39- <-c
40-
41- logger.Warn().Msg("Graceful shutdown initiated")
42- logger.Warn().Msg("Server will shutdown in 5 seconds press ENTER to cancel")
43-
44- // Now, set raw mode
45- SetRawMode()
46- defer RestoreTerminal()
47-
48- // Channel to signal a key press
49- keypress := make(chan struct{})
50-
51- // Goroutine to listen for a key press
52- go func() {
53- var b []byte = make([]byte, 1)
54- os.Stdin.Read(b)
55- keypress <- struct{}{}
56- }()
57-
58- // Wait for either a key press or the 5-second timer
59- select {
60- case <-time.After(5 * time.Second):
61- logger.Info().Msg("Server shutdown")
62- case <-keypress:
63- logger.Info().Msg("Shutdown cancelled by user")
64- }
65-}
@@ -1,65 +0,0 @@
1-package utils
2-
3-import (
4- "net/http"
5- "os"
6- "os/exec"
7- "os/signal"
8- "syscall"
9- "time"
10-
11- "github.com/rs/zerolog"
12-)
13-
14-// SetRawMode sets the terminal to raw mode.
15-func SetRawMode() {
16- cmd := exec.Command("stty", "raw", "-echo")
17- cmd.Stdin = os.Stdin
18- _ = cmd.Run()
19-}
20-
21-// RestoreTerminal restores the terminal settings.
22-func RestoreTerminal() {
23- cmd := exec.Command("stty", "-raw", "echo")
24- cmd.Stdin = os.Stdin
25- _ = cmd.Run()
26-}
27-
28-func RunAppCatchSIGINT(startServerFunc func() error, logger *zerolog.Logger) {
29- // Start the server in a separate goroutine
30- go func() {
31- if err := startServerFunc(); err != nil && err != http.ErrServerClosed {
32- logger.Fatal().Err(err).Msg("Failed to start the server")
33- }
34- }()
35-
36- // Catch SIGINT and SIGTERM signals without raw mode initially
37- c := make(chan os.Signal, 1)
38- signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
39- <-c
40-
41- logger.Warn().Msg("Graceful shutdown initiated")
42- logger.Warn().Msg("Server will shutdown in 5 seconds press ENTER to cancel")
43-
44- // Now, set raw mode
45- SetRawMode()
46- defer RestoreTerminal()
47-
48- // Channel to signal a key press
49- keypress := make(chan struct{})
50-
51- // Goroutine to listen for a key press
52- go func() {
53- var b []byte = make([]byte, 1)
54- os.Stdin.Read(b)
55- keypress <- struct{}{}
56- }()
57-
58- // Wait for either a key press or the 5-second timer
59- select {
60- case <-time.After(5 * time.Second):
61- logger.Info().Msg("Server shutdown")
62- case <-keypress:
63- logger.Info().Msg("Shutdown cancelled by user")
64- }
65-}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close