diff --git a/.air.toml b/.air.toml index 85efed1..2966485 100644 --- a/.air.toml +++ b/.air.toml @@ -7,14 +7,14 @@ tmp_dir = "tmp" bin = "./tmp/main" cmd = "make dev" delay = 0 - exclude_dir = ["assets", "tmp", "testdata", "frontend/node_modules", "data", "bin"] + exclude_dir = ["assets", "tmp", "testdata", "node_modules", "data", "bin", "internal/handler/views/assets"] exclude_file = [] - exclude_regex = ["_test.go"] + exclude_regex = ["_test.go", ".*_templ.go"] exclude_unchanged = false follow_symlink = false full_bin = "" include_dir = [] - include_ext = ["go", "tpl", "tmpl", "html", "css", "js"] + include_ext = ["go", "templ", "html", "css", "js"] include_file = [] kill_delay = "0s" log = "build-errors.log" diff --git a/.gitignore b/.gitignore index df7bc84..4f8f074 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ /node_modules /tmp /assets/geoip +/internal/handler/views/assets/css/**/* +/internal/handler/views/assets/js/**/* diff --git a/Makefile b/Makefile index 5333259..f4ee109 100644 --- a/Makefile +++ b/Makefile @@ -31,16 +31,12 @@ BUILD_LDFLAGS := -s -w -X github.com/ditatompel/xmr-remote-nodes/internal/config # This called from air cmd (see .air.toml) .PHONY: dev -dev: +dev: templ tailwind go build -ldflags="$(BUILD_LDFLAGS)" -tags server -o ./tmp/main . .PHONY: build build: client server -.PHONY: ui -ui: - go generate ./... - .PHONY: client client: CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build \ @@ -51,7 +47,7 @@ client: -o bin/${BINARY_NAME}-client-linux-arm64 .PHONY: server -server: ui +server: prepare templ tailwind CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build \ -ldflags="$(BUILD_LDFLAGS)" -tags server \ -o bin/${BINARY_NAME}-server-linux-amd64 @@ -59,11 +55,34 @@ server: ui -ldflags="$(BUILD_LDFLAGS)" -tags server \ -o bin/${BINARY_NAME}-server-linux-arm64 +.PHONY: prepare +prepare: + bun install --frozen-lockfile + @mkdir -p ./internal/handler/views/assets/js + cp ./node_modules/htmx.org/dist/htmx.min.js ./internal/handler/views/assets/js + +# Compile template +.PHONY: templ +templ: + @echo "Compiling Templ template..." + templ generate + +.PHONY: tailwind +tailwind: + mkdir -p ./internal/handler/views/assets/css + @echo "Compiling TailwindCSS..." + bun tailwindcss -i ./internal/handler/views/src/css/main.css \ + -o ./internal/handler/views/assets/css/main.min.css \ + -c ./tailwind.config.js \ + --minify + .PHONY: clean clean: go clean rm -rfv ./bin - rm -rf ./frontend/build + rm -rfv ./tmp/main + rm -rf ./internal/handler/views/*_templ.go + rm -rf ./internal/handler/views/assets/css/ .PHONY: lint lint: @@ -82,8 +101,10 @@ bench: # And make sure the inventory and deploy-*.yml file is properly configured. .PHONY: deploy-server deploy-server: - ansible-playbook -i ./deployment/ansible/inventory.ini -l server ./deployment/ansible/deploy-server.yml -K + ansible-playbook -i ./deployment/ansible/inventory.ini \ + -l server ./deployment/ansible/deploy-server.yml -K .PHONY: deploy-prober deploy-prober: - ansible-playbook -i ./deployment/ansible/inventory.ini -l prober ./deployment/ansible/deploy-prober.yml -K + ansible-playbook -i ./deployment/ansible/inventory.ini \ + -l prober ./deployment/ansible/deploy-prober.yml -K diff --git a/README.md b/README.md index d6e4ec6..81d72a5 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,15 @@ serves the `/api` endpoint that is used by the clients and the Web UI itself. To build the executable binaries, you need: - Go >= 1.22 -- NodeJS >= 20 +- Bun >= 1.1.26 +- templ v0.2.778 + +> **Note**: +> +> - If you want to contribute to the code, please use exact templ version +> (v0.2.778). +> - The UI is using Preline UI that uses [Lucide Icons][lucide-icons], use +> that for SVG icons. ### Server & Prober requirements @@ -67,6 +75,10 @@ Systemd example: [xmr-nodes-prober.service][prober-systemd-service] and ## Development and Deployment +1. Clone or fork this repository. +2. Prepare the assets: `make prepare`, +3. Run `air serve` (live reload using [air-verse/air][air-repo]). + See the [Makefile](./Makefile). ## ToDo's @@ -113,6 +125,7 @@ This project is licensed under [GLWTPL](./LICENSE). [server-systemd-service]: ./deployment/init/xmr-nodes-server.service "systemd service example for server" [prober-systemd-service]: ./deployment/init/xmr-nodes-prober.service "systemd service example for prober" [prober-systemd-timer]: ./deployment/init/xmr-nodes-prober.timer "systemd timer example for prober" +[air-repo]: https://github.com/air-verse/air "Air - Live reload for Go apps" [jtgrassie-monero-pool]: https://github.com/jtgrassie/monero-pool "A Monero mining pool server written in C" [rclone]: https://github.com/rclone/rclone "rclone GitHub repository" [monerofail-repo]: https://github.com/lalanza808/monero.fail "Lalanza808's monero.fail GitHub repository" diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..0178cf3 Binary files /dev/null and b/bun.lockb differ diff --git a/cmd/server/serve.go b/cmd/server/serve.go index 34e0b1c..645fc23 100644 --- a/cmd/server/serve.go +++ b/cmd/server/serve.go @@ -13,6 +13,7 @@ import ( "github.com/ditatompel/xmr-remote-nodes/internal/cron" "github.com/ditatompel/xmr-remote-nodes/internal/database" "github.com/ditatompel/xmr-remote-nodes/internal/handler" + "github.com/ditatompel/xmr-remote-nodes/internal/handler/views" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cors" @@ -79,6 +80,8 @@ func serve() { AllowCredentials: true, })) + app.Use("/assets", views.EmbedAssets()) + handler.V1Api(app) app.Use("/", filesystem.New(filesystem.Config{ Root: frontend.SvelteKitHandler(), diff --git a/frontend/README.md b/frontend/README.md index 0e4ddb5..cf6b32b 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,3 +1,9 @@ # UI -The UI is generated and embedded when the Go project is built. See [./frontend/embed.go](https://github.com/ditatompel/xmr-remote-nodes/blob/main/frontend/embed.go#L10-L13). +The UI is generated and embedded when the Go project is built. See +[./frontend/embed.go](https://github.com/ditatompel/xmr-remote-nodes/blob/main/frontend/embed.go#L10-L13). + +> **NOTE**: +> +> Since this project will not using Svelte anymore, anything under this +> directory will soon be removed. diff --git a/go.mod b/go.mod index 62ce0c7..5795639 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/ditatompel/xmr-remote-nodes go 1.22.2 require ( + github.com/a-h/templ v0.2.778 github.com/go-sql-driver/mysql v1.8.1 github.com/gofiber/fiber/v2 v2.52.5 github.com/google/uuid v1.6.0 @@ -15,7 +16,7 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect - github.com/andybalholm/brotli v1.0.5 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/klauspost/compress v1.17.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/go.sum b/go.sum index d3b5266..b63f146 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,9 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= -github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/a-h/templ v0.2.778 h1:VzhOuvWECrwOec4790lcLlZpP4Iptt5Q4K9aFxQmtaM= +github.com/a-h/templ v0.2.778/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -9,6 +11,8 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= diff --git a/internal/handler/response.go b/internal/handler/response.go index 4c1e954..2c4c124 100644 --- a/internal/handler/response.go +++ b/internal/handler/response.go @@ -1,13 +1,35 @@ package handler import ( + "fmt" "strconv" + "github.com/a-h/templ" + "github.com/ditatompel/xmr-remote-nodes/internal/handler/views" "github.com/ditatompel/xmr-remote-nodes/internal/monero" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/adaptor" ) +// Render Home Page +func homeHandler(c *fiber.Ctx) error { + p := views.Meta{ + Title: "Monero Remote Node", + Description: "A website that helps you monitor your favourite Monero remote nodes, but YOU BETTER RUN AND USE YOUR OWN NODE.", + Keywords: "monero,monero,xmr,monero node,xmrnode,cryptocurrency,monero remote node,monero testnet,monero stagenet", + Robots: "INDEX,FOLLOW", + Permalink: "https://xmr.ditatompel.com", + Identifier: "/", + } + + c.Set("Link", fmt.Sprintf(`<%s>; rel="canonical"`, p.Permalink)) + home := views.BaseLayout(p, views.Home()) + handler := adaptor.HTTPHandler(templ.Handler(home)) + + return handler(c) +} + // Returns a single node information based on `id` query param func Node(c *fiber.Ctx) error { nodeId, err := c.ParamsInt("id", 0) diff --git a/internal/handler/routes.go b/internal/handler/routes.go index 3535ea6..ff84ef3 100644 --- a/internal/handler/routes.go +++ b/internal/handler/routes.go @@ -6,6 +6,7 @@ import ( // V1 API routes func V1Api(app *fiber.App) { + app.Get("/", homeHandler) v1 := app.Group("/api/v1") // these routes are public, they don't require a prober api key diff --git a/internal/handler/views/embed.go b/internal/handler/views/embed.go new file mode 100644 index 0000000..8030e1e --- /dev/null +++ b/internal/handler/views/embed.go @@ -0,0 +1,20 @@ +package views + +import ( + "embed" + "net/http" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/filesystem" +) + +//go:embed assets/* +var embedStatic embed.FS + +func EmbedAssets() fiber.Handler { + return filesystem.New(filesystem.Config{ + Root: http.FS(embedStatic), + PathPrefix: "assets", + Browse: false, + }) +} diff --git a/internal/handler/views/home.templ b/internal/handler/views/home.templ new file mode 100644 index 0000000..6e7a6f7 --- /dev/null +++ b/internal/handler/views/home.templ @@ -0,0 +1,4 @@ +package views + +templ Home() { +} diff --git a/internal/handler/views/home_templ.go b/internal/handler/views/home_templ.go new file mode 100644 index 0000000..44d399d --- /dev/null +++ b/internal/handler/views/home_templ.go @@ -0,0 +1,36 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.778 +package views + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +func Home() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + return templ_7745c5c3_Err + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/internal/handler/views/layout.templ b/internal/handler/views/layout.templ new file mode 100644 index 0000000..c78cb3f --- /dev/null +++ b/internal/handler/views/layout.templ @@ -0,0 +1,64 @@ +package views + +import ( + "fmt" + "github.com/ditatompel/xmr-remote-nodes/internal/config" + "time" +) + +var buildTime = time.Now().Unix() + +type Meta struct { + Title string + Description string + Keywords string + Robots string + Permalink string + Identifier string +} + +templ base(m Meta) { + + +
+ +