diff --git a/gearbox/cmd/server/main.go b/gearbox/cmd/server/main.go index e33b5c6..dc8a48f 100644 --- a/gearbox/cmd/server/main.go +++ b/gearbox/cmd/server/main.go @@ -27,7 +27,7 @@ import ( "github.com/sarg3nt/gearbox/internal/framework/services" "github.com/sarg3nt/gearbox/internal/framework/services/agent_keyring" "github.com/sarg3nt/gearbox/internal/framework/services/alerts" - "github.com/sarg3nt/gearbox/internal/framework/services/crypto" + "github.com/sarg3nt/webcore/core/crypto" "github.com/sarg3nt/gearbox/internal/framework/services/email" // Import gears - blank identifier triggers init() registration @@ -188,7 +188,7 @@ func main() { logger.Info("email service initialized") // Initialize encryptor for secrets - encryptor, err := crypto.NewEncryptor(cfg.SessionSecret) + encryptor, err := crypto.NewFromHashedKey(cfg.SessionSecret) if err != nil { log.Fatalf("Failed to create encryptor: %v", err) } diff --git a/gearbox/go.mod b/gearbox/go.mod index a9ab876..9298680 100644 --- a/gearbox/go.mod +++ b/gearbox/go.mod @@ -3,23 +3,24 @@ module github.com/sarg3nt/gearbox go 1.25.5 require ( - github.com/a-h/templ v0.3.977 + github.com/a-h/templ v0.3.1020 github.com/go-chi/chi/v5 v5.2.5 - github.com/go-webauthn/webauthn v0.17.3 + github.com/go-webauthn/webauthn v0.17.4 github.com/golang-migrate/migrate/v4 v4.19.1 github.com/google/uuid v1.6.0 github.com/gorilla/sessions v1.4.0 github.com/gorilla/websocket v1.5.3 + github.com/sarg3nt/webcore v0.1.0 github.com/wagslane/go-password-validator v0.3.0 - golang.org/x/crypto v0.51.0 - modernc.org/sqlite v1.46.1 + golang.org/x/crypto v0.52.0 + modernc.org/sqlite v1.50.1 ) require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/fxamacker/cbor/v2 v2.9.2 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect - github.com/go-webauthn/x v0.2.5 // indirect + github.com/go-webauthn/x v0.2.6 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/google/go-tpm v0.9.8 // indirect github.com/gorilla/securecookie v1.1.2 // indirect @@ -29,9 +30,8 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/tinylib/msgp v1.6.4 // indirect github.com/x448/float16 v0.8.4 // indirect - golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect - golang.org/x/sys v0.44.0 // indirect - modernc.org/libc v1.67.6 // indirect + golang.org/x/sys v0.45.0 // indirect + modernc.org/libc v1.72.3 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect ) diff --git a/gearbox/go.sum b/gearbox/go.sum index 21e37b1..60d905b 100644 --- a/gearbox/go.sum +++ b/gearbox/go.sum @@ -1,5 +1,5 @@ -github.com/a-h/templ v0.3.977 h1:kiKAPXTZE2Iaf8JbtM21r54A8bCNsncrfnokZZSrSDg= -github.com/a-h/templ v0.3.977/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= +github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw= +github.com/a-h/templ v0.3.1020/go.mod h1:A2DlK61v+K+NRoGnhmYbNYVmtYHcFO5/AisMvBdDxTM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -10,10 +10,10 @@ github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/go-webauthn/webauthn v0.17.3 h1:XHZ0TXV7k8vChcE4TFgPitOPJ5cb7h1dpAeFDS0cjCo= -github.com/go-webauthn/webauthn v0.17.3/go.mod h1:PlkMgmuL9McCT7dvgBj/Sz/fgs3V6ZID6/KnFkEcPvQ= -github.com/go-webauthn/x v0.2.5 h1:wEVTfU04XFyPTXGQbKOQwMKhcDWfDAkdsDDBsDaG9yY= -github.com/go-webauthn/x v0.2.5/go.mod h1:Qna/yJz9rV6lRzwl5BfYbmTJpVGxcBIds3gJtw2tlGg= +github.com/go-webauthn/webauthn v0.17.4 h1:KFTSz3R2RYDiUn/0cDi3XTJgFenSG74eKTTHlqWhlxk= +github.com/go-webauthn/webauthn v0.17.4/go.mod h1:pZk63EE/BdztlmyS4Yc+9H5g4a8blNlbtGmdHQHbZX8= +github.com/go-webauthn/x v0.2.6 h1:TEyDuQAIiEgYpx60nKiBJIX/5nSUC8LxNbH+uf5U9uk= +github.com/go-webauthn/x v0.2.6/go.mod h1:45bA7YEqyQhRcQJ/TiBb46Ww8yqHBGvgEhQ3WWF0aDo= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA= @@ -50,6 +50,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/sarg3nt/webcore v0.1.0 h1:lB3PtuMb4/5guPj6DTn2Azi7drOY0deNDAWWwZZPOtc= +github.com/sarg3nt/webcore v0.1.0/go.mod h1:LQipBfXp8zn3tWhCvjpopvYQVDFTv65EGilgGeJ4ZTU= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ= @@ -60,45 +62,43 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= -golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= -golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= -golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= -golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= -golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= -modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= -modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM= -modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= -modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY= +modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ= +modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE= -modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= +modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI= -modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE= +modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU= +modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= -modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= -modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= +modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU= -modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= +modernc.org/sqlite v1.50.1 h1:l+cQvn0sd0zJJtfygGHuQJ5AjlrwXmWPw4KP3ZMwr9w= +modernc.org/sqlite v1.50.1/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/gearbox/internal/framework/errors/errors.go b/gearbox/internal/framework/errors/errors.go deleted file mode 100644 index 5482a45..0000000 --- a/gearbox/internal/framework/errors/errors.go +++ /dev/null @@ -1,241 +0,0 @@ -// Package errors provides error sanitization and structured error handling -// for the HAProxy monitoring application. -// -// This package implements a centralized error handling system that: -// - Sanitizes internal errors before exposing them to users -// - Provides structured logging for error diagnosis -// - Maintains consistent HTTP status codes across the application -// - Separates user-facing messages from internal error details -// -// Example usage: -// -// // In an HTTP handler: -// user, err := db.GetUser(id) -// if err != nil { -// errors.WriteHTTPError(w, logger, errors.Internal("fetch user", err)) -// return -// } -// -// // Or using specific error constructors: -// if !authorized { -// errors.WriteHTTPError(w, logger, errors.Forbidden("Access denied", nil)) -// return -// } -package errors - -import ( - "context" - "database/sql" - "encoding/json" - "errors" - "fmt" - "log/slog" - "net/http" -) - -// AppError represents a structured application error with user-safe message -// and internal details for logging. -type AppError struct { - // Code is the HTTP status code - Code int - // UserMessage is safe to display to end users - UserMessage string - // InternalError contains the actual error (logged but not shown to user) - InternalError error - // LogContext contains additional context for structured logging - LogContext map[string]any -} - -// Error implements the error interface. -func (e *AppError) Error() string { - if e.InternalError != nil { - return fmt.Sprintf("%s: %v", e.UserMessage, e.InternalError) - } - return e.UserMessage -} - -// Unwrap implements error unwrapping for errors.Is and errors.As. -func (e *AppError) Unwrap() error { - return e.InternalError -} - -// ErrorType represents common error categories. -type ErrorType string - -const ( - ErrorTypeNotFound ErrorType = "not_found" - ErrorTypeUnauthorized ErrorType = "unauthorized" - ErrorTypeForbidden ErrorType = "forbidden" - ErrorTypeBadRequest ErrorType = "bad_request" - ErrorTypeInternal ErrorType = "internal_error" - ErrorTypeServiceUnavailable ErrorType = "service_unavailable" - ErrorTypeConflict ErrorType = "conflict" -) - -// New creates a new AppError with the given parameters. -func New(code int, userMsg string, internalErr error) *AppError { - return &AppError{ - Code: code, - UserMessage: userMsg, - InternalError: internalErr, - LogContext: make(map[string]any), - } -} - -// WithContext adds structured logging context to the error. -func (e *AppError) WithContext(key string, value any) *AppError { - e.LogContext[key] = value - return e -} - -// Common error constructors - -// NotFound creates a 404 error. -func NotFound(resource string, internalErr error) *AppError { - return New( - http.StatusNotFound, - fmt.Sprintf("%s not found", resource), - internalErr, - ).WithContext("resource", resource) -} - -// Unauthorized creates a 401 error. -func Unauthorized(message string, internalErr error) *AppError { - return New(http.StatusUnauthorized, message, internalErr) -} - -// Forbidden creates a 403 error. -func Forbidden(message string, internalErr error) *AppError { - return New(http.StatusForbidden, message, internalErr) -} - -// BadRequest creates a 400 error. -func BadRequest(message string, internalErr error) *AppError { - return New(http.StatusBadRequest, message, internalErr) -} - -// Internal creates a 500 error with a generic user message. -func Internal(operation string, internalErr error) *AppError { - return New( - http.StatusInternalServerError, - fmt.Sprintf("Failed to %s. Please try again later.", operation), - internalErr, - ).WithContext("operation", operation) -} - -// ServiceUnavailable creates a 503 error. -func ServiceUnavailable(service string, internalErr error) *AppError { - return New( - http.StatusServiceUnavailable, - fmt.Sprintf("%s is currently unavailable", service), - internalErr, - ).WithContext("service", service) -} - -// Conflict creates a 409 error. -func Conflict(message string, internalErr error) *AppError { - return New(http.StatusConflict, message, internalErr) -} - -// WrapDatabaseError converts database errors to appropriate AppErrors. -// Use this for database operations where ErrNoRows is unexpected. -func WrapDatabaseError(err error, operation string) *AppError { - if err == nil { - return nil - } - - // Handle specific database errors - if errors.Is(err, sql.ErrNoRows) { - return NotFound("Resource", err) - } - - // Generic database error - don't expose internal details - return Internal(operation, err) -} - -// IsNotFound checks if an error is sql.ErrNoRows. -// Use this helper when ErrNoRows is expected behavior (e.g., optional lookups). -func IsNotFound(err error) bool { - return errors.Is(err, sql.ErrNoRows) -} - -// IgnoreNotFound returns nil if the error is sql.ErrNoRows, otherwise returns the error. -// Use this when a record not existing is acceptable/expected behavior. -func IgnoreNotFound(err error) error { - if errors.Is(err, sql.ErrNoRows) { - return nil - } - return err -} - -// WriteHTTPError writes an AppError to an HTTP response and logs it. -// -// The wire format is a JSON envelope: `{"success": false, "message": "..."}` -// with `Content-Type: application/json`. This matches the shape produced by -// Handler.jsonError() and lets the frontend `response.json()` parse error -// responses just like success responses — historically this used -// `http.Error` (plain text), which caused JS callers that unconditionally -// `JSON.parse` the body to throw `Unexpected token 'F', "Failed to "...` -// (see issue #112 for the user-visible symptom on the Logs and Services -// pages). -func WriteHTTPError(w http.ResponseWriter, logger *slog.Logger, err error) { - var appErr *AppError - - // Convert to AppError if not already - if !errors.As(err, &appErr) { - // Unknown error - create generic internal error - appErr = Internal("process request", err) - } - - // Log the error with full details - logAttrs := []slog.Attr{ - slog.Int("status_code", appErr.Code), - slog.String("user_message", appErr.UserMessage), - } - - if appErr.InternalError != nil { - logAttrs = append(logAttrs, slog.String("internal_error", appErr.InternalError.Error())) - } - - // Add context fields - for key, value := range appErr.LogContext { - logAttrs = append(logAttrs, slog.Any(key, value)) - } - - logger.LogAttrs(context.Background(), slog.LevelError, "HTTP error", logAttrs...) - - // Write sanitized error to client as a JSON envelope so JS callers can - // `response.json()` it without throwing on plain-text bodies. - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(appErr.Code) - if encErr := json.NewEncoder(w).Encode(map[string]any{ - "success": false, - "message": appErr.UserMessage, - }); encErr != nil { - // Encoding can fail only if the writer is broken — the header is - // already on the wire so we can't recover the response, just log. - logger.LogAttrs(context.Background(), slog.LevelError, "encode error envelope failed", - slog.String("error", encErr.Error())) - } -} - -// SanitizeError converts any error to a user-safe message. -// This is a fallback for cases where structured errors aren't used. -func SanitizeError(err error) string { - if err == nil { - return "" - } - - var appErr *AppError - if errors.As(err, &appErr) { - return appErr.UserMessage - } - - // Database errors - if errors.Is(err, sql.ErrNoRows) { - return "Resource not found" - } - - // Generic fallback - never expose internal error details - return "An error occurred. Please try again later." -} diff --git a/gearbox/internal/framework/errors/errors_writehttp_test.go b/gearbox/internal/framework/errors/errors_writehttp_test.go deleted file mode 100644 index 54622d0..0000000 --- a/gearbox/internal/framework/errors/errors_writehttp_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package errors - -import ( - "encoding/json" - "io" - "log/slog" - "net/http" - "net/http/httptest" - "strings" - "testing" -) - -// TestWriteHTTPError_JSONEnvelope guards the wire format consumers of -// WriteHTTPError rely on — Logs page, Services page, and any other JS that -// calls `response.json()` on both success and error responses. Historically -// WriteHTTPError emitted `text/plain` ("Failed to ...") which caused -// `JSON.parse` to throw `Unexpected token 'F'` in the browser; the fix in -// issue #112 standardizes on a JSON envelope. -func TestWriteHTTPError_JSONEnvelope(t *testing.T) { - logger := slog.New(slog.NewTextHandler(io.Discard, nil)) - w := httptest.NewRecorder() - - WriteHTTPError(w, logger, Internal("fetch logs", io.EOF)) - - if got := w.Header().Get("Content-Type"); !strings.Contains(got, "application/json") { - t.Fatalf("Content-Type = %q, want application/json", got) - } - if got := w.Code; got != http.StatusInternalServerError { - t.Fatalf("Status = %d, want 500", got) - } - - var body map[string]any - if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil { - t.Fatalf("response body is not JSON: %v\nbody=%q", err, w.Body.String()) - } - if body["success"] != false { - t.Errorf("body.success = %v, want false", body["success"]) - } - msg, _ := body["message"].(string) - if !strings.Contains(msg, "fetch logs") { - t.Errorf("body.message = %q, want it to contain 'fetch logs'", msg) - } -} - -// TestWriteHTTPError_UnknownErrorWraps verifies that a plain error (not an -// AppError) still serializes as a JSON envelope with the generic -// "process request" wrapping that Internal() produces. -func TestWriteHTTPError_UnknownErrorWraps(t *testing.T) { - logger := slog.New(slog.NewTextHandler(io.Discard, nil)) - w := httptest.NewRecorder() - - WriteHTTPError(w, logger, io.ErrUnexpectedEOF) - - if got := w.Header().Get("Content-Type"); !strings.Contains(got, "application/json") { - t.Fatalf("Content-Type = %q, want application/json", got) - } - var body map[string]any - if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil { - t.Fatalf("response body is not JSON: %v\nbody=%q", err, w.Body.String()) - } - if body["success"] != false { - t.Errorf("body.success = %v, want false", body["success"]) - } -} diff --git a/gearbox/internal/framework/events/hub.go b/gearbox/internal/framework/events/hub.go index d5e2cd2..7367ee8 100644 --- a/gearbox/internal/framework/events/hub.go +++ b/gearbox/internal/framework/events/hub.go @@ -137,6 +137,7 @@ type Hub struct { register chan *Subscriber unregister chan *Subscriber done chan struct{} + stopOnce sync.Once // subscriberDrops is owned by run() and must not be touched from other // goroutines. subscriberDrops map[dropKey]*dropAggregator @@ -166,9 +167,10 @@ func (h *Hub) Start() { go h.run() } -// Stop gracefully shuts down the hub. +// Stop gracefully shuts down the hub. Safe to call more than once (e.g. from +// both a signal handler and a defer). func (h *Hub) Stop() { - close(h.done) + h.stopOnce.Do(func() { close(h.done) }) } // run is the main event loop. @@ -221,7 +223,11 @@ func (h *Hub) run() { } } -// Subscribe creates a new subscriber. +// Subscribe creates a new subscriber. If the hub has already been stopped, +// the returned subscriber's Events channel is pre-closed so consumers (e.g. +// the SSE handler's `event, ok := <-sub.Events`) observe !ok and exit — +// instead of this call blocking forever on a registration that nothing will +// ever drain (shutdown-race goroutine/connection leak). func (h *Hub) Subscribe(id string, serverID string) *Subscriber { sub := &Subscriber{ ID: id, @@ -230,7 +236,11 @@ func (h *Hub) Subscribe(id string, serverID string) *Subscriber { done: make(chan struct{}), } - h.register <- sub + select { + case h.register <- sub: + case <-h.done: + close(sub.Events) + } return sub } diff --git a/gearbox/internal/framework/handler/alerts.go b/gearbox/internal/framework/handler/alerts.go index 060bb75..c13cc3b 100644 --- a/gearbox/internal/framework/handler/alerts.go +++ b/gearbox/internal/framework/handler/alerts.go @@ -9,7 +9,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/sarg3nt/gearbox/internal/framework/auth" "github.com/sarg3nt/gearbox/internal/framework/database" - apperrors "github.com/sarg3nt/gearbox/internal/framework/errors" + apperrors "github.com/sarg3nt/webcore/core/errors" "github.com/sarg3nt/gearbox/internal/framework/models" "github.com/sarg3nt/gearbox/internal/framework/templates/pages" ) diff --git a/gearbox/internal/framework/handler/api_certificates.go b/gearbox/internal/framework/handler/api_certificates.go index fa74c0a..6dd0d72 100644 --- a/gearbox/internal/framework/handler/api_certificates.go +++ b/gearbox/internal/framework/handler/api_certificates.go @@ -6,7 +6,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/sarg3nt/gearbox/internal/framework/agent" - apperrors "github.com/sarg3nt/gearbox/internal/framework/errors" + apperrors "github.com/sarg3nt/webcore/core/errors" "github.com/sarg3nt/gearbox/internal/framework/models" ) diff --git a/gearbox/internal/framework/handler/api_logs.go b/gearbox/internal/framework/handler/api_logs.go index 69c75e7..ce95410 100644 --- a/gearbox/internal/framework/handler/api_logs.go +++ b/gearbox/internal/framework/handler/api_logs.go @@ -4,7 +4,7 @@ import ( "net/http" "github.com/go-chi/chi/v5" - apperrors "github.com/sarg3nt/gearbox/internal/framework/errors" + apperrors "github.com/sarg3nt/webcore/core/errors" "github.com/sarg3nt/gearbox/internal/framework/models" ) diff --git a/gearbox/internal/framework/handler/api_services.go b/gearbox/internal/framework/handler/api_services.go index 4b3503b..d443dfa 100644 --- a/gearbox/internal/framework/handler/api_services.go +++ b/gearbox/internal/framework/handler/api_services.go @@ -9,7 +9,7 @@ import ( "github.com/sarg3nt/gearbox/internal/framework/agent" "github.com/sarg3nt/gearbox/internal/framework/auth" "github.com/sarg3nt/gearbox/internal/framework/database" - apperrors "github.com/sarg3nt/gearbox/internal/framework/errors" + apperrors "github.com/sarg3nt/webcore/core/errors" "github.com/sarg3nt/gearbox/internal/framework/models" "github.com/sarg3nt/gearbox/internal/gears/services" ) diff --git a/gearbox/internal/framework/handler/api_traffic.go b/gearbox/internal/framework/handler/api_traffic.go index bd52939..f322bcb 100644 --- a/gearbox/internal/framework/handler/api_traffic.go +++ b/gearbox/internal/framework/handler/api_traffic.go @@ -10,7 +10,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/sarg3nt/gearbox/internal/framework/agent" "github.com/sarg3nt/gearbox/internal/framework/database" - apperrors "github.com/sarg3nt/gearbox/internal/framework/errors" + apperrors "github.com/sarg3nt/webcore/core/errors" "github.com/sarg3nt/gearbox/internal/framework/models" ) diff --git a/gearbox/internal/framework/handler/backup.go b/gearbox/internal/framework/handler/backup.go index b9dbbf8..b60bbad 100644 --- a/gearbox/internal/framework/handler/backup.go +++ b/gearbox/internal/framework/handler/backup.go @@ -10,7 +10,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/sarg3nt/gearbox/internal/framework/database" - apperrors "github.com/sarg3nt/gearbox/internal/framework/errors" + apperrors "github.com/sarg3nt/webcore/core/errors" "github.com/sarg3nt/gearbox/internal/framework/templates/pages" ) diff --git a/gearbox/internal/framework/handler/config.go b/gearbox/internal/framework/handler/config.go index 5900061..7be3518 100644 --- a/gearbox/internal/framework/handler/config.go +++ b/gearbox/internal/framework/handler/config.go @@ -11,7 +11,7 @@ import ( "github.com/sarg3nt/gearbox/internal/framework/agent" "github.com/sarg3nt/gearbox/internal/framework/auth" "github.com/sarg3nt/gearbox/internal/framework/database" - apperrors "github.com/sarg3nt/gearbox/internal/framework/errors" + apperrors "github.com/sarg3nt/webcore/core/errors" "github.com/sarg3nt/gearbox/internal/framework/models" "github.com/sarg3nt/gearbox/internal/framework/templates/pages" ) diff --git a/gearbox/internal/framework/handler/handler.go b/gearbox/internal/framework/handler/handler.go index 2d3fd3c..77a29f4 100644 --- a/gearbox/internal/framework/handler/handler.go +++ b/gearbox/internal/framework/handler/handler.go @@ -13,7 +13,7 @@ import ( "github.com/sarg3nt/gearbox/internal/framework/database" "github.com/sarg3nt/gearbox/internal/framework/events" "github.com/sarg3nt/gearbox/internal/framework/models" - "github.com/sarg3nt/gearbox/internal/framework/services/crypto" + "github.com/sarg3nt/webcore/core/crypto" "github.com/sarg3nt/gearbox/internal/framework/services/email" "github.com/sarg3nt/gearbox/internal/framework/services/geoip" ) diff --git a/gearbox/internal/framework/handler/haproxy_config.go b/gearbox/internal/framework/handler/haproxy_config.go index 221d8f6..a2be88a 100644 --- a/gearbox/internal/framework/handler/haproxy_config.go +++ b/gearbox/internal/framework/handler/haproxy_config.go @@ -11,7 +11,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/sarg3nt/gearbox/internal/framework/agent" "github.com/sarg3nt/gearbox/internal/framework/auth" - "github.com/sarg3nt/gearbox/internal/framework/services/crypto" + "github.com/sarg3nt/webcore/core/crypto" "github.com/sarg3nt/gearbox/internal/framework/database" "github.com/sarg3nt/gearbox/internal/framework/models" "github.com/sarg3nt/gearbox/internal/framework/templates/pages" diff --git a/gearbox/internal/framework/handler/security.go b/gearbox/internal/framework/handler/security.go index 3999128..39dda2a 100644 --- a/gearbox/internal/framework/handler/security.go +++ b/gearbox/internal/framework/handler/security.go @@ -7,7 +7,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/sarg3nt/gearbox/internal/framework/auth" - apperrors "github.com/sarg3nt/gearbox/internal/framework/errors" + apperrors "github.com/sarg3nt/webcore/core/errors" "github.com/sarg3nt/gearbox/internal/framework/models" "github.com/sarg3nt/gearbox/internal/framework/templates/pages" ) diff --git a/gearbox/internal/framework/handler/users.go b/gearbox/internal/framework/handler/users.go index 4108c6c..947de88 100644 --- a/gearbox/internal/framework/handler/users.go +++ b/gearbox/internal/framework/handler/users.go @@ -11,7 +11,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/sarg3nt/gearbox/internal/framework/auth" - apperrors "github.com/sarg3nt/gearbox/internal/framework/errors" + apperrors "github.com/sarg3nt/webcore/core/errors" "github.com/sarg3nt/gearbox/internal/framework/models" "github.com/sarg3nt/gearbox/internal/framework/templates/pages" ) diff --git a/gearbox/internal/framework/services/agent_keyring/rotator.go b/gearbox/internal/framework/services/agent_keyring/rotator.go index 44ae8f7..8b790d3 100644 --- a/gearbox/internal/framework/services/agent_keyring/rotator.go +++ b/gearbox/internal/framework/services/agent_keyring/rotator.go @@ -20,7 +20,7 @@ import ( "github.com/sarg3nt/gearbox/internal/framework/agent" "github.com/sarg3nt/gearbox/internal/framework/database" - "github.com/sarg3nt/gearbox/internal/framework/services/crypto" + "github.com/sarg3nt/webcore/core/crypto" ) // DefaultOverlapWindow is the time that elapses between flipping diff --git a/gearbox/internal/framework/services/agent_keyring/rotator_test.go b/gearbox/internal/framework/services/agent_keyring/rotator_test.go index 77ee50c..071c397 100644 --- a/gearbox/internal/framework/services/agent_keyring/rotator_test.go +++ b/gearbox/internal/framework/services/agent_keyring/rotator_test.go @@ -16,7 +16,7 @@ import ( "time" "github.com/sarg3nt/gearbox/internal/framework/database" - dashcrypto "github.com/sarg3nt/gearbox/internal/framework/services/crypto" + dashcrypto "github.com/sarg3nt/webcore/core/crypto" ) // agentMock fakes the subset of the agent's /api/v1/system/keyring/* @@ -191,9 +191,9 @@ func setupRotator(t *testing.T) (*Rotator, *agentMock, *database.DB, *database.B } t.Cleanup(func() { _ = db.Close() }) - enc, err := dashcrypto.NewEncryptor("test-encryption-secret-32-bytes!") + enc, err := dashcrypto.NewFromHashedKey("test-encryption-secret-32-bytes!") if err != nil { - t.Fatalf("NewEncryptor: %v", err) + t.Fatalf("NewFromHashedKey: %v", err) } box := &database.BoxDB{ diff --git a/gearbox/internal/framework/services/auth_adapter.go b/gearbox/internal/framework/services/auth_adapter.go index 8473c58..bbe992d 100644 --- a/gearbox/internal/framework/services/auth_adapter.go +++ b/gearbox/internal/framework/services/auth_adapter.go @@ -10,7 +10,7 @@ import ( "github.com/sarg3nt/gearbox/internal/framework/database" "github.com/sarg3nt/gearbox/internal/framework/gear" "github.com/sarg3nt/gearbox/internal/framework/models" - "github.com/sarg3nt/gearbox/internal/framework/services/crypto" + "github.com/sarg3nt/webcore/core/crypto" ) // AuthAdapter wraps the auth.Manager to implement gear.AuthChecker. diff --git a/gearbox/internal/framework/services/crypto/encryption.go b/gearbox/internal/framework/services/crypto/encryption.go deleted file mode 100644 index 23b6727..0000000 --- a/gearbox/internal/framework/services/crypto/encryption.go +++ /dev/null @@ -1,116 +0,0 @@ -package crypto - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "crypto/sha256" - "errors" - "io" -) - -var ( - ErrInvalidCiphertext = errors.New("invalid ciphertext") - ErrInvalidKey = errors.New("invalid encryption key") -) - -// Encryptor handles AES-256-GCM encryption/decryption for secrets. -type Encryptor struct { - key []byte -} - -// NewEncryptor creates a new encryptor using the provided secret key. -// The key is hashed with SHA-256 to ensure it's exactly 32 bytes for AES-256. -func NewEncryptor(secret string) (*Encryptor, error) { - if len(secret) < 32 { - return nil, ErrInvalidKey - } - - // Derive a 32-byte key using SHA-256 - hash := sha256.Sum256([]byte(secret)) - key := hash[:] - - return &Encryptor{key: key}, nil -} - -// Encrypt encrypts plaintext using AES-256-GCM. -// Returns ciphertext with prepended nonce (12 bytes + encrypted data). -func (e *Encryptor) Encrypt(plaintext []byte) ([]byte, error) { - if len(plaintext) == 0 { - return nil, nil - } - - block, err := aes.NewCipher(e.key) - if err != nil { - return nil, err - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - // Generate a random nonce - nonce := make([]byte, gcm.NonceSize()) - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return nil, err - } - - // Encrypt and prepend nonce to ciphertext - ciphertext := gcm.Seal(nonce, nonce, plaintext, nil) - return ciphertext, nil -} - -// Decrypt decrypts ciphertext using AES-256-GCM. -// Expects ciphertext with prepended nonce (12 bytes + encrypted data). -func (e *Encryptor) Decrypt(ciphertext []byte) ([]byte, error) { - if len(ciphertext) == 0 { - return nil, nil - } - - block, err := aes.NewCipher(e.key) - if err != nil { - return nil, err - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - nonceSize := gcm.NonceSize() - if len(ciphertext) < nonceSize { - return nil, ErrInvalidCiphertext - } - - // Extract nonce and actual ciphertext - nonce, encryptedData := ciphertext[:nonceSize], ciphertext[nonceSize:] - - // Decrypt - plaintext, err := gcm.Open(nil, nonce, encryptedData, nil) - if err != nil { - return nil, ErrInvalidCiphertext - } - - return plaintext, nil -} - -// EncryptString is a convenience method for encrypting strings. -func (e *Encryptor) EncryptString(plaintext string) ([]byte, error) { - if plaintext == "" { - return nil, nil - } - return e.Encrypt([]byte(plaintext)) -} - -// DecryptString is a convenience method for decrypting to strings. -func (e *Encryptor) DecryptString(ciphertext []byte) (string, error) { - if len(ciphertext) == 0 { - return "", nil - } - plaintext, err := e.Decrypt(ciphertext) - if err != nil { - return "", err - } - return string(plaintext), nil -} diff --git a/gearbox/internal/framework/services/server_adapter.go b/gearbox/internal/framework/services/server_adapter.go index 520ec21..e6dcc3c 100644 --- a/gearbox/internal/framework/services/server_adapter.go +++ b/gearbox/internal/framework/services/server_adapter.go @@ -6,7 +6,7 @@ import ( "sync" "github.com/sarg3nt/gearbox/internal/framework/agent" - "github.com/sarg3nt/gearbox/internal/framework/services/crypto" + "github.com/sarg3nt/webcore/core/crypto" "github.com/sarg3nt/gearbox/internal/framework/database" "github.com/sarg3nt/gearbox/internal/framework/gear" "github.com/sarg3nt/gearbox/internal/framework/models" diff --git a/gearbox/internal/framework/templates/components/helpers.go b/gearbox/internal/framework/templates/components/helpers.go index 492ee42..8cc035b 100644 --- a/gearbox/internal/framework/templates/components/helpers.go +++ b/gearbox/internal/framework/templates/components/helpers.go @@ -50,8 +50,3 @@ func calculateCPUPercentage(loadAvg float64) float64 { return percentage } -// intToString converts an integer to a string. -// Used by settings components for HTML attribute values. -func intToString(n int) string { - return fmt.Sprintf("%d", n) -} diff --git a/gearbox/internal/framework/templates/components/info_tooltip.templ b/gearbox/internal/framework/templates/components/info_tooltip.templ deleted file mode 100644 index acbeb37..0000000 --- a/gearbox/internal/framework/templates/components/info_tooltip.templ +++ /dev/null @@ -1,29 +0,0 @@ -package components - -// InfoTooltip renders the canonical "info-circle + hover tooltip" widget -// used across the app — small grey filled-circle "i" icon that, on hover, -// reveals a short tooltip bubble. Behaviour matches the HAProxy overview -// pattern verbatim: pure CSS via Tailwind `group` / `group-hover:visible` -// (no JS), no cursor change (cursor stays as the default arrow), no -// transition delay. -// -// Use this anywhere you want to attach a short explanation to a label, -// table column header, KPI title, etc. For richer multi-paragraph -// content (the "VPN Gateway Architecture" style with a bold title + -// body), drop the same wrapper markup inline so the tooltip can hold -// arbitrary HTML; this component is the simple text-only variant. -// -// Width defaults to w-64 (16rem). Anchor is left-0 / top-6 — the -// tooltip extends down-and-right from the icon, which keeps it inside -// content cards whose icons sit near the left edge. -templ InfoTooltip(text string) { - - - - -} diff --git a/gearbox/internal/framework/templates/components/live_refresh.templ b/gearbox/internal/framework/templates/components/live_refresh.templ deleted file mode 100644 index 0a95f9d..0000000 --- a/gearbox/internal/framework/templates/components/live_refresh.templ +++ /dev/null @@ -1,58 +0,0 @@ -package components - -// LiveRefreshButton creates a refresh button that changes color based on SSE connection status -// The button shows: -// - Green when connected (Live) -// - Yellow when connecting -// - Red when disconnected -// - Gray as default -// -// Usage: Include this button in your page header controls section, then call updateSSEStatus(status) -// from your JavaScript to update the button's appearance based on connection state. -templ LiveRefreshButton() { - -} - -// LiveRefreshButtonScript provides the JavaScript function to update the button status -// This should be included once per page that uses the LiveRefreshButton component -templ LiveRefreshButtonScript() { - -} diff --git a/gearbox/internal/framework/templates/components/settings.templ b/gearbox/internal/framework/templates/components/settings.templ deleted file mode 100644 index 39f5405..0000000 --- a/gearbox/internal/framework/templates/components/settings.templ +++ /dev/null @@ -1,123 +0,0 @@ -package components - -// SettingsSection wraps a group of settings with a title and description. -templ SettingsSection(title, description string) { -
-
-

{ title }

-

- { description } -

-
-
- { children... } -
-
-} - -// SettingsCheckbox renders a checkbox input with a label and help text. -templ SettingsCheckbox(id, label, helpText string, checked bool) { -
-
- - -
- if helpText != "" { -

- { helpText } -

- } -
-} - -// SelectOption represents an option in a select dropdown. -type SelectOption struct { - Value string - Label string - Selected bool -} - -// SettingsSelect renders a select dropdown with a label and help text. -templ SettingsSelect(id, label, helpText string, options []SelectOption) { -
- - - if helpText != "" { -

- { helpText } -

- } -
-} - -// SettingsTextInput renders a text input with a label and help text. -templ SettingsTextInput(id, label, placeholder, helpText, value string) { -
- - - if helpText != "" { -

- { helpText } -

- } -
-} - -// SettingsNumberInput renders a number input with a label and help text. -templ SettingsNumberInput(id, label, helpText string, value, min, max int) { -
- - - if helpText != "" { -

- { helpText } -

- } -
-} diff --git a/gearbox/internal/framework/templates/components/table.templ b/gearbox/internal/framework/templates/components/table.templ deleted file mode 100644 index ee23f7e..0000000 --- a/gearbox/internal/framework/templates/components/table.templ +++ /dev/null @@ -1,410 +0,0 @@ -package components - -import ( - "fmt" -) - -// TableConfig holds configuration for enhanced tables -type TableConfig struct { - ID string - Title string - Description string - EnableSort bool - EnableSearch bool - EnablePaging bool - EnableExport bool - PageSize int - Columns []TableColumn -} - -// TableColumn defines a column in the table -type TableColumn struct { - Key string - Label string - Sortable bool - DataType string // "string", "number", "bytes", "percent", "date" - Width string // CSS width - Alignment string // "left", "center", "right" -} - -// SortableTableHeader renders a sortable table header -templ SortableTableHeader(tableID string, columns []TableColumn, defaultSort string, defaultOrder string) { - - - for _, col := range columns { - if col.Sortable { - -
- { col.Label } - - - - - -
- - } else { - - { col.Label } - - } - } - - -} - -// TableToolbar renders the search, filter, and export controls -templ TableToolbar(tableID string, config TableConfig) { -
-
-
-

{ config.Title }

- if config.Description != "" { -

{ config.Description }

- } -
-
- if config.EnableSearch { -
- - - - -
- } - if config.EnableExport { - - } -
-
-
-} - -// TablePagination renders pagination controls -templ TablePagination(tableID string, pageSize int) { -
-
- Showing 1-{ fmt.Sprintf("%d", pageSize) } of 0 -
-
- -
- - -
-
-
-} - -// EnhancedTableScripts provides the JavaScript for table functionality -templ EnhancedTableScripts() { - -} diff --git a/gearbox/internal/framework/templates/components/webcore_reexport.go b/gearbox/internal/framework/templates/components/webcore_reexport.go new file mode 100644 index 0000000..bddcd7b --- /dev/null +++ b/gearbox/internal/framework/templates/components/webcore_reexport.go @@ -0,0 +1,12 @@ +package components + +import wc "github.com/sarg3nt/webcore/ui/components" + +// Type aliases for the moved components' parameter types, so existing +// gearbox call sites using components.SelectOption / TableConfig / TableColumn +// keep compiling against the webcore definitions (identical types). +type ( + SelectOption = wc.SelectOption + TableConfig = wc.TableConfig + TableColumn = wc.TableColumn +) diff --git a/gearbox/internal/framework/templates/components/webcore_reexport.templ b/gearbox/internal/framework/templates/components/webcore_reexport.templ new file mode 100644 index 0000000..0931c6d --- /dev/null +++ b/gearbox/internal/framework/templates/components/webcore_reexport.templ @@ -0,0 +1,59 @@ +package components + +// Re-exports of UI components that now live in webcore. These thin wrappers +// keep gearbox's existing `components.X` call sites working while the actual +// markup/JS/CSS is owned by github.com/sarg3nt/webcore/ui/components — a single +// source of truth, no duplicated HTML. Gearbox-only components (console, +// container_diagram, metrics/SystemMetrics, doughnut) remain in this package. + +import wc "github.com/sarg3nt/webcore/ui/components" + +templ InfoTooltip(text string) { + @wc.InfoTooltip(text) +} + +templ SettingsSection(title, description string) { + @wc.SettingsSection(title, description) { + { children... } + } +} + +templ SettingsCheckbox(id, label, helpText string, checked bool) { + @wc.SettingsCheckbox(id, label, helpText, checked) +} + +templ SettingsSelect(id, label, helpText string, options []SelectOption) { + @wc.SettingsSelect(id, label, helpText, options) +} + +templ SettingsTextInput(id, label, placeholder, helpText, value string) { + @wc.SettingsTextInput(id, label, placeholder, helpText, value) +} + +templ SettingsNumberInput(id, label, helpText string, value, min, max int) { + @wc.SettingsNumberInput(id, label, helpText, value, min, max) +} + +templ SortableTableHeader(tableID string, columns []TableColumn, defaultSort string, defaultOrder string) { + @wc.SortableTableHeader(tableID, columns, defaultSort, defaultOrder) +} + +templ TableToolbar(tableID string, config TableConfig) { + @wc.TableToolbar(tableID, config) +} + +templ TablePagination(tableID string, pageSize int) { + @wc.TablePagination(tableID, pageSize) +} + +templ EnhancedTableScripts() { + @wc.EnhancedTableScripts() +} + +templ LiveRefreshButton() { + @wc.LiveRefreshButton() +} + +templ LiveRefreshButtonScript() { + @wc.LiveRefreshButtonScript() +} diff --git a/gearbox/internal/framework/templates/layouts/base.templ b/gearbox/internal/framework/templates/layouts/base.templ index 20ed246..072071d 100644 --- a/gearbox/internal/framework/templates/layouts/base.templ +++ b/gearbox/internal/framework/templates/layouts/base.templ @@ -5,7 +5,7 @@ import "encoding/json" import "github.com/sarg3nt/gearbox/internal/framework/auth" import "github.com/sarg3nt/gearbox/internal/framework/models" import "github.com/sarg3nt/gearbox/internal/framework/templates/components" -import "github.com/sarg3nt/gearbox/internal/framework/ui" +import ui "github.com/sarg3nt/webcore/ui/components" import "github.com/sarg3nt/gearbox/internal/framework/middleware" import "strings" diff --git a/gearbox/internal/framework/templates/pages/admin_permissions.templ b/gearbox/internal/framework/templates/pages/admin_permissions.templ index 84966c8..5795663 100644 --- a/gearbox/internal/framework/templates/pages/admin_permissions.templ +++ b/gearbox/internal/framework/templates/pages/admin_permissions.templ @@ -3,7 +3,7 @@ package pages import ( "fmt" "github.com/sarg3nt/gearbox/internal/framework/models" - "github.com/sarg3nt/gearbox/internal/framework/ui" + ui "github.com/sarg3nt/webcore/ui/components" "github.com/sarg3nt/gearbox/internal/framework/templates/layouts" ) diff --git a/gearbox/internal/framework/templates/pages/admin_user_permissions.templ b/gearbox/internal/framework/templates/pages/admin_user_permissions.templ index 6460c63..69220fe 100644 --- a/gearbox/internal/framework/templates/pages/admin_user_permissions.templ +++ b/gearbox/internal/framework/templates/pages/admin_user_permissions.templ @@ -3,7 +3,7 @@ package pages import ( "fmt" "github.com/sarg3nt/gearbox/internal/framework/models" - templcomponents "github.com/sarg3nt/gearbox/internal/framework/ui" + templcomponents "github.com/sarg3nt/webcore/ui/components" "github.com/sarg3nt/gearbox/internal/framework/templates/layouts" ) diff --git a/gearbox/internal/framework/templates/pages/alert_rules.templ b/gearbox/internal/framework/templates/pages/alert_rules.templ index 2dece16..fe76f07 100644 --- a/gearbox/internal/framework/templates/pages/alert_rules.templ +++ b/gearbox/internal/framework/templates/pages/alert_rules.templ @@ -2,7 +2,7 @@ package pages import ( "github.com/sarg3nt/gearbox/internal/framework/models" - "github.com/sarg3nt/gearbox/internal/framework/ui" + ui "github.com/sarg3nt/webcore/ui/components" "github.com/sarg3nt/gearbox/internal/framework/templates/layouts" ) diff --git a/gearbox/internal/framework/templates/pages/backup.templ b/gearbox/internal/framework/templates/pages/backup.templ index 36e7b11..e55b7b6 100644 --- a/gearbox/internal/framework/templates/pages/backup.templ +++ b/gearbox/internal/framework/templates/pages/backup.templ @@ -4,7 +4,7 @@ import ( "fmt" "github.com/sarg3nt/gearbox/internal/framework/database" "github.com/sarg3nt/gearbox/internal/framework/models" - "github.com/sarg3nt/gearbox/internal/framework/ui" + ui "github.com/sarg3nt/webcore/ui/components" "github.com/sarg3nt/gearbox/internal/framework/templates/layouts" ) diff --git a/gearbox/internal/framework/templates/pages/gears.templ b/gearbox/internal/framework/templates/pages/gears.templ index c213c19..6ae1769 100644 --- a/gearbox/internal/framework/templates/pages/gears.templ +++ b/gearbox/internal/framework/templates/pages/gears.templ @@ -5,7 +5,7 @@ import ( "fmt" "github.com/sarg3nt/gearbox/internal/framework/database" "github.com/sarg3nt/gearbox/internal/framework/models" - "github.com/sarg3nt/gearbox/internal/framework/ui" + ui "github.com/sarg3nt/webcore/ui/components" "github.com/sarg3nt/gearbox/internal/framework/templates/layouts" ) diff --git a/gearbox/internal/framework/templates/pages/haproxy_gear_settings.templ b/gearbox/internal/framework/templates/pages/haproxy_gear_settings.templ index 294a8e1..cbcc6e1 100644 --- a/gearbox/internal/framework/templates/pages/haproxy_gear_settings.templ +++ b/gearbox/internal/framework/templates/pages/haproxy_gear_settings.templ @@ -5,7 +5,7 @@ import ( "github.com/sarg3nt/gearbox/internal/framework/database" "github.com/sarg3nt/gearbox/internal/framework/models" "github.com/sarg3nt/gearbox/internal/framework/templates/layouts" - "github.com/sarg3nt/gearbox/internal/framework/ui" + ui "github.com/sarg3nt/webcore/ui/components" ) // HAProxyGearSettingsPage renders the HAProxy gear settings page. diff --git a/gearbox/internal/framework/templates/pages/haproxy_settings.templ b/gearbox/internal/framework/templates/pages/haproxy_settings.templ index 144d5e0..eadeb7c 100644 --- a/gearbox/internal/framework/templates/pages/haproxy_settings.templ +++ b/gearbox/internal/framework/templates/pages/haproxy_settings.templ @@ -5,7 +5,7 @@ import ( "github.com/sarg3nt/gearbox/internal/framework/database" "github.com/sarg3nt/gearbox/internal/framework/middleware" "github.com/sarg3nt/gearbox/internal/framework/models" - "github.com/sarg3nt/gearbox/internal/framework/ui" + ui "github.com/sarg3nt/webcore/ui/components" "github.com/sarg3nt/gearbox/internal/framework/templates/layouts" ) diff --git a/gearbox/internal/framework/templates/pages/log_settings.templ b/gearbox/internal/framework/templates/pages/log_settings.templ index 1c6cd37..389454d 100644 --- a/gearbox/internal/framework/templates/pages/log_settings.templ +++ b/gearbox/internal/framework/templates/pages/log_settings.templ @@ -5,7 +5,7 @@ import ( "github.com/sarg3nt/gearbox/internal/framework/agent" "github.com/sarg3nt/gearbox/internal/framework/database" "github.com/sarg3nt/gearbox/internal/framework/models" - "github.com/sarg3nt/gearbox/internal/framework/ui" + ui "github.com/sarg3nt/webcore/ui/components" "github.com/sarg3nt/gearbox/internal/framework/templates/layouts" ) diff --git a/gearbox/internal/framework/templates/pages/overview.templ b/gearbox/internal/framework/templates/pages/overview.templ index f5eb8fa..bf73ba9 100644 --- a/gearbox/internal/framework/templates/pages/overview.templ +++ b/gearbox/internal/framework/templates/pages/overview.templ @@ -3,7 +3,7 @@ package pages import "github.com/sarg3nt/gearbox/internal/framework/models" import "github.com/sarg3nt/gearbox/internal/framework/database" import "github.com/sarg3nt/gearbox/internal/framework/templates/layouts" -import "github.com/sarg3nt/gearbox/internal/framework/ui" +import ui "github.com/sarg3nt/webcore/ui/components" import "github.com/sarg3nt/gearbox/internal/framework/templates/components" import "fmt" diff --git a/gearbox/internal/framework/templates/pages/user_pages.templ b/gearbox/internal/framework/templates/pages/user_pages.templ index 4c3b409..7c5f863 100644 --- a/gearbox/internal/framework/templates/pages/user_pages.templ +++ b/gearbox/internal/framework/templates/pages/user_pages.templ @@ -3,7 +3,7 @@ package pages import ( "fmt" "github.com/sarg3nt/gearbox/internal/framework/models" - "github.com/sarg3nt/gearbox/internal/framework/ui" + ui "github.com/sarg3nt/webcore/ui/components" "github.com/sarg3nt/gearbox/internal/framework/templates/layouts" ) diff --git a/gearbox/internal/framework/templates/pages/welcome.templ b/gearbox/internal/framework/templates/pages/welcome.templ index 7ce9188..180fdd0 100644 --- a/gearbox/internal/framework/templates/pages/welcome.templ +++ b/gearbox/internal/framework/templates/pages/welcome.templ @@ -4,7 +4,7 @@ import ( "github.com/sarg3nt/gearbox/internal/framework/database" "github.com/sarg3nt/gearbox/internal/framework/middleware" "github.com/sarg3nt/gearbox/internal/framework/models" - "github.com/sarg3nt/gearbox/internal/framework/ui" + ui "github.com/sarg3nt/webcore/ui/components" ) // WelcomePage is the first-run landing screen shown to an admin when nothing diff --git a/gearbox/internal/framework/ui/alerts.templ b/gearbox/internal/framework/ui/alerts.templ deleted file mode 100644 index c7a869e..0000000 --- a/gearbox/internal/framework/ui/alerts.templ +++ /dev/null @@ -1,78 +0,0 @@ -package ui - -// ErrorAlert displays an error message with an icon -templ ErrorAlert(message string) { -
-
- - - - { message } -
-
-} - -// InfoAlert displays an informational message with an icon -templ InfoAlert(message string) { -
-
- - - - { message } -
-
-} - -// WarningAlert displays a warning message with an icon -templ WarningAlert(message string) { -
-
- - - - { message } -
-
-} - -// SuccessAlert displays a success message with an icon -templ SuccessAlert(message string) { -
-
- - - - { message } -
-
-} - -// LoadingSpinner displays a centered loading spinner animation -templ LoadingSpinner() { -
-
-
-} - -// SmallLoadingSpinner displays a smaller inline loading spinner -templ SmallLoadingSpinner() { -
-
-
-} - -// EmptyState displays a centered message when there's no data -templ EmptyState(icon templ.Component, title string, description string) { -
- if icon != nil { -
- @icon -
- } -

{ title }

- if description != "" { -

{ description }

- } -
-} diff --git a/gearbox/internal/framework/ui/badge.templ b/gearbox/internal/framework/ui/badge.templ deleted file mode 100644 index 6d06cd0..0000000 --- a/gearbox/internal/framework/ui/badge.templ +++ /dev/null @@ -1,87 +0,0 @@ -package ui - -import "fmt" - -// Badge displays a small status badge with customizable colors -templ Badge(text string, colorClass string) { - - { text } - -} - -// StatusBadge displays a badge with preset colors based on status -// status: "success", "error", "warning", "info", "default" -templ StatusBadge(text string, status string) { - switch status { - case "success": - - { text } - - case "error": - - { text } - - case "warning": - - { text } - - case "info": - - { text } - - default: - - { text } - - } -} - -// CountBadge displays a numeric count badge (like notification counts) -templ CountBadge(count int, maxDisplay int) { - if count > 0 { - if count > maxDisplay { - - { fmt.Sprintf("%d+", maxDisplay) } - - } else { - - { fmt.Sprintf("%d", count) } - - } - } -} - -// DotIndicator displays a small colored dot (for status indicators) -// status: "online", "offline", "warning", "unknown" -templ DotIndicator(status string) { - switch status { - case "online": - - case "offline": - - case "warning": - - default: - - } -} - -// PulseDotIndicator displays an animated pulsing dot -templ PulseDotIndicator(status string) { - switch status { - case "online": - - - - - case "offline": - - case "warning": - - - - - default: - - } -} diff --git a/gearbox/internal/framework/ui/collapsible.templ b/gearbox/internal/framework/ui/collapsible.templ deleted file mode 100644 index 821da76..0000000 --- a/gearbox/internal/framework/ui/collapsible.templ +++ /dev/null @@ -1,147 +0,0 @@ -package ui - -// CollapsibleSection creates a collapsible container with a header and content -templ CollapsibleSection(id string, title string, badge string, badgeColor string, defaultOpen bool) { -
- -
-
-
- if badge != "" { -
{ badge }
- } -

{ title }

-
-
- - - - -
-
-
- - -
-
- { children... } -
-
-
-} - -// CollapsibleSectionWithStats creates a collapsible container with stats shown in the header bar -templ CollapsibleSectionWithStats(id string, title string, badge string, badgeColor string, statsText string, defaultOpen bool) { -
- -
-
-
- if badge != "" { -
{ badge }
- } -

{ title }

- if statsText != "" { - - } -
-
- - - - -
-
-
- - -
-
- { children... } -
-
-
-} - -script toggleCollapsible(id string) { - // Get the content element - const content = document.getElementById(id); - const icon = document.querySelector(`[data-target="${id}"]`); - - if (content && icon) { - // Toggle visibility - content.classList.toggle('hidden'); - - // Rotate icon - if (content.classList.contains('hidden')) { - icon.style.transform = 'rotate(-90deg)'; - } else { - icon.style.transform = 'rotate(0deg)'; - } - - // Save state to localStorage - const isOpen = !content.classList.contains('hidden'); - localStorage.setItem(`collapsible-${id}`, isOpen ? 'open' : 'closed'); - } -} - -// Global function to restore collapsible states from localStorage -// This is called on page load and after HTMX content updates -templ CollapsibleRestoreScript() { - -} diff --git a/gearbox/internal/framework/ui/icons.templ b/gearbox/internal/framework/ui/icons.templ deleted file mode 100644 index a905ca5..0000000 --- a/gearbox/internal/framework/ui/icons.templ +++ /dev/null @@ -1,259 +0,0 @@ -package ui - -// Common icons used throughout the application. -// All icons use the heroicons style (outline, stroke-based). -// Default size is w-6 h-6, but can be overridden with CSS classes. - -// IconDashboard - Home/Dashboard icon -templ IconDashboard() { - - - -} - -// IconGrid - Grid/Status Grid icon -templ IconGrid() { - - - -} - -// IconLogs - Document/Logs icon -templ IconLogs() { - - - -} - -// IconMetrics - Chart/Metrics icon -templ IconMetrics() { - - - - -} - -// IconHistory - Bar Chart/History icon -templ IconHistory() { - - - -} - -// IconCertificate - Shield/Certificate icon -templ IconCertificate() { - - - -} - -// IconServices - Server/Services icon -templ IconServices() { - - - -} - -// IconTraffic - Lightning bolt/Traffic icon -templ IconTraffic() { - - - -} - -// IconAlerts - Bell/Alerts icon -templ IconAlerts() { - - - -} - -// IconOSUpdates - Download/Updates icon -templ IconOSUpdates() { - - - -} - -// IconSettings - Gear/Settings icon -templ IconSettings() { - - - - -} - -// IconUser - User profile icon -templ IconUser() { - - - -} - -// IconLogout - Logout icon -templ IconLogout() { - - - -} - -// IconKey - Key/Password icon -templ IconKey() { - - - -} - -// IconSun - Light mode icon -templ IconSun() { - - - -} - -// IconMoon - Dark mode icon -templ IconMoon() { - - - -} - -// IconMonitor - System/Monitor icon -templ IconMonitor() { - - - -} - -// IconRefresh - Refresh/Sync icon -templ IconRefresh() { - - - -} - -// IconSearch - Search/Magnifying glass icon -templ IconSearch() { - - - -} - -// IconClose - X/Close icon -templ IconClose() { - - - -} - -// IconCheck - Checkmark icon -templ IconCheck() { - - - -} - -// IconWarning - Warning triangle icon -templ IconWarning() { - - - -} - -// IconInfo - Info circle icon -templ IconInfo() { - - - -} - -// IconPlus - Plus/Add icon -templ IconPlus() { - - - -} - -// IconTrash - Trash/Delete icon -templ IconTrash() { - - - -} - -// IconEdit - Pencil/Edit icon -templ IconEdit() { - - - -} - -// IconCopy - Copy/Clipboard icon -templ IconCopy() { - - - -} - -// IconDownload - Download icon -templ IconDownload() { - - - -} - -// IconExternalLink - External link icon -templ IconExternalLink() { - - - -} - -// IconChevronDown - Chevron down icon -templ IconChevronDown() { - - - -} - -// IconChevronUp - Chevron up icon -templ IconChevronUp() { - - - -} - -// IconChevronLeft - Chevron left icon -templ IconChevronLeft() { - - - -} - -// IconChevronRight - Chevron right icon -templ IconChevronRight() { - - - -} - -// IconSecurity - Shield/Security icon -templ IconSecurity() { - - - -} - -// IconBackup - Cloud/Backup icon -templ IconBackup() { - - - -} - -// IconDatabase - Database icon -templ IconDatabase() { - - - -} diff --git a/gearbox/internal/framework/ui/live_refresh.templ b/gearbox/internal/framework/ui/live_refresh.templ deleted file mode 100644 index 1feb025..0000000 --- a/gearbox/internal/framework/ui/live_refresh.templ +++ /dev/null @@ -1,58 +0,0 @@ -package ui - -// LiveRefreshButton creates a refresh button that changes color based on SSE connection status -// The button shows: -// - Green when connected (Live) -// - Yellow when connecting -// - Red when disconnected -// - Gray as default -// -// Usage: Include this button in your page header controls section, then call updateSSEStatus(status) -// from your JavaScript to update the button's appearance based on connection state. -templ LiveRefreshButton() { - -} - -// LiveRefreshButtonScript provides the JavaScript function to update the button status -// This should be included once per page that uses the LiveRefreshButton component -templ LiveRefreshButtonScript() { - -} diff --git a/gearbox/internal/framework/ui/modal.templ b/gearbox/internal/framework/ui/modal.templ deleted file mode 100644 index c27a955..0000000 --- a/gearbox/internal/framework/ui/modal.templ +++ /dev/null @@ -1,170 +0,0 @@ -package ui - -// Modal creates a generic modal dialog container -// id: unique identifier for the modal -// title: displayed in the modal header -// size: "sm", "md", "lg", "xl", "full" for different widths -templ Modal(id string, title string, size string) { - -} - -// ModalFooter creates a standard footer with action buttons -// Typically used inside a Modal component -templ ModalFooter() { -
- { children... } -
-} - -// ConfirmModal creates a confirmation dialog with customizable icon and buttons -// type: "warning", "danger", "info" -templ ConfirmModal(id string, title string, message string, confirmText string, modalType string) { - -} - -script closeModal(id string) { - const modal = document.getElementById(id); - if (modal) { - modal.classList.add('hidden'); - } -} - -// ModalScripts provides JavaScript functions for modal management -templ ModalScripts() { - -} diff --git a/gearbox/internal/framework/ui/table.templ b/gearbox/internal/framework/ui/table.templ deleted file mode 100644 index cbc91cd..0000000 --- a/gearbox/internal/framework/ui/table.templ +++ /dev/null @@ -1,409 +0,0 @@ -package ui - -import ( - "fmt" -) - -// TableConfig holds configuration for enhanced tables -type TableConfig struct { - ID string - Title string - Description string - EnableSort bool - EnableSearch bool - EnablePaging bool - EnableExport bool - PageSize int - Columns []TableColumn -} - -// TableColumn defines a column in the table -type TableColumn struct { - Key string - Label string - Sortable bool - DataType string // "string", "number", "bytes", "percent", "date" - Width string // CSS width - Alignment string // "left", "center", "right" -} - -// SortableTableHeader renders a sortable table header -templ SortableTableHeader(tableID string, columns []TableColumn, defaultSort string, defaultOrder string) { - - - for _, col := range columns { - if col.Sortable { - -
- { col.Label } - - - - - -
- - } else { - - { col.Label } - - } - } - - -} - -// TableToolbar renders the search, filter, and export controls -templ TableToolbar(tableID string, config TableConfig) { -
-
-
-

{ config.Title }

- if config.Description != "" { -

{ config.Description }

- } -
-
- if config.EnableSearch { -
- - - - -
- } - if config.EnableExport { - - } -
-
-
-} - -// TablePagination renders pagination controls -templ TablePagination(tableID string, pageSize int) { -
-
- Showing 1-{ fmt.Sprintf("%d", pageSize) } of 0 -
-
- -
- - -
-
-
-} - -// EnhancedTableScripts provides the JavaScript for table functionality -templ EnhancedTableScripts() { - -} diff --git a/gearbox/internal/framework/ui/toast.templ b/gearbox/internal/framework/ui/toast.templ deleted file mode 100644 index 06c4c1c..0000000 --- a/gearbox/internal/framework/ui/toast.templ +++ /dev/null @@ -1,21 +0,0 @@ -// Package ui provides reusable UI components for the HAProxy monitoring framework. -// These components can be used by plugins and core application code. -package ui - -// ToastOnLoad triggers a toast notification when the page loads. -// Use this to show success/error messages from query parameters. -// Uses a data attribute to pass the message safely to JavaScript. -templ ToastOnLoad(message, toastType string) { - -} - -// Toast provides a reusable toast notification component -// that appears in the top-right corner of the viewport -templ Toast() { - -
- -
- - -} diff --git a/gearbox/internal/framework/ui/toggle.templ b/gearbox/internal/framework/ui/toggle.templ deleted file mode 100644 index ce9252f..0000000 --- a/gearbox/internal/framework/ui/toggle.templ +++ /dev/null @@ -1,73 +0,0 @@ -package ui - -// toggleValue defaults an empty value to "on" so the underlying checkbox -// submits a non-empty form value when checked. This matches the HTML -// default for a value-less checkbox and keeps `r.FormValue(name) == "on"` -// callers working without forcing every site to pass an explicit value. -func toggleValue(v string) string { - if v == "" { - return "on" - } - return v -} - -// Toggle creates a switch/toggle input component. -// -// id: unique identifier for the input -// name: form field name -// value: form value submitted when checked. If empty, defaults to "on" -// (the HTML default for a checkbox without a value attribute). Pass -// a unique non-empty value when multiple toggles share a name -// (multi-select checkbox group). -// checked: initial state -// disabled: whether the toggle is disabled -// -// This is the canonical toggle for the app. Always use it for boolean form -// inputs in templates instead of a bare . See -// CLAUDE.md → UI Conventions → Toggle switches. -templ Toggle(id string, name string, value string, checked bool, disabled bool) { - -} - -// ToggleWithLabel creates a toggle with an accompanying label -templ ToggleWithLabel(id string, name string, value string, label string, description string, checked bool, disabled bool) { -
-
- @Toggle(id, name, value, checked, disabled) -
-
- - if description != "" { -

{ description }

- } -
-
-} - -// ToggleGroup creates a group of toggles with a heading -templ ToggleGroup(title string, description string) { -
-
-

{ title }

- if description != "" { -

{ description }

- } -
-
- { children... } -
-
-} diff --git a/gearbox/internal/gears/home/handlers.go b/gearbox/internal/gears/home/handlers.go index bf30174..d2f4b16 100644 --- a/gearbox/internal/gears/home/handlers.go +++ b/gearbox/internal/gears/home/handlers.go @@ -9,7 +9,7 @@ import ( "github.com/sarg3nt/gearbox/internal/framework/gear" "github.com/sarg3nt/gearbox/internal/framework/models" "github.com/sarg3nt/gearbox/internal/framework/services" - "github.com/sarg3nt/gearbox/internal/framework/services/crypto" + "github.com/sarg3nt/webcore/core/crypto" ) // Handlers serves the Home dashboard pages and APIs. diff --git a/gearbox/internal/gears/home/widget_runner.go b/gearbox/internal/gears/home/widget_runner.go index e824dfa..b206514 100644 --- a/gearbox/internal/gears/home/widget_runner.go +++ b/gearbox/internal/gears/home/widget_runner.go @@ -7,7 +7,7 @@ import ( "time" "github.com/sarg3nt/gearbox/internal/framework/database" - "github.com/sarg3nt/gearbox/internal/framework/services/crypto" + "github.com/sarg3nt/webcore/core/crypto" "github.com/sarg3nt/gearbox/internal/gears/home/widget" )