Merge pull request #81 from eduardolat/refactor-nodx

Refactor components to use NodX library, replacing gomponents referen…
This commit is contained in:
Luis Eduardo
2025-02-03 20:42:31 -06:00
committed by GitHub
83 changed files with 1809 additions and 1895 deletions

4
go.mod
View File

@@ -5,14 +5,14 @@ go 1.23.1
require (
github.com/adhocore/gronx v1.8.1
github.com/aws/aws-sdk-go v1.54.20
github.com/eduardolat/gomponents-lucide v1.2.0
github.com/go-co-op/gocron/v2 v2.11.0
github.com/go-playground/validator/v10 v10.22.0
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
github.com/labstack/echo/v4 v4.12.0
github.com/lib/pq v1.10.9
github.com/maragudk/gomponents v0.20.4
github.com/nodxdev/nodxgo v0.2.1
github.com/nodxdev/nodxgo-lucide v0.1.0
github.com/orsinium-labs/enum v1.4.0
github.com/stretchr/testify v1.9.0
golang.org/x/crypto v0.25.0

8
go.sum
View File

@@ -6,8 +6,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eduardolat/gomponents-lucide v1.2.0 h1:IzvsHfdfMjDF0c8aOxG7P0lawaSjwVLBo7z6VASOBbc=
github.com/eduardolat/gomponents-lucide v1.2.0/go.mod h1:C0Yx64QM2RXIal8pl/HdWU0xac0uek+IQI2eZAaeAwk=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/go-co-op/gocron/v2 v2.11.0 h1:IOowNA6SzwdRFnD4/Ol3Kj6G2xKfsoiiGq2Jhhm9bvE=
@@ -42,8 +40,6 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/maragudk/gomponents v0.20.4 h1:8ayYSzCyz1EUl/+LU5vOBR/K2wI18W7SscNAzzsxsDo=
github.com/maragudk/gomponents v0.20.4/go.mod h1:nHkNnZL6ODgMBeJhrZjkMHVvNdoYsfmpKB2/hjdQ0Hg=
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@@ -51,6 +47,10 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/nodxdev/nodxgo v0.2.1 h1:itU0ynZnPOgTtMpf7wDa4XR00cLMT9YUUK1QMg+zaUI=
github.com/nodxdev/nodxgo v0.2.1/go.mod h1:6RhpuOptMO8HT7ZGIzyAF+iH8ozJfRaneJZ1ehBp2YQ=
github.com/nodxdev/nodxgo-lucide v0.1.0 h1:C+ftEo944pzC2lHcwuzr1o1PghZVxy3s0CF+6WEM8xE=
github.com/nodxdev/nodxgo-lucide v0.1.0/go.mod h1:nw7hR6bEb9FtXzd+vnk0wkJZTl/YfM87faSx5pMWDBY=
github.com/orsinium-labs/enum v1.4.0 h1:3NInlfV76kuAg0kq2FFUondmg3WO7gMEgrPPrlzLDUM=
github.com/orsinium-labs/enum v1.4.0/go.mod h1:Qj5IK2pnElZtkZbGDxZMjpt7SUsn4tqE5vRelmWaBbc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=

View File

@@ -12,9 +12,9 @@ type renderer interface {
Render(w io.Writer) error
}
// RenderGomponent renders a gomponents component to the response of
// RenderNodx renders a NodX component to the response of
// the echo context.
func RenderGomponent(c echo.Context, code int, component renderer) error {
func RenderNodx(c echo.Context, code int, component renderer) error {
if component == nil {
return c.NoContent(code)
}

View File

@@ -24,7 +24,7 @@ func (m *nokRenderer) Render(w io.Writer) error {
return errors.New("render nok")
}
func TestRenderGomponent(t *testing.T) {
func TestRenderNodx(t *testing.T) {
// Setup renderer mocks
okRendererIns := new(okRenderer)
nokRendererIns := new(nokRenderer)
@@ -35,7 +35,7 @@ func TestRenderGomponent(t *testing.T) {
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
err := RenderGomponent(c, http.StatusOK, okRendererIns)
err := RenderNodx(c, http.StatusOK, okRendererIns)
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, c.Response().Status)
assert.Equal(t, "render ok", rec.Body.String())
@@ -47,20 +47,20 @@ func TestRenderGomponent(t *testing.T) {
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
err := RenderGomponent(c, http.StatusOK, nokRendererIns)
err := RenderNodx(c, http.StatusOK, nokRendererIns)
assert.NoError(t, err)
assert.Equal(t, http.StatusInternalServerError, rec.Code)
assert.Equal(t, "render nok", rec.Body.String())
})
}
func TestRenderNilGomponent(t *testing.T) {
func TestRenderNilNodx(t *testing.T) {
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
err := RenderGomponent(c, http.StatusOK, nil)
err := RenderNodx(c, http.StatusOK, nil)
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code)
assert.Equal(t, "", rec.Body.String())

View File

@@ -1,3 +0,0 @@
svg[data-glucide="icon"]:not([class*="size-"]) {
@apply size-4;
}

View File

@@ -0,0 +1,3 @@
svg[data-nodxgo="lucide"]:not([class*="size-"]) {
@apply size-4;
}

View File

@@ -1,7 +1,7 @@
@import "./partials/tailwind.css";
@import "./partials/alpine.css";
@import "./partials/general.css";
@import "./partials/glucide.css";
@import "./partials/nodx-lucide.css";
@import "./partials/htmx.css";
@import "./partials/slim-select.css";
@import "./partials/notyf.css";

View File

@@ -1,120 +1,122 @@
package alpine
import "github.com/maragudk/gomponents"
import (
nodx "github.com/nodxdev/nodxgo"
)
// Template is a generic function for rendering an <template> element.
func Template(children ...gomponents.Node) gomponents.Node {
return gomponents.El("template", children...)
func Template(children ...nodx.Node) nodx.Node {
return nodx.El("template", children...)
}
// XData is an attribute that renders a x-data="value]" attribute.
//
// https://alpinejs.dev/directives/data
func XData(value string) gomponents.Node {
return gomponents.Attr("x-data", value)
func XData(value string) nodx.Node {
return nodx.Attr("x-data", value)
}
// XFor is an attribute that renders a x-for="value]" attribute.
//
// https://alpinejs.dev/directives/for
func XFor(value string) gomponents.Node {
return gomponents.Attr("x-for", value)
func XFor(value string) nodx.Node {
return nodx.Attr("x-for", value)
}
// XInit is an attribute that renders a x-init="[value]" attribute.
//
// https://alpinejs.dev/directives/init
func XInit(value string) gomponents.Node {
return gomponents.Attr("x-init", value)
func XInit(value string) nodx.Node {
return nodx.Attr("x-init", value)
}
// XShow is an attribute that renders a x-show="[vlue]" attribute.
//
// https://alpinejs.dev/directives/show
func XShow(value string) gomponents.Node {
return gomponents.Attr("x-show", value)
func XShow(value string) nodx.Node {
return nodx.Attr("x-show", value)
}
// XBind is an attribute that renders a x-bind:[targetAttr]="[value]" attribute.
//
// https://alpinejs.dev/directives/bind
func XBind(targetAttr string, value string) gomponents.Node {
return gomponents.Attr("x-bind:"+targetAttr, value)
func XBind(targetAttr string, value string) nodx.Node {
return nodx.Attr("x-bind:"+targetAttr, value)
}
// XOn is an attribute that renders a x-on:[targetEvent]="[value]" attribute.
//
// https://alpinejs.dev/directives/on
func XOn(targetEvent string, value string) gomponents.Node {
return gomponents.Attr("x-on:"+targetEvent, value)
func XOn(targetEvent string, value string) nodx.Node {
return nodx.Attr("x-on:"+targetEvent, value)
}
// XText is an attribute that renders a x-text="[value]" attribute.
//
// https://alpinejs.dev/directives/text
func XText(value string) gomponents.Node {
return gomponents.Attr("x-text", value)
func XText(value string) nodx.Node {
return nodx.Attr("x-text", value)
}
// XHTML is an attribute that renders a x-html="[value]" attribute.
//
// https://alpinejs.dev/directives/html
func XHTML(value string) gomponents.Node {
return gomponents.Attr("x-html", value)
func XHTML(value string) nodx.Node {
return nodx.Attr("x-html", value)
}
// XModel is an attribute that renders a x-model="[value]" attribute.
//
// https://alpinejs.dev/directives/model
func XModel(value string) gomponents.Node {
return gomponents.Attr("x-model", value)
func XModel(value string) nodx.Node {
return nodx.Attr("x-model", value)
}
// XTransition is an attribute that renders a x-transition attribute.
//
// https://alpinejs.dev/directives/transition
func XTransition() gomponents.Node {
return gomponents.Attr("x-transition")
func XTransition() nodx.Node {
return nodx.Attr("x-transition", "")
}
// XTransitionFade is an attribute that renders a x-transition.opacity attribute.
//
// https://alpinejs.dev/directives/transition
func XTransitionFade() gomponents.Node {
return gomponents.Attr("x-transition.opacity")
func XTransitionFade() nodx.Node {
return nodx.Attr("x-transition.opacity", "")
}
// XIgnore is an attribute that renders a x-ignore attribute.
//
// https://alpinejs.dev/directives/ignore
func XIgnore() gomponents.Node {
return gomponents.Attr("x-ignore")
func XIgnore() nodx.Node {
return nodx.Attr("x-ignore", "")
}
// XRef is an attribute that renders a x-ref="[value]" attribute.
//
// https://alpinejs.dev/directives/ref
func XRef(value string) gomponents.Node {
return gomponents.Attr("x-ref", value)
func XRef(value string) nodx.Node {
return nodx.Attr("x-ref", value)
}
// XCloak is an attribute that renders a x-cloak attribute.
//
// https://alpinejs.dev/directives/cloak
func XCloak() gomponents.Node {
return gomponents.Attr("x-cloak")
func XCloak() nodx.Node {
return nodx.Attr("x-cloak", "")
}
// XTeleport is an attribute that renders a x-teleport="[value]" attribute.
//
// https://alpinejs.dev/directives/teleport
func XTeleport(value string) gomponents.Node {
return gomponents.Attr("x-teleport", value)
func XTeleport(value string) nodx.Node {
return nodx.Attr("x-teleport", value)
}
// IF is an attribute that renders a x-if="[value]" attribute.
//
// https://alpinejs.dev/directives/if
func XIf(value string) gomponents.Node {
return gomponents.Attr("x-if", value)
func XIf(value string) nodx.Node {
return nodx.Attr("x-if", value)
}

View File

@@ -3,7 +3,6 @@ package auth
import (
"net/http"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/logger"
"github.com/eduardolat/pgbackweb/internal/util/echoutil"
@@ -12,8 +11,8 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/eduardolat/pgbackweb/internal/view/web/layout"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) createFirstUserPageHandler(c echo.Context) error {
@@ -32,19 +31,19 @@ func (h *handlers) createFirstUserPageHandler(c echo.Context) error {
return c.Redirect(http.StatusFound, "/auth/login")
}
return echoutil.RenderGomponent(c, http.StatusOK, createFirstUserPage())
return echoutil.RenderNodx(c, http.StatusOK, createFirstUserPage())
}
func createFirstUserPage() gomponents.Node {
content := []gomponents.Node{
func createFirstUserPage() nodx.Node {
content := []nodx.Node{
component.H1Text("Create first user"),
html.Form(
nodx.FormEl(
htmx.HxPost("/auth/create-first-user"),
htmx.HxDisabledELT("find button"),
html.Class("mt-4 space-y-2"),
nodx.Class("mt-4 space-y-2"),
html.Div(
nodx.Div(
component.InputControl(component.InputControlParams{
Name: "name",
Label: "Full name",
@@ -70,9 +69,9 @@ func createFirstUserPage() gomponents.Node {
Required: true,
Type: component.InputTypePassword,
AutoComplete: "new-password",
Children: []gomponents.Node{
html.MinLength("6"),
html.MaxLength("50"),
Children: []nodx.Node{
nodx.Minlength("6"),
nodx.Maxlength("50"),
},
}),
@@ -82,19 +81,19 @@ func createFirstUserPage() gomponents.Node {
Placeholder: "******",
Required: true,
Type: component.InputTypePassword,
Children: []gomponents.Node{
html.MinLength("6"),
html.MaxLength("50"),
Children: []nodx.Node{
nodx.Minlength("6"),
nodx.Maxlength("50"),
},
}),
html.Div(
html.Class("pt-2 flex justify-end items-center space-x-2"),
nodx.Div(
nodx.Class("pt-2 flex justify-end items-center space-x-2"),
component.HxLoadingMd(),
html.Button(
html.ID("create-first-user-button"),
html.Class("btn btn-primary"),
html.Type("submit"),
nodx.Button(
nodx.Id("create-first-user-button"),
nodx.Class("btn btn-primary"),
nodx.Type("submit"),
component.SpanText("Create user and continue"),
lucide.UserPlus(),
),

View File

@@ -3,7 +3,6 @@ package auth
import (
"net/http"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/logger"
"github.com/eduardolat/pgbackweb/internal/util/echoutil"
"github.com/eduardolat/pgbackweb/internal/validate"
@@ -11,8 +10,8 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/eduardolat/pgbackweb/internal/view/web/layout"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) loginPageHandler(c echo.Context) error {
@@ -31,17 +30,17 @@ func (h *handlers) loginPageHandler(c echo.Context) error {
return c.Redirect(http.StatusFound, "/auth/create-first-user")
}
return echoutil.RenderGomponent(c, http.StatusOK, loginPage())
return echoutil.RenderNodx(c, http.StatusOK, loginPage())
}
func loginPage() gomponents.Node {
content := []gomponents.Node{
func loginPage() nodx.Node {
content := []nodx.Node{
component.H1Text("Login"),
html.Form(
nodx.FormEl(
htmx.HxPost("/auth/login"),
htmx.HxDisabledELT("find button"),
html.Class("mt-4 space-y-2"),
nodx.Class("mt-4 space-y-2"),
component.InputControl(component.InputControlParams{
Name: "email",
@@ -61,12 +60,12 @@ func loginPage() gomponents.Node {
AutoComplete: "current-password",
}),
html.Div(
html.Class("pt-2 flex justify-end items-center space-x-2"),
nodx.Div(
nodx.Class("pt-2 flex justify-end items-center space-x-2"),
component.HxLoadingMd(),
html.Button(
html.Class("btn btn-primary"),
html.Type("submit"),
nodx.Button(
nodx.Class("btn btn-primary"),
nodx.Type("submit"),
component.SpanText("Login"),
lucide.LogIn(),
),

View File

@@ -1,30 +1,28 @@
package component
import (
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
type CardBoxParams struct {
Class string
Children []gomponents.Node
Children []nodx.Node
}
// CardBox renders a card box with the given children.
func CardBox(params CardBoxParams) gomponents.Node {
return html.Div(
components.Classes{
func CardBox(params CardBoxParams) nodx.Node {
return nodx.Div(
nodx.ClassMap{
"rounded-box shadow-md bg-base-100 p-4": true,
params.Class: true,
},
gomponents.Group(params.Children),
nodx.Group(params.Children...),
)
}
// CardBoxSimple is the same as CardBox, but with a less verbose
// api and default props. It renders a card box with the given children.
func CardBoxSimple(children ...gomponents.Node) gomponents.Node {
func CardBoxSimple(children ...nodx.Node) nodx.Node {
return CardBox(CardBoxParams{
Children: children,
})

View File

@@ -1,11 +1,9 @@
package component
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/alpine"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
type ChangeThemeButtonParams struct {
@@ -14,12 +12,12 @@ type ChangeThemeButtonParams struct {
Size size
}
func ChangeThemeButton(params ChangeThemeButtonParams) gomponents.Node {
return html.Div(
func ChangeThemeButton(params ChangeThemeButtonParams) nodx.Node {
return nodx.Div(
alpine.XData("alpineChangeThemeButton()"),
alpine.XCloak(),
components.Classes{
nodx.ClassMap{
"dropdown": true,
"dropdown-end": params.AlignsToEnd,
"dropdown-right": params.Position == DropdownPositionRight,
@@ -27,17 +25,17 @@ func ChangeThemeButton(params ChangeThemeButtonParams) gomponents.Node {
"dropdown-top": params.Position == DropdownPositionTop,
"dropdown-bottom": params.Position == DropdownPositionBottom,
},
html.Div(
html.TabIndex("0"),
html.Role("button"),
components.Classes{
nodx.Div(
nodx.Tabindex("0"),
nodx.Role("button"),
nodx.ClassMap{
"btn btn-neutral space-x-1": true,
"btn-sm": params.Size == SizeSm,
"btn-lg": params.Size == SizeLg,
},
html.Div(
html.Class("inline-block size-4"),
nodx.Div(
nodx.Class("inline-block size-4"),
lucide.Laptop(alpine.XShow(`theme === "system"`)),
lucide.Sun(alpine.XShow(`theme === "light"`)),
lucide.Moon(alpine.XShow(`theme === "dark"`)),
@@ -46,38 +44,38 @@ func ChangeThemeButton(params ChangeThemeButtonParams) gomponents.Node {
SpanText("Theme"),
lucide.ChevronDown(),
),
html.Ul(
html.TabIndex("0"),
components.Classes{
nodx.Ul(
nodx.Tabindex("0"),
nodx.ClassMap{
"dropdown-content": true,
"bg-base-100": true,
"rounded-btn shadow-md": true,
"z-[1] w-[150px] p-2 space-y-2 my-2": true,
},
html.Li(
html.Button(
nodx.Li(
nodx.Button(
alpine.XOn("click", "setTheme('')"),
html.Class("btn btn-neutral btn-block"),
html.Type("button"),
lucide.Laptop(html.Class("mr-1")),
nodx.Class("btn btn-neutral btn-block"),
nodx.Type("button"),
lucide.Laptop(nodx.Class("mr-1")),
SpanText("System"),
),
),
html.Li(
html.Button(
nodx.Li(
nodx.Button(
alpine.XOn("click", "setTheme('light')"),
html.Class("btn btn-neutral btn-block"),
html.Type("button"),
lucide.Sun(html.Class("mr-1")),
nodx.Class("btn btn-neutral btn-block"),
nodx.Type("button"),
lucide.Sun(nodx.Class("mr-1")),
SpanText("Light"),
),
),
html.Li(
html.Button(
nodx.Li(
nodx.Button(
alpine.XOn("click", "setTheme('dark')"),
html.Class("btn btn-neutral btn-block"),
html.Type("button"),
lucide.Moon(html.Class("mr-1")),
nodx.Class("btn btn-neutral btn-block"),
nodx.Type("button"),
lucide.Moon(nodx.Class("mr-1")),
SpanText("Dark"),
),
),

View File

@@ -4,15 +4,13 @@ import (
"fmt"
"strings"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/google/uuid"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
// CopyButtonSm is a small copy button.
func CopyButtonSm(textToCopy string) gomponents.Node {
func CopyButtonSm(textToCopy string) nodx.Node {
return copyButton(copyButtonProps{
TextToCopy: textToCopy,
Size: SizeSm,
@@ -20,14 +18,14 @@ func CopyButtonSm(textToCopy string) gomponents.Node {
}
// CopyButtonMd is a medium copy button.
func CopyButtonMd(textToCopy string) gomponents.Node {
func CopyButtonMd(textToCopy string) nodx.Node {
return copyButton(copyButtonProps{
TextToCopy: textToCopy,
})
}
// CopyButtonLg is a large copy button.
func CopyButtonLg(textToCopy string) gomponents.Node {
func CopyButtonLg(textToCopy string) nodx.Node {
return copyButton(copyButtonProps{
TextToCopy: textToCopy,
Size: SizeLg,
@@ -44,24 +42,24 @@ type copyButtonProps struct {
}
// copyButton is a button that copies text to the clipboard when clicked.
func copyButton(props copyButtonProps) gomponents.Node {
func copyButton(props copyButtonProps) nodx.Node {
id := uuid.NewString()
id = strings.ReplaceAll(id, "-", "")
sc := copyButtonScript(id, props.TextToCopy)
return html.Div(
html.Class("inline-block tooltip tooltip-right"),
html.Data("tip", "Copy to clipboard"),
return nodx.Div(
nodx.Class("inline-block tooltip tooltip-right"),
nodx.Data("tip", "Copy to clipboard"),
sc.script,
html.Button(
components.Classes{
nodx.Button(
nodx.ClassMap{
"btn btn-neutral btn-square btn-ghost": true,
"btn-sm": props.Size == SizeSm,
"btn-lg": props.Size == SizeLg,
},
html.ID(id),
html.Title("Copy to clipboard"),
nodx.Id(id),
nodx.TitleAttr("Copy to clipboard"),
sc.copyEvent,
lucide.Copy(),
),
@@ -74,8 +72,8 @@ func copyButtonScript(
id string,
textToCopy string,
) struct {
script gomponents.Node
copyEvent gomponents.Node
script nodx.Node
copyEvent nodx.Node
} {
escapedTextToCopy := strings.ReplaceAll(textToCopy, "`", "\\`")
@@ -85,12 +83,12 @@ func copyButtonScript(
escapedTextToCopy,
)
script := gomponents.Raw(rawScript)
copyEvent := gomponents.Attr("onclick", fmt.Sprintf("copy%s()", id))
script := nodx.Raw(rawScript)
copyEvent := nodx.Attr("onclick", fmt.Sprintf("copy%s()", id))
return struct {
script gomponents.Node
copyEvent gomponents.Node
script nodx.Node
copyEvent nodx.Node
}{
script: script,
copyEvent: copyEvent,

View File

@@ -1,9 +1,8 @@
package component
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
type EmptyResultsParams struct {
@@ -11,32 +10,32 @@ type EmptyResultsParams struct {
Subtitle string
}
func EmptyResults(params EmptyResultsParams) gomponents.Node {
return html.Div(
html.Class("flex flex-col justify-center items-center space-x-1"),
lucide.FileSearch(html.Class("size-8")),
gomponents.If(
func EmptyResults(params EmptyResultsParams) nodx.Node {
return nodx.Div(
nodx.Class("flex flex-col justify-center items-center space-x-1"),
lucide.FileSearch(nodx.Class("size-8")),
nodx.If(
params.Title != "",
html.Span(
html.Class("text-xl"),
gomponents.Text(params.Title),
nodx.SpanEl(
nodx.Class("text-xl"),
nodx.Text(params.Title),
),
),
gomponents.If(
nodx.If(
params.Subtitle != "",
html.Span(
html.Class("text-base"),
gomponents.Text(params.Subtitle),
nodx.SpanEl(
nodx.Class("text-base"),
nodx.Text(params.Subtitle),
),
),
)
}
func EmptyResultsTr(params EmptyResultsParams) gomponents.Node {
return html.Tr(
html.Td(
html.ColSpan("100%"),
html.Class("py-10"),
func EmptyResultsTr(params EmptyResultsParams) nodx.Node {
return nodx.Tr(
nodx.Td(
nodx.Colspan("100%"),
nodx.Class("py-10"),
EmptyResults(params),
),
)

View File

@@ -1,9 +0,0 @@
package component
import "github.com/maragudk/gomponents"
// GMap is a convenience function to render a gomponents.Group
// with a map inside.
func GMap[T any](ts []T, cb func(T) gomponents.Node) gomponents.Node {
return gomponents.Group(gomponents.Map(ts, cb))
}

View File

@@ -1,33 +1,32 @@
package component
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
type HelpButtonModalParams struct {
ModalTitle string
ModalSize size
Children []gomponents.Node
Children []nodx.Node
}
func HelpButtonModal(params HelpButtonModalParams) gomponents.Node {
func HelpButtonModal(params HelpButtonModalParams) nodx.Node {
mo := Modal(ModalParams{
Size: params.ModalSize,
Title: params.ModalTitle,
Content: params.Children,
})
button := html.Button(
button := nodx.Button(
mo.OpenerAttr,
html.Class("btn btn-neutral btn-ghost btn-circle btn-sm"),
html.Type("button"),
nodx.Class("btn btn-neutral btn-ghost btn-circle btn-sm"),
nodx.Type("button"),
lucide.CircleHelp(),
)
return html.Div(
html.Class("inline-block"),
return nodx.Div(
nodx.Class("inline-block"),
mo.HTML,
button,
)

View File

@@ -1,38 +1,37 @@
package component
import (
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
// HxLoadingSm returns a small loading indicator.
func HxLoadingSm(id ...string) gomponents.Node {
func HxLoadingSm(id ...string) nodx.Node {
return hxLoading(SizeSm, id...)
}
// HxLoadingMd returns a loading indicator.
func HxLoadingMd(id ...string) gomponents.Node {
func HxLoadingMd(id ...string) nodx.Node {
return hxLoading(SizeMd, id...)
}
// HxLoadingLg returns a large loading indicator.
func HxLoadingLg(id ...string) gomponents.Node {
func HxLoadingLg(id ...string) nodx.Node {
return hxLoading(SizeLg, id...)
}
func hxLoading(size size, id ...string) gomponents.Node {
func hxLoading(size size, id ...string) nodx.Node {
pickedID := ""
if len(id) > 0 {
pickedID = id[0]
}
return html.Div(
gomponents.If(
return nodx.Div(
nodx.If(
pickedID != "",
html.ID(pickedID),
nodx.Id(pickedID),
),
html.Class("htmx-indicator inline-block"),
func() gomponents.Node {
nodx.Class("htmx-indicator inline-block"),
func() nodx.Node {
switch size {
case SizeSm:
return SpinnerSm()

View File

@@ -1,11 +1,9 @@
package component
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/google/uuid"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
type InputControlParams struct {
@@ -19,11 +17,11 @@ type InputControlParams struct {
Color color
AutoComplete string
Pattern string
Children []gomponents.Node
HelpButtonChildren []gomponents.Node
Children []nodx.Node
HelpButtonChildren []nodx.Node
}
func InputControl(params InputControlParams) gomponents.Node {
func InputControl(params InputControlParams) nodx.Node {
id := params.ID
if id == "" {
id = "input-control-" + uuid.NewString()
@@ -33,23 +31,23 @@ func InputControl(params InputControlParams) gomponents.Node {
params.Type = InputTypeText
}
return html.Div(
components.Classes{
return nodx.Div(
nodx.ClassMap{
"form-control w-full": true,
getTextColorClass(params.Color): true,
},
html.Div(
html.Class("label flex justify-start"),
html.Label(
html.For(id),
html.Class("flex justify-start items-center space-x-1"),
nodx.Div(
nodx.Class("label flex justify-start"),
nodx.LabelEl(
nodx.For(id),
nodx.Class("flex justify-start items-center space-x-1"),
SpanText(params.Label),
gomponents.If(
nodx.If(
params.Required,
lucide.Asterisk(html.Class("text-error")),
lucide.Asterisk(nodx.Class("text-error")),
),
),
gomponents.If(
nodx.If(
len(params.HelpButtonChildren) > 0,
HelpButtonModal(HelpButtonModalParams{
ModalTitle: params.Label,
@@ -57,34 +55,34 @@ func InputControl(params InputControlParams) gomponents.Node {
}),
),
),
html.Input(
components.Classes{
nodx.Input(
nodx.ClassMap{
"input input-bordered w-full": true,
getInputColorClass(params.Color): true,
},
html.ID(id),
html.Type(params.Type.Value),
html.Name(params.Name),
html.Placeholder(params.Placeholder),
gomponents.If(
nodx.Id(id),
nodx.Type(params.Type.Value),
nodx.Name(params.Name),
nodx.Placeholder(params.Placeholder),
nodx.If(
params.Required,
html.Required(),
nodx.Required(""),
),
gomponents.If(
nodx.If(
params.AutoComplete != "",
html.AutoComplete(params.AutoComplete),
nodx.Autocomplete(params.AutoComplete),
),
gomponents.If(
nodx.If(
params.Pattern != "",
html.Pattern(params.Pattern),
nodx.Pattern(params.Pattern),
),
gomponents.Group(params.Children),
nodx.Group(params.Children...),
),
gomponents.If(
nodx.If(
params.HelpText != "",
html.Label(
html.Class("label"),
html.For(id),
nodx.LabelEl(
nodx.Class("label"),
nodx.For(id),
SpanText(params.HelpText),
),
),

View File

@@ -1,25 +1,23 @@
package component
import (
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
func Logotype() gomponents.Node {
return html.Div(
components.Classes{
func Logotype() nodx.Node {
return nodx.Div(
nodx.ClassMap{
"inline space-x-2 select-none": true,
"flex justify-start items-center": true,
},
html.Img(
html.Class("w-[60px] h-auto"),
html.Src("/images/logo.png"),
html.Alt("PG Back Web"),
nodx.Img(
nodx.Class("w-[60px] h-auto"),
nodx.Src("/images/logo.png"),
nodx.Alt("PG Back Web"),
),
html.Span(
html.Class("text-2xl font-bold"),
gomponents.Text("PG Back Web"),
nodx.SpanEl(
nodx.Class("text-2xl font-bold"),
nodx.Text("PG Back Web"),
),
)
}

View File

@@ -3,12 +3,10 @@ package component
import (
"fmt"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/alpine"
"github.com/google/uuid"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
// ModalParams are the props for the Modal component.
@@ -16,7 +14,7 @@ type ModalParams struct {
// ID is the ID of the modal dialog. If empty, a random ID will be generated.
ID string
// Content is the content of the modal dialog.
Content []gomponents.Node
Content []nodx.Node
// Size is the size of the modal dialog.
// Can be "sm", "md", and "lg".
// The default is "md".
@@ -26,7 +24,7 @@ type ModalParams struct {
Title string
// TitleNode is the title of the modal dialog.
// If you need only a string, use Title instead.
TitleNode gomponents.Node
TitleNode nodx.Node
// HTMXIndicator is an optional ID of an HTMX indicator that
// should be inserted in the modal header.
HTMXIndicator string
@@ -34,10 +32,10 @@ type ModalParams struct {
// ModalResult is the result of creating a modal dialog.
type ModalResult struct {
// HTML is the modal dialog HTML.
HTML gomponents.Node
// HTML is the modal dialog nodx.
HTML nodx.Node
// OpenerAttr is the attribute to add to the element that opens the modal dialog.
OpenerAttr gomponents.Node
OpenerAttr nodx.Node
}
// Modal renders a modal dialog.
@@ -49,11 +47,11 @@ func Modal(params ModalParams) ModalResult {
openEventName := fmt.Sprintf("%s_open", id)
closeEventName := fmt.Sprintf("%s_close", id)
openerAttr := gomponents.Attr(
openerAttr := nodx.Attr(
"onClick",
"event.preventDefault(); window.dispatchEvent(new Event('"+openEventName+"'));",
)
closerAttr := gomponents.Attr(
closerAttr := nodx.Attr(
"onClick",
"event.preventDefault(); window.dispatchEvent(new Event('"+closeEventName+"'));",
)
@@ -68,23 +66,23 @@ func Modal(params ModalParams) ModalResult {
hasHTMXIndicator := params.HTMXIndicator != ""
content := html.Div(
content := nodx.Div(
alpine.XData(`{}`),
alpine.XOn(fmt.Sprintf("%s.window", openEventName), openCode),
alpine.XOn(fmt.Sprintf("%s.window", closeEventName), closeCode),
alpine.XOn("keyup.escape.window", closeCode),
html.ID(id),
components.Classes{
nodx.Id(id),
nodx.ClassMap{
"hidden": true,
"!p-0 !m-0 w-[100dvw] h-[100dvh]": true,
"fixed left-0 top-0 z-[1000]": true,
},
// Backdrop
html.Div(
nodx.Div(
closerAttr,
components.Classes{
nodx.ClassMap{
"bg-black opacity-25": true,
"!w-full !h-full": true,
"z-[1001]": true,
@@ -92,8 +90,8 @@ func Modal(params ModalParams) ModalResult {
),
// Dialog
html.Div(
components.Classes{
nodx.Div(
nodx.ClassMap{
"absolute z-[1002] top-[50%] left-[50%]": true,
"translate-y-[-50%] translate-x-[-50%]": true,
"max-w-[calc(100dvw-30px)] max-h-[85dvh]": true,
@@ -105,46 +103,46 @@ func Modal(params ModalParams) ModalResult {
"w-[800px]": size == SizeLg,
},
html.Div(
components.Classes{
nodx.Div(
nodx.ClassMap{
"w-full sticky top-0 right-0 z-[1003] bg-base-100": true,
"flex items-center justify-between": true,
"border-b border-base-300 px-4 py-3": true,
},
html.Div(
gomponents.If(
nodx.Div(
nodx.If(
params.TitleNode != nil,
params.TitleNode,
),
gomponents.If(
nodx.If(
params.Title != "",
html.Span(
html.Class("text-xl font-bold desk:text-2xl"),
gomponents.Text(params.Title),
nodx.SpanEl(
nodx.Class("text-xl font-bold desk:text-2xl"),
nodx.Text(params.Title),
),
),
gomponents.If(
nodx.If(
hasHTMXIndicator,
html.Div(
html.Class("inline-flex h-full items-center pl-2"),
nodx.Div(
nodx.Class("inline-flex h-full items-center pl-2"),
HxLoadingSm(params.HTMXIndicator),
),
),
),
html.Button(
html.Class("btn btn-circle btn-ghost btn-sm"),
lucide.X(html.Class("size-6")),
nodx.Button(
nodx.Class("btn btn-circle btn-ghost btn-sm"),
lucide.X(nodx.Class("size-6")),
closerAttr,
),
),
html.Div(
html.Class("p-4"),
gomponents.Group(params.Content),
nodx.Div(
nodx.Class("p-4"),
nodx.Group(params.Content...),
),
),
)
@@ -152,7 +150,7 @@ func Modal(params ModalParams) ModalResult {
content = alpine.Template(
alpine.XData(""),
alpine.XTeleport("body"),
html.Div(content),
nodx.Div(content),
)
return ModalResult{

View File

@@ -1,50 +1,48 @@
package component
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/alpine"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func OptionsDropdown(children ...gomponents.Node) gomponents.Node {
return html.Div(
html.Class("inline-block"),
func OptionsDropdown(children ...nodx.Node) nodx.Node {
return nodx.Div(
nodx.Class("inline-block"),
alpine.XData("alpineOptionsDropdown()"),
alpine.XOn("mouseenter", "open()"),
alpine.XOn("mouseleave", "close()"),
html.Button(
nodx.Button(
alpine.XRef("button"),
html.Class("btn btn-sm btn-ghost btn-square"),
nodx.Class("btn btn-sm btn-ghost btn-square"),
alpine.XBind("class", "isOpen ? 'btn-active' : ''"),
lucide.EllipsisVertical(
html.Class("transition-transform"),
nodx.Class("transition-transform"),
alpine.XBind("class", "isOpen ? 'rotate-90' : ''"),
),
),
html.Div(
nodx.Div(
alpine.XRef("content"),
components.Classes{
nodx.ClassMap{
"fixed hidden": true,
"bg-base-100 rounded-box border border-base-200": true,
"z-40 max-w-[250px] p-2 shadow-md": true,
},
gomponents.Group(children),
nodx.Group(children...),
),
)
}
func OptionsDropdownButton(children ...gomponents.Node) gomponents.Node {
return html.Button(
html.Class("btn btn-neutral btn-ghost btn-sm w-full flex justify-start"),
gomponents.Group(children),
func OptionsDropdownButton(children ...nodx.Node) nodx.Node {
return nodx.Button(
nodx.Class("btn btn-neutral btn-ghost btn-sm w-full flex justify-start"),
nodx.Group(children...),
)
}
func OptionsDropdownA(children ...gomponents.Node) gomponents.Node {
return html.A(
html.Class("btn btn-neutral btn-ghost btn-sm w-full flex justify-start"),
gomponents.Group(children),
func OptionsDropdownA(children ...nodx.Node) nodx.Node {
return nodx.A(
nodx.Class("btn btn-neutral btn-ghost btn-sm w-full flex justify-start"),
nodx.Group(children...),
)
}

View File

@@ -4,20 +4,19 @@ import (
"database/sql"
"github.com/eduardolat/pgbackweb/internal/integration/postgres"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
func PGVersionSelectOptions(selectedVersion sql.NullString) gomponents.Node {
return GMap(
func PGVersionSelectOptions(selectedVersion sql.NullString) nodx.Node {
return nodx.Map(
postgres.PGVersions,
func(pgVersion postgres.PGVersion) gomponents.Node {
return html.Option(
html.Value(pgVersion.Value.Version),
gomponents.Textf("PostgreSQL %s", pgVersion.Value.Version),
gomponents.If(
func(pgVersion postgres.PGVersion) nodx.Node {
return nodx.Option(
nodx.Value(pgVersion.Value.Version),
nodx.Textf("PostgreSQL %s", pgVersion.Value.Version),
nodx.If(
selectedVersion.Valid && selectedVersion.String == pgVersion.Value.Version,
html.Selected(),
nodx.Selected(""),
),
)
},

View File

@@ -4,12 +4,10 @@ import (
"database/sql"
"github.com/eduardolat/pgbackweb/internal/util/timeutil"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
func Ping(color color) gomponents.Node {
func Ping(color color) nodx.Node {
if color.Value == "" {
color = ColorNeutral
}
@@ -34,17 +32,17 @@ func Ping(color color) gomponents.Node {
bgClass = "bg-error"
}
return html.Span(
html.Class("relative flex h-3 w-3"),
html.Span(
components.Classes{
return nodx.SpanEl(
nodx.Class("relative flex h-3 w-3"),
nodx.SpanEl(
nodx.ClassMap{
"absolute inline-flex h-full w-full": true,
"animate-ping rounded-full opacity-75": true,
bgClass: true,
},
),
html.Span(
components.Classes{
nodx.SpanEl(
nodx.ClassMap{
"relative inline-flex rounded-full h-3 w-3": true,
bgClass: true,
},
@@ -52,23 +50,23 @@ func Ping(color color) gomponents.Node {
)
}
func IsActivePing(isActive bool) gomponents.Node {
func IsActivePing(isActive bool) nodx.Node {
pingColor := ColorSuccess
if !isActive {
pingColor = ColorError
}
return html.Div(
html.Class("tooltip tooltip-right"),
gomponents.If(isActive, html.Data("tip", "Active")),
gomponents.If(!isActive, html.Data("tip", "Inactive")),
return nodx.Div(
nodx.Class("tooltip tooltip-right"),
nodx.If(isActive, nodx.Data("tip", "Active")),
nodx.If(!isActive, nodx.Data("tip", "Inactive")),
Ping(pingColor),
)
}
func HealthStatusPing(
testOk sql.NullBool, testError sql.NullString, lastTestAt sql.NullTime,
) gomponents.Node {
) nodx.Node {
pingColor := ColorWarning
if testOk.Valid {
if testOk.Bool {
@@ -78,7 +76,7 @@ func HealthStatusPing(
}
}
var moOpenerAttr, moHTML gomponents.Node
var moOpenerAttr, moHTML nodx.Node
if testOk.Valid {
statusText := "Healthy"
@@ -89,37 +87,37 @@ func HealthStatusPing(
mo := Modal(ModalParams{
Size: SizeSm,
Title: "Health check details",
Content: []gomponents.Node{
html.Div(
html.Class("overflow-x-auto"),
html.Table(
html.Class("table [&_th]:text-nowrap"),
html.Tr(
html.Th(SpanText("Status")),
html.Td(SpanText(statusText)),
Content: []nodx.Node{
nodx.Div(
nodx.Class("overflow-x-auto"),
nodx.Table(
nodx.Class("table [&_th]:text-nowrap"),
nodx.Tr(
nodx.Th(SpanText("Status")),
nodx.Td(SpanText(statusText)),
),
gomponents.If(
nodx.If(
testError.Valid && testError.String != "",
html.Tr(
html.Th(SpanText("Error")),
html.Td(
html.Class("break-all"),
nodx.Tr(
nodx.Th(SpanText("Error")),
nodx.Td(
nodx.Class("break-all"),
SpanText(testError.String),
),
),
),
gomponents.If(
nodx.If(
lastTestAt.Valid,
html.Tr(
html.Th(SpanText("Tested at")),
html.Td(SpanText(
nodx.Tr(
nodx.Th(SpanText("Tested at")),
nodx.Td(SpanText(
lastTestAt.Time.Local().Format(timeutil.LayoutYYYYMMDDHHMMSSPretty),
)),
),
),
html.Tr(
html.Td(
html.ColSpan("2"),
nodx.Tr(
nodx.Td(
nodx.Colspan("2"),
PText(`
The health check runs automatically every 10 minutes, when
PG Back Web starts, and when you click the "Test connection"
@@ -146,13 +144,13 @@ func HealthStatusPing(
return "Waiting for next test"
}()
return html.Div(
html.Class("tooltip tooltip-right"),
html.Data("tip", tooltipText),
return nodx.Div(
nodx.Class("tooltip tooltip-right"),
nodx.Data("tip", tooltipText),
moHTML,
html.Span(
nodx.SpanEl(
moOpenerAttr,
html.Class("cursor-pointer"),
nodx.Class("cursor-pointer"),
Ping(pingColor),
),
)

View File

@@ -3,14 +3,13 @@ package component
import (
"database/sql"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func PrettyDestinationName(
isLocal bool, destinationName sql.NullString,
) gomponents.Node {
) nodx.Node {
icon := lucide.Cloud
if !destinationName.Valid {
destinationName = sql.NullString{
@@ -27,8 +26,8 @@ func PrettyDestinationName(
}
}
return html.Span(
html.Class("inline flex justify-start items-center space-x-1 font-mono"),
return nodx.SpanEl(
nodx.Class("inline flex justify-start items-center space-x-1 font-mono"),
icon(),
SpanText(destinationName.String),
)

View File

@@ -4,8 +4,7 @@ import (
"database/sql"
"github.com/eduardolat/pgbackweb/internal/util/strutil"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
// PrettyFileSize pretty prints a file size (in bytes) to a human-readable format.
@@ -14,10 +13,10 @@ import (
// e.g. 1024 -> 1 KB
func PrettyFileSize(
size sql.NullInt64,
) gomponents.Node {
return gomponents.If(
) nodx.Node {
return nodx.If(
size.Valid,
html.Span(
nodx.SpanEl(
SpanText(strutil.FormatFileSize(size.Int64)),
),
)

View File

@@ -3,20 +3,20 @@ package component
import (
"bytes"
"github.com/maragudk/gomponents"
nodx "github.com/nodxdev/nodxgo"
)
// RenderableGroup renders a group of nodes without a parent element.
//
// This is because gomponents.Group() cannot be directly rendered and
// This is because nodx.Group() cannot be directly rendered and
// needs to be wrapped in a parent element.
func RenderableGroup(children []gomponents.Node) gomponents.Node {
func RenderableGroup(children []nodx.Node) nodx.Node {
buf := bytes.Buffer{}
for _, child := range children {
err := child.Render(&buf)
if err != nil {
return gomponents.Raw("Error rendering group")
return nodx.Raw("Error rendering group")
}
}
return gomponents.Raw(buf.String())
return nodx.Raw(buf.String())
}

View File

@@ -4,16 +4,15 @@ import (
"bytes"
"testing"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
"github.com/stretchr/testify/assert"
)
func TestRenderableGroupRenderer(t *testing.T) {
t.Run("renders a group of string nodes without a parent element", func(t *testing.T) {
gotRenderer := RenderableGroup([]gomponents.Node{
gomponents.Text("foo"),
gomponents.Text("bar"),
gotRenderer := RenderableGroup([]nodx.Node{
nodx.Text("foo"),
nodx.Text("bar"),
})
got := bytes.Buffer{}
@@ -26,12 +25,12 @@ func TestRenderableGroupRenderer(t *testing.T) {
})
t.Run("renders a group of tag nodes without a parent element", func(t *testing.T) {
gotRenderer := RenderableGroup([]gomponents.Node{
html.Span(
gomponents.Text("foo"),
gotRenderer := RenderableGroup([]nodx.Node{
nodx.SpanEl(
nodx.Text("foo"),
),
html.P(
gomponents.Text("bar"),
nodx.P(
nodx.Text("bar"),
),
})

View File

@@ -1,11 +1,9 @@
package component
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/google/uuid"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
type SelectControlParams struct {
@@ -17,33 +15,33 @@ type SelectControlParams struct {
HelpText string
Color color
AutoComplete string
Children []gomponents.Node
HelpButtonChildren []gomponents.Node
Children []nodx.Node
HelpButtonChildren []nodx.Node
}
func SelectControl(params SelectControlParams) gomponents.Node {
func SelectControl(params SelectControlParams) nodx.Node {
id := params.ID
if id == "" {
id = "select-control-" + uuid.NewString()
}
return html.Div(
components.Classes{
return nodx.Div(
nodx.ClassMap{
"form-control w-full": true,
getTextColorClass(params.Color): true,
},
html.Div(
html.Class("label flex justify-start"),
html.Label(
html.For(id),
html.Class("flex justify-start items-center space-x-1"),
nodx.Div(
nodx.Class("label flex justify-start"),
nodx.LabelEl(
nodx.For(id),
nodx.Class("flex justify-start items-center space-x-1"),
SpanText(params.Label),
gomponents.If(
nodx.If(
params.Required,
lucide.Asterisk(html.Class("text-error")),
lucide.Asterisk(nodx.Class("text-error")),
),
),
gomponents.If(
nodx.If(
len(params.HelpButtonChildren) > 0,
HelpButtonModal(HelpButtonModalParams{
ModalTitle: params.Label,
@@ -51,40 +49,40 @@ func SelectControl(params SelectControlParams) gomponents.Node {
}),
),
),
html.Select(
components.Classes{
nodx.Select(
nodx.ClassMap{
"w-full": true,
},
html.ID(id),
html.Name(params.Name),
gomponents.If(
nodx.Id(id),
nodx.Name(params.Name),
nodx.If(
params.Required,
html.Required(),
nodx.Required(""),
),
gomponents.If(
nodx.If(
params.AutoComplete != "",
html.AutoComplete(params.AutoComplete),
nodx.Autocomplete(params.AutoComplete),
),
gomponents.If(
nodx.If(
params.Placeholder != "",
html.Option(
html.Value(""),
html.Disabled(),
html.Selected(),
gomponents.Text(params.Placeholder),
nodx.Option(
nodx.Value(""),
nodx.Disabled(""),
nodx.Selected(""),
nodx.Text(params.Placeholder),
),
),
gomponents.Group(params.Children),
nodx.Group(params.Children...),
),
gomponents.If(
nodx.If(
params.HelpText != "",
html.Label(
html.Class("label"),
html.For(id),
nodx.LabelEl(
nodx.Class("label"),
nodx.For(id),
SpanText(params.HelpText),
),
),
html.Script(gomponents.Raw(`
nodx.Script(nodx.Raw(`
new SlimSelect({select: '#`+id+`'})
`)),
)

View File

@@ -1,23 +1,22 @@
package component
import (
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
func SkeletonTr(rows int) gomponents.Node {
rs := make([]gomponents.Node, rows)
func SkeletonTr(rows int) nodx.Node {
rs := make([]nodx.Node, rows)
for i := range rs {
rs[i] = html.Tr(
html.Td(
html.ColSpan("100%"),
html.Div(
html.Class("animate-pulse h-4 w-full bg-base-300 rounded-badge"),
rs[i] = nodx.Tr(
nodx.Td(
nodx.Colspan("100%"),
nodx.Div(
nodx.Class("animate-pulse h-4 w-full bg-base-300 rounded-badge"),
),
),
)
}
return gomponents.Group(rs)
return nodx.Group(rs...)
}

View File

@@ -3,14 +3,12 @@ package component
import (
"fmt"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func spinner(size size) gomponents.Node {
return lucide.LoaderCircle(components.Classes{
func spinner(size size) nodx.Node {
return lucide.LoaderCircle(nodx.ClassMap{
"animate-spin inline-block": true,
"size-5": size == SizeSm,
"size-8": size == SizeMd,
@@ -18,30 +16,30 @@ func spinner(size size) gomponents.Node {
})
}
func SpinnerSm() gomponents.Node {
func SpinnerSm() nodx.Node {
return spinner(SizeSm)
}
func SpinnerMd() gomponents.Node {
func SpinnerMd() nodx.Node {
return spinner(SizeMd)
}
func SpinnerLg() gomponents.Node {
func SpinnerLg() nodx.Node {
return spinner(SizeLg)
}
func spinnerContainer(size size, height string) gomponents.Node {
return html.Div(
components.Classes{
func spinnerContainer(size size, height string) nodx.Node {
return nodx.Div(
nodx.ClassMap{
"flex justify-center": true,
"items-center w-full": true,
},
html.Style(fmt.Sprintf("height: %s;", height)),
nodx.StyleAttr(fmt.Sprintf("height: %s;", height)),
spinner(size),
)
}
func SpinnerContainerSm(height ...string) gomponents.Node {
func SpinnerContainerSm(height ...string) nodx.Node {
pickedHeight := "300px"
if len(height) > 0 {
pickedHeight = height[0]
@@ -49,7 +47,7 @@ func SpinnerContainerSm(height ...string) gomponents.Node {
return spinnerContainer(SizeSm, pickedHeight)
}
func SpinnerContainerMd(height ...string) gomponents.Node {
func SpinnerContainerMd(height ...string) nodx.Node {
pickedHeight := "300px"
if len(height) > 0 {
pickedHeight = height[0]
@@ -57,7 +55,7 @@ func SpinnerContainerMd(height ...string) gomponents.Node {
return spinnerContainer(SizeMd, pickedHeight)
}
func SpinnerContainerLg(height ...string) gomponents.Node {
func SpinnerContainerLg(height ...string) nodx.Node {
pickedHeight := "300px"
if len(height) > 0 {
pickedHeight = height[0]

View File

@@ -1,27 +1,25 @@
package component
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/alpine"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func StarOnGithub(size size) gomponents.Node {
return html.A(
func StarOnGithub(size size) nodx.Node {
return nodx.A(
alpine.XData("alpineStarOnGithub()"),
alpine.XCloak(),
components.Classes{
nodx.ClassMap{
"btn btn-neutral": true,
"btn-sm": size == SizeSm,
"btn-lg": size == SizeLg,
},
html.Href("https://github.com/eduardolat/pgbackweb"),
html.Target("_blank"),
nodx.Href("https://github.com/eduardolat/pgbackweb"),
nodx.Target("_blank"),
lucide.Github(),
SpanText("Star on Github"),
html.Span(
nodx.SpanEl(
alpine.XShow("stars"),
alpine.XText("'( ' + stars + ' )'"),
),

View File

@@ -1,12 +1,10 @@
package component
import (
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
func StatusBadge(status string) gomponents.Node {
func StatusBadge(status string) nodx.Node {
class := ""
switch status {
case "running":
@@ -21,11 +19,11 @@ func StatusBadge(status string) gomponents.Node {
class = "badge-neutral"
}
return html.Span(
components.Classes{
return nodx.SpanEl(
nodx.ClassMap{
"badge": true,
class: true,
},
gomponents.Text(status),
nodx.Text(status),
)
}

View File

@@ -1,11 +1,9 @@
package component
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/google/uuid"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
type TextareaControlParams struct {
@@ -18,33 +16,33 @@ type TextareaControlParams struct {
Color color
AutoComplete string
Pattern string
Children []gomponents.Node
HelpButtonChildren []gomponents.Node
Children []nodx.Node
HelpButtonChildren []nodx.Node
}
func TextareaControl(params TextareaControlParams) gomponents.Node {
func TextareaControl(params TextareaControlParams) nodx.Node {
id := params.ID
if id == "" {
id = "textarea-control-" + uuid.NewString()
}
return html.Div(
components.Classes{
return nodx.Div(
nodx.ClassMap{
"form-control w-full": true,
getTextColorClass(params.Color): true,
},
html.Div(
html.Class("label flex justify-start"),
html.Label(
html.For(id),
html.Class("flex justify-start items-center space-x-1"),
nodx.Div(
nodx.Class("label flex justify-start"),
nodx.LabelEl(
nodx.For(id),
nodx.Class("flex justify-start items-center space-x-1"),
SpanText(params.Label),
gomponents.If(
nodx.If(
params.Required,
lucide.Asterisk(html.Class("text-error")),
lucide.Asterisk(nodx.Class("text-error")),
),
),
gomponents.If(
nodx.If(
len(params.HelpButtonChildren) > 0,
HelpButtonModal(HelpButtonModalParams{
ModalTitle: params.Label,
@@ -52,33 +50,33 @@ func TextareaControl(params TextareaControlParams) gomponents.Node {
}),
),
),
html.Textarea(
components.Classes{
nodx.Textarea(
nodx.ClassMap{
"textarea textarea-bordered w-full": true,
getTextareaColorClass(params.Color): true,
},
html.ID(id),
html.Name(params.Name),
html.Placeholder(params.Placeholder),
gomponents.If(
nodx.Id(id),
nodx.Name(params.Name),
nodx.Placeholder(params.Placeholder),
nodx.If(
params.Required,
html.Required(),
nodx.Required(""),
),
gomponents.If(
nodx.If(
params.AutoComplete != "",
html.AutoComplete(params.AutoComplete),
nodx.Autocomplete(params.AutoComplete),
),
gomponents.If(
nodx.If(
params.Pattern != "",
html.Pattern(params.Pattern),
nodx.Pattern(params.Pattern),
),
gomponents.Group(params.Children),
nodx.Group(params.Children...),
),
gomponents.If(
nodx.If(
params.HelpText != "",
html.Label(
html.Class("label"),
html.For(id),
nodx.LabelEl(
nodx.Class("label"),
nodx.For(id),
SpanText(params.HelpText),
),
),

View File

@@ -1,8 +1,7 @@
package component
import (
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
// getTextColorClass returns the text color class for a text that
@@ -30,98 +29,98 @@ func getTextColorClass(color color) string {
}
}
func H1(children ...gomponents.Node) gomponents.Node {
return html.H1(
html.Class("text-2xl font-bold desk:text-4xl"),
gomponents.Group(children),
func H1(children ...nodx.Node) nodx.Node {
return nodx.H1(
nodx.Class("text-2xl font-bold desk:text-4xl"),
nodx.Group(children...),
)
}
func H2(children ...gomponents.Node) gomponents.Node {
return html.H2(
html.Class("text-xl font-bold desk:text-2xl"),
gomponents.Group(children),
func H2(children ...nodx.Node) nodx.Node {
return nodx.H2(
nodx.Class("text-xl font-bold desk:text-2xl"),
nodx.Group(children...),
)
}
func H3(children ...gomponents.Node) gomponents.Node {
return html.H3(
html.Class("text-lg font-bold desk:text-xl"),
gomponents.Group(children),
func H3(children ...nodx.Node) nodx.Node {
return nodx.H3(
nodx.Class("text-lg font-bold desk:text-xl"),
nodx.Group(children...),
)
}
func H4(children ...gomponents.Node) gomponents.Node {
return html.H4(
html.Class("text-base font-bold desk:text-lg"),
gomponents.Group(children),
func H4(children ...nodx.Node) nodx.Node {
return nodx.H4(
nodx.Class("text-base font-bold desk:text-lg"),
nodx.Group(children...),
)
}
func H5(children ...gomponents.Node) gomponents.Node {
return html.H5(
html.Class("text-sm font-bold desk:text-base"),
gomponents.Group(children),
func H5(children ...nodx.Node) nodx.Node {
return nodx.H5(
nodx.Class("text-sm font-bold desk:text-base"),
nodx.Group(children...),
)
}
func H6(children ...gomponents.Node) gomponents.Node {
return html.H6(
html.Class("text-xs font-bold desk:text-sm"),
gomponents.Group(children),
func H6(children ...nodx.Node) nodx.Node {
return nodx.H6(
nodx.Class("text-xs font-bold desk:text-sm"),
nodx.Group(children...),
)
}
// H1Text is a convenience function to create an H1 element with a
// simple text node as its child.
func H1Text(text string) gomponents.Node {
return H1(gomponents.Text(text))
func H1Text(text string) nodx.Node {
return H1(nodx.Text(text))
}
// H2Text is a convenience function to create an H2 element with a
// simple text node as its child.
func H2Text(text string) gomponents.Node {
return H2(gomponents.Text(text))
func H2Text(text string) nodx.Node {
return H2(nodx.Text(text))
}
// H3Text is a convenience function to create an H3 element with a
// simple text node as its child.
func H3Text(text string) gomponents.Node {
return H3(gomponents.Text(text))
func H3Text(text string) nodx.Node {
return H3(nodx.Text(text))
}
// H4Text is a convenience function to create an H4 element with a
// simple text node as its child.
func H4Text(text string) gomponents.Node {
return H4(gomponents.Text(text))
func H4Text(text string) nodx.Node {
return H4(nodx.Text(text))
}
// H5Text is a convenience function to create an H5 element with a
// simple text node as its child.
func H5Text(text string) gomponents.Node {
return H5(gomponents.Text(text))
func H5Text(text string) nodx.Node {
return H5(nodx.Text(text))
}
// H6Text is a convenience function to create an H6 element with a
// simple text node as its child.
func H6Text(text string) gomponents.Node {
return H6(gomponents.Text(text))
func H6Text(text string) nodx.Node {
return H6(nodx.Text(text))
}
// PText is a convenience function to create a P element with a
// simple text node as its child.
func PText(text string) gomponents.Node {
return html.P(gomponents.Text(text))
func PText(text string) nodx.Node {
return nodx.P(nodx.Text(text))
}
// SpanText is a convenience function to create a Span element with a
// simple text node as its child.
func SpanText(text string) gomponents.Node {
return html.Span(gomponents.Text(text))
func SpanText(text string) nodx.Node {
return nodx.SpanEl(nodx.Text(text))
}
// BText is a convenience function to create a B element with a
// simple text node as its child.
func BText(text string) gomponents.Node {
return html.B(gomponents.Text(text))
func BText(text string) nodx.Node {
return nodx.B(nodx.Text(text))
}

View File

@@ -9,25 +9,24 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/layout"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
func (h *handlers) indexPageHandler(c echo.Context) error {
reqCtx := reqctx.GetCtx(c)
return echoutil.RenderGomponent(c, http.StatusOK, indexPage(reqCtx))
return echoutil.RenderNodx(c, http.StatusOK, indexPage(reqCtx))
}
func indexPage(reqCtx reqctx.Ctx) gomponents.Node {
content := []gomponents.Node{
func indexPage(reqCtx reqctx.Ctx) nodx.Node {
content := []nodx.Node{
component.H1Text("About PG Back Web"),
component.H2Text(config.Version),
html.Div(
html.Class("grid grid-cols-2 gap-4 mt-4"),
nodx.Div(
nodx.Class("grid grid-cols-2 gap-4 mt-4"),
component.CardBox(component.CardBoxParams{
Children: []gomponents.Node{
Children: []nodx.Node{
component.PText(`
PG Back Web was born in July 2024 out of a need for a simple and
user-friendly backup solution for self-hosted PostgreSQL databases.
@@ -40,38 +39,38 @@ func indexPage(reqCtx reqctx.Ctx) gomponents.Node {
}),
component.CardBox(component.CardBoxParams{
Children: []gomponents.Node{
html.Table(
html.Class("table"),
html.Tr(
html.Th(component.SpanText("License")),
html.Td(
html.A(
html.Class("link"),
html.Href("https://github.com/eduardolat/pgbackweb/blob/main/LICENSE"),
html.Target("_blank"),
Children: []nodx.Node{
nodx.Table(
nodx.Class("table"),
nodx.Tr(
nodx.Th(component.SpanText("License")),
nodx.Td(
nodx.A(
nodx.Class("link"),
nodx.Href("https://github.com/eduardolat/pgbackweb/blob/main/LICENSE"),
nodx.Target("_blank"),
component.SpanText("MIT"),
),
),
),
html.Tr(
html.Th(component.SpanText("About the author")),
html.Td(
html.A(
html.Class("link"),
html.Href("https://eduardo.lat"),
html.Target("_blank"),
nodx.Tr(
nodx.Th(component.SpanText("About the author")),
nodx.Td(
nodx.A(
nodx.Class("link"),
nodx.Href("https://eduardo.lat"),
nodx.Target("_blank"),
component.SpanText("https://eduardo.lat"),
),
),
),
html.Tr(
html.Th(component.SpanText("Repository")),
html.Td(
html.A(
html.Class("link"),
html.Href("https://github.com/eduardolat/pgbackweb"),
html.Target("_blank"),
nodx.Tr(
nodx.Th(component.SpanText("Repository")),
nodx.Td(
nodx.A(
nodx.Class("link"),
nodx.Href("https://github.com/eduardolat/pgbackweb"),
nodx.Target("_blank"),
component.SpanText("https://github.com/eduardolat/pgbackweb"),
),
),

View File

@@ -3,15 +3,13 @@ package backups
import (
"time"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func localBackupsHelp() []gomponents.Node {
return []gomponents.Node{
func localBackupsHelp() []nodx.Node {
return []nodx.Node{
component.H3Text("Local backups"),
component.PText(`
Local backups are stored in the server where PG Back Web is running.
@@ -19,8 +17,8 @@ func localBackupsHelp() []gomponents.Node {
volume to this directory to persist the backups in any way you want.
`),
html.Div(
html.Class("mt-2"),
nodx.Div(
nodx.Class("mt-2"),
component.H3Text("Remote backups"),
component.PText(`
Remote backups are stored in a destination. A destination is a remote
@@ -31,8 +29,8 @@ func localBackupsHelp() []gomponents.Node {
}
}
func cronExpressionHelp() []gomponents.Node {
return []gomponents.Node{
func cronExpressionHelp() []nodx.Node {
return []nodx.Node{
component.PText(`
A cron expression is a string used to define a schedule for running tasks
in Unix-like operating systems. It consists of five fields representing
@@ -40,19 +38,19 @@ func cronExpressionHelp() []gomponents.Node {
Cron expressions enable precise scheduling of periodic tasks.
`),
html.Div(
html.Class("mt-4 flex justify-end items-center space-x-1"),
html.A(
html.Href("https://en.wikipedia.org/wiki/Cron"),
html.Target("_blank"),
html.Class("btn btn-ghost"),
nodx.Div(
nodx.Class("mt-4 flex justify-end items-center space-x-1"),
nodx.A(
nodx.Href("https://en.wikipedia.org/wiki/Cron"),
nodx.Target("_blank"),
nodx.Class("btn btn-ghost"),
component.SpanText("Learn more"),
lucide.ExternalLink(),
),
html.A(
html.Href("https://crontab.guru/examples.html"),
html.Target("_blank"),
html.Class("btn btn-ghost"),
nodx.A(
nodx.Href("https://crontab.guru/examples.html"),
nodx.Target("_blank"),
nodx.Class("btn btn-ghost"),
component.SpanText("Examples & common expressions"),
lucide.ExternalLink(),
),
@@ -60,14 +58,14 @@ func cronExpressionHelp() []gomponents.Node {
}
}
func timezoneFilenamesHelp() []gomponents.Node {
func timezoneFilenamesHelp() []nodx.Node {
serverTimezone := time.Now().Location().String()
return []gomponents.Node{
return []nodx.Node{
component.PText(`
This is the time zone in which the cron expression will be evaluated.
`),
html.P(
nodx.P(
component.SpanText(`
Backup filenames will always use the server timezone (currently
`),
@@ -75,12 +73,12 @@ func timezoneFilenamesHelp() []gomponents.Node {
component.SpanText(")."),
),
html.Div(
html.Class("mt-4 flex justify-end items-center"),
html.A(
html.Href("https://github.com/eduardolat/pgbackweb?tab=readme-ov-file#configuration"),
html.Target("_blank"),
html.Class("btn btn-ghost"),
nodx.Div(
nodx.Class("mt-4 flex justify-end items-center"),
nodx.A(
nodx.Href("https://github.com/eduardolat/pgbackweb?tab=readme-ov-file#configuration"),
nodx.Target("_blank"),
nodx.Class("btn btn-ghost"),
component.SpanText("Learn more in project README"),
lucide.ExternalLink(),
),
@@ -88,8 +86,8 @@ func timezoneFilenamesHelp() []gomponents.Node {
}
}
func destinationDirectoryHelp() []gomponents.Node {
return []gomponents.Node{
func destinationDirectoryHelp() []nodx.Node {
return []nodx.Node{
component.PText(`
The destination directory is the directory where the backups will be
stored. This directory is relative to the base directory of the
@@ -97,15 +95,15 @@ func destinationDirectoryHelp() []gomponents.Node {
spaces, and should not end with a slash.
`),
html.Div(
html.Class("mt-2"),
nodx.Div(
nodx.Class("mt-2"),
component.H3Text("Local backups"),
component.PText(`
For local backups, the base directory is /backups. So, the backup files
will be stored in:
`),
html.Div(
components.Classes{
nodx.Div(
nodx.ClassMap{
"whitespace-nowrap p-1": true,
"overflow-x-scroll": true,
"font-mono": true,
@@ -116,15 +114,15 @@ func destinationDirectoryHelp() []gomponents.Node {
),
),
html.Div(
html.Class("mt-2"),
nodx.Div(
nodx.Class("mt-2"),
component.H3Text("Remote backups"),
component.PText(`
For remote backups, the base directory is the root of the bucket. So,
the backup files will be stored in:
`),
html.Div(
components.Classes{
nodx.Div(
nodx.ClassMap{
"whitespace-nowrap p-1": true,
"overflow-x-scroll": true,
"font-mono": true,
@@ -137,10 +135,10 @@ func destinationDirectoryHelp() []gomponents.Node {
}
}
func retentionDaysHelp() []gomponents.Node {
return []gomponents.Node{
html.Div(
html.Class("space-y-2"),
func retentionDaysHelp() []nodx.Node {
return []nodx.Node{
nodx.Div(
nodx.Class("space-y-2"),
component.PText(`
Retention days specifies the number of days to keep backup files before
@@ -155,10 +153,10 @@ func retentionDaysHelp() []gomponents.Node {
}
}
func pgDumpOptionsHelp() []gomponents.Node {
return []gomponents.Node{
html.Div(
html.Class("space-y-2"),
func pgDumpOptionsHelp() []nodx.Node {
return []nodx.Node{
nodx.Div(
nodx.Class("space-y-2"),
component.PText(`
This software uses the battle tested pg_dump utility to create backups. It
@@ -170,14 +168,14 @@ func pgDumpOptionsHelp() []gomponents.Node {
PG Back Web does not pass any options so the backups are full backups.
`),
html.Div(
html.Class("flex justify-end"),
html.A(
html.Class("btn btn-ghost"),
html.Href("https://www.postgresql.org/docs/current/app-pgdump.html"),
html.Target("_blank"),
nodx.Div(
nodx.Class("flex justify-end"),
nodx.A(
nodx.Class("btn btn-ghost"),
nodx.Href("https://www.postgresql.org/docs/current/app-pgdump.html"),
nodx.Target("_blank"),
component.SpanText("Learn more in pg_dump documentation"),
lucide.ExternalLink(html.Class("ml-1")),
lucide.ExternalLink(nodx.Class("ml-1")),
),
),
),

View File

@@ -4,7 +4,6 @@ import (
"net/http"
"time"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/staticdata"
"github.com/eduardolat/pgbackweb/internal/util/echoutil"
@@ -14,8 +13,8 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) createBackupHandler(c echo.Context) error {
@@ -86,7 +85,7 @@ func (h *handlers) createBackupFormHandler(c echo.Context) error {
return htmx.RespondToastError(c, err.Error())
}
return echoutil.RenderGomponent(
return echoutil.RenderNodx(
c, http.StatusOK, createBackupForm(databases, destinations),
)
}
@@ -94,20 +93,20 @@ func (h *handlers) createBackupFormHandler(c echo.Context) error {
func createBackupForm(
databases []dbgen.DatabasesServiceGetAllDatabasesRow,
destinations []dbgen.DestinationsServiceGetAllDestinationsRow,
) gomponents.Node {
yesNoOptions := func() gomponents.Node {
return gomponents.Group([]gomponents.Node{
html.Option(html.Value("true"), gomponents.Text("Yes")),
html.Option(html.Value("false"), gomponents.Text("No"), html.Selected()),
})
) nodx.Node {
yesNoOptions := func() nodx.Node {
return nodx.Group(
nodx.Option(nodx.Value("true"), nodx.Text("Yes")),
nodx.Option(nodx.Value("false"), nodx.Text("No"), nodx.Selected("")),
)
}
serverTZ := time.Now().Location().String()
return html.Form(
return nodx.FormEl(
htmx.HxPost("/dashboard/backups"),
htmx.HxDisabledELT("find button"),
html.Class("space-y-2 text-base"),
nodx.Class("space-y-2 text-base"),
alpine.XData(`{
is_local: "false",
@@ -126,11 +125,11 @@ func createBackupForm(
Label: "Database",
Required: true,
Placeholder: "Select a database",
Children: []gomponents.Node{
component.GMap(
Children: []nodx.Node{
nodx.Map(
databases,
func(db dbgen.DatabasesServiceGetAllDatabasesRow) gomponents.Node {
return html.Option(html.Value(db.ID.String()), gomponents.Text(db.Name))
func(db dbgen.DatabasesServiceGetAllDatabasesRow) nodx.Node {
return nodx.Option(nodx.Value(db.ID.String()), nodx.Text(db.Name))
},
),
},
@@ -140,10 +139,10 @@ func createBackupForm(
Name: "is_local",
Label: "Local backup",
Required: true,
Children: []gomponents.Node{
Children: []nodx.Node{
alpine.XModel("is_local"),
html.Option(html.Value("true"), gomponents.Text("Yes")),
html.Option(html.Value("false"), gomponents.Text("No"), html.Selected()),
nodx.Option(nodx.Value("true"), nodx.Text("Yes")),
nodx.Option(nodx.Value("false"), nodx.Text("No"), nodx.Selected("")),
},
HelpButtonChildren: localBackupsHelp(),
}),
@@ -155,11 +154,11 @@ func createBackupForm(
Label: "Destination",
Required: true,
Placeholder: "Select a destination",
Children: []gomponents.Node{
component.GMap(
Children: []nodx.Node{
nodx.Map(
destinations,
func(dest dbgen.DestinationsServiceGetAllDestinationsRow) gomponents.Node {
return html.Option(html.Value(dest.ID.String()), gomponents.Text(dest.Name))
func(dest dbgen.DestinationsServiceGetAllDestinationsRow) nodx.Node {
return nodx.Option(nodx.Value(dest.ID.String()), nodx.Text(dest.Name))
},
),
},
@@ -182,16 +181,16 @@ func createBackupForm(
Label: "Time zone",
Required: true,
Placeholder: "Select a time zone",
Children: []gomponents.Node{
component.GMap(
Children: []nodx.Node{
nodx.Map(
staticdata.Timezones,
func(tz staticdata.Timezone) gomponents.Node {
var selected gomponents.Node
func(tz staticdata.Timezone) nodx.Node {
var selected nodx.Node
if tz.TzCode == serverTZ {
selected = html.Selected()
selected = nodx.Selected("")
}
return html.Option(html.Value(tz.TzCode), gomponents.Text(tz.Label), selected)
return nodx.Option(nodx.Value(tz.TzCode), nodx.Text(tz.Label), selected)
},
),
},
@@ -217,9 +216,9 @@ func createBackupForm(
Type: component.InputTypeNumber,
Pattern: "[0-9]+",
HelpButtonChildren: retentionDaysHelp(),
Children: []gomponents.Node{
html.Min("0"),
html.Max("36500"),
Children: []nodx.Node{
nodx.Min("0"),
nodx.Max("36500"),
},
}),
@@ -227,16 +226,16 @@ func createBackupForm(
Name: "is_active",
Label: "Activate backup",
Required: true,
Children: []gomponents.Node{
html.Option(html.Value("true"), gomponents.Text("Yes")),
html.Option(html.Value("false"), gomponents.Text("No")),
Children: []nodx.Node{
nodx.Option(nodx.Value("true"), nodx.Text("Yes")),
nodx.Option(nodx.Value("false"), nodx.Text("No")),
},
}),
html.Div(
html.Class("pt-4"),
html.Div(
html.Class("flex justify-start items-center space-x-1"),
nodx.Div(
nodx.Class("pt-4"),
nodx.Div(
nodx.Class("flex justify-start items-center space-x-1"),
component.H2Text("Options"),
component.HelpButtonModal(component.HelpButtonModalParams{
ModalTitle: "Backup options",
@@ -244,14 +243,14 @@ func createBackupForm(
}),
),
html.Div(
html.Class("mt-2 grid grid-cols-2 gap-2"),
nodx.Div(
nodx.Class("mt-2 grid grid-cols-2 gap-2"),
component.SelectControl(component.SelectControlParams{
Name: "opt_data_only",
Label: "--data-only",
Required: true,
Children: []gomponents.Node{
Children: []nodx.Node{
yesNoOptions(),
},
}),
@@ -260,7 +259,7 @@ func createBackupForm(
Name: "opt_schema_only",
Label: "--schema-only",
Required: true,
Children: []gomponents.Node{
Children: []nodx.Node{
yesNoOptions(),
},
}),
@@ -269,7 +268,7 @@ func createBackupForm(
Name: "opt_clean",
Label: "--clean",
Required: true,
Children: []gomponents.Node{
Children: []nodx.Node{
yesNoOptions(),
},
}),
@@ -278,7 +277,7 @@ func createBackupForm(
Name: "opt_if_exists",
Label: "--if-exists",
Required: true,
Children: []gomponents.Node{
Children: []nodx.Node{
yesNoOptions(),
},
}),
@@ -287,7 +286,7 @@ func createBackupForm(
Name: "opt_create",
Label: "--create",
Required: true,
Children: []gomponents.Node{
Children: []nodx.Node{
yesNoOptions(),
},
}),
@@ -296,19 +295,19 @@ func createBackupForm(
Name: "opt_no_comments",
Label: "--no-comments",
Required: true,
Children: []gomponents.Node{
Children: []nodx.Node{
yesNoOptions(),
},
}),
),
),
html.Div(
html.Class("flex justify-end items-center space-x-2 pt-2"),
nodx.Div(
nodx.Class("flex justify-end items-center space-x-2 pt-2"),
component.HxLoadingMd(),
html.Button(
html.Class("btn btn-primary"),
html.Type("submit"),
nodx.Button(
nodx.Class("btn btn-primary"),
nodx.Type("submit"),
component.SpanText("Save"),
lucide.Save(),
),
@@ -316,30 +315,30 @@ func createBackupForm(
)
}
func createBackupButton() gomponents.Node {
func createBackupButton() nodx.Node {
mo := component.Modal(component.ModalParams{
Size: component.SizeLg,
Title: "Create backup",
Content: []gomponents.Node{
html.Div(
Content: []nodx.Node{
nodx.Div(
htmx.HxGet("/dashboard/backups/create-form"),
htmx.HxSwap("outerHTML"),
htmx.HxTrigger("intersect once"),
html.Class("p-10 flex justify-center"),
nodx.Class("p-10 flex justify-center"),
component.HxLoadingMd(),
),
},
})
button := html.Button(
button := nodx.Button(
mo.OpenerAttr,
html.Class("btn btn-primary"),
nodx.Class("btn btn-primary"),
component.SpanText("Create backup"),
lucide.Plus(),
)
return html.Div(
html.Class("inline-block"),
return nodx.Div(
nodx.Class("inline-block"),
mo.HTML,
button,
)

View File

@@ -1,12 +1,12 @@
package backups
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) deleteBackupHandler(c echo.Context) error {
@@ -24,7 +24,7 @@ func (h *handlers) deleteBackupHandler(c echo.Context) error {
return htmx.RespondRefresh(c)
}
func deleteBackupButton(backupID uuid.UUID) gomponents.Node {
func deleteBackupButton(backupID uuid.UUID) nodx.Node {
return component.OptionsDropdownButton(
htmx.HxDelete("/dashboard/backups/"+backupID.String()),
htmx.HxConfirm("Are you sure you want to delete this backup?"),

View File

@@ -1,12 +1,12 @@
package backups
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) duplicateBackupHandler(c echo.Context) error {
@@ -24,7 +24,7 @@ func (h *handlers) duplicateBackupHandler(c echo.Context) error {
return htmx.RespondRefresh(c)
}
func duplicateBackupButton(backupID uuid.UUID) gomponents.Node {
func duplicateBackupButton(backupID uuid.UUID) nodx.Node {
return component.OptionsDropdownButton(
htmx.HxPost("/dashboard/backups/"+backupID.String()+"/duplicate"),
htmx.HxConfirm("Are you sure you want to duplicate this backup?"),

View File

@@ -4,7 +4,6 @@ import (
"database/sql"
"fmt"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/staticdata"
"github.com/eduardolat/pgbackweb/internal/validate"
@@ -12,8 +11,8 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) editBackupHandler(c echo.Context) error {
@@ -69,30 +68,30 @@ func (h *handlers) editBackupHandler(c echo.Context) error {
return htmx.RespondAlertWithRefresh(c, "Backup updated")
}
func editBackupButton(backup dbgen.BackupsServicePaginateBackupsRow) gomponents.Node {
yesNoOptions := func(value bool) gomponents.Node {
return gomponents.Group([]gomponents.Node{
html.Option(
html.Value("true"),
gomponents.Text("Yes"),
gomponents.If(value, html.Selected()),
func editBackupButton(backup dbgen.BackupsServicePaginateBackupsRow) nodx.Node {
yesNoOptions := func(value bool) nodx.Node {
return nodx.Group(
nodx.Option(
nodx.Value("true"),
nodx.Text("Yes"),
nodx.If(value, nodx.Selected("")),
),
html.Option(
html.Value("false"),
gomponents.Text("No"),
gomponents.If(!value, html.Selected()),
nodx.Option(
nodx.Value("false"),
nodx.Text("No"),
nodx.If(!value, nodx.Selected("")),
),
})
)
}
mo := component.Modal(component.ModalParams{
Size: component.SizeLg,
Title: "Edit backup",
Content: []gomponents.Node{
html.Form(
Content: []nodx.Node{
nodx.FormEl(
htmx.HxPost("/dashboard/backups/"+backup.ID.String()+"/edit"),
htmx.HxDisabledELT("find button"),
html.Class("space-y-2 text-base"),
nodx.Class("space-y-2 text-base"),
component.InputControl(component.InputControlParams{
Name: "name",
@@ -100,8 +99,8 @@ func editBackupButton(backup dbgen.BackupsServicePaginateBackupsRow) gomponents.
Placeholder: "My backup",
Required: true,
Type: component.InputTypeText,
Children: []gomponents.Node{
html.Value(backup.Name),
Children: []nodx.Node{
nodx.Value(backup.Name),
},
}),
@@ -113,8 +112,8 @@ func editBackupButton(backup dbgen.BackupsServicePaginateBackupsRow) gomponents.
Type: component.InputTypeText,
HelpText: "The cron expression to schedule the backup",
Pattern: `^\S+\s+\S+\s+\S+\s+\S+\s+\S+$`,
Children: []gomponents.Node{
html.Value(backup.CronExpression),
Children: []nodx.Node{
nodx.Value(backup.CronExpression),
},
HelpButtonChildren: cronExpressionHelp(),
}),
@@ -124,16 +123,16 @@ func editBackupButton(backup dbgen.BackupsServicePaginateBackupsRow) gomponents.
Label: "Time zone",
Required: true,
Placeholder: "Select a time zone",
Children: []gomponents.Node{
component.GMap(
Children: []nodx.Node{
nodx.Map(
staticdata.Timezones,
func(tz staticdata.Timezone) gomponents.Node {
return html.Option(
html.Value(tz.TzCode),
gomponents.Text(tz.Label),
gomponents.If(
func(tz staticdata.Timezone) nodx.Node {
return nodx.Option(
nodx.Value(tz.TzCode),
nodx.Text(tz.Label),
nodx.If(
tz.TzCode == backup.TimeZone,
html.Selected(),
nodx.Selected(""),
),
)
},
@@ -151,8 +150,8 @@ func editBackupButton(backup dbgen.BackupsServicePaginateBackupsRow) gomponents.
HelpText: "Relative to the base directory of the destination",
HelpButtonChildren: destinationDirectoryHelp(),
Pattern: `^\/\S*[^\/]$`,
Children: []gomponents.Node{
html.Value(backup.DestDir),
Children: []nodx.Node{
nodx.Value(backup.DestDir),
},
}),
@@ -164,10 +163,10 @@ func editBackupButton(backup dbgen.BackupsServicePaginateBackupsRow) gomponents.
Type: component.InputTypeNumber,
Pattern: "[0-9]+",
HelpButtonChildren: retentionDaysHelp(),
Children: []gomponents.Node{
html.Min("0"),
html.Max("36500"),
html.Value(fmt.Sprintf("%d", backup.RetentionDays)),
Children: []nodx.Node{
nodx.Min("0"),
nodx.Max("36500"),
nodx.Value(fmt.Sprintf("%d", backup.RetentionDays)),
},
}),
@@ -175,15 +174,15 @@ func editBackupButton(backup dbgen.BackupsServicePaginateBackupsRow) gomponents.
Name: "is_active",
Label: "Activate backup",
Required: true,
Children: []gomponents.Node{
Children: []nodx.Node{
yesNoOptions(backup.IsActive),
},
}),
html.Div(
html.Class("pt-4"),
html.Div(
html.Class("flex justify-start items-center space-x-1"),
nodx.Div(
nodx.Class("pt-4"),
nodx.Div(
nodx.Class("flex justify-start items-center space-x-1"),
component.H2Text("Options"),
component.HelpButtonModal(component.HelpButtonModalParams{
ModalTitle: "Backup options",
@@ -191,13 +190,13 @@ func editBackupButton(backup dbgen.BackupsServicePaginateBackupsRow) gomponents.
}),
),
html.Div(
html.Class("mt-2 grid grid-cols-2 gap-2"),
nodx.Div(
nodx.Class("mt-2 grid grid-cols-2 gap-2"),
component.SelectControl(component.SelectControlParams{
Name: "opt_data_only",
Label: "--data-only",
Required: true,
Children: []gomponents.Node{
Children: []nodx.Node{
yesNoOptions(backup.OptDataOnly),
},
}),
@@ -206,7 +205,7 @@ func editBackupButton(backup dbgen.BackupsServicePaginateBackupsRow) gomponents.
Name: "opt_schema_only",
Label: "--schema-only",
Required: true,
Children: []gomponents.Node{
Children: []nodx.Node{
yesNoOptions(backup.OptSchemaOnly),
},
}),
@@ -215,7 +214,7 @@ func editBackupButton(backup dbgen.BackupsServicePaginateBackupsRow) gomponents.
Name: "opt_clean",
Label: "--clean",
Required: true,
Children: []gomponents.Node{
Children: []nodx.Node{
yesNoOptions(backup.OptClean),
},
}),
@@ -224,7 +223,7 @@ func editBackupButton(backup dbgen.BackupsServicePaginateBackupsRow) gomponents.
Name: "opt_if_exists",
Label: "--if-exists",
Required: true,
Children: []gomponents.Node{
Children: []nodx.Node{
yesNoOptions(backup.OptIfExists),
},
}),
@@ -233,7 +232,7 @@ func editBackupButton(backup dbgen.BackupsServicePaginateBackupsRow) gomponents.
Name: "opt_create",
Label: "--create",
Required: true,
Children: []gomponents.Node{
Children: []nodx.Node{
yesNoOptions(backup.OptCreate),
},
}),
@@ -242,19 +241,19 @@ func editBackupButton(backup dbgen.BackupsServicePaginateBackupsRow) gomponents.
Name: "opt_no_comments",
Label: "--no-comments",
Required: true,
Children: []gomponents.Node{
Children: []nodx.Node{
yesNoOptions(backup.OptNoComments),
},
}),
),
),
html.Div(
html.Class("flex justify-end items-center space-x-2 pt-2"),
nodx.Div(
nodx.Class("flex justify-end items-center space-x-2 pt-2"),
component.HxLoadingMd(),
html.Button(
html.Class("btn btn-primary"),
html.Type("submit"),
nodx.Button(
nodx.Class("btn btn-primary"),
nodx.Type("submit"),
component.SpanText("Save"),
lucide.Save(),
),
@@ -263,7 +262,7 @@ func editBackupButton(backup dbgen.BackupsServicePaginateBackupsRow) gomponents.
},
})
return html.Div(
return nodx.Div(
mo.HTML,
component.OptionsDropdownButton(
mo.OpenerAttr,

View File

@@ -9,47 +9,46 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/eduardolat/pgbackweb/internal/view/web/layout"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
func (h *handlers) indexPageHandler(c echo.Context) error {
reqCtx := reqctx.GetCtx(c)
return echoutil.RenderGomponent(c, http.StatusOK, indexPage(reqCtx))
return echoutil.RenderNodx(c, http.StatusOK, indexPage(reqCtx))
}
func indexPage(reqCtx reqctx.Ctx) gomponents.Node {
content := []gomponents.Node{
html.Div(
html.Class("flex justify-between items-start"),
func indexPage(reqCtx reqctx.Ctx) nodx.Node {
content := []nodx.Node{
nodx.Div(
nodx.Class("flex justify-between items-start"),
component.H1Text("Backups"),
createBackupButton(),
),
component.CardBox(component.CardBoxParams{
Class: "mt-4",
Children: []gomponents.Node{
html.Div(
html.Class("overflow-x-auto"),
html.Table(
html.Class("table text-nowrap"),
html.THead(
html.Tr(
html.Th(html.Class("w-1")),
html.Th(component.SpanText("Name")),
html.Th(component.SpanText("Database")),
html.Th(component.SpanText("Destination")),
html.Th(component.SpanText("Schedule")),
html.Th(component.SpanText("Retention")),
html.Th(component.SpanText("--data-only")),
html.Th(component.SpanText("--schema-only")),
html.Th(component.SpanText("--clean")),
html.Th(component.SpanText("--if-exists")),
html.Th(component.SpanText("--create")),
html.Th(component.SpanText("--no-comments")),
html.Th(component.SpanText("Created at")),
Children: []nodx.Node{
nodx.Div(
nodx.Class("overflow-x-auto"),
nodx.Table(
nodx.Class("table text-nowrap"),
nodx.Thead(
nodx.Tr(
nodx.Th(nodx.Class("w-1")),
nodx.Th(component.SpanText("Name")),
nodx.Th(component.SpanText("Database")),
nodx.Th(component.SpanText("Destination")),
nodx.Th(component.SpanText("Schedule")),
nodx.Th(component.SpanText("Retention")),
nodx.Th(component.SpanText("--data-only")),
nodx.Th(component.SpanText("--schema-only")),
nodx.Th(component.SpanText("--clean")),
nodx.Th(component.SpanText("--if-exists")),
nodx.Th(component.SpanText("--create")),
nodx.Th(component.SpanText("--no-comments")),
nodx.Th(component.SpanText("Created at")),
),
),
html.TBody(
nodx.Tbody(
component.SkeletonTr(8),
htmx.HxGet("/dashboard/backups/list?page=1"),
htmx.HxTrigger("load"),

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"net/http"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/service/backups"
"github.com/eduardolat/pgbackweb/internal/util/echoutil"
@@ -14,8 +13,8 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) listBackupsHandler(c echo.Context) error {
@@ -41,7 +40,7 @@ func (h *handlers) listBackupsHandler(c echo.Context) error {
return htmx.RespondToastError(c, err.Error())
}
return echoutil.RenderGomponent(
return echoutil.RenderNodx(
c, http.StatusOK, listBackups(pagination, backups),
)
}
@@ -49,7 +48,7 @@ func (h *handlers) listBackupsHandler(c echo.Context) error {
func listBackups(
pagination paginateutil.PaginateResponse,
backups []dbgen.BackupsServicePaginateBackupsRow,
) gomponents.Node {
) nodx.Node {
if len(backups) < 1 {
return component.EmptyResultsTr(component.EmptyResultsParams{
Title: "No backups found",
@@ -57,23 +56,23 @@ func listBackups(
})
}
yesNoSpan := func(b bool) gomponents.Node {
yesNoSpan := func(b bool) nodx.Node {
if b {
return component.SpanText("Yes")
}
return component.SpanText("No")
}
trs := []gomponents.Node{}
trs := []nodx.Node{}
for _, backup := range backups {
trs = append(trs, html.Tr(
html.Td(component.OptionsDropdown(
trs = append(trs, nodx.Tr(
nodx.Td(component.OptionsDropdown(
component.OptionsDropdownA(
html.Class("btn btn-sm btn-ghost btn-square"),
html.Href(
nodx.Class("btn btn-sm btn-ghost btn-square"),
nodx.Href(
fmt.Sprintf("/dashboard/executions?backup=%s", backup.ID),
),
html.Target("_blank"),
nodx.Target("_blank"),
lucide.List(),
component.SpanText("Show executions"),
),
@@ -82,49 +81,49 @@ func listBackups(
duplicateBackupButton(backup.ID),
deleteBackupButton(backup.ID),
)),
html.Td(
html.Div(
html.Class("flex items-center space-x-2"),
nodx.Td(
nodx.Div(
nodx.Class("flex items-center space-x-2"),
component.IsActivePing(backup.IsActive),
component.SpanText(backup.Name),
),
),
html.Td(component.SpanText(backup.DatabaseName)),
html.Td(component.PrettyDestinationName(
nodx.Td(component.SpanText(backup.DatabaseName)),
nodx.Td(component.PrettyDestinationName(
backup.IsLocal, backup.DestinationName,
)),
html.Td(
html.Class("font-mono"),
html.Div(
html.Class("flex flex-col items-start text-xs"),
nodx.Td(
nodx.Class("font-mono"),
nodx.Div(
nodx.Class("flex flex-col items-start text-xs"),
component.SpanText(backup.CronExpression),
component.SpanText(backup.TimeZone),
),
),
html.Td(
gomponents.If(
nodx.Td(
nodx.If(
backup.RetentionDays == 0,
lucide.Infinity(),
),
gomponents.If(
nodx.If(
backup.RetentionDays > 0,
component.SpanText(fmt.Sprintf("%d days", backup.RetentionDays)),
),
),
html.Td(yesNoSpan(backup.OptDataOnly)),
html.Td(yesNoSpan(backup.OptSchemaOnly)),
html.Td(yesNoSpan(backup.OptClean)),
html.Td(yesNoSpan(backup.OptIfExists)),
html.Td(yesNoSpan(backup.OptCreate)),
html.Td(yesNoSpan(backup.OptNoComments)),
html.Td(component.SpanText(
nodx.Td(yesNoSpan(backup.OptDataOnly)),
nodx.Td(yesNoSpan(backup.OptSchemaOnly)),
nodx.Td(yesNoSpan(backup.OptClean)),
nodx.Td(yesNoSpan(backup.OptIfExists)),
nodx.Td(yesNoSpan(backup.OptCreate)),
nodx.Td(yesNoSpan(backup.OptNoComments)),
nodx.Td(component.SpanText(
backup.CreatedAt.Local().Format(timeutil.LayoutYYYYMMDDHHMMSSPretty),
)),
))
}
if pagination.HasNextPage {
trs = append(trs, html.Tr(
trs = append(trs, nodx.Tr(
htmx.HxGet(fmt.Sprintf(
"/dashboard/backups/list?page=%d", pagination.NextPage,
)),

View File

@@ -3,12 +3,12 @@ package backups
import (
"context"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) manualRunHandler(c echo.Context) error {
@@ -24,7 +24,7 @@ func (h *handlers) manualRunHandler(c echo.Context) error {
return htmx.RespondToastSuccess(c, "Backup started, check the backup executions for more details")
}
func manualRunbutton(backupID uuid.UUID) gomponents.Node {
func manualRunbutton(backupID uuid.UUID) nodx.Node {
return component.OptionsDropdownButton(
htmx.HxPost("/dashboard/backups/"+backupID.String()+"/run"),
htmx.HxDisabledELT("this"),

View File

@@ -3,14 +3,13 @@ package databases
import (
"database/sql"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/validate"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
type createDatabaseDTO struct {
@@ -44,24 +43,24 @@ func (h *handlers) createDatabaseHandler(c echo.Context) error {
return htmx.RespondRedirect(c, "/dashboard/databases")
}
func createDatabaseButton() gomponents.Node {
htmxAttributes := func(url string) gomponents.Node {
return gomponents.Group([]gomponents.Node{
func createDatabaseButton() nodx.Node {
htmxAttributes := func(url string) nodx.Node {
return nodx.Group(
htmx.HxPost(url),
htmx.HxInclude("#create-database-form"),
htmx.HxDisabledELT(".create-database-btn"),
htmx.HxIndicator("#create-database-loading"),
htmx.HxValidate("true"),
})
)
}
mo := component.Modal(component.ModalParams{
Size: component.SizeMd,
Title: "Create database",
Content: []gomponents.Node{
html.Form(
html.ID("create-database-form"),
html.Class("space-y-2"),
Content: []nodx.Node{
nodx.FormEl(
nodx.Id("create-database-form"),
nodx.Class("space-y-2"),
component.InputControl(component.InputControlParams{
Name: "name",
@@ -78,7 +77,7 @@ func createDatabaseButton() gomponents.Node {
Placeholder: "Select a version",
Required: true,
HelpText: "The version of the database",
Children: []gomponents.Node{
Children: []nodx.Node{
component.PGVersionSelectOptions(sql.NullString{}),
},
}),
@@ -93,24 +92,24 @@ func createDatabaseButton() gomponents.Node {
}),
),
html.Div(
html.Class("flex justify-between items-center pt-4"),
html.Div(
html.Button(
nodx.Div(
nodx.Class("flex justify-between items-center pt-4"),
nodx.Div(
nodx.Button(
htmxAttributes("/dashboard/databases/test"),
html.Class("create-database-btn btn btn-neutral btn-outline"),
html.Type("button"),
nodx.Class("create-database-btn btn btn-neutral btn-outline"),
nodx.Type("button"),
component.SpanText("Test connection"),
lucide.DatabaseZap(),
),
),
html.Div(
html.Class("flex justify-end items-center space-x-2"),
nodx.Div(
nodx.Class("flex justify-end items-center space-x-2"),
component.HxLoadingMd("create-database-loading"),
html.Button(
nodx.Button(
htmxAttributes("/dashboard/databases"),
html.Class("create-database-btn btn btn-primary"),
html.Type("button"),
nodx.Class("create-database-btn btn btn-primary"),
nodx.Type("button"),
component.SpanText("Save"),
lucide.Save(),
),
@@ -119,15 +118,15 @@ func createDatabaseButton() gomponents.Node {
},
})
button := html.Button(
button := nodx.Button(
mo.OpenerAttr,
html.Class("btn btn-primary"),
nodx.Class("btn btn-primary"),
component.SpanText("Create database"),
lucide.Plus(),
)
return html.Div(
html.Class("inline-block"),
return nodx.Div(
nodx.Class("inline-block"),
mo.HTML,
button,
)

View File

@@ -1,12 +1,12 @@
package databases
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) deleteDatabaseHandler(c echo.Context) error {
@@ -24,7 +24,7 @@ func (h *handlers) deleteDatabaseHandler(c echo.Context) error {
return htmx.RespondRefresh(c)
}
func deleteDatabaseButton(databaseID uuid.UUID) gomponents.Node {
func deleteDatabaseButton(databaseID uuid.UUID) nodx.Node {
return component.OptionsDropdownButton(
htmx.HxDelete("/dashboard/databases/"+databaseID.String()),
htmx.HxConfirm("Are you sure you want to delete this database?"),

View File

@@ -3,16 +3,14 @@ package databases
import (
"database/sql"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/validate"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) editDatabaseHandler(c echo.Context) error {
@@ -48,29 +46,29 @@ func (h *handlers) editDatabaseHandler(c echo.Context) error {
func editDatabaseButton(
database dbgen.DatabasesServicePaginateDatabasesRow,
) gomponents.Node {
) nodx.Node {
idPref := "edit-database-" + database.ID.String()
formID := idPref + "-form"
btnClass := idPref + "-btn"
loadingID := idPref + "-loading"
htmxAttributes := func(url string) gomponents.Node {
return gomponents.Group([]gomponents.Node{
htmxAttributes := func(url string) nodx.Node {
return nodx.Group(
htmx.HxPost(url),
htmx.HxInclude("#" + formID),
htmx.HxDisabledELT("." + btnClass),
htmx.HxIndicator("#" + loadingID),
htmx.HxInclude("#"+formID),
htmx.HxDisabledELT("."+btnClass),
htmx.HxIndicator("#"+loadingID),
htmx.HxValidate("true"),
})
)
}
mo := component.Modal(component.ModalParams{
Size: component.SizeMd,
Title: "Edit database",
Content: []gomponents.Node{
html.Form(
html.ID(formID),
html.Class("space-y-2"),
Content: []nodx.Node{
nodx.FormEl(
nodx.Id(formID),
nodx.Class("space-y-2"),
component.InputControl(component.InputControlParams{
Name: "name",
@@ -79,8 +77,8 @@ func editDatabaseButton(
Required: true,
Type: component.InputTypeText,
HelpText: "A name to easily identify the database",
Children: []gomponents.Node{
html.Value(database.Name),
Children: []nodx.Node{
nodx.Value(database.Name),
},
}),
@@ -89,7 +87,7 @@ func editDatabaseButton(
Label: "Version",
Required: true,
HelpText: "The version of the database",
Children: []gomponents.Node{
Children: []nodx.Node{
component.PGVersionSelectOptions(sql.NullString{
Valid: true,
String: database.PgVersion,
@@ -104,36 +102,36 @@ func editDatabaseButton(
Required: true,
Type: component.InputTypeText,
HelpText: "It should be a valid PostgreSQL connection string including the database name. It will be stored securely using PGP encryption.",
Children: []gomponents.Node{
html.Value(database.DecryptedConnectionString),
Children: []nodx.Node{
nodx.Value(database.DecryptedConnectionString),
},
}),
),
html.Div(
html.Class("flex justify-between items-center pt-4"),
html.Div(
html.Button(
nodx.Div(
nodx.Class("flex justify-between items-center pt-4"),
nodx.Div(
nodx.Button(
htmxAttributes("/dashboard/databases/test"),
components.Classes{
nodx.ClassMap{
btnClass: true,
"btn btn-neutral btn-outline": true,
},
html.Type("button"),
nodx.Type("button"),
component.SpanText("Test connection"),
lucide.DatabaseZap(),
),
),
html.Div(
html.Class("flex justify-end items-center space-x-2"),
nodx.Div(
nodx.Class("flex justify-end items-center space-x-2"),
component.HxLoadingMd(loadingID),
html.Button(
nodx.Button(
htmxAttributes("/dashboard/databases/"+database.ID.String()+"/edit"),
components.Classes{
nodx.ClassMap{
btnClass: true,
"btn btn-primary": true,
},
html.Type("button"),
nodx.Type("button"),
component.SpanText("Save"),
lucide.Save(),
),
@@ -142,7 +140,7 @@ func editDatabaseButton(
},
})
return html.Div(
return nodx.Div(
mo.HTML,
component.OptionsDropdownButton(
mo.OpenerAttr,

View File

@@ -9,39 +9,38 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/eduardolat/pgbackweb/internal/view/web/layout"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
func (h *handlers) indexPageHandler(c echo.Context) error {
reqCtx := reqctx.GetCtx(c)
return echoutil.RenderGomponent(c, http.StatusOK, indexPage(reqCtx))
return echoutil.RenderNodx(c, http.StatusOK, indexPage(reqCtx))
}
func indexPage(reqCtx reqctx.Ctx) gomponents.Node {
content := []gomponents.Node{
html.Div(
html.Class("flex justify-between items-start"),
func indexPage(reqCtx reqctx.Ctx) nodx.Node {
content := []nodx.Node{
nodx.Div(
nodx.Class("flex justify-between items-start"),
component.H1Text("Databases"),
createDatabaseButton(),
),
component.CardBox(component.CardBoxParams{
Class: "mt-4",
Children: []gomponents.Node{
html.Div(
html.Class("overflow-x-auto"),
html.Table(
html.Class("table text-nowrap"),
html.THead(
html.Tr(
html.Th(html.Class("w-1")),
html.Th(component.SpanText("Name")),
html.Th(component.SpanText("Version")),
html.Th(component.SpanText("Connection string")),
html.Th(component.SpanText("Created at")),
Children: []nodx.Node{
nodx.Div(
nodx.Class("overflow-x-auto"),
nodx.Table(
nodx.Class("table text-nowrap"),
nodx.Thead(
nodx.Tr(
nodx.Th(nodx.Class("w-1")),
nodx.Th(component.SpanText("Name")),
nodx.Th(component.SpanText("Version")),
nodx.Th(component.SpanText("Connection string")),
nodx.Th(component.SpanText("Created at")),
),
),
html.TBody(
nodx.Tbody(
component.SkeletonTr(8),
htmx.HxGet("/dashboard/databases/list?page=1"),
htmx.HxTrigger("load"),

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"net/http"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/service/databases"
"github.com/eduardolat/pgbackweb/internal/util/echoutil"
@@ -14,8 +13,8 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) listDatabasesHandler(c echo.Context) error {
@@ -41,7 +40,7 @@ func (h *handlers) listDatabasesHandler(c echo.Context) error {
return htmx.RespondToastError(c, err.Error())
}
return echoutil.RenderGomponent(
return echoutil.RenderNodx(
c, http.StatusOK, listDatabases(pagination, databases),
)
}
@@ -49,7 +48,7 @@ func (h *handlers) listDatabasesHandler(c echo.Context) error {
func listDatabases(
pagination paginateutil.PaginateResponse,
databases []dbgen.DatabasesServicePaginateDatabasesRow,
) gomponents.Node {
) nodx.Node {
if len(databases) < 1 {
return component.EmptyResultsTr(component.EmptyResultsParams{
Title: "No databases found",
@@ -57,17 +56,17 @@ func listDatabases(
})
}
trs := []gomponents.Node{}
trs := []nodx.Node{}
for _, database := range databases {
trs = append(trs, html.Tr(
html.Td(component.OptionsDropdown(
html.Div(
html.Class("flex flex-col space-y-1"),
trs = append(trs, nodx.Tr(
nodx.Td(component.OptionsDropdown(
nodx.Div(
nodx.Class("flex flex-col space-y-1"),
component.OptionsDropdownA(
html.Href(
nodx.Href(
fmt.Sprintf("/dashboard/executions?database=%s", database.ID),
),
html.Target("_blank"),
nodx.Target("_blank"),
lucide.List(),
component.SpanText("Show executions"),
),
@@ -81,29 +80,29 @@ func listDatabases(
deleteDatabaseButton(database.ID),
),
)),
html.Td(
html.Div(
html.Class("flex items-center space-x-2"),
nodx.Td(
nodx.Div(
nodx.Class("flex items-center space-x-2"),
component.HealthStatusPing(
database.TestOk, database.TestError, database.LastTestAt,
),
component.SpanText(database.Name),
),
),
html.Td(component.SpanText("PostgreSQL "+database.PgVersion)),
html.Td(
html.Class("space-x-1"),
nodx.Td(component.SpanText("PostgreSQL "+database.PgVersion)),
nodx.Td(
nodx.Class("space-x-1"),
component.CopyButtonSm(database.DecryptedConnectionString),
component.SpanText("****************"),
),
html.Td(component.SpanText(
nodx.Td(component.SpanText(
database.CreatedAt.Local().Format(timeutil.LayoutYYYYMMDDHHMMSSPretty),
)),
))
}
if pagination.HasNextPage {
trs = append(trs, html.Tr(
trs = append(trs, nodx.Tr(
htmx.HxGet(fmt.Sprintf(
"/dashboard/databases/list?page=%d", pagination.NextPage,
)),

View File

@@ -1,14 +1,13 @@
package destinations
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/validate"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
type createDestinationDTO struct {
@@ -48,24 +47,24 @@ func (h *handlers) createDestinationHandler(c echo.Context) error {
return htmx.RespondRedirect(c, "/dashboard/destinations")
}
func createDestinationButton() gomponents.Node {
htmxAttributes := func(url string) gomponents.Node {
return gomponents.Group([]gomponents.Node{
func createDestinationButton() nodx.Node {
htmxAttributes := func(url string) nodx.Node {
return nodx.Group(
htmx.HxPost(url),
htmx.HxInclude("#create-destination-form"),
htmx.HxDisabledELT(".create-destination-btn"),
htmx.HxIndicator("#create-destination-loading"),
htmx.HxValidate("true"),
})
)
}
mo := component.Modal(component.ModalParams{
Size: component.SizeMd,
Title: "Create destination",
Content: []gomponents.Node{
html.Form(
html.ID("create-destination-form"),
html.Class("space-y-2"),
Content: []nodx.Node{
nodx.FormEl(
nodx.Id("create-destination-form"),
nodx.Class("space-y-2"),
component.InputControl(component.InputControlParams{
Name: "name",
@@ -119,24 +118,24 @@ func createDestinationButton() gomponents.Node {
}),
),
html.Div(
html.Class("flex justify-between items-center pt-4"),
html.Div(
html.Button(
nodx.Div(
nodx.Class("flex justify-between items-center pt-4"),
nodx.Div(
nodx.Button(
htmxAttributes("/dashboard/destinations/test"),
html.Class("create-destination-btn btn btn-neutral btn-outline"),
html.Type("button"),
nodx.Class("create-destination-btn btn btn-neutral btn-outline"),
nodx.Type("button"),
component.SpanText("Test connection"),
lucide.PlugZap(),
),
),
html.Div(
html.Class("flex justify-end items-center space-x-2"),
nodx.Div(
nodx.Class("flex justify-end items-center space-x-2"),
component.HxLoadingMd("create-destination-loading"),
html.Button(
nodx.Button(
htmxAttributes("/dashboard/destinations"),
html.Class("create-destination-btn btn btn-primary"),
html.Type("button"),
nodx.Class("create-destination-btn btn btn-primary"),
nodx.Type("button"),
component.SpanText("Save"),
lucide.Save(),
),
@@ -145,15 +144,15 @@ func createDestinationButton() gomponents.Node {
},
})
button := html.Button(
button := nodx.Button(
mo.OpenerAttr,
html.Class("btn btn-primary"),
nodx.Class("btn btn-primary"),
component.SpanText("Create destination"),
lucide.Plus(),
)
return html.Div(
html.Class("inline-block"),
return nodx.Div(
nodx.Class("inline-block"),
mo.HTML,
button,
)

View File

@@ -1,12 +1,12 @@
package destinations
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) deleteDestinationHandler(c echo.Context) error {
@@ -25,7 +25,7 @@ func (h *handlers) deleteDestinationHandler(c echo.Context) error {
return htmx.RespondRefresh(c)
}
func deleteDestinationButton(destinationID uuid.UUID) gomponents.Node {
func deleteDestinationButton(destinationID uuid.UUID) nodx.Node {
return component.OptionsDropdownButton(
htmx.HxDelete("/dashboard/destinations/"+destinationID.String()),
htmx.HxConfirm("Are you sure you want to delete this destination?"),

View File

@@ -3,16 +3,14 @@ package destinations
import (
"database/sql"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/validate"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) editDestinationHandler(c echo.Context) error {
@@ -51,29 +49,29 @@ func (h *handlers) editDestinationHandler(c echo.Context) error {
func editDestinationButton(
destination dbgen.DestinationsServicePaginateDestinationsRow,
) gomponents.Node {
) nodx.Node {
idPref := "edit-destination-" + destination.ID.String()
formID := idPref + "-form"
btnClass := idPref + "-btn"
loadingID := idPref + "-loading"
htmxAttributes := func(url string) gomponents.Node {
return gomponents.Group([]gomponents.Node{
htmxAttributes := func(url string) nodx.Node {
return nodx.Group(
htmx.HxPost(url),
htmx.HxInclude("#" + formID),
htmx.HxDisabledELT("." + btnClass),
htmx.HxIndicator("#" + loadingID),
htmx.HxInclude("#"+formID),
htmx.HxDisabledELT("."+btnClass),
htmx.HxIndicator("#"+loadingID),
htmx.HxValidate("true"),
})
)
}
mo := component.Modal(component.ModalParams{
Size: component.SizeMd,
Title: "Edit destination",
Content: []gomponents.Node{
html.Form(
html.ID(formID),
html.Class("space-y-2"),
Content: []nodx.Node{
nodx.FormEl(
nodx.Id(formID),
nodx.Class("space-y-2"),
component.InputControl(component.InputControlParams{
Name: "name",
@@ -82,8 +80,8 @@ func editDestinationButton(
Required: true,
Type: component.InputTypeText,
HelpText: "A name to easily identify the destination",
Children: []gomponents.Node{
html.Value(destination.Name),
Children: []nodx.Node{
nodx.Value(destination.Name),
},
}),
@@ -93,8 +91,8 @@ func editDestinationButton(
Placeholder: "my-bucket",
Required: true,
Type: component.InputTypeText,
Children: []gomponents.Node{
html.Value(destination.BucketName),
Children: []nodx.Node{
nodx.Value(destination.BucketName),
},
}),
@@ -104,8 +102,8 @@ func editDestinationButton(
Placeholder: "s3-us-west-1.amazonaws.com",
Required: true,
Type: component.InputTypeText,
Children: []gomponents.Node{
html.Value(destination.Endpoint),
Children: []nodx.Node{
nodx.Value(destination.Endpoint),
},
}),
@@ -115,8 +113,8 @@ func editDestinationButton(
Placeholder: "us-west-1",
Required: true,
Type: component.InputTypeText,
Children: []gomponents.Node{
html.Value(destination.Region),
Children: []nodx.Node{
nodx.Value(destination.Region),
},
}),
@@ -127,8 +125,8 @@ func editDestinationButton(
Required: true,
Type: component.InputTypeText,
HelpText: "It will be stored securely using PGP encryption.",
Children: []gomponents.Node{
html.Value(destination.DecryptedAccessKey),
Children: []nodx.Node{
nodx.Value(destination.DecryptedAccessKey),
},
}),
@@ -139,36 +137,36 @@ func editDestinationButton(
Required: true,
Type: component.InputTypeText,
HelpText: "It will be stored securely using PGP encryption.",
Children: []gomponents.Node{
html.Value(destination.DecryptedSecretKey),
Children: []nodx.Node{
nodx.Value(destination.DecryptedSecretKey),
},
}),
),
html.Div(
html.Class("flex justify-between items-center pt-4"),
html.Div(
html.Button(
nodx.Div(
nodx.Class("flex justify-between items-center pt-4"),
nodx.Div(
nodx.Button(
htmxAttributes("/dashboard/destinations/test"),
components.Classes{
nodx.ClassMap{
btnClass: true,
"btn btn-neutral btn-outline": true,
},
html.Type("button"),
nodx.Type("button"),
component.SpanText("Test connection"),
lucide.PlugZap(),
),
),
html.Div(
html.Class("flex justify-end items-center space-x-2"),
nodx.Div(
nodx.Class("flex justify-end items-center space-x-2"),
component.HxLoadingMd(loadingID),
html.Button(
nodx.Button(
htmxAttributes("/dashboard/destinations/"+destination.ID.String()+"/edit"),
components.Classes{
nodx.ClassMap{
btnClass: true,
"btn btn-primary": true,
},
html.Type("button"),
nodx.Type("button"),
component.SpanText("Save"),
lucide.Save(),
),
@@ -177,7 +175,7 @@ func editDestinationButton(
},
})
return html.Div(
return nodx.Div(
mo.HTML,
component.OptionsDropdownButton(
mo.OpenerAttr,

View File

@@ -9,52 +9,51 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/eduardolat/pgbackweb/internal/view/web/layout"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
func (h *handlers) indexPageHandler(c echo.Context) error {
reqCtx := reqctx.GetCtx(c)
return echoutil.RenderGomponent(c, http.StatusOK, indexPage(reqCtx))
return echoutil.RenderNodx(c, http.StatusOK, indexPage(reqCtx))
}
func indexPage(reqCtx reqctx.Ctx) gomponents.Node {
content := []gomponents.Node{
html.Div(
html.Class("flex justify-between items-start space-x-2"),
html.Div(
func indexPage(reqCtx reqctx.Ctx) nodx.Node {
content := []nodx.Node{
nodx.Div(
nodx.Class("flex justify-between items-start space-x-2"),
nodx.Div(
component.H1Text("S3 Destinations"),
component.PText(`
Here you can manage your S3 destinations. You can skip creating a S3
destination if you want to use the local storage for your backups.
`),
),
html.Div(
html.Class("flex-none"),
nodx.Div(
nodx.Class("flex-none"),
createDestinationButton(),
),
),
component.CardBox(component.CardBoxParams{
Class: "mt-4",
Children: []gomponents.Node{
html.Div(
html.Class("overflow-x-auto"),
html.Table(
html.Class("table text-nowrap"),
html.THead(
html.Tr(
html.Th(html.Class("w-1")),
html.Th(component.SpanText("Name")),
html.Th(component.SpanText("Bucket name")),
html.Th(component.SpanText("Endpoint")),
html.Th(component.SpanText("Region")),
html.Th(component.SpanText("Access key")),
html.Th(component.SpanText("Secret key")),
html.Th(component.SpanText("Created at")),
Children: []nodx.Node{
nodx.Div(
nodx.Class("overflow-x-auto"),
nodx.Table(
nodx.Class("table text-nowrap"),
nodx.Thead(
nodx.Tr(
nodx.Th(nodx.Class("w-1")),
nodx.Th(component.SpanText("Name")),
nodx.Th(component.SpanText("Bucket name")),
nodx.Th(component.SpanText("Endpoint")),
nodx.Th(component.SpanText("Region")),
nodx.Th(component.SpanText("Access key")),
nodx.Th(component.SpanText("Secret key")),
nodx.Th(component.SpanText("Created at")),
),
),
html.TBody(
nodx.Tbody(
component.SkeletonTr(8),
htmx.HxGet("/dashboard/destinations/list?page=1"),
htmx.HxTrigger("load"),

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"net/http"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/service/destinations"
"github.com/eduardolat/pgbackweb/internal/util/echoutil"
@@ -14,8 +13,8 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) listDestinationsHandler(c echo.Context) error {
@@ -41,7 +40,7 @@ func (h *handlers) listDestinationsHandler(c echo.Context) error {
return htmx.RespondToastError(c, err.Error())
}
return echoutil.RenderGomponent(
return echoutil.RenderNodx(
c, http.StatusOK, listDestinations(pagination, destinations),
)
}
@@ -49,7 +48,7 @@ func (h *handlers) listDestinationsHandler(c echo.Context) error {
func listDestinations(
pagination paginateutil.PaginateResponse,
destinations []dbgen.DestinationsServicePaginateDestinationsRow,
) gomponents.Node {
) nodx.Node {
if len(destinations) < 1 {
return component.EmptyResultsTr(component.EmptyResultsParams{
Title: "No destinations found",
@@ -57,15 +56,15 @@ func listDestinations(
})
}
trs := []gomponents.Node{}
trs := []nodx.Node{}
for _, destination := range destinations {
trs = append(trs, html.Tr(
html.Td(component.OptionsDropdown(
trs = append(trs, nodx.Tr(
nodx.Td(component.OptionsDropdown(
component.OptionsDropdownA(
html.Href(
nodx.Href(
fmt.Sprintf("/dashboard/executions?destination=%s", destination.ID),
),
html.Target("_blank"),
nodx.Target("_blank"),
lucide.List(),
component.SpanText("Show executions"),
),
@@ -78,58 +77,58 @@ func listDestinations(
),
deleteDestinationButton(destination.ID),
)),
html.Td(
html.Div(
html.Class("flex items-center space-x-2"),
nodx.Td(
nodx.Div(
nodx.Class("flex items-center space-x-2"),
component.HealthStatusPing(
destination.TestOk, destination.TestError, destination.LastTestAt,
),
component.SpanText(destination.Name),
),
),
html.Td(
html.Div(
html.Class("flex items-center space-x-1"),
nodx.Td(
nodx.Div(
nodx.Class("flex items-center space-x-1"),
component.CopyButtonSm(destination.BucketName),
component.SpanText(destination.BucketName),
),
),
html.Td(
html.Div(
html.Class("flex items-center space-x-1"),
nodx.Td(
nodx.Div(
nodx.Class("flex items-center space-x-1"),
component.CopyButtonSm(destination.Endpoint),
component.SpanText(destination.Endpoint),
),
),
html.Td(
html.Div(
html.Class("flex items-center space-x-1"),
nodx.Td(
nodx.Div(
nodx.Class("flex items-center space-x-1"),
component.CopyButtonSm(destination.Region),
component.SpanText(destination.Region),
),
),
html.Td(
html.Div(
html.Class("flex items-center space-x-1"),
nodx.Td(
nodx.Div(
nodx.Class("flex items-center space-x-1"),
component.CopyButtonSm(destination.DecryptedAccessKey),
component.SpanText("**********"),
),
),
html.Td(
html.Div(
html.Class("flex items-center space-x-1"),
nodx.Td(
nodx.Div(
nodx.Class("flex items-center space-x-1"),
component.CopyButtonSm(destination.DecryptedSecretKey),
component.SpanText("**********"),
),
),
html.Td(component.SpanText(
nodx.Td(component.SpanText(
destination.CreatedAt.Local().Format(timeutil.LayoutYYYYMMDDHHMMSSPretty),
)),
))
}
if pagination.HasNextPage {
trs = append(trs, html.Tr(
trs = append(trs, nodx.Tr(
htmx.HxGet(fmt.Sprintf(
"/dashboard/destinations/list?page=%d", pagination.NextPage,
)),

View File

@@ -12,8 +12,7 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/layout"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
type execsQueryData struct {
@@ -33,33 +32,33 @@ func (h *handlers) indexPageHandler(c echo.Context) error {
return c.String(http.StatusBadRequest, err.Error())
}
return echoutil.RenderGomponent(c, http.StatusOK, indexPage(reqCtx, queryData))
return echoutil.RenderNodx(c, http.StatusOK, indexPage(reqCtx, queryData))
}
func indexPage(reqCtx reqctx.Ctx, queryData execsQueryData) gomponents.Node {
content := []gomponents.Node{
func indexPage(reqCtx reqctx.Ctx, queryData execsQueryData) nodx.Node {
content := []nodx.Node{
component.H1Text("Executions"),
component.CardBox(component.CardBoxParams{
Class: "mt-4",
Children: []gomponents.Node{
html.Div(
html.Class("overflow-x-auto"),
html.Table(
html.Class("table text-nowrap"),
html.THead(
html.Tr(
html.Th(html.Class("w-1")),
html.Th(component.SpanText("Status")),
html.Th(component.SpanText("Backup")),
html.Th(component.SpanText("Database")),
html.Th(component.SpanText("Destination")),
html.Th(component.SpanText("Started at")),
html.Th(component.SpanText("Finished at")),
html.Th(component.SpanText("Duration")),
html.Th(component.SpanText("File size")),
Children: []nodx.Node{
nodx.Div(
nodx.Class("overflow-x-auto"),
nodx.Table(
nodx.Class("table text-nowrap"),
nodx.Thead(
nodx.Tr(
nodx.Th(nodx.Class("w-1")),
nodx.Th(component.SpanText("Status")),
nodx.Th(component.SpanText("Backup")),
nodx.Th(component.SpanText("Database")),
nodx.Th(component.SpanText("Destination")),
nodx.Th(component.SpanText("Started at")),
nodx.Th(component.SpanText("Finished at")),
nodx.Th(component.SpanText("Duration")),
nodx.Th(component.SpanText("File size")),
),
),
html.TBody(
nodx.Tbody(
component.SkeletonTr(8),
htmx.HxGet(func() string {
url := "/dashboard/executions/list?page=1"

View File

@@ -15,8 +15,7 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
type listExecsQueryData struct {
@@ -56,7 +55,7 @@ func (h *handlers) listExecutionsHandler(c echo.Context) error {
return htmx.RespondToastError(c, err.Error())
}
return echoutil.RenderGomponent(
return echoutil.RenderNodx(
c, http.StatusOK, listExecutions(queryData, pagination, executions),
)
}
@@ -65,7 +64,7 @@ func listExecutions(
queryData listExecsQueryData,
pagination paginateutil.PaginateResponse,
executions []dbgen.ExecutionsServicePaginateExecutionsRow,
) gomponents.Node {
) nodx.Node {
if len(executions) < 1 {
return component.EmptyResultsTr(component.EmptyResultsParams{
Title: "No executions found",
@@ -73,40 +72,40 @@ func listExecutions(
})
}
trs := []gomponents.Node{}
trs := []nodx.Node{}
for _, execution := range executions {
trs = append(trs, html.Tr(
html.Td(component.OptionsDropdown(
trs = append(trs, nodx.Tr(
nodx.Td(component.OptionsDropdown(
showExecutionButton(execution),
restoreExecutionButton(execution),
)),
html.Td(component.StatusBadge(execution.Status)),
html.Td(component.SpanText(execution.BackupName)),
html.Td(component.SpanText(execution.DatabaseName)),
html.Td(component.PrettyDestinationName(
nodx.Td(component.StatusBadge(execution.Status)),
nodx.Td(component.SpanText(execution.BackupName)),
nodx.Td(component.SpanText(execution.DatabaseName)),
nodx.Td(component.PrettyDestinationName(
execution.BackupIsLocal, execution.DestinationName,
)),
html.Td(component.SpanText(
nodx.Td(component.SpanText(
execution.StartedAt.Local().Format(timeutil.LayoutYYYYMMDDHHMMSSPretty),
)),
html.Td(
gomponents.If(
nodx.Td(
nodx.If(
execution.FinishedAt.Valid,
component.SpanText(
execution.FinishedAt.Time.Local().Format(timeutil.LayoutYYYYMMDDHHMMSSPretty),
),
),
),
html.Td(
gomponents.If(
nodx.Td(
nodx.If(
execution.FinishedAt.Valid,
component.SpanText(
execution.FinishedAt.Time.Sub(execution.StartedAt).String(),
),
),
),
html.Td(
gomponents.If(
nodx.Td(
nodx.If(
execution.FileSize.Valid,
component.PrettyFileSize(execution.FileSize),
),
@@ -115,7 +114,7 @@ func listExecutions(
}
if pagination.HasNextPage {
trs = append(trs, html.Tr(
trs = append(trs, nodx.Tr(
htmx.HxGet(func() string {
url := "/dashboard/executions/list"
url = strutil.AddQueryParamToUrl(url, "page", fmt.Sprintf("%d", pagination.NextPage))

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"net/http"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/util/echoutil"
"github.com/eduardolat/pgbackweb/internal/validate"
@@ -14,8 +13,8 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) restoreExecutionHandler(c echo.Context) error {
@@ -97,7 +96,7 @@ func (h *handlers) restoreExecutionFormHandler(c echo.Context) error {
return c.String(http.StatusInternalServerError, err.Error())
}
return echoutil.RenderGomponent(c, http.StatusOK, restoreExecutionForm(
return echoutil.RenderNodx(c, http.StatusOK, restoreExecutionForm(
execution, databases,
))
}
@@ -105,38 +104,38 @@ func (h *handlers) restoreExecutionFormHandler(c echo.Context) error {
func restoreExecutionForm(
execution dbgen.ExecutionsServiceGetExecutionRow,
databases []dbgen.DatabasesServiceGetAllDatabasesRow,
) gomponents.Node {
return html.Form(
) nodx.Node {
return nodx.FormEl(
htmx.HxPost("/dashboard/executions/"+execution.ID.String()+"/restore"),
htmx.HxConfirm("Are you sure you want to restore this backup?"),
htmx.HxDisabledELT("find button"),
alpine.XData(`{ backup_to: "database" }`),
html.Input(
html.Type("hidden"),
html.Name("execution_id"),
html.Value(execution.ID.String()),
nodx.Input(
nodx.Type("hidden"),
nodx.Name("execution_id"),
nodx.Value(execution.ID.String()),
),
html.Div(
html.Class("space-y-2 text-base"),
nodx.Div(
nodx.Class("space-y-2 text-base"),
component.SelectControl(component.SelectControlParams{
Name: "backup_to",
Label: "Backup to",
Required: true,
HelpText: "You can restore the backup to an existing database or any other database using a connection string",
Children: []gomponents.Node{
Children: []nodx.Node{
alpine.XModel("backup_to"),
html.Option(
html.Value("database"),
gomponents.Text("Existing database"),
html.Selected(),
nodx.Option(
nodx.Value("database"),
nodx.Text("Existing database"),
nodx.Selected(""),
),
html.Option(
html.Value("conn_string"),
gomponents.Text("Other database"),
nodx.Option(
nodx.Value("conn_string"),
nodx.Text("Other database"),
),
},
}),
@@ -148,16 +147,16 @@ func restoreExecutionForm(
Label: "Database",
Placeholder: "Select a database",
Required: true,
Children: []gomponents.Node{
component.GMap(
Children: []nodx.Node{
nodx.Map(
databases,
func(db dbgen.DatabasesServiceGetAllDatabasesRow) gomponents.Node {
return html.Option(
html.Value(db.ID.String()),
gomponents.Text(db.Name),
gomponents.If(
func(db dbgen.DatabasesServiceGetAllDatabasesRow) nodx.Node {
return nodx.Option(
nodx.Value(db.ID.String()),
nodx.Text(db.Name),
nodx.If(
db.ID == execution.DatabaseID,
html.Selected(),
nodx.Selected(""),
),
)
},
@@ -177,14 +176,14 @@ func restoreExecutionForm(
}),
),
html.Div(
html.Class("pt-2"),
html.Div(
html.Role("alert"),
html.Class("alert alert-warning"),
nodx.Div(
nodx.Class("pt-2"),
nodx.Div(
nodx.Role("alert"),
nodx.Class("alert alert-warning"),
lucide.TriangleAlert(),
html.Div(
html.P(
nodx.Div(
nodx.P(
component.BText(fmt.Sprintf(
"This restoration uses psql v%s", execution.DatabasePgVersion,
)),
@@ -198,12 +197,12 @@ func restoreExecutionForm(
),
),
html.Div(
html.Class("flex justify-end items-center space-x-2 pt-2"),
nodx.Div(
nodx.Class("flex justify-end items-center space-x-2 pt-2"),
component.HxLoadingMd(),
html.Button(
html.Class("btn btn-primary"),
html.Type("submit"),
nodx.Button(
nodx.Class("btn btn-primary"),
nodx.Type("submit"),
component.SpanText("Start restoration"),
lucide.Zap(),
),
@@ -212,7 +211,7 @@ func restoreExecutionForm(
)
}
func restoreExecutionButton(execution dbgen.ExecutionsServicePaginateExecutionsRow) gomponents.Node {
func restoreExecutionButton(execution dbgen.ExecutionsServicePaginateExecutionsRow) nodx.Node {
if execution.Status != "success" || !execution.Path.Valid {
return nil
}
@@ -220,18 +219,18 @@ func restoreExecutionButton(execution dbgen.ExecutionsServicePaginateExecutionsR
mo := component.Modal(component.ModalParams{
Size: component.SizeMd,
Title: "Restore backup execution",
Content: []gomponents.Node{
html.Div(
Content: []nodx.Node{
nodx.Div(
htmx.HxGet("/dashboard/executions/"+execution.ID.String()+"/restore-form"),
htmx.HxSwap("outerHTML"),
htmx.HxTrigger("intersect once"),
html.Class("p-10 flex justify-center"),
nodx.Class("p-10 flex justify-center"),
component.HxLoadingMd(),
),
},
})
return html.Div(
return nodx.Div(
mo.HTML,
component.OptionsDropdownButton(
mo.OpenerAttr,

View File

@@ -4,14 +4,13 @@ import (
"net/http"
"path/filepath"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/util/timeutil"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) downloadExecutionHandler(c echo.Context) error {
@@ -38,93 +37,93 @@ func (h *handlers) downloadExecutionHandler(c echo.Context) error {
func showExecutionButton(
execution dbgen.ExecutionsServicePaginateExecutionsRow,
) gomponents.Node {
) nodx.Node {
mo := component.Modal(component.ModalParams{
Title: "Execution details",
Size: component.SizeMd,
Content: []gomponents.Node{
html.Div(
html.Class("overflow-x-auto"),
html.Table(
html.Class("table [&_th]:text-nowrap"),
html.Tr(
html.Th(component.SpanText("ID")),
html.Td(component.SpanText(execution.ID.String())),
Content: []nodx.Node{
nodx.Div(
nodx.Class("overflow-x-auto"),
nodx.Table(
nodx.Class("table [&_th]:text-nowrap"),
nodx.Tr(
nodx.Th(component.SpanText("ID")),
nodx.Td(component.SpanText(execution.ID.String())),
),
html.Tr(
html.Th(component.SpanText("Status")),
html.Td(component.StatusBadge(execution.Status)),
nodx.Tr(
nodx.Th(component.SpanText("Status")),
nodx.Td(component.StatusBadge(execution.Status)),
),
html.Tr(
html.Th(component.SpanText("Database")),
html.Td(component.SpanText(execution.DatabaseName)),
nodx.Tr(
nodx.Th(component.SpanText("Database")),
nodx.Td(component.SpanText(execution.DatabaseName)),
),
html.Tr(
html.Th(component.SpanText("Destination")),
html.Td(component.PrettyDestinationName(
nodx.Tr(
nodx.Th(component.SpanText("Destination")),
nodx.Td(component.PrettyDestinationName(
execution.BackupIsLocal, execution.DestinationName,
)),
),
gomponents.If(
nodx.If(
execution.Message.Valid,
html.Tr(
html.Th(component.SpanText("Message")),
html.Td(
html.Class("break-all"),
nodx.Tr(
nodx.Th(component.SpanText("Message")),
nodx.Td(
nodx.Class("break-all"),
component.SpanText(execution.Message.String),
),
),
),
html.Tr(
html.Th(component.SpanText("Started at")),
html.Td(component.SpanText(
nodx.Tr(
nodx.Th(component.SpanText("Started at")),
nodx.Td(component.SpanText(
execution.StartedAt.Local().Format(timeutil.LayoutYYYYMMDDHHMMSSPretty),
)),
),
gomponents.If(
nodx.If(
execution.FinishedAt.Valid,
html.Tr(
html.Th(component.SpanText("Finished at")),
html.Td(component.SpanText(
nodx.Tr(
nodx.Th(component.SpanText("Finished at")),
nodx.Td(component.SpanText(
execution.FinishedAt.Time.Local().Format(timeutil.LayoutYYYYMMDDHHMMSSPretty),
)),
),
),
gomponents.If(
nodx.If(
execution.FinishedAt.Valid,
html.Tr(
html.Th(component.SpanText("Took")),
html.Td(component.SpanText(
nodx.Tr(
nodx.Th(component.SpanText("Took")),
nodx.Td(component.SpanText(
execution.FinishedAt.Time.Sub(execution.StartedAt).String(),
)),
),
),
gomponents.If(
nodx.If(
execution.DeletedAt.Valid,
html.Tr(
html.Th(component.SpanText("Deleted at")),
html.Td(component.SpanText(
nodx.Tr(
nodx.Th(component.SpanText("Deleted at")),
nodx.Td(component.SpanText(
execution.DeletedAt.Time.Local().Format(timeutil.LayoutYYYYMMDDHHMMSSPretty),
)),
),
),
gomponents.If(
nodx.If(
execution.FileSize.Valid,
html.Tr(
html.Th(component.SpanText("File size")),
html.Td(component.PrettyFileSize(execution.FileSize)),
nodx.Tr(
nodx.Th(component.SpanText("File size")),
nodx.Td(component.PrettyFileSize(execution.FileSize)),
),
),
),
gomponents.If(
nodx.If(
execution.Status == "success",
html.Div(
html.Class("flex justify-end items-center space-x-2"),
nodx.Div(
nodx.Class("flex justify-end items-center space-x-2"),
deleteExecutionButton(execution.ID),
html.A(
html.Href("/dashboard/executions/"+execution.ID.String()+"/download"),
html.Target("_blank"),
html.Class("btn btn-primary"),
nodx.A(
nodx.Href("/dashboard/executions/"+execution.ID.String()+"/download"),
nodx.Target("_blank"),
nodx.Class("btn btn-primary"),
component.SpanText("Download"),
lucide.Download(),
),
@@ -134,7 +133,7 @@ func showExecutionButton(
},
})
return html.Div(
return nodx.Div(
mo.HTML,
component.OptionsDropdownButton(
mo.OpenerAttr,

View File

@@ -1,13 +1,12 @@
package executions
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) deleteExecutionHandler(c echo.Context) error {
@@ -26,12 +25,12 @@ func (h *handlers) deleteExecutionHandler(c echo.Context) error {
return htmx.RespondRefresh(c)
}
func deleteExecutionButton(executionID uuid.UUID) gomponents.Node {
return html.Button(
func deleteExecutionButton(executionID uuid.UUID) nodx.Node {
return nodx.Button(
htmx.HxDelete("/dashboard/executions/"+executionID.String()),
htmx.HxDisabledELT("this"),
htmx.HxConfirm("Are you sure you want to delete this execution? It will delete the backup file from the destination and it can't be recovered."),
html.Class("btn btn-error btn-outline"),
nodx.Class("btn btn-error btn-outline"),
component.SpanText("Delete"),
lucide.Trash(),
)

View File

@@ -10,8 +10,7 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
func healthButtonHandler(servs *service.Service) echo.HandlerFunc {
@@ -27,7 +26,7 @@ func healthButtonHandler(servs *service.Service) echo.HandlerFunc {
return htmx.RespondToastError(c, err.Error())
}
return echoutil.RenderGomponent(c, http.StatusOK, healthButton(
return echoutil.RenderNodx(c, http.StatusOK, healthButton(
databasesQty, destinationsQty,
))
}
@@ -36,7 +35,7 @@ func healthButtonHandler(servs *service.Service) echo.HandlerFunc {
func healthButton(
databasesQty dbgen.DatabasesServiceGetDatabasesQtyRow,
destinationsQty dbgen.DestinationsServiceGetDestinationsQtyRow,
) gomponents.Node {
) nodx.Node {
isHealthy := true
if databasesQty.Unhealthy > 0 {
@@ -54,7 +53,7 @@ func healthButton(
mo := component.Modal(component.ModalParams{
Size: component.SizeMd,
Title: "Health status",
Content: []gomponents.Node{
Content: []nodx.Node{
component.PText(`
The health check for both databases and destinations runs automatically
every 10 minutes, when PG Back Web starts, and when you click the
@@ -62,40 +61,40 @@ func healthButton(
information and error messages by clicking the health check button
for each resource.
`),
html.Table(
html.Class("table mt-2"),
html.THead(
html.Tr(
html.Th(component.SpanText("Resource")),
html.Th(component.SpanText("Total")),
html.Th(component.SpanText("Healthy")),
html.Th(component.SpanText("Unhealthy")),
nodx.Table(
nodx.Class("table mt-2"),
nodx.Thead(
nodx.Tr(
nodx.Th(component.SpanText("Resource")),
nodx.Th(component.SpanText("Total")),
nodx.Th(component.SpanText("Healthy")),
nodx.Th(component.SpanText("Unhealthy")),
),
),
html.TBody(
html.Tr(
html.Td(component.SpanText("Databases")),
html.Td(component.SpanText(fmt.Sprintf("%d", databasesQty.All))),
html.Td(component.SpanText(fmt.Sprintf("%d", databasesQty.Healthy))),
html.Td(component.SpanText(fmt.Sprintf("%d", databasesQty.Unhealthy))),
nodx.Tbody(
nodx.Tr(
nodx.Td(component.SpanText("Databases")),
nodx.Td(component.SpanText(fmt.Sprintf("%d", databasesQty.All))),
nodx.Td(component.SpanText(fmt.Sprintf("%d", databasesQty.Healthy))),
nodx.Td(component.SpanText(fmt.Sprintf("%d", databasesQty.Unhealthy))),
),
html.Tr(
html.Td(component.SpanText("Destinations")),
html.Td(component.SpanText(fmt.Sprintf("%d", destinationsQty.All))),
html.Td(component.SpanText(fmt.Sprintf("%d", destinationsQty.Healthy))),
html.Td(component.SpanText(fmt.Sprintf("%d", destinationsQty.Unhealthy))),
nodx.Tr(
nodx.Td(component.SpanText("Destinations")),
nodx.Td(component.SpanText(fmt.Sprintf("%d", destinationsQty.All))),
nodx.Td(component.SpanText(fmt.Sprintf("%d", destinationsQty.Healthy))),
nodx.Td(component.SpanText(fmt.Sprintf("%d", destinationsQty.Unhealthy))),
),
),
),
},
})
return html.Div(
html.Class("inline-block"),
return nodx.Div(
nodx.Class("inline-block"),
mo.HTML,
html.Button(
nodx.Button(
mo.OpenerAttr,
html.Class("btn btn-ghost btn-neutral"),
nodx.Class("btn btn-ghost btn-neutral"),
component.SpanText("Health status"),
component.Ping(pingColor),
),

View File

@@ -1,52 +1,51 @@
package profile
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/util/timeutil"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func closeAllSessionsForm(sessions []dbgen.Session) gomponents.Node {
func closeAllSessionsForm(sessions []dbgen.Session) nodx.Node {
return component.CardBox(component.CardBoxParams{
Children: []gomponents.Node{
Children: []nodx.Node{
component.H2Text("Close all sessions"),
component.PText("This will log you out from all devices including this one."),
html.Button(
nodx.Button(
htmx.HxPost("/auth/logout-all"),
htmx.HxDisabledELT("this"),
htmx.HxConfirm("Are you sure you want to close all your sessions?"),
html.Class("mt-2 btn btn-error"),
nodx.Class("mt-2 btn btn-error"),
component.SpanText("Close all sessions"),
lucide.LogOut(),
),
html.Div(html.Class("divider")),
nodx.Div(nodx.Class("divider")),
component.H2Text("Active sessions"),
component.PText("All sessions are open for a maximum of 12 hours."),
html.Div(
html.Class("overflow-x-auto"),
html.Table(
html.Class("table"),
html.THead(
html.Tr(
html.Th(component.SpanText("Login time")),
html.Th(component.SpanText("IP address")),
html.Th(component.SpanText("User agent")),
nodx.Div(
nodx.Class("overflow-x-auto"),
nodx.Table(
nodx.Class("table"),
nodx.Thead(
nodx.Tr(
nodx.Th(component.SpanText("Login time")),
nodx.Th(component.SpanText("IP address")),
nodx.Th(component.SpanText("User agent")),
),
),
html.TBody(
component.GMap(sessions, func(session dbgen.Session) gomponents.Node {
return html.Tr(
html.Td(component.SpanText(
nodx.Tbody(
nodx.Map(sessions, func(session dbgen.Session) nodx.Node {
return nodx.Tr(
nodx.Td(component.SpanText(
session.CreatedAt.Local().Format(timeutil.LayoutYYYYMMDDHHMMSSPretty),
)),
html.Td(component.SpanText(session.Ip)),
html.Td(component.SpanText(session.UserAgent)),
nodx.Td(component.SpanText(session.Ip)),
nodx.Td(component.SpanText(session.UserAgent)),
)
}),
),

View File

@@ -10,8 +10,7 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/layout"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
func (h *handlers) indexPageHandler(c echo.Context) error {
@@ -24,19 +23,19 @@ func (h *handlers) indexPageHandler(c echo.Context) error {
return c.String(http.StatusInternalServerError, "failed to get user sessions")
}
return echoutil.RenderGomponent(
return echoutil.RenderNodx(
c, http.StatusOK, indexPage(reqCtx, sessions),
)
}
func indexPage(reqCtx reqctx.Ctx, sessions []dbgen.Session) gomponents.Node {
content := []gomponents.Node{
func indexPage(reqCtx reqctx.Ctx, sessions []dbgen.Session) nodx.Node {
content := []nodx.Node{
component.H1Text("Profile"),
html.Div(
html.Class("mt-4 grid grid-cols-2 gap-4"),
html.Div(updateUserForm(reqCtx.User)),
html.Div(closeAllSessionsForm(sessions)),
nodx.Div(
nodx.Class("mt-4 grid grid-cols-2 gap-4"),
nodx.Div(updateUserForm(reqCtx.User)),
nodx.Div(closeAllSessionsForm(sessions)),
),
}

View File

@@ -3,15 +3,14 @@ package profile
import (
"database/sql"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/validate"
"github.com/eduardolat/pgbackweb/internal/view/reqctx"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) updateUserHandler(c echo.Context) error {
@@ -44,13 +43,13 @@ func (h *handlers) updateUserHandler(c echo.Context) error {
return htmx.RespondToastSuccess(c, "Profile updated")
}
func updateUserForm(user dbgen.User) gomponents.Node {
func updateUserForm(user dbgen.User) nodx.Node {
return component.CardBox(component.CardBoxParams{
Children: []gomponents.Node{
html.Form(
Children: []nodx.Node{
nodx.FormEl(
htmx.HxPost("/dashboard/profile"),
htmx.HxDisabledELT("find button"),
html.Class("space-y-2"),
nodx.Class("space-y-2"),
component.H2Text("Update profile"),
@@ -61,8 +60,8 @@ func updateUserForm(user dbgen.User) gomponents.Node {
Required: true,
Type: component.InputTypeText,
AutoComplete: "name",
Children: []gomponents.Node{
html.Value(user.Name),
Children: []nodx.Node{
nodx.Value(user.Name),
},
}),
@@ -73,8 +72,8 @@ func updateUserForm(user dbgen.User) gomponents.Node {
Required: true,
AutoComplete: "email",
Type: component.InputTypeEmail,
Children: []gomponents.Node{
html.Value(user.Email),
Children: []nodx.Node{
nodx.Value(user.Email),
},
}),
@@ -95,12 +94,12 @@ func updateUserForm(user dbgen.User) gomponents.Node {
Type: component.InputTypePassword,
}),
html.Div(
html.Class("flex justify-end items-center space-x-2 pt-2"),
nodx.Div(
nodx.Class("flex justify-end items-center space-x-2 pt-2"),
component.HxLoadingMd(),
html.Button(
html.Class("btn btn-primary"),
html.Type("submit"),
nodx.Button(
nodx.Class("btn btn-primary"),
nodx.Type("submit"),
component.SpanText("Save changes"),
lucide.Save(),
),

View File

@@ -12,8 +12,7 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/layout"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
type resQueryData struct {
@@ -32,32 +31,32 @@ func (h *handlers) indexPageHandler(c echo.Context) error {
return c.String(http.StatusBadRequest, err.Error())
}
return echoutil.RenderGomponent(c, http.StatusOK, indexPage(reqCtx, queryData))
return echoutil.RenderNodx(c, http.StatusOK, indexPage(reqCtx, queryData))
}
func indexPage(reqCtx reqctx.Ctx, queryData resQueryData) gomponents.Node {
content := []gomponents.Node{
func indexPage(reqCtx reqctx.Ctx, queryData resQueryData) nodx.Node {
content := []nodx.Node{
component.H1Text("Restorations"),
component.CardBox(component.CardBoxParams{
Class: "mt-4",
Children: []gomponents.Node{
html.Div(
html.Class("overflow-x-auto"),
html.Table(
html.Class("table text-nowrap"),
html.THead(
html.Tr(
html.Th(html.Class("w-1")),
html.Th(component.SpanText("Status")),
html.Th(component.SpanText("Backup")),
html.Th(component.SpanText("Database")),
html.Th(component.SpanText("Execution")),
html.Th(component.SpanText("Started at")),
html.Th(component.SpanText("Finished at")),
html.Th(component.SpanText("Duration")),
Children: []nodx.Node{
nodx.Div(
nodx.Class("overflow-x-auto"),
nodx.Table(
nodx.Class("table text-nowrap"),
nodx.Thead(
nodx.Tr(
nodx.Th(nodx.Class("w-1")),
nodx.Th(component.SpanText("Status")),
nodx.Th(component.SpanText("Backup")),
nodx.Th(component.SpanText("Database")),
nodx.Th(component.SpanText("Execution")),
nodx.Th(component.SpanText("Started at")),
nodx.Th(component.SpanText("Finished at")),
nodx.Th(component.SpanText("Duration")),
),
),
html.TBody(
nodx.Tbody(
component.SkeletonTr(8),
htmx.HxGet(func() string {
url := "/dashboard/restorations/list?page=1"

View File

@@ -15,8 +15,7 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
type listResQueryData struct {
@@ -52,7 +51,7 @@ func (h *handlers) listRestorationsHandler(c echo.Context) error {
return htmx.RespondToastError(c, err.Error())
}
return echoutil.RenderGomponent(
return echoutil.RenderNodx(
c, http.StatusOK, listRestorations(queryData, pagination, restorations),
)
}
@@ -61,7 +60,7 @@ func listRestorations(
queryData listResQueryData,
pagination paginateutil.PaginateResponse,
restorations []dbgen.RestorationsServicePaginateRestorationsRow,
) gomponents.Node {
) nodx.Node {
if len(restorations) < 1 {
return component.EmptyResultsTr(component.EmptyResultsParams{
Title: "No restorations found",
@@ -69,34 +68,34 @@ func listRestorations(
})
}
trs := []gomponents.Node{}
trs := []nodx.Node{}
for _, restoration := range restorations {
trs = append(trs, html.Tr(
html.Td(
trs = append(trs, nodx.Tr(
nodx.Td(
showRestorationButton(restoration),
),
html.Td(component.StatusBadge(restoration.Status)),
html.Td(component.SpanText(restoration.BackupName)),
html.Td(component.SpanText(func() string {
nodx.Td(component.StatusBadge(restoration.Status)),
nodx.Td(component.SpanText(restoration.BackupName)),
nodx.Td(component.SpanText(func() string {
if restoration.DatabaseName.Valid {
return restoration.DatabaseName.String
}
return "Other database"
}())),
html.Td(component.SpanText(restoration.ExecutionID.String())),
html.Td(component.SpanText(
nodx.Td(component.SpanText(restoration.ExecutionID.String())),
nodx.Td(component.SpanText(
restoration.StartedAt.Local().Format(timeutil.LayoutYYYYMMDDHHMMSSPretty),
)),
html.Td(
gomponents.If(
nodx.Td(
nodx.If(
restoration.FinishedAt.Valid,
component.SpanText(
restoration.FinishedAt.Time.Local().Format(timeutil.LayoutYYYYMMDDHHMMSSPretty),
),
),
),
html.Td(
gomponents.If(
nodx.Td(
nodx.If(
restoration.FinishedAt.Valid,
component.SpanText(
restoration.FinishedAt.Time.Sub(restoration.StartedAt).String(),
@@ -107,7 +106,7 @@ func listRestorations(
}
if pagination.HasNextPage {
trs = append(trs, html.Tr(
trs = append(trs, nodx.Tr(
htmx.HxGet(func() string {
url := "/dashboard/restorations/list"
url = strutil.AddQueryParamToUrl(url, "page", fmt.Sprintf("%d", pagination.NextPage))

View File

@@ -1,76 +1,75 @@
package restorations
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/util/timeutil"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func showRestorationButton(
restoration dbgen.RestorationsServicePaginateRestorationsRow,
) gomponents.Node {
) nodx.Node {
mo := component.Modal(component.ModalParams{
Title: "Restoration details",
Size: component.SizeMd,
Content: []gomponents.Node{
html.Div(
html.Class("overflow-x-auto"),
html.Table(
html.Class("table [&_th]:text-nowrap"),
html.Tr(
html.Th(component.SpanText("ID")),
html.Td(component.SpanText(restoration.ID.String())),
Content: []nodx.Node{
nodx.Div(
nodx.Class("overflow-x-auto"),
nodx.Table(
nodx.Class("table [&_th]:text-nowrap"),
nodx.Tr(
nodx.Th(component.SpanText("ID")),
nodx.Td(component.SpanText(restoration.ID.String())),
),
html.Tr(
html.Th(component.SpanText("Status")),
html.Td(component.StatusBadge(restoration.Status)),
nodx.Tr(
nodx.Th(component.SpanText("Status")),
nodx.Td(component.StatusBadge(restoration.Status)),
),
html.Tr(
html.Th(component.SpanText("Backup")),
html.Td(component.SpanText(restoration.BackupName)),
nodx.Tr(
nodx.Th(component.SpanText("Backup")),
nodx.Td(component.SpanText(restoration.BackupName)),
),
html.Tr(
html.Th(component.SpanText("Database")),
html.Td(component.SpanText(func() string {
nodx.Tr(
nodx.Th(component.SpanText("Database")),
nodx.Td(component.SpanText(func() string {
if restoration.DatabaseName.Valid {
return restoration.DatabaseName.String
}
return "Other database"
}())),
),
gomponents.If(
nodx.If(
restoration.Message.Valid,
html.Tr(
html.Th(component.SpanText("Message")),
html.Td(
html.Class("break-all"),
nodx.Tr(
nodx.Th(component.SpanText("Message")),
nodx.Td(
nodx.Class("break-all"),
component.SpanText(restoration.Message.String),
),
),
),
html.Tr(
html.Th(component.SpanText("Started At")),
html.Td(component.SpanText(
nodx.Tr(
nodx.Th(component.SpanText("Started At")),
nodx.Td(component.SpanText(
restoration.StartedAt.Local().Format(timeutil.LayoutYYYYMMDDHHMMSSPretty),
)),
),
gomponents.If(
nodx.If(
restoration.FinishedAt.Valid,
html.Tr(
html.Th(component.SpanText("Finished At")),
html.Td(component.SpanText(
nodx.Tr(
nodx.Th(component.SpanText("Finished At")),
nodx.Td(component.SpanText(
restoration.FinishedAt.Time.Local().Format(timeutil.LayoutYYYYMMDDHHMMSSPretty),
)),
),
),
gomponents.If(
nodx.If(
restoration.FinishedAt.Valid,
html.Tr(
html.Th(component.SpanText("Took")),
html.Td(component.SpanText(
nodx.Tr(
nodx.Th(component.SpanText("Took")),
nodx.Td(component.SpanText(
restoration.FinishedAt.Time.Sub(restoration.StartedAt).String(),
)),
),
@@ -80,15 +79,15 @@ func showRestorationButton(
},
})
button := html.Button(
button := nodx.Button(
mo.OpenerAttr,
html.Class("btn btn-square btn-sm btn-ghost"),
nodx.Class("btn btn-square btn-sm btn-ghost"),
lucide.Eye(),
)
return html.Div(
html.Class("inline-block tooltip tooltip-right"),
html.Data("tip", "Show details"),
return nodx.Div(
nodx.Class("inline-block tooltip tooltip-right"),
nodx.Data("tip", "Show details"),
mo.HTML,
button,
)

View File

@@ -11,8 +11,7 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/layout"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
func (h *handlers) indexPageHandler(c echo.Context) error {
@@ -40,7 +39,7 @@ func (h *handlers) indexPageHandler(c echo.Context) error {
return c.String(http.StatusInternalServerError, err.Error())
}
return echoutil.RenderGomponent(
return echoutil.RenderNodx(
c, http.StatusOK,
indexPage(
reqCtx, databasesQty, destinationsQty, backupsQty, executionsQty,
@@ -56,7 +55,7 @@ func indexPage(
backupsQty dbgen.BackupsServiceGetBackupsQtyRow,
executionsQty dbgen.ExecutionsServiceGetExecutionsQtyRow,
restorationsQty dbgen.RestorationsServiceGetRestorationsQtyRow,
) gomponents.Node {
) nodx.Node {
type ChartData struct {
Label string
Labels []string
@@ -68,13 +67,13 @@ func indexPage(
title string,
count int64,
chartData ChartData,
) gomponents.Node {
chart := func() gomponents.Node {
notAvailable := html.Div(
html.Class("size-[218px] flex flex-col justify-center items-center"),
html.Span(
html.Class("text-sm text-base-content pb-[32px]"),
gomponents.Text("Chart waiting for data"),
) nodx.Node {
chart := func() nodx.Node {
notAvailable := nodx.Div(
nodx.Class("size-[218px] flex flex-col justify-center items-center"),
nodx.SpanEl(
nodx.Class("text-sm text-base-content pb-[32px]"),
nodx.Text("Chart waiting for data"),
),
)
@@ -110,10 +109,10 @@ func indexPage(
bgColors += fmt.Sprintf("'%s',", color)
}
return html.Div(
html.Class("mt-2"),
html.Div(html.Canvas(html.ID(chartID))),
html.Script(gomponents.Raw(`
return nodx.Div(
nodx.Class("mt-2"),
nodx.Div(nodx.Canvas(nodx.Id(chartID))),
nodx.Script(nodx.Raw(`
new Chart(document.getElementById('`+chartID+`'), {
type: 'doughnut',
data: {
@@ -140,7 +139,7 @@ func indexPage(
return component.CardBox(component.CardBoxParams{
Class: "flex-none text-center w-[250px]",
Children: []gomponents.Node{
Children: []nodx.Node{
component.H2Text(fmt.Sprintf("%d %s", count, title)),
chart(),
},
@@ -154,13 +153,13 @@ func indexPage(
blueColor = "#00b6ff"
)
content := []gomponents.Node{
html.Div(
html.Class("flex justify-center"),
content := []nodx.Node{
nodx.Div(
nodx.Class("flex justify-center"),
component.H1Text("Summary"),
),
html.Div(
html.Class("mt-4 flex justify-center flex-wrap gap-4"),
nodx.Div(
nodx.Class("mt-4 flex justify-center flex-wrap gap-4"),
countCard("Databases", databasesQty.All, ChartData{
Label: "Quantity",

View File

@@ -1,52 +1,51 @@
package summary
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/alpine"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func indexHowTo() gomponents.Node {
return html.Div(
func indexHowTo() nodx.Node {
return nodx.Div(
alpine.XData("alpineSummaryHowToSlider()"),
alpine.XCloak(),
html.Class("mt-6 flex flex-col justify-center items-center mx-auto"),
nodx.Class("mt-6 flex flex-col justify-center items-center mx-auto"),
component.H2Text("How to use PG Back Web"),
component.CardBox(component.CardBoxParams{
Class: "mt-4 space-y-4 max-w-[600px]",
Children: []gomponents.Node{
html.Div(
html.Class("flex justify-center"),
html.Ul(
html.Class("steps"),
html.Li(
html.Class("step"),
Children: []nodx.Node{
nodx.Div(
nodx.Class("flex justify-center"),
nodx.Ul(
nodx.Class("steps"),
nodx.Li(
nodx.Class("step"),
alpine.XBind("class", "currentSlide >= 1 ? 'step-primary' : ''"),
gomponents.Text("Create database"),
nodx.Text("Create database"),
),
html.Li(
html.Class("step"),
nodx.Li(
nodx.Class("step"),
alpine.XBind("class", "currentSlide >= 2 ? 'step-primary' : ''"),
gomponents.Text("Create destination"),
nodx.Text("Create destination"),
),
html.Li(
html.Class("step"),
nodx.Li(
nodx.Class("step"),
alpine.XBind("class", "currentSlide >= 3 ? 'step-primary' : ''"),
gomponents.Text("Create backup"),
nodx.Text("Create backup"),
),
html.Li(
html.Class("step"),
nodx.Li(
nodx.Class("step"),
alpine.XBind("class", "currentSlide >= 4 ? 'step-primary' : ''"),
gomponents.Text("Wait for executions"),
nodx.Text("Wait for executions"),
),
),
),
html.Div(
nodx.Div(
alpine.XShow("currentSlide === 1"),
component.H3Text("Create database"),
component.PText(`
@@ -57,7 +56,7 @@ func indexHowTo() gomponents.Node {
`),
),
html.Div(
nodx.Div(
alpine.XShow("currentSlide === 2"),
component.H3Text("Create S3 destination (optional)"),
component.PText(`
@@ -70,7 +69,7 @@ func indexHowTo() gomponents.Node {
`),
),
html.Div(
nodx.Div(
alpine.XShow("currentSlide === 3"),
component.H3Text("Create backup"),
component.PText(`
@@ -82,7 +81,7 @@ func indexHowTo() gomponents.Node {
`),
),
html.Div(
nodx.Div(
alpine.XShow("currentSlide === 4"),
component.H3Text("Wait for executions"),
component.PText(`
@@ -96,19 +95,19 @@ func indexHowTo() gomponents.Node {
`),
),
html.Div(
html.Class("mt-4 flex justify-between"),
html.Button(
nodx.Div(
nodx.Class("mt-4 flex justify-between"),
nodx.Button(
alpine.XBind("disabled", "!hasPrevSlide"),
alpine.XOn("click", "prevSlide"),
html.Class("btn btn-neutral btn-ghost"),
nodx.Class("btn btn-neutral btn-ghost"),
lucide.ChevronLeft(),
component.SpanText("Previous"),
),
html.Button(
nodx.Button(
alpine.XBind("disabled", "!hasNextSlide"),
alpine.XOn("click", "nextSlide"),
html.Class("btn btn-neutral btn-ghost"),
nodx.Class("btn btn-neutral btn-ghost"),
component.SpanText("Next"),
lucide.ChevronRight(),
),

View File

@@ -9,8 +9,7 @@ import (
"github.com/eduardolat/pgbackweb/internal/util/maputil"
"github.com/eduardolat/pgbackweb/internal/view/web/alpine"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
func createAndUpdateWebhookForm(
@@ -18,55 +17,55 @@ func createAndUpdateWebhookForm(
destinations []dbgen.DestinationsServiceGetAllDestinationsRow,
backups []dbgen.Backup,
webhook ...dbgen.Webhook,
) gomponents.Node {
) nodx.Node {
shouldPrefill, pickedWebhook := false, dbgen.Webhook{}
if len(webhook) > 0 {
shouldPrefill = true
pickedWebhook = webhook[0]
}
eventTypeOptions := gomponents.Group([]gomponents.Node{
html.Option(
gomponents.Text("Select an event type"),
html.Disabled(),
gomponents.If(
eventTypeOptions := nodx.Group(
nodx.Option(
nodx.Text("Select an event type"),
nodx.Disabled(""),
nodx.If(
!shouldPrefill,
html.Selected(),
nodx.Selected(""),
),
),
component.GMap(
nodx.Map(
maputil.GetSortedStringKeys(webhooks.FullEventTypes),
func(key string) gomponents.Node {
func(key string) nodx.Node {
val := webhooks.FullEventTypes[key]
return html.Option(
html.Value(key),
gomponents.Text(val),
gomponents.If(
return nodx.Option(
nodx.Value(key),
nodx.Text(val),
nodx.If(
shouldPrefill && key == pickedWebhook.EventType,
html.Selected(),
nodx.Selected(""),
),
)
},
),
})
)
databaseSelect := component.SelectControl(component.SelectControlParams{
Name: "target_ids",
Label: "Database targets",
Required: true,
Children: []gomponents.Node{
Children: []nodx.Node{
alpine.XModel("targetIds"),
html.Multiple(),
component.GMap(
nodx.Multiple(""),
nodx.Map(
databases,
func(db dbgen.DatabasesServiceGetAllDatabasesRow) gomponents.Node {
return html.Option(
html.Value(db.ID.String()),
gomponents.Text(db.Name),
gomponents.If(
func(db dbgen.DatabasesServiceGetAllDatabasesRow) nodx.Node {
return nodx.Option(
nodx.Value(db.ID.String()),
nodx.Text(db.Name),
nodx.If(
shouldPrefill && slices.Contains(pickedWebhook.TargetIds, db.ID),
html.Selected(),
nodx.Selected(""),
),
)
},
@@ -78,18 +77,18 @@ func createAndUpdateWebhookForm(
Name: "target_ids",
Label: "Destination targets",
Required: true,
Children: []gomponents.Node{
Children: []nodx.Node{
alpine.XModel("targetIds"),
html.Multiple(),
component.GMap(
nodx.Multiple(""),
nodx.Map(
destinations,
func(dest dbgen.DestinationsServiceGetAllDestinationsRow) gomponents.Node {
return html.Option(
html.Value(dest.ID.String()),
gomponents.Text(dest.Name),
gomponents.If(
func(dest dbgen.DestinationsServiceGetAllDestinationsRow) nodx.Node {
return nodx.Option(
nodx.Value(dest.ID.String()),
nodx.Text(dest.Name),
nodx.If(
shouldPrefill && slices.Contains(pickedWebhook.TargetIds, dest.ID),
html.Selected(),
nodx.Selected(""),
),
)
},
@@ -101,18 +100,18 @@ func createAndUpdateWebhookForm(
Name: "target_ids",
Label: "Backup targets",
Required: true,
Children: []gomponents.Node{
Children: []nodx.Node{
alpine.XModel("targetIds"),
html.Multiple(),
component.GMap(
nodx.Multiple(""),
nodx.Map(
backups,
func(backup dbgen.Backup) gomponents.Node {
return html.Option(
html.Value(backup.ID.String()),
gomponents.Text(backup.Name),
gomponents.If(
func(backup dbgen.Backup) nodx.Node {
return nodx.Option(
nodx.Value(backup.ID.String()),
nodx.Text(backup.Name),
nodx.If(
shouldPrefill && slices.Contains(pickedWebhook.TargetIds, backup.ID),
html.Selected(),
nodx.Selected(""),
),
)
},
@@ -120,7 +119,7 @@ func createAndUpdateWebhookForm(
},
})
eventTypeSelects := map[string]gomponents.Node{
eventTypeSelects := map[string]nodx.Node{
webhooks.EventTypeDatabaseHealthy.Value.Key: databaseSelect,
webhooks.EventTypeDatabaseUnhealthy.Value.Key: databaseSelect,
webhooks.EventTypeDestinationHealthy.Value.Key: destinationSelect,
@@ -129,7 +128,7 @@ func createAndUpdateWebhookForm(
webhooks.EventTypeExecutionFailed.Value.Key: backupSelect,
}
targetIdsSelect := []gomponents.Node{}
targetIdsSelect := []nodx.Node{}
for eventType, selectNode := range eventTypeSelects {
targetIdsSelect = append(targetIdsSelect, alpine.Template(
alpine.XIf(fmt.Sprintf("isEventType('%s')", eventType)),
@@ -144,8 +143,8 @@ func createAndUpdateWebhookForm(
}
}
return html.Div(
html.Class("space-y-2"),
return nodx.Div(
nodx.Class("space-y-2"),
alpine.XData(`{
eventType: "`+pickedWebhook.EventType+`",
@@ -193,8 +192,8 @@ func createAndUpdateWebhookForm(
Placeholder: "My webhook",
Required: true,
Type: component.InputTypeText,
Children: []gomponents.Node{
gomponents.If(shouldPrefill, html.Value(pickedWebhook.Name)),
Children: []nodx.Node{
nodx.If(shouldPrefill, nodx.Value(pickedWebhook.Name)),
},
}),
@@ -202,14 +201,14 @@ func createAndUpdateWebhookForm(
Name: "event_type",
Label: "Event type",
Required: true,
HelpButtonChildren: []gomponents.Node{
HelpButtonChildren: []nodx.Node{
component.H3Text("Event types"),
component.PText(`
These are the event types that can trigger a webhook.
`),
html.Div(
html.Class("space-y-2"),
nodx.Div(
nodx.Class("space-y-2"),
component.CardBoxSimple(
component.H4Text("Database healthy"),
@@ -259,27 +258,27 @@ func createAndUpdateWebhookForm(
),
),
},
Children: []gomponents.Node{
Children: []nodx.Node{
alpine.XModel("eventType"),
eventTypeOptions,
},
}),
html.Div(targetIdsSelect...),
nodx.Div(targetIdsSelect...),
component.SelectControl(component.SelectControlParams{
Name: "is_active",
Label: "Activate webhook",
Required: true,
Children: []gomponents.Node{
html.Option(
html.Value("true"), gomponents.Text("Yes"),
gomponents.If(!shouldPrefill, html.Selected()),
gomponents.If(shouldPrefill && pickedWebhook.IsActive, html.Selected()),
Children: []nodx.Node{
nodx.Option(
nodx.Value("true"), nodx.Text("Yes"),
nodx.If(!shouldPrefill, nodx.Selected("")),
nodx.If(shouldPrefill && pickedWebhook.IsActive, nodx.Selected("")),
),
html.Option(
html.Value("false"), gomponents.Text("No"),
gomponents.If(shouldPrefill && !pickedWebhook.IsActive, html.Selected()),
nodx.Option(
nodx.Value("false"), nodx.Text("No"),
nodx.If(shouldPrefill && !pickedWebhook.IsActive, nodx.Selected("")),
),
},
}),
@@ -290,8 +289,8 @@ func createAndUpdateWebhookForm(
Placeholder: "https://example.com/webhook",
Required: true,
Type: component.InputTypeUrl,
Children: []gomponents.Node{
gomponents.If(shouldPrefill, html.Value(pickedWebhook.Url)),
Children: []nodx.Node{
nodx.If(shouldPrefill, nodx.Value(pickedWebhook.Url)),
},
}),
@@ -299,22 +298,22 @@ func createAndUpdateWebhookForm(
Name: "method",
Label: "Method",
Required: true,
Children: []gomponents.Node{
html.Option(
html.Value("POST"),
gomponents.Text("POST"),
gomponents.If(!shouldPrefill, html.Selected()),
gomponents.If(
Children: []nodx.Node{
nodx.Option(
nodx.Value("POST"),
nodx.Text("POST"),
nodx.If(!shouldPrefill, nodx.Selected("")),
nodx.If(
shouldPrefill && pickedWebhook.Method == "POST",
html.Selected(),
nodx.Selected(""),
),
),
html.Option(
html.Value("GET"),
gomponents.Text("GET"),
gomponents.If(
nodx.Option(
nodx.Value("GET"),
nodx.Text("GET"),
nodx.If(
shouldPrefill && pickedWebhook.Method == "GET",
html.Selected(),
nodx.Selected(""),
),
),
},
@@ -325,12 +324,12 @@ func createAndUpdateWebhookForm(
Label: "Headers",
Placeholder: `{ "Authorization": "Bearer my-token" }`,
HelpText: `By default it will send a { "Content-Type": "application/json" } header.`,
Children: []gomponents.Node{
Children: []nodx.Node{
alpine.XRef("headersTextarea"),
alpine.XOn("click.outside", "formatHeadersTextarea()"),
alpine.XOn("input", "autoGrowHeadersTextarea()"),
gomponents.If(
shouldPrefill, gomponents.Text(pickedWebhook.Headers.String),
nodx.If(
shouldPrefill, nodx.Text(pickedWebhook.Headers.String),
),
},
}),
@@ -340,12 +339,12 @@ func createAndUpdateWebhookForm(
Label: "Body",
Placeholder: `{ "key": "value" }`,
HelpText: `By default it will send an empty json object {}.`,
Children: []gomponents.Node{
Children: []nodx.Node{
alpine.XRef("bodyTextarea"),
alpine.XOn("click.outside", "formatBodyTextarea()"),
alpine.XOn("input", "autoGrowBodyTextarea()"),
gomponents.If(
shouldPrefill, gomponents.Text(pickedWebhook.Body.String),
nodx.If(
shouldPrefill, nodx.Text(pickedWebhook.Body.String),
),
},
}),

View File

@@ -4,7 +4,6 @@ import (
"database/sql"
"net/http"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/util/echoutil"
"github.com/eduardolat/pgbackweb/internal/validate"
@@ -12,8 +11,8 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
type createWebhookDTO struct {
@@ -75,7 +74,7 @@ func (h *handlers) createWebhookFormHandler(c echo.Context) error {
return htmx.RespondToastError(c, err.Error())
}
return echoutil.RenderGomponent(c, http.StatusOK, createWebhookForm(
return echoutil.RenderNodx(c, http.StatusOK, createWebhookForm(
databases, destinations, backups,
))
}
@@ -84,20 +83,20 @@ func createWebhookForm(
databases []dbgen.DatabasesServiceGetAllDatabasesRow,
destinations []dbgen.DestinationsServiceGetAllDestinationsRow,
backups []dbgen.Backup,
) gomponents.Node {
return html.Form(
) nodx.Node {
return nodx.FormEl(
htmx.HxPost("/dashboard/webhooks/create"),
htmx.HxDisabledELT("find button[type='submit']"),
html.Class("space-y-2"),
nodx.Class("space-y-2"),
createAndUpdateWebhookForm(databases, destinations, backups),
html.Div(
html.Class("flex justify-end items-center space-x-2 pt-2"),
nodx.Div(
nodx.Class("flex justify-end items-center space-x-2 pt-2"),
component.HxLoadingMd(),
html.Button(
html.Class("btn btn-primary"),
html.Type("submit"),
nodx.Button(
nodx.Class("btn btn-primary"),
nodx.Type("submit"),
component.SpanText("Save"),
lucide.Save(),
),
@@ -105,30 +104,30 @@ func createWebhookForm(
)
}
func createWebhookButton() gomponents.Node {
func createWebhookButton() nodx.Node {
mo := component.Modal(component.ModalParams{
Size: component.SizeLg,
Title: "Create webhook",
Content: []gomponents.Node{
html.Div(
Content: []nodx.Node{
nodx.Div(
htmx.HxGet("/dashboard/webhooks/create"),
htmx.HxSwap("outerHTML"),
htmx.HxTrigger("intersect once"),
html.Class("p-10 flex justify-center"),
nodx.Class("p-10 flex justify-center"),
component.HxLoadingMd(),
),
},
})
button := html.Button(
button := nodx.Button(
mo.OpenerAttr,
html.Class("btn btn-primary"),
nodx.Class("btn btn-primary"),
component.SpanText("Create webhook"),
lucide.Plus(),
)
return html.Div(
html.Class("inline-block"),
return nodx.Div(
nodx.Class("inline-block"),
mo.HTML,
button,
)

View File

@@ -1,12 +1,12 @@
package webhooks
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) deleteWebhookHandler(c echo.Context) error {
@@ -24,7 +24,7 @@ func (h *handlers) deleteWebhookHandler(c echo.Context) error {
return htmx.RespondRefresh(c)
}
func deleteWebhookButton(webhookID uuid.UUID) gomponents.Node {
func deleteWebhookButton(webhookID uuid.UUID) nodx.Node {
return component.OptionsDropdownButton(
htmx.HxDelete("/dashboard/webhooks/"+webhookID.String()),
htmx.HxConfirm("Are you sure you want to delete this webhook?"),

View File

@@ -1,12 +1,12 @@
package webhooks
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) duplicateWebhookHandler(c echo.Context) error {
@@ -24,7 +24,7 @@ func (h *handlers) duplicateWebhookHandler(c echo.Context) error {
return htmx.RespondRefresh(c)
}
func duplicateWebhookButton(webhookID uuid.UUID) gomponents.Node {
func duplicateWebhookButton(webhookID uuid.UUID) nodx.Node {
return component.OptionsDropdownButton(
htmx.HxPost("/dashboard/webhooks/"+webhookID.String()+"/duplicate"),
htmx.HxConfirm("Are you sure you want to duplicate this webhook?"),

View File

@@ -4,7 +4,6 @@ import (
"database/sql"
"net/http"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/util/echoutil"
"github.com/eduardolat/pgbackweb/internal/validate"
@@ -12,8 +11,8 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
type editWebhookDTO struct {
@@ -89,7 +88,7 @@ func (h *handlers) editWebhookFormHandler(c echo.Context) error {
return htmx.RespondToastError(c, err.Error())
}
return echoutil.RenderGomponent(c, http.StatusOK, editWebhookForm(
return echoutil.RenderNodx(c, http.StatusOK, editWebhookForm(
webhook, databases, destinations, backups,
))
}
@@ -99,20 +98,20 @@ func editWebhookForm(
databases []dbgen.DatabasesServiceGetAllDatabasesRow,
destinations []dbgen.DestinationsServiceGetAllDestinationsRow,
backups []dbgen.Backup,
) gomponents.Node {
return html.Form(
) nodx.Node {
return nodx.FormEl(
htmx.HxPost("/dashboard/webhooks/"+webhook.ID.String()+"/edit"),
htmx.HxDisabledELT("find button[type='submit']"),
html.Class("space-y-2"),
nodx.Class("space-y-2"),
createAndUpdateWebhookForm(databases, destinations, backups, webhook),
html.Div(
html.Class("flex justify-end items-center space-x-2 pt-2"),
nodx.Div(
nodx.Class("flex justify-end items-center space-x-2 pt-2"),
component.HxLoadingMd(),
html.Button(
html.Class("btn btn-primary"),
html.Type("submit"),
nodx.Button(
nodx.Class("btn btn-primary"),
nodx.Type("submit"),
component.SpanText("Save"),
lucide.Save(),
),
@@ -120,22 +119,22 @@ func editWebhookForm(
)
}
func editWebhookButton(webhookID uuid.UUID) gomponents.Node {
func editWebhookButton(webhookID uuid.UUID) nodx.Node {
mo := component.Modal(component.ModalParams{
Size: component.SizeLg,
Title: "Edit webhook",
Content: []gomponents.Node{
html.Div(
Content: []nodx.Node{
nodx.Div(
htmx.HxGet("/dashboard/webhooks/"+webhookID.String()+"/edit"),
htmx.HxSwap("outerHTML"),
htmx.HxTrigger("intersect once"),
html.Class("p-10 flex justify-center"),
nodx.Class("p-10 flex justify-center"),
component.HxLoadingMd(),
),
},
})
return html.Div(
return nodx.Div(
mo.HTML,
component.OptionsDropdownButton(
mo.OpenerAttr,

View File

@@ -9,42 +9,41 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/eduardolat/pgbackweb/internal/view/web/layout"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
func (h *handlers) indexPageHandler(c echo.Context) error {
reqCtx := reqctx.GetCtx(c)
return echoutil.RenderGomponent(
return echoutil.RenderNodx(
c, http.StatusOK, indexPage(reqCtx),
)
}
func indexPage(reqCtx reqctx.Ctx) gomponents.Node {
content := []gomponents.Node{
html.Div(
html.Class("flex justify-between items-start"),
func indexPage(reqCtx reqctx.Ctx) nodx.Node {
content := []nodx.Node{
nodx.Div(
nodx.Class("flex justify-between items-start"),
component.H1Text("Webhooks"),
createWebhookButton(),
),
component.CardBox(component.CardBoxParams{
Class: "mt-4",
Children: []gomponents.Node{
html.Div(
html.Class("overflow-x-auto"),
html.Table(
html.Class("table text-nowrap"),
html.THead(
html.Tr(
html.Th(html.Class("w-1")),
html.Th(component.SpanText("Name")),
html.Th(component.SpanText("Event type")),
html.Th(component.SpanText("Targets")),
html.Th(component.SpanText("Created at")),
Children: []nodx.Node{
nodx.Div(
nodx.Class("overflow-x-auto"),
nodx.Table(
nodx.Class("table text-nowrap"),
nodx.Thead(
nodx.Tr(
nodx.Th(nodx.Class("w-1")),
nodx.Th(component.SpanText("Name")),
nodx.Th(component.SpanText("Event type")),
nodx.Th(component.SpanText("Targets")),
nodx.Th(component.SpanText("Created at")),
),
),
html.TBody(
nodx.Tbody(
component.SkeletonTr(8),
htmx.HxGet("/dashboard/webhooks/list?page=1"),
htmx.HxTrigger("load"),

View File

@@ -14,8 +14,7 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
func (h *handlers) listWebhooksHandler(c echo.Context) error {
@@ -41,7 +40,7 @@ func (h *handlers) listWebhooksHandler(c echo.Context) error {
return htmx.RespondToastError(c, err.Error())
}
return echoutil.RenderGomponent(
return echoutil.RenderNodx(
c, http.StatusOK, listWebhooks(pagination, whooks),
)
}
@@ -49,7 +48,7 @@ func (h *handlers) listWebhooksHandler(c echo.Context) error {
func listWebhooks(
pagination paginateutil.PaginateResponse,
whooks []dbgen.Webhook,
) gomponents.Node {
) nodx.Node {
if len(whooks) < 1 {
return component.EmptyResultsTr(component.EmptyResultsParams{
Title: "No webhooks found",
@@ -57,24 +56,24 @@ func listWebhooks(
})
}
trs := []gomponents.Node{}
trs := []nodx.Node{}
for _, whook := range whooks {
trs = append(trs, html.Tr(
html.Td(component.OptionsDropdown(
trs = append(trs, nodx.Tr(
nodx.Td(component.OptionsDropdown(
webhookExecutionsButton(whook.ID),
runWebhookButton(whook.ID),
editWebhookButton(whook.ID),
duplicateWebhookButton(whook.ID),
deleteWebhookButton(whook.ID),
)),
html.Td(
html.Div(
html.Class("flex items-center space-x-2"),
nodx.Td(
nodx.Div(
nodx.Class("flex items-center space-x-2"),
component.IsActivePing(whook.IsActive),
component.SpanText(whook.Name),
),
),
html.Td(component.SpanText(
nodx.Td(component.SpanText(
func() string {
if name, ok := webhooks.FullEventTypes[whook.EventType]; ok {
return name
@@ -82,15 +81,15 @@ func listWebhooks(
return whook.EventType
}(),
)),
html.Td(component.SpanText(fmt.Sprintf("%d", len(whook.TargetIds)))),
html.Td(component.SpanText(
nodx.Td(component.SpanText(fmt.Sprintf("%d", len(whook.TargetIds)))),
nodx.Td(component.SpanText(
whook.CreatedAt.Local().Format(timeutil.LayoutYYYYMMDDHHMMSSPretty),
)),
))
}
if pagination.HasNextPage {
trs = append(trs, html.Tr(
trs = append(trs, nodx.Tr(
htmx.HxGet(func() string {
url := "/dashboard/webhooks/list"
url = strutil.AddQueryParamToUrl(url, "page", fmt.Sprintf("%d", pagination.NextPage))

View File

@@ -3,13 +3,13 @@ package webhooks
import (
"context"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/logger"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) runWebhookHandler(c echo.Context) error {
@@ -40,7 +40,7 @@ func (h *handlers) runWebhookHandler(c echo.Context) error {
return htmx.RespondToastSuccess(c, "Running webhook, check the webhook executions for more details")
}
func runWebhookButton(webhookID uuid.UUID) gomponents.Node {
func runWebhookButton(webhookID uuid.UUID) nodx.Node {
return component.OptionsDropdownButton(
htmx.HxPost("/dashboard/webhooks/"+webhookID.String()+"/run"),
htmx.HxDisabledELT("this"),

View File

@@ -5,7 +5,6 @@ import (
"net/http"
"time"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/service/webhooks"
"github.com/eduardolat/pgbackweb/internal/util/echoutil"
@@ -18,8 +17,8 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func (h *handlers) paginateWebhookExecutionsHandler(c echo.Context) error {
@@ -50,7 +49,7 @@ func (h *handlers) paginateWebhookExecutionsHandler(c echo.Context) error {
return htmx.RespondToastError(c, err.Error())
}
return echoutil.RenderGomponent(
return echoutil.RenderNodx(
c, http.StatusOK, webhookExecutionsList(webhookID, pagination, execs),
)
}
@@ -59,7 +58,7 @@ func webhookExecutionsList(
webhookID uuid.UUID,
pagination paginateutil.PaginateResponse,
execs []dbgen.WebhookExecution,
) gomponents.Node {
) nodx.Node {
if len(execs) == 0 {
return component.EmptyResultsTr(component.EmptyResultsParams{
Title: "No executions found",
@@ -67,26 +66,26 @@ func webhookExecutionsList(
})
}
trs := []gomponents.Node{}
trs := []nodx.Node{}
for _, exec := range execs {
durationMillis := exec.ResDuration.Int32
duration := time.Duration(durationMillis) * time.Millisecond
trs = append(trs, html.Tr(
html.Td(
trs = append(trs, nodx.Tr(
nodx.Td(
webhookExecutionDetailsButton(exec, duration),
),
html.Td(component.SpanText(fmt.Sprintf("%d", exec.ResStatus.Int16))),
html.Td(component.SpanText(exec.ReqMethod.String)),
html.Td(component.SpanText(duration.String())),
html.Td(component.SpanText(
nodx.Td(component.SpanText(fmt.Sprintf("%d", exec.ResStatus.Int16))),
nodx.Td(component.SpanText(exec.ReqMethod.String)),
nodx.Td(component.SpanText(duration.String())),
nodx.Td(component.SpanText(
exec.CreatedAt.Local().Format(timeutil.LayoutYYYYMMDDHHMMSSPretty),
)),
))
}
if pagination.HasNextPage {
trs = append(trs, html.Tr(
trs = append(trs, nodx.Tr(
htmx.HxGet(func() string {
url := "/dashboard/webhooks/" + webhookID.String() + "/executions"
url = strutil.AddQueryParamToUrl(url, "page", fmt.Sprintf("%d", pagination.NextPage))
@@ -104,12 +103,12 @@ func webhookExecutionsList(
func webhookExecutionDetailsButton(
exec dbgen.WebhookExecution,
duration time.Duration,
) gomponents.Node {
) nodx.Node {
mo := component.Modal(component.ModalParams{
Title: "Webhook execution details",
Content: []gomponents.Node{
html.Div(
html.Class("space-y-4"),
Content: []nodx.Node{
nodx.Div(
nodx.Class("space-y-4"),
alpine.XData(`{
processTextareas() {
@@ -127,98 +126,98 @@ func webhookExecutionDetailsButton(
}`),
alpine.XOn("mouseenter.once", "processTextareas()"),
html.Table(
html.Class("table [&_th]:text-nowrap"),
html.Tr(
html.Td(
html.ColSpan("100%"),
nodx.Table(
nodx.Class("table [&_th]:text-nowrap"),
nodx.Tr(
nodx.Td(
nodx.Colspan("100%"),
component.H3Text("General"),
),
),
html.Tr(
html.Th(component.SpanText("ID")),
html.Td(component.SpanText(exec.ID.String())),
nodx.Tr(
nodx.Th(component.SpanText("ID")),
nodx.Td(component.SpanText(exec.ID.String())),
),
html.Tr(
html.Th(component.SpanText("Date")),
html.Td(component.SpanText(
nodx.Tr(
nodx.Th(component.SpanText("Date")),
nodx.Td(component.SpanText(
exec.CreatedAt.Local().Format(timeutil.LayoutYYYYMMDDHHMMSSPretty),
)),
),
),
html.Table(
html.Class("table [&_th]:text-nowrap"),
html.Tr(
html.Td(
html.ColSpan("100%"),
nodx.Table(
nodx.Class("table [&_th]:text-nowrap"),
nodx.Tr(
nodx.Td(
nodx.Colspan("100%"),
component.H3Text("Request"),
),
),
html.Tr(
html.Th(component.SpanText("Method")),
html.Td(component.SpanText(exec.ReqMethod.String)),
nodx.Tr(
nodx.Th(component.SpanText("Method")),
nodx.Td(component.SpanText(exec.ReqMethod.String)),
),
html.Tr(
html.Th(component.SpanText("Headers")),
html.Td(
nodx.Tr(
nodx.Th(component.SpanText("Headers")),
nodx.Td(
component.TextareaControl(component.TextareaControlParams{
Children: []gomponents.Node{
Children: []nodx.Node{
alpine.XRef("reqHeadersTextarea"),
gomponents.Text(exec.ReqHeaders.String),
nodx.Text(exec.ReqHeaders.String),
},
}),
),
),
html.Tr(
html.Th(component.SpanText("Body")),
html.Td(
nodx.Tr(
nodx.Th(component.SpanText("Body")),
nodx.Td(
component.TextareaControl(component.TextareaControlParams{
Children: []gomponents.Node{
Children: []nodx.Node{
alpine.XRef("reqBodyTextarea"),
gomponents.Text(exec.ReqBody.String),
nodx.Text(exec.ReqBody.String),
},
}),
),
),
),
html.Table(
html.Class("table [&_th]:text-nowrap"),
html.Tr(
html.Td(
html.ColSpan("100%"),
nodx.Table(
nodx.Class("table [&_th]:text-nowrap"),
nodx.Tr(
nodx.Td(
nodx.Colspan("100%"),
component.H3Text("Response"),
),
),
html.Tr(
html.Th(component.SpanText("Status")),
html.Td(component.SpanText(
nodx.Tr(
nodx.Th(component.SpanText("Status")),
nodx.Td(component.SpanText(
fmt.Sprintf("%d", exec.ResStatus.Int16),
)),
),
html.Tr(
html.Th(component.SpanText("Duration")),
html.Td(component.SpanText(duration.String())),
nodx.Tr(
nodx.Th(component.SpanText("Duration")),
nodx.Td(component.SpanText(duration.String())),
),
html.Tr(
html.Th(component.SpanText("Headers")),
html.Td(
nodx.Tr(
nodx.Th(component.SpanText("Headers")),
nodx.Td(
component.TextareaControl(component.TextareaControlParams{
Children: []gomponents.Node{
Children: []nodx.Node{
alpine.XRef("resHeadersTextarea"),
gomponents.Text(exec.ResHeaders.String),
nodx.Text(exec.ResHeaders.String),
},
}),
),
),
html.Tr(
html.Th(component.SpanText("Body")),
html.Td(
nodx.Tr(
nodx.Th(component.SpanText("Body")),
nodx.Td(
component.TextareaControl(component.TextareaControlParams{
Children: []gomponents.Node{
Children: []nodx.Node{
alpine.XRef("resBodyTextarea"),
gomponents.Text(exec.ResBody.String),
nodx.Text(exec.ResBody.String),
},
}),
),
@@ -228,35 +227,35 @@ func webhookExecutionDetailsButton(
},
})
return html.Div(
html.Class("inline-block tooltip tooltip-right"),
html.Data("tip", "More details"),
return nodx.Div(
nodx.Class("inline-block tooltip tooltip-right"),
nodx.Data("tip", "More details"),
mo.HTML,
html.Button(
html.Class("btn btn-error btn-square btn-sm btn-ghost"),
nodx.Button(
nodx.Class("btn btn-error btn-square btn-sm btn-ghost"),
lucide.Eye(),
mo.OpenerAttr,
),
)
}
func webhookExecutionsButton(webhookID uuid.UUID) gomponents.Node {
func webhookExecutionsButton(webhookID uuid.UUID) nodx.Node {
mo := component.Modal(component.ModalParams{
Size: component.SizeMd,
Title: "Webhook executions",
Content: []gomponents.Node{
html.Table(
html.Class("table"),
html.THead(
html.Tr(
html.Th(html.Class("w-1")),
html.Th(component.SpanText("Status")),
html.Th(component.SpanText("Method")),
html.Th(component.SpanText("Duration")),
html.Th(component.SpanText("Date")),
Content: []nodx.Node{
nodx.Table(
nodx.Class("table"),
nodx.Thead(
nodx.Tr(
nodx.Th(nodx.Class("w-1")),
nodx.Th(component.SpanText("Status")),
nodx.Th(component.SpanText("Method")),
nodx.Th(component.SpanText("Duration")),
nodx.Th(component.SpanText("Date")),
),
),
html.TBody(
nodx.Tbody(
htmx.HxGet(
"/dashboard/webhooks/"+webhookID.String()+"/executions?page=1",
),
@@ -264,14 +263,14 @@ func webhookExecutionsButton(webhookID uuid.UUID) gomponents.Node {
htmx.HxTrigger("intersect once"),
),
),
html.Div(
html.Class("flex justify-center pt-2"),
nodx.Div(
nodx.Class("flex justify-center pt-2"),
component.HxLoadingMd("webhook-executions-loading"),
),
},
})
return html.Div(
return nodx.Div(
mo.HTML,
component.OptionsDropdownButton(
mo.OpenerAttr,

View File

@@ -1,269 +1,267 @@
package htmx
import (
"github.com/maragudk/gomponents"
)
import nodx "github.com/nodxdev/nodxgo"
// HxGet returns a gomponents node with the hx-get
// HxGet returns a NodX node with the hx-get
// attribute set to the given path.
//
// https://htmx.org/attributes/hx-get/
func HxGet(path string) gomponents.Node {
return gomponents.Attr("hx-get", path)
func HxGet(path string) nodx.Node {
return nodx.Attr("hx-get", path)
}
// HxPost returns a gomponents node with the hx-post
// HxPost returns a NodX node with the hx-post
// attribute set to the given path.
//
// https://htmx.org/attributes/hx-post/
func HxPost(path string) gomponents.Node {
return gomponents.Attr("hx-post", path)
func HxPost(path string) nodx.Node {
return nodx.Attr("hx-post", path)
}
// HxPut returns a gomponents node with the hx-put
// HxPut returns a NodX node with the hx-put
// attribute set to the given path.
//
// https://htmx.org/attributes/hx-put/
func HxPut(path string) gomponents.Node {
return gomponents.Attr("hx-put", path)
func HxPut(path string) nodx.Node {
return nodx.Attr("hx-put", path)
}
// HxPatch returns a gomponents node with the hx-patch
// HxPatch returns a NodX node with the hx-patch
// attribute set to the given path.
//
// https://htmx.org/attributes/hx-patch/
func HxPatch(path string) gomponents.Node {
return gomponents.Attr("hx-patch", path)
func HxPatch(path string) nodx.Node {
return nodx.Attr("hx-patch", path)
}
// HxDelete returns a gomponents node with the hx-delete
// HxDelete returns a NodX node with the hx-delete
// attribute set to the given path.
//
// https://htmx.org/attributes/hx-delete/
func HxDelete(path string) gomponents.Node {
return gomponents.Attr("hx-delete", path)
func HxDelete(path string) nodx.Node {
return nodx.Attr("hx-delete", path)
}
// HxTrigger returns a gomponents node with the hx-trigger
// HxTrigger returns a NodX node with the hx-trigger
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-trigger/
func HxTrigger(value string) gomponents.Node {
return gomponents.Attr("hx-trigger", value)
func HxTrigger(value string) nodx.Node {
return nodx.Attr("hx-trigger", value)
}
// HxTarget returns a gomponents node with the hx-target
// HxTarget returns a NodX node with the hx-target
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-target/
func HxTarget(value string) gomponents.Node {
return gomponents.Attr("hx-target", value)
func HxTarget(value string) nodx.Node {
return nodx.Attr("hx-target", value)
}
// HxSwap returns a gomponents node with the hx-swap
// HxSwap returns a NodX node with the hx-swap
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-swap/
func HxSwap(value string) gomponents.Node {
return gomponents.Attr("hx-swap", value)
func HxSwap(value string) nodx.Node {
return nodx.Attr("hx-swap", value)
}
// HxIndicator returns a gomponents node with the hx-indicator
// HxIndicator returns a NodX node with the hx-indicator
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-indicator/
func HxIndicator(value string) gomponents.Node {
return gomponents.Attr("hx-indicator", value)
func HxIndicator(value string) nodx.Node {
return nodx.Attr("hx-indicator", value)
}
// HxConfirm returns a gomponents node with the hx-confirm
// HxConfirm returns a NodX node with the hx-confirm
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-confirm/
func HxConfirm(value string) gomponents.Node {
return gomponents.Attr("hx-confirm", value)
func HxConfirm(value string) nodx.Node {
return nodx.Attr("hx-confirm", value)
}
// HxBoost returns a gomponents node with the hx-boost
// HxBoost returns a NodX node with the hx-boost
// attribute set to the given value.
//
// See https://htmx.org/attributes/hx-boost/
func HxBoost(value string) gomponents.Node {
return gomponents.Attr("hx-boost", value)
func HxBoost(value string) nodx.Node {
return nodx.Attr("hx-boost", value)
}
// HxOn returns a gomponents node with the hx-on:name="value"
// HxOn returns a NodX node with the hx-on:name="value"
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-on/
func HxOn(name string, value string) gomponents.Node {
return gomponents.Attr("hx-on:"+name, value)
func HxOn(name string, value string) nodx.Node {
return nodx.Attr("hx-on:"+name, value)
}
// HxPushURL returns a gomponents node with the hx-push-url
// HxPushURL returns a NodX node with the hx-push-url
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-push-url/
func HxPushURL(value string) gomponents.Node {
return gomponents.Attr("hx-push-url", value)
func HxPushURL(value string) nodx.Node {
return nodx.Attr("hx-push-url", value)
}
// HxSelect returns a gomponents node with the hx-select
// HxSelect returns a NodX node with the hx-select
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-select/
func HxSelect(value string) gomponents.Node {
return gomponents.Attr("hx-select", value)
func HxSelect(value string) nodx.Node {
return nodx.Attr("hx-select", value)
}
// HxSelectOOB returns a gomponents node with the hx-select-oob
// HxSelectOOB returns a NodX node with the hx-select-oob
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-select-oob/
func HxSelectOOB(value string) gomponents.Node {
return gomponents.Attr("hx-select-oob", value)
func HxSelectOOB(value string) nodx.Node {
return nodx.Attr("hx-select-oob", value)
}
// HxSwapOOB returns a gomponents node with the hx-swap-oob
// HxSwapOOB returns a NodX node with the hx-swap-oob
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-swap-oob/
func HxSwapOOB(value string) gomponents.Node {
return gomponents.Attr("hx-swap-oob", value)
func HxSwapOOB(value string) nodx.Node {
return nodx.Attr("hx-swap-oob", value)
}
// HxVals returns a gomponents node with the hx-vals
// HxVals returns a NodX node with the hx-vals
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-vals/
func HxVals(value string) gomponents.Node {
return gomponents.Attr("hx-vals", value)
func HxVals(value string) nodx.Node {
return nodx.Attr("hx-vals", value)
}
// HxDisable returns a gomponents node with the hx-disable
// HxDisable returns a NodX node with the hx-disable
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-disable/
func HxDisable(value string) gomponents.Node {
return gomponents.Attr("hx-disable", value)
func HxDisable(value string) nodx.Node {
return nodx.Attr("hx-disable", value)
}
// HxDisabledELT returns a gomponents node with the hx-disabled-elt
// HxDisabledELT returns a NodX node with the hx-disabled-elt
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-disabled-elt/
func HxDisabledELT(value string) gomponents.Node {
return gomponents.Attr("hx-disabled-elt", value)
func HxDisabledELT(value string) nodx.Node {
return nodx.Attr("hx-disabled-elt", value)
}
// HxDisinherit returns a gomponents node with the hx-disinherit
// HxDisinherit returns a NodX node with the hx-disinherit
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-disinherit/
func HxDisinherit(value string) gomponents.Node {
return gomponents.Attr("hx-disinherit", value)
func HxDisinherit(value string) nodx.Node {
return nodx.Attr("hx-disinherit", value)
}
// HxEncoding returns a gomponents node with the hx-encoding
// HxEncoding returns a NodX node with the hx-encoding
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-encoding/
func HxEncoding(value string) gomponents.Node {
return gomponents.Attr("hx-encoding", value)
func HxEncoding(value string) nodx.Node {
return nodx.Attr("hx-encoding", value)
}
// HxExt returns a gomponents node with the hx-ext
// HxExt returns a NodX node with the hx-ext
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-ext/
func HxExt(value string) gomponents.Node {
return gomponents.Attr("hx-ext", value)
func HxExt(value string) nodx.Node {
return nodx.Attr("hx-ext", value)
}
// HxHeaders returns a gomponents node with the hx-headers
// HxHeaders returns a NodX node with the hx-headers
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-headers/
func HxHeaders(value string) gomponents.Node {
return gomponents.Attr("hx-headers", value)
func HxHeaders(value string) nodx.Node {
return nodx.Attr("hx-headers", value)
}
// HxHistory returns a gomponents node with the hx-history
// HxHistory returns a NodX node with the hx-history
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-history/
func HxHistory(value string) gomponents.Node {
return gomponents.Attr("hx-history", value)
func HxHistory(value string) nodx.Node {
return nodx.Attr("hx-history", value)
}
// HxHistoryElt returns a gomponents node with the hx-history-elt
// HxHistoryElt returns a NodX node with the hx-history-elt
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-history-elt/
func HxHistoryElt(value string) gomponents.Node {
return gomponents.Attr("hx-history-elt", value)
func HxHistoryElt(value string) nodx.Node {
return nodx.Attr("hx-history-elt", value)
}
// HxInclude returns a gomponents node with the hx-include
// HxInclude returns a NodX node with the hx-include
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-include/
func HxInclude(value string) gomponents.Node {
return gomponents.Attr("hx-include", value)
func HxInclude(value string) nodx.Node {
return nodx.Attr("hx-include", value)
}
// HxParams returns a gomponents node with the hx-params
// HxParams returns a NodX node with the hx-params
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-params/
func HxParams(value string) gomponents.Node {
return gomponents.Attr("hx-params", value)
func HxParams(value string) nodx.Node {
return nodx.Attr("hx-params", value)
}
// HxPreserve returns a gomponents node with the hx-preserve
// HxPreserve returns a NodX node with the hx-preserve
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-preserve/
func HxPreserve(value string) gomponents.Node {
return gomponents.Attr("hx-preserve", value)
func HxPreserve(value string) nodx.Node {
return nodx.Attr("hx-preserve", value)
}
// HxPrompt returns a gomponents node with the hx-prompt
// HxPrompt returns a NodX node with the hx-prompt
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-prompt/
func HxPrompt(value string) gomponents.Node {
return gomponents.Attr("hx-prompt", value)
func HxPrompt(value string) nodx.Node {
return nodx.Attr("hx-prompt", value)
}
// HxReplaceURL returns a gomponents node with the hx-replace-url
// HxReplaceURL returns a NodX node with the hx-replace-url
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-replace-url/
func HxReplaceURL(value string) gomponents.Node {
return gomponents.Attr("hx-replace-url", value)
func HxReplaceURL(value string) nodx.Node {
return nodx.Attr("hx-replace-url", value)
}
// HxRequest returns a gomponents node with the hx-request
// HxRequest returns a NodX node with the hx-request
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-request/
func HxRequest(value string) gomponents.Node {
return gomponents.Attr("hx-request", value)
func HxRequest(value string) nodx.Node {
return nodx.Attr("hx-request", value)
}
// HxSync returns a gomponents node with the hx-sync
// HxSync returns a NodX node with the hx-sync
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-sync/
func HxSync(value string) gomponents.Node {
return gomponents.Attr("hx-sync", value)
func HxSync(value string) nodx.Node {
return nodx.Attr("hx-sync", value)
}
// HxValidate returns a gomponents node with the hx-validate
// HxValidate returns a NodX node with the hx-validate
// attribute set to the given value.
//
// https://htmx.org/attributes/hx-validate/
func HxValidate(value string) gomponents.Node {
return gomponents.Attr("hx-validate", value)
func HxValidate(value string) nodx.Node {
return nodx.Attr("hx-validate", value)
}

View File

@@ -2,54 +2,47 @@ package layout
import (
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
type AuthParams struct {
Title string
Body []gomponents.Node
Body []nodx.Node
}
func Auth(params AuthParams) gomponents.Node {
func Auth(params AuthParams) nodx.Node {
title := "PG Back Web"
if params.Title != "" {
title = params.Title + " - " + title
}
return components.HTML5(components.HTML5Props{
Language: "en",
Title: title,
Head: []gomponents.Node{
head(),
body := nodx.Group(
nodx.ClassMap{
"w-screen h-screen px-4 py-[40px]": true,
"grid grid-cols-1 place-items-center": true,
"bg-base-300 overflow-y-auto": true,
},
Body: []gomponents.Node{
components.Classes{
"w-screen h-screen px-4 py-[40px]": true,
"grid grid-cols-1 place-items-center": true,
"bg-base-300 overflow-y-auto": true,
},
html.Div(
html.Class("w-full max-w-[600px] space-y-4"),
html.Div(
html.Class("flex justify-center"),
component.Logotype(),
),
html.Main(
html.Class("rounded-box shadow-md bg-base-100 p-4"),
gomponents.Group(params.Body),
),
html.Div(
html.Class("flex justify-start space-x-2 items-center"),
component.ChangeThemeButton(component.ChangeThemeButtonParams{
Position: component.DropdownPositionTop,
AlignsToEnd: false,
Size: component.SizeMd,
}),
component.StarOnGithub(component.SizeMd),
),
nodx.Div(
nodx.Class("w-full max-w-[600px] space-y-4"),
nodx.Div(
nodx.Class("flex justify-center"),
component.Logotype(),
),
},
})
nodx.Main(
nodx.Class("rounded-box shadow-md bg-base-100 p-4"),
nodx.Group(params.Body...),
),
nodx.Div(
nodx.Class("flex justify-start space-x-2 items-center"),
component.ChangeThemeButton(component.ChangeThemeButtonParams{
Position: component.DropdownPositionTop,
AlignsToEnd: false,
Size: component.SizeMd,
}),
component.StarOnGithub(component.SizeMd),
),
),
)
return commonHtmlDoc(title, body)
}

View File

@@ -2,33 +2,49 @@ package layout
import (
"github.com/eduardolat/pgbackweb/internal/view/static"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
func head() gomponents.Node {
href := func(path string) gomponents.Node {
return html.Href(static.GetVersionedFilePath(path))
}
src := func(path string) gomponents.Node {
return html.Src(static.GetVersionedFilePath(path))
}
return gomponents.Group([]gomponents.Node{
html.Link(html.Rel("shortcut icon"), href("/favicon.ico")),
html.Link(html.Rel("stylesheet"), href("/build/style.min.css")),
html.Script(src("/build/app.min.js")),
html.Script(src("/libs/htmx/htmx-2.0.1.min.js"), html.Defer()),
html.Script(src("/libs/alpinejs/alpinejs-3.14.1.min.js"), html.Defer()),
html.Script(src("/libs/sweetalert2/sweetalert2-11.13.1.min.js")),
html.Script(src("/libs/chartjs/chartjs-4.4.3.umd.min.js")),
html.Link(html.Rel("stylesheet"), href("/libs/notyf/notyf-3.10.0.min.css")),
html.Script(src("/libs/notyf/notyf-3.10.0.min.js")),
html.Link(html.Rel("stylesheet"), href("/libs/slim-select/slimselect-2.8.2.css")),
html.Script(src("/libs/slim-select/slimselect-2.8.2.min.js")),
})
func commonHtmlDoc(title string, bodyContentGroup nodx.Node) nodx.Node {
return nodx.Group(
nodx.DocType(),
nodx.Html(
nodx.Lang("en"),
nodx.Head(
nodx.TitleEl(nodx.Text(title)),
commonHead(),
),
nodx.Body(bodyContentGroup),
),
)
}
func commonHead() nodx.Node {
href := func(path string) nodx.Node {
return nodx.Href(static.GetVersionedFilePath(path))
}
src := func(path string) nodx.Node {
return nodx.Src(static.GetVersionedFilePath(path))
}
return nodx.Group(
nodx.Meta(nodx.Charset("utf-8")),
nodx.Meta(nodx.Name("viewport"), nodx.Content("width=device-width, initial-scale=1")),
nodx.Link(nodx.Rel("shortcut icon"), href("/favicon.ico")),
nodx.Link(nodx.Rel("stylesheet"), href("/build/style.min.css")),
nodx.Script(src("/build/app.min.js")),
nodx.Script(src("/libs/htmx/htmx-2.0.1.min.js"), nodx.Defer("")),
nodx.Script(src("/libs/alpinejs/alpinejs-3.14.1.min.js"), nodx.Defer("")),
nodx.Script(src("/libs/sweetalert2/sweetalert2-11.13.1.min.js")),
nodx.Script(src("/libs/chartjs/chartjs-4.4.3.umd.min.js")),
nodx.Link(nodx.Rel("stylesheet"), href("/libs/notyf/notyf-3.10.0.min.css")),
nodx.Script(src("/libs/notyf/notyf-3.10.0.min.js")),
nodx.Link(nodx.Rel("stylesheet"), href("/libs/slim-select/slimselect-2.8.2.css")),
nodx.Script(src("/libs/slim-select/slimselect-2.8.2.min.js")),
)
}

View File

@@ -4,49 +4,42 @@ import (
"github.com/eduardolat/pgbackweb/internal/view/reqctx"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
)
type DashboardParams struct {
Title string
Body []gomponents.Node
Body []nodx.Node
}
func Dashboard(reqCtx reqctx.Ctx, params DashboardParams) gomponents.Node {
func Dashboard(reqCtx reqctx.Ctx, params DashboardParams) nodx.Node {
title := "PG Back Web"
if params.Title != "" {
title = params.Title + " - " + title
}
if reqCtx.IsHTMXBoosted {
body := append(params.Body, html.TitleEl(gomponents.Text(title)))
body := append(params.Body, nodx.TitleEl(nodx.Text(title)))
return component.RenderableGroup(body)
}
return components.HTML5(components.HTML5Props{
Language: "en",
Title: title,
Head: []gomponents.Node{
head(),
body := nodx.Group(
htmx.HxIndicator("#header-indicator"),
nodx.ClassMap{
"w-screen h-screen bg-base-200": true,
"flex justify-start overflow-hidden": true,
},
Body: []gomponents.Node{
htmx.HxIndicator("#header-indicator"),
components.Classes{
"w-screen h-screen bg-base-200": true,
"flex justify-start overflow-hidden": true,
},
dashboardAside(),
html.Div(
html.Class("flex-grow overflow-y-auto"),
dashboardHeader(),
html.Main(
html.ID("dashboard-main"),
html.Class("p-4"),
gomponents.Group(params.Body),
),
dashboardAside(),
nodx.Div(
nodx.Class("flex-grow overflow-y-auto"),
dashboardHeader(),
nodx.Main(
nodx.Id("dashboard-main"),
nodx.Class("p-4"),
nodx.Group(params.Body...),
),
},
})
),
)
return commonHtmlDoc(title, body)
}

View File

@@ -3,49 +3,47 @@ package layout
import (
"fmt"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/alpine"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func dashboardAside() gomponents.Node {
return html.Aside(
html.ID("dashboard-aside"),
components.Classes{
func dashboardAside() nodx.Node {
return nodx.Aside(
nodx.Id("dashboard-aside"),
nodx.ClassMap{
"flex-none h-[100dvh] bg-base-300 shadow-sm p-4": true,
"overflow-y-auto overflow-x-hidden": true,
},
html.A(
html.Class("block flex flex-col justify-center items-center"),
html.Href("https://github.com/eduardolat/pgbackweb"),
html.Target("_blank"),
html.Img(
html.Src("/images/logo.png"),
html.Alt("PG Back Web"),
html.Class("w-[50px] h-auto"),
nodx.A(
nodx.Class("block flex flex-col justify-center items-center"),
nodx.Href("https://github.com/eduardolat/pgbackweb"),
nodx.Target("_blank"),
nodx.Img(
nodx.Src("/images/logo.png"),
nodx.Alt("PG Back Web"),
nodx.Class("w-[50px] h-auto"),
),
html.Span(
html.Class("text-xs text-nowrap text-center font-bold mt-1"),
html.Span(
html.Class("block"),
gomponents.Text("PG Back"),
nodx.SpanEl(
nodx.Class("text-xs text-nowrap text-center font-bold mt-1"),
nodx.SpanEl(
nodx.Class("block"),
nodx.Text("PG Back"),
),
html.Span(
html.Class("block"),
gomponents.Text("Web"),
nodx.SpanEl(
nodx.Class("block"),
nodx.Text("Web"),
),
),
),
html.Div(
nodx.Div(
htmx.HxBoost("true"),
htmx.HxTarget("#dashboard-main"),
htmx.HxSwap("transition:true show:unset"),
html.Class("mt-6 space-y-4"),
nodx.Class("mt-6 space-y-4"),
dashboardAsideItem(
lucide.LayoutDashboard,
@@ -114,21 +112,21 @@ func dashboardAside() gomponents.Node {
}
func dashboardAsideItem(
icon func(children ...gomponents.Node) gomponents.Node,
icon func(children ...nodx.Node) nodx.Node,
text, link string, strict bool,
) gomponents.Node {
return html.A(
) nodx.Node {
return nodx.A(
alpine.XData(fmt.Sprintf("alpineDashboardAsideItem('%s', %t)", link, strict)),
html.Class("block flex flex-col items-center justify-center group"),
html.Href(link),
html.Button(
nodx.Class("block flex flex-col items-center justify-center group"),
nodx.Href(link),
nodx.Button(
alpine.XBind("class", `{'btn-active': is_active}`),
html.Class("btn btn-ghost btn-neutral btn-square group-hover:btn-active"),
icon(html.Class("size-6")),
nodx.Class("btn btn-ghost btn-neutral btn-square group-hover:btn-active"),
icon(nodx.Class("size-6")),
),
html.Span(
html.Class("text-xs"),
gomponents.Text(text),
nodx.SpanEl(
nodx.Class("text-xs"),
nodx.Text(text),
),
)
}

View File

@@ -1,23 +1,21 @@
package layout
import (
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/components"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func dashboardHeader() gomponents.Node {
return html.Header(
components.Classes{
func dashboardHeader() nodx.Node {
return nodx.Header(
nodx.ClassMap{
"w-[full] bg-base-200 p-4 shadow-sm": true,
"flex items-center justify-between": true,
"sticky top-0 z-50": true,
},
html.Div(
html.Class("flex justify-start items-center space-x-2"),
nodx.Div(
nodx.Class("flex justify-start items-center space-x-2"),
component.ChangeThemeButton(component.ChangeThemeButtonParams{
Position: component.DropdownPositionBottom,
AlignsToEnd: true,
@@ -27,24 +25,24 @@ func dashboardHeader() gomponents.Node {
dashboardHeaderUpdates(),
component.HxLoadingMd("header-indicator"),
),
html.Div(
html.Class("flex justify-end items-center space-x-2"),
html.Div(
nodx.Div(
nodx.Class("flex justify-end items-center space-x-2"),
nodx.Div(
htmx.HxGet("/dashboard/health-button"),
htmx.HxSwap("outerHTML"),
htmx.HxTrigger("load once"),
),
html.A(
html.Href("https://discord.gg/BmAwq29UZ8"),
html.Target("_blank"),
html.Class("btn btn-ghost btn-neutral"),
nodx.A(
nodx.Href("https://discord.gg/BmAwq29UZ8"),
nodx.Target("_blank"),
nodx.Class("btn btn-ghost btn-neutral"),
component.SpanText("Chat on Discord"),
lucide.ExternalLink(),
),
html.Button(
nodx.Button(
htmx.HxPost("/auth/logout"),
htmx.HxDisabledELT("this"),
html.Class("btn btn-ghost btn-neutral"),
nodx.Class("btn btn-ghost btn-neutral"),
component.SpanText("Log out"),
lucide.LogOut(),
),

View File

@@ -3,16 +3,15 @@ package layout
import (
"fmt"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/config"
"github.com/eduardolat/pgbackweb/internal/view/web/alpine"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
nodx "github.com/nodxdev/nodxgo"
lucide "github.com/nodxdev/nodxgo-lucide"
)
func dashboardHeaderUpdates() gomponents.Node {
return html.A(
func dashboardHeaderUpdates() nodx.Node {
return nodx.A(
alpine.XData("alpineDashboardHeaderUpdates()"),
alpine.XCloak(),
alpine.XShow(fmt.Sprintf(
@@ -20,12 +19,12 @@ func dashboardHeaderUpdates() gomponents.Node {
config.Version,
)),
html.Class("btn btn-warning"),
html.Href("https://github.com/eduardolat/pgbackweb/releases"),
html.Target("_blank"),
nodx.Class("btn btn-warning"),
nodx.Href("https://github.com/eduardolat/pgbackweb/releases"),
nodx.Target("_blank"),
lucide.ExternalLink(),
component.SpanText("Update available"),
html.Span(
nodx.SpanEl(
alpine.XText("'( ' + latestRelease + ' )'"),
),
)