From cbc66c565198084363da3ca0dec7dfc256354e9a Mon Sep 17 00:00:00 2001 From: zervo Date: Thu, 31 Jul 2025 05:19:15 +0200 Subject: [PATCH] Updates A bunch of updatesss --- cmd/fileserver/main.go | 15 +++++++- embed.go | 3 ++ internal/config/config.go | 40 ++++++++++++++++++--- internal/server/controllers/directories.go | 16 +++++++++ internal/server/server.go | 39 ++++++++++++++++---- internal/server/views/directories.go | 29 +++++++++++++++ internal/server/views/error.go | 8 +++++ internal/util/data/layout.go | 13 +++++++ static/favicon.ico | Bin 0 -> 270398 bytes templates/directories.html | 23 ++++++++++++ templates/layouts/main.html | 27 ++++++++++++++ templates/{index.html => list.html} | 1 - templates/partials/badge.html | 3 ++ templates/partials/footer.html | 23 ++++++++++++ 14 files changed, 227 insertions(+), 13 deletions(-) create mode 100644 internal/server/controllers/directories.go create mode 100644 internal/server/views/directories.go create mode 100644 internal/server/views/error.go create mode 100644 internal/util/data/layout.go create mode 100644 static/favicon.ico create mode 100644 templates/directories.html create mode 100644 templates/layouts/main.html rename templates/{index.html => list.html} (70%) create mode 100644 templates/partials/badge.html create mode 100644 templates/partials/footer.html diff --git a/cmd/fileserver/main.go b/cmd/fileserver/main.go index def7ee4..185592d 100644 --- a/cmd/fileserver/main.go +++ b/cmd/fileserver/main.go @@ -1,6 +1,7 @@ package main import ( + "flag" "fmt" "log" @@ -9,7 +10,19 @@ import ( ) func main() { - cfg, err := config.LoadConfig("config.yaml") + var cfgFlag = flag.String("c", "config.yaml", "Configuration file to load") + var genFlag = flag.String("g", "", "Generate an example configuration to the given file") + flag.Parse() + + if *genFlag != "" { + if err := config.WriteExampleConfig(*genFlag); err != nil { + log.Fatalf("Failed to create example config: %v", err) + } + log.Printf("Generated example configuration at: %s\n", *genFlag) + return + } + + cfg, err := config.LoadConfig(*cfgFlag) if err != nil { log.Fatalf("Failed to load config: %v", err) } diff --git a/embed.go b/embed.go index b7a8e7f..1e16e82 100644 --- a/embed.go +++ b/embed.go @@ -4,3 +4,6 @@ import "embed" //go:embed templates/* var TemplateFS embed.FS + +//go:embed static/* +var StaticFS embed.FS diff --git a/internal/config/config.go b/internal/config/config.go index 4d0de72..29f5925 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,12 +6,34 @@ import ( "gopkg.in/yaml.v3" ) -type Config struct { - ServeDirectory string `yaml:"serveDirectory"` - Port int `int:"port"` +var defaultCfg = Config{ + ServerName: "My Fileserver", + Port: 8080, + Directories: []Directory{ + { + Id: "example", + DisplayName: "Example Directory", + Path: "/path/to/directory", + }, + }, } -// LoadConfig loads a Config from the given path. +// Directory represents a configuration for a directory to be served. +type Directory struct { + Id string `yaml:"id"` + DisplayName string `yaml:"display_name"` + Description string `yaml:"description"` + Path string `yaml:"path"` +} + +// Config represents a configuration for the fileserver. +type Config struct { + ServerName string `yaml:"server_name"` + Port int `yaml:"port"` + Directories []Directory `yaml:"directories"` +} + +// LoadConfig loads configuration from a YAML file. func LoadConfig(path string) (*Config, error) { data, err := os.ReadFile(path) if err != nil { @@ -25,3 +47,13 @@ func LoadConfig(path string) (*Config, error) { return &cfg, nil } + +// WriteExampleConfig writes an example configuration to a YAML file. +func WriteExampleConfig(filepath string) error { + data, err := yaml.Marshal(&defaultCfg) + if err != nil { + return err + } + + return os.WriteFile(filepath, data, 0644) +} diff --git a/internal/server/controllers/directories.go b/internal/server/controllers/directories.go new file mode 100644 index 0000000..deb97f7 --- /dev/null +++ b/internal/server/controllers/directories.go @@ -0,0 +1,16 @@ +package controllers + +import ( + "git.zervo.org/zervo/fileserver/internal/config" + "git.zervo.org/zervo/fileserver/internal/server/views" + "github.com/kataras/iris/v12" +) + +// DirectoriesRoute registers all routes under /directories +func DirectoriesRoute(app *iris.Application, cfg *config.Config) { + party := app.Party("/directories") + + party.Get("/", func(ctx iris.Context) { + views.DirectoriesView(ctx, cfg) + }) +} diff --git a/internal/server/server.go b/internal/server/server.go index 0489f38..e642c7b 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -1,6 +1,7 @@ package server import ( + "errors" "fmt" "io/fs" "net/http" @@ -9,6 +10,7 @@ import ( "git.zervo.org/zervo/fileserver" "git.zervo.org/zervo/fileserver/internal/config" + "git.zervo.org/zervo/fileserver/internal/server/controllers" "github.com/kataras/iris/v12" "github.com/kataras/iris/v12/view" ) @@ -17,16 +19,23 @@ import ( func Start(cfg *config.Config) error { app := iris.New() - subFs, err := fs.Sub(fileserver.TemplateFS, "templates") + templates, err := fs.Sub(fileserver.TemplateFS, "templates") if err != nil { - return fmt.Errorf("Failed to sub fs") + return errors.New("fs.Sub operation on TemplateFS") } - tmpl := view.HTML(subFs, ".html") - app.RegisterView(tmpl) + templater := view.HTML(templates, ".html").Layout("layouts/main.html") + app.RegisterView(templater) - app.Get("/", func(ctx iris.Context) { - files, err := os.ReadDir(cfg.ServeDirectory) + // Register core routes + app.Get("/", index) + app.Get("/favicon.ico", favicon) + + // Register controller routes + controllers.DirectoriesRoute(app, cfg) + + app.Get("/aaa", func(ctx iris.Context) { + files, err := os.ReadDir("test") if err != nil { ctx.StatusCode(http.StatusInternalServerError) ctx.WriteString("Could not read directory") @@ -48,9 +57,25 @@ func Start(cfg *config.Config) error { app.Get("/download/{filename:string}", func(ctx iris.Context) { filename := ctx.Params().Get("filename") - filePath := filepath.Join(cfg.ServeDirectory, filename) + filePath := filepath.Join("test", filename) ctx.SendFile(filePath, filename) }) return app.Listen(":" + fmt.Sprint(cfg.Port)) } + +// index redirects request from '/' to '/directories'. +func index(ctx iris.Context) { + ctx.Redirect("/directories") +} + +// favicon returns favicon data upon request. +func favicon(ctx iris.Context) { + data, err := fileserver.StaticFS.ReadFile("static/favicon.ico") + if err != nil { + ctx.StatusCode(http.StatusNotFound) + return + } + ctx.ContentType("image/x-icon") + ctx.Write(data) +} diff --git a/internal/server/views/directories.go b/internal/server/views/directories.go new file mode 100644 index 0000000..8b3a989 --- /dev/null +++ b/internal/server/views/directories.go @@ -0,0 +1,29 @@ +package views + +import ( + "fmt" + + "git.zervo.org/zervo/fileserver/internal/config" + "git.zervo.org/zervo/fileserver/internal/util/data" + "github.com/kataras/iris/v12" +) + +type directorypage struct { + Directories []config.Directory +} + +// DirectoriesView renders the view for '/directories'. +func DirectoriesView(ctx iris.Context, cfg *config.Config) { + ctx.CompressWriter(true) + ctx.ViewData("", data.LayoutData{ + ServerName: cfg.ServerName, + ServerVersion: "0.0.1", + Page: directorypage{ + Directories: cfg.Directories, + }, + }) + if err := ctx.View("directories.html"); err != nil { + errorView(ctx, fmt.Errorf("Failed to render DirectoriesView: %v", err)) + return + } +} diff --git a/internal/server/views/error.go b/internal/server/views/error.go new file mode 100644 index 0000000..f8a6cbd --- /dev/null +++ b/internal/server/views/error.go @@ -0,0 +1,8 @@ +package views + +import "github.com/kataras/iris/v12" + +// errorView renders a view for unexpected errors. +func errorView(ctx iris.Context, err error) { + ctx.HTML(`

Unexpected Error


%s

`, err.Error()) +} diff --git a/internal/util/data/layout.go b/internal/util/data/layout.go new file mode 100644 index 0000000..faf0349 --- /dev/null +++ b/internal/util/data/layout.go @@ -0,0 +1,13 @@ +package data + +// LayoutData holds templating data for a layout. +type LayoutData struct { + // ServerName is the custom name of the server instance. + ServerName string + + // ServerVersion is a string representing the server version. + ServerVersion string + + // Page contains page/view-specific data. + Page any +} diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..eae56c0f8c4bb9846c879b6d4d8890f14ec9faa7 GIT binary patch literal 270398 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 Ln+%WvTL%6gCosQT literal 0 HcmV?d00001 diff --git a/templates/directories.html b/templates/directories.html new file mode 100644 index 0000000..0b50f1d --- /dev/null +++ b/templates/directories.html @@ -0,0 +1,23 @@ + + + +

Welcome To

+ {{ render "partials/badge.html" . }} +

Select Directory

+
+ {{range .Page.Directories}} +
+
+
{{.DisplayName}}
+

{{.Description}}

+ Open +
+
+ {{else}} +
+ No directories found.
If you are the administrator of this server,
you can add directories in the server configuration. +
+ {{end}} +
+ + \ No newline at end of file diff --git a/templates/layouts/main.html b/templates/layouts/main.html new file mode 100644 index 0000000..c0b7fcf --- /dev/null +++ b/templates/layouts/main.html @@ -0,0 +1,27 @@ + + + + + + + + {{.ServerName}} + + + + +
+
+ {{ yield . }} +
+
+ + {{ render "partials/footer.html" . }} + + + + \ No newline at end of file diff --git a/templates/index.html b/templates/list.html similarity index 70% rename from templates/index.html rename to templates/list.html index f692b5e..84482e3 100644 --- a/templates/index.html +++ b/templates/list.html @@ -2,7 +2,6 @@ Go FileServer -

File Server

diff --git a/templates/partials/badge.html b/templates/partials/badge.html new file mode 100644 index 0000000..a472c44 --- /dev/null +++ b/templates/partials/badge.html @@ -0,0 +1,3 @@ +
+

{{if .ServerName}}{{.ServerName}}{{else}}My Fileserver{{end}}

+
\ No newline at end of file diff --git a/templates/partials/footer.html b/templates/partials/footer.html new file mode 100644 index 0000000..41dba5d --- /dev/null +++ b/templates/partials/footer.html @@ -0,0 +1,23 @@ + \ No newline at end of file