feat!: templ + htmx build system

This commit is contained in:
Christian Ditaputratama 2024-10-29 20:41:22 +07:00
parent 03570a2200
commit be32011cfa
No known key found for this signature in database
GPG key ID: 31D3D06D77950979
19 changed files with 527 additions and 17 deletions

View file

@ -7,14 +7,14 @@ tmp_dir = "tmp"
bin = "./tmp/main" bin = "./tmp/main"
cmd = "make dev" cmd = "make dev"
delay = 0 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_file = []
exclude_regex = ["_test.go"] exclude_regex = ["_test.go", ".*_templ.go"]
exclude_unchanged = false exclude_unchanged = false
follow_symlink = false follow_symlink = false
full_bin = "" full_bin = ""
include_dir = [] include_dir = []
include_ext = ["go", "tpl", "tmpl", "html", "css", "js"] include_ext = ["go", "templ", "html", "css", "js"]
include_file = [] include_file = []
kill_delay = "0s" kill_delay = "0s"
log = "build-errors.log" log = "build-errors.log"

2
.gitignore vendored
View file

@ -3,3 +3,5 @@
/node_modules /node_modules
/tmp /tmp
/assets/geoip /assets/geoip
/internal/handler/views/assets/css/**/*
/internal/handler/views/assets/js/**/*

View file

@ -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) # This called from air cmd (see .air.toml)
.PHONY: dev .PHONY: dev
dev: dev: templ tailwind
go build -ldflags="$(BUILD_LDFLAGS)" -tags server -o ./tmp/main . go build -ldflags="$(BUILD_LDFLAGS)" -tags server -o ./tmp/main .
.PHONY: build .PHONY: build
build: client server build: client server
.PHONY: ui
ui:
go generate ./...
.PHONY: client .PHONY: client
client: client:
CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build \ CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build \
@ -51,7 +47,7 @@ client:
-o bin/${BINARY_NAME}-client-linux-arm64 -o bin/${BINARY_NAME}-client-linux-arm64
.PHONY: server .PHONY: server
server: ui server: prepare templ tailwind
CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build \ CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build \
-ldflags="$(BUILD_LDFLAGS)" -tags server \ -ldflags="$(BUILD_LDFLAGS)" -tags server \
-o bin/${BINARY_NAME}-server-linux-amd64 -o bin/${BINARY_NAME}-server-linux-amd64
@ -59,11 +55,34 @@ server: ui
-ldflags="$(BUILD_LDFLAGS)" -tags server \ -ldflags="$(BUILD_LDFLAGS)" -tags server \
-o bin/${BINARY_NAME}-server-linux-arm64 -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 .PHONY: clean
clean: clean:
go clean go clean
rm -rfv ./bin 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 .PHONY: lint
lint: lint:
@ -82,8 +101,10 @@ bench:
# And make sure the inventory and deploy-*.yml file is properly configured. # And make sure the inventory and deploy-*.yml file is properly configured.
.PHONY: deploy-server .PHONY: deploy-server
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 .PHONY: deploy-prober
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

View file

@ -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: To build the executable binaries, you need:
- Go >= 1.22 - 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 ### Server & Prober requirements
@ -67,6 +75,10 @@ Systemd example: [xmr-nodes-prober.service][prober-systemd-service] and
## Development and Deployment ## 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). See the [Makefile](./Makefile).
## ToDo's ## 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" [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-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" [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" [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" [rclone]: https://github.com/rclone/rclone "rclone GitHub repository"
[monerofail-repo]: https://github.com/lalanza808/monero.fail "Lalanza808's monero.fail GitHub repository" [monerofail-repo]: https://github.com/lalanza808/monero.fail "Lalanza808's monero.fail GitHub repository"

BIN
bun.lockb Executable file

Binary file not shown.

View file

@ -13,6 +13,7 @@ import (
"github.com/ditatompel/xmr-remote-nodes/internal/cron" "github.com/ditatompel/xmr-remote-nodes/internal/cron"
"github.com/ditatompel/xmr-remote-nodes/internal/database" "github.com/ditatompel/xmr-remote-nodes/internal/database"
"github.com/ditatompel/xmr-remote-nodes/internal/handler" "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"
"github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/cors"
@ -79,6 +80,8 @@ func serve() {
AllowCredentials: true, AllowCredentials: true,
})) }))
app.Use("/assets", views.EmbedAssets())
handler.V1Api(app) handler.V1Api(app)
app.Use("/", filesystem.New(filesystem.Config{ app.Use("/", filesystem.New(filesystem.Config{
Root: frontend.SvelteKitHandler(), Root: frontend.SvelteKitHandler(),

View file

@ -1,3 +1,9 @@
# UI # 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.

3
go.mod
View file

@ -3,6 +3,7 @@ module github.com/ditatompel/xmr-remote-nodes
go 1.22.2 go 1.22.2
require ( require (
github.com/a-h/templ v0.2.778
github.com/go-sql-driver/mysql v1.8.1 github.com/go-sql-driver/mysql v1.8.1
github.com/gofiber/fiber/v2 v2.52.5 github.com/gofiber/fiber/v2 v2.52.5
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
@ -15,7 +16,7 @@ require (
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect 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/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.17.0 // indirect github.com/klauspost/compress v1.17.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect

8
go.sum
View file

@ -1,7 +1,9 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/a-h/templ v0.2.778 h1:VzhOuvWECrwOec4790lcLlZpP4Iptt5Q4K9aFxQmtaM=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 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/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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/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 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= 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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=

View file

@ -1,13 +1,35 @@
package handler package handler
import ( import (
"fmt"
"strconv" "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/ditatompel/xmr-remote-nodes/internal/monero"
"github.com/gofiber/fiber/v2" "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 // Returns a single node information based on `id` query param
func Node(c *fiber.Ctx) error { func Node(c *fiber.Ctx) error {
nodeId, err := c.ParamsInt("id", 0) nodeId, err := c.ParamsInt("id", 0)

View file

@ -6,6 +6,7 @@ import (
// V1 API routes // V1 API routes
func V1Api(app *fiber.App) { func V1Api(app *fiber.App) {
app.Get("/", homeHandler)
v1 := app.Group("/api/v1") v1 := app.Group("/api/v1")
// these routes are public, they don't require a prober api key // these routes are public, they don't require a prober api key

View file

@ -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,
})
}

View file

@ -0,0 +1,4 @@
package views
templ Home() {
}

View file

@ -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

View file

@ -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) {
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="utf-8"/>
<title>{ m.Title } — xmr.ditatompel.com</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="title" content={ fmt.Sprintf("%s — xmr.ditatompel.com", m.Title) }/>
<meta name="description" content={ m.Description }/>
<meta name="keywords" content={ m.Keywords }/>
<meta name="robots" content={ m.Robots }/>
<meta name="theme-color" content="#272b31"/>
<meta name="author" content="ditatompel"/>
<meta property="og:site_name" content="xmr.ditatompel.com"/>
<meta property="og:type" content="website"/>
<meta property="og:url" content={ m.Permalink }/>
<meta property="og:locale" content="en_US"/>
<meta property="og:title" content={ fmt.Sprintf("%s — xmr.ditatompel.com", m.Title) }/>
<meta property="og:description" content={ m.Description }/>
<link href={ fmt.Sprintf("/assets/css/main.min.css?t=%d", buildTime) } rel="stylesheet"/>
<script src={ fmt.Sprintf("/assets/js/htmx.min.js?t=%d", buildTime) }></script>
</head>
<body class="bg-neutral-900" hx-boost="true" hx-indicator="#hx-indicator-main">
<main class="flex flex-col h-screen">
{ children... }
</main>
<footer class="absolute bottom-0 inset-x-0 text-center py-5">
<div class="max-w-[85rem] mx-auto px-4 sm:px-6 lg:px-8">
<p class="text-sm text-white/50">XMR Nodes { config.Version }, <a href="https://github.com/ditatompel/xmr-remote-nodes">source code</a> licensed under <strong>GLWTPL</strong>.</p>
</div>
</footer>
</body>
</html>
}
templ BaseLayout(m Meta, cmp templ.Component) {
@base(m) {
@cmp
}
}
templ BlankLayout(cmp templ.Component) {
@cmp
}

View file

@ -0,0 +1,284 @@
// 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"
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
}
func base(m Meta) 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)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"en\" class=\"dark\"><head><meta charset=\"utf-8\"><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(m.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 25, Col: 19}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" — xmr.ditatompel.com</title><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"title\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s — xmr.ditatompel.com", m.Title))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 28, Col: 81}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><meta name=\"description\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(m.Description)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 29, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><meta name=\"keywords\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(m.Keywords)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 30, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><meta name=\"robots\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(m.Robots)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 31, Col: 41}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><meta name=\"theme-color\" content=\"#272b31\"><meta name=\"author\" content=\"ditatompel\"><meta property=\"og:site_name\" content=\"xmr.ditatompel.com\"><meta property=\"og:type\" content=\"website\"><meta property=\"og:url\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(m.Permalink)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 36, Col: 48}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><meta property=\"og:locale\" content=\"en_US\"><meta property=\"og:title\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s — xmr.ditatompel.com", m.Title))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 38, Col: 88}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><meta property=\"og:description\" content=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(m.Description)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 39, Col: 58}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><link href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/assets/css/main.min.css?t=%d", buildTime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 40, Col: 71}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" rel=\"stylesheet\"><script src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/assets/js/htmx.min.js?t=%d", buildTime))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 41, Col: 70}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></script></head><body class=\"bg-neutral-900\" hx-boost=\"true\" hx-indicator=\"#hx-indicator-main\"><main class=\"flex flex-col h-screen\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</main><footer class=\"absolute bottom-0 inset-x-0 text-center py-5\"><div class=\"max-w-[85rem] mx-auto px-4 sm:px-6 lg:px-8\"><p class=\"text-sm text-white/50\">XMR Nodes ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(config.Version)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/handler/views/layout.templ`, Line: 49, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(", <a href=\"https://github.com/ditatompel/xmr-remote-nodes\">source code</a> licensed under <strong>GLWTPL</strong>.</p></div></footer></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func BaseLayout(m Meta, cmp templ.Component) 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_Var13 := templ.GetChildren(ctx)
if templ_7745c5c3_Var13 == nil {
templ_7745c5c3_Var13 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var14 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
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_Err = cmp.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
templ_7745c5c3_Err = base(m).Render(templ.WithChildren(ctx, templ_7745c5c3_Var14), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
func BlankLayout(cmp templ.Component) 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_Var15 := templ.GetChildren(ctx)
if templ_7745c5c3_Var15 == nil {
templ_7745c5c3_Var15 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = cmp.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return templ_7745c5c3_Err
})
}
var _ = templruntime.GeneratedTemplate

View file

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

9
package.json Normal file
View file

@ -0,0 +1,9 @@
{
"dependencies": {
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"htmx.org": "^1.9.12",
"preline": "^2.5.1",
"tailwindcss": "^3.4.14"
}
}

17
tailwind.config.js Normal file
View file

@ -0,0 +1,17 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./internal/handler/views/*.templ",
"node_modules/preline/dist/*.js",
],
// enable dark mode via class strategy
// darkMode: "class",
theme: {
extend: {},
},
plugins: [
require("@tailwindcss/typography"),
require("@tailwindcss/forms"),
require("preline/plugin"),
],
};