From 6607e1dc5e7c09172da45011eafca727056a7380 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Wed, 31 Jan 2024 15:13:57 +0800 Subject: [PATCH] :sparkles: Status pages --- .idea/RoadSign.iml | 1 + .idea/jsLibraryMappings.xml | 6 + go.mod | 3 + go.sum | 6 + pkg/hypertext/proxies.go | 118 +++++++++--------- pkg/hypertext/server.go | 12 +- pkg/hypertext/status/embed.go | 6 + pkg/hypertext/status/serve.go | 56 +++++++++ pkg/hypertext/status/views/bad-gateway.gohtml | 6 + pkg/hypertext/status/views/fallback.gohtml | 6 + .../status/views/gateway-timeout.gohtml | 6 + pkg/hypertext/status/views/index.gohtml | 83 ++++++++++++ pkg/hypertext/status/views/not-found.gohtml | 6 + .../status/views/request-too-large.gohtml | 6 + .../status/views/too-many-requests.gohtml | 6 + pkg/sideload/view/tailwind.config.js | 37 ++++-- 16 files changed, 291 insertions(+), 73 deletions(-) create mode 100644 .idea/jsLibraryMappings.xml create mode 100644 pkg/hypertext/status/embed.go create mode 100644 pkg/hypertext/status/serve.go create mode 100644 pkg/hypertext/status/views/bad-gateway.gohtml create mode 100644 pkg/hypertext/status/views/fallback.gohtml create mode 100644 pkg/hypertext/status/views/gateway-timeout.gohtml create mode 100644 pkg/hypertext/status/views/index.gohtml create mode 100644 pkg/hypertext/status/views/not-found.gohtml create mode 100644 pkg/hypertext/status/views/request-too-large.gohtml create mode 100644 pkg/hypertext/status/views/too-many-requests.gohtml diff --git a/.idea/RoadSign.iml b/.idea/RoadSign.iml index 9dda0dd..15a54e1 100644 --- a/.idea/RoadSign.iml +++ b/.idea/RoadSign.iml @@ -11,5 +11,6 @@ + \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml new file mode 100644 index 0000000..31191ee --- /dev/null +++ b/.idea/jsLibraryMappings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/go.mod b/go.mod index 0aada8f..93cb3d9 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,9 @@ require ( require ( github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/gofiber/template v1.8.2 // indirect + github.com/gofiber/template/html/v2 v2.1.0 // indirect + github.com/gofiber/utils v1.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect diff --git a/go.sum b/go.sum index e834ea5..3febcd8 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,12 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofiber/fiber/v2 v2.52.0 h1:S+qXi7y+/Pgvqq4DrSmREGiFwtB7Bu6+QFLuIHYw/UE= github.com/gofiber/fiber/v2 v2.52.0/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/gofiber/template v1.8.2 h1:PIv9s/7Uq6m+Fm2MDNd20pAFFKt5wWs7ZBd8iV9pWwk= +github.com/gofiber/template v1.8.2/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8= +github.com/gofiber/template/html/v2 v2.1.0 h1:FjwzqhhdJpnhyCvav60Z1ytnBqOUr5sGO/aTeob9/ng= +github.com/gofiber/template/html/v2 v2.1.0/go.mod h1:txXsRQN/G7Fr2cqGfr6zhVHgreCfpsBS+9+DJyrddJc= +github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM= +github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= diff --git a/pkg/hypertext/proxies.go b/pkg/hypertext/proxies.go index 3b12566..e71d921 100644 --- a/pkg/hypertext/proxies.go +++ b/pkg/hypertext/proxies.go @@ -10,88 +10,86 @@ import ( "github.com/samber/lo" ) -func UseProxies(app *fiber.App) { - app.All("/*", func(ctx *fiber.Ctx) error { - host := ctx.Hostname() - path := ctx.Path() - queries := ctx.Queries() - headers := ctx.GetReqHeaders() +func ProxiesHandler(ctx *fiber.Ctx) error { + host := ctx.Hostname() + path := ctx.Path() + queries := ctx.Queries() + headers := ctx.GetReqHeaders() - // Filtering sites - for _, region := range navi.R.Regions { - // Matching rules - for _, location := range region.Locations { - if !lo.Contains(location.Host, host) { - continue - } + // Filtering sites + for _, region := range navi.R.Regions { + // Matching rules + for _, location := range region.Locations { + if !lo.Contains(location.Host, host) { + continue + } - if !func() bool { - flag := false - for _, pattern := range location.Path { - if ok, _ := regexp.MatchString(pattern, path); ok { - flag = true - break - } + if !func() bool { + flag := false + for _, pattern := range location.Path { + if ok, _ := regexp.MatchString(pattern, path); ok { + flag = true + break } - return flag - }() { - continue } + return flag + }() { + continue + } - // Filter query strings - flag := true - for rk, rv := range location.Queries { - for ik, iv := range queries { - if rk != ik && rv != iv { - flag = false - break - } - } - if !flag { + // Filter query strings + flag := true + for rk, rv := range location.Queries { + for ik, iv := range queries { + if rk != ik && rv != iv { + flag = false break } } if !flag { - continue + break } + } + if !flag { + continue + } - // Filter headers - for rk, rv := range location.Headers { - for ik, iv := range headers { - if rk == ik { - for _, ov := range iv { - if !lo.Contains(rv, ov) { - flag = false - break - } + // Filter headers + for rk, rv := range location.Headers { + for ik, iv := range headers { + if rk == ik { + for _, ov := range iv { + if !lo.Contains(rv, ov) { + flag = false + break } } - if !flag { - break - } } if !flag { break } } if !flag { - continue + break } - - idx := rand.Intn(len(location.Destinations)) - dest := location.Destinations[idx] - - // Passing all the rules means the site is what we are looking for. - // Let us respond to our client! - return makeResponse(ctx, region, &location, &dest) } - } + if !flag { + continue + } - // There is no site available for this request. - // Just ignore it and give our client a not found status. - // Do not care about the user experience, we can do it in custom error handler. - return fiber.ErrNotFound - }) + idx := rand.Intn(len(location.Destinations)) + dest := location.Destinations[idx] + + // Passing all the rules means the site is what we are looking for. + // Let us respond to our client! + return makeResponse(ctx, region, &location, &dest) + } + } + + // There is no site available for this request. + // Just ignore it and give our client a not found status. + // Do not care about the user experience, we can do it in custom error handler. + return fiber.ErrNotFound } func makeResponse(c *fiber.Ctx, region *navi.Region, location *navi.Location, dest *navi.Destination) error { diff --git a/pkg/hypertext/server.go b/pkg/hypertext/server.go index d153994..27682f9 100644 --- a/pkg/hypertext/server.go +++ b/pkg/hypertext/server.go @@ -1,25 +1,32 @@ package hypertext import ( + "code.smartsheep.studio/goatworks/roadsign/pkg/hypertext/status" "crypto/tls" jsoniter "github.com/json-iterator/go" "net" + "net/http" "strings" "time" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/limiter" "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/template/html/v2" "github.com/rs/zerolog/log" "github.com/spf13/viper" ) func InitServer() *fiber.App { + views := html.NewFileSystem(http.FS(status.FS), ".gohtml") app := fiber.New(fiber.Config{ + ViewsLayout: "views/index", AppName: "RoadSign", ServerHeader: "RoadSign", DisableStartupMessage: true, EnableIPValidation: true, + Views: views, + ErrorHandler: status.StatusPageHandler, JSONDecoder: jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal, JSONEncoder: jsoniter.ConfigCompatibleWithStandardLibrary.Marshal, Prefork: viper.GetBool("performance.prefork"), @@ -50,10 +57,13 @@ func InitServer() *fiber.App { app.Use(limiter.New(limiter.Config{ Max: viper.GetInt("hypertext.limitation.max_qps"), Expiration: 1 * time.Second, + LimitReached: func(c *fiber.Ctx) error { + return fiber.ErrTooManyRequests + }, })) } - UseProxies(app) + app.All("/*", ProxiesHandler) return app } diff --git a/pkg/hypertext/status/embed.go b/pkg/hypertext/status/embed.go new file mode 100644 index 0000000..14c2898 --- /dev/null +++ b/pkg/hypertext/status/embed.go @@ -0,0 +1,6 @@ +package status + +import "embed" + +//go:embed all:views +var FS embed.FS diff --git a/pkg/hypertext/status/serve.go b/pkg/hypertext/status/serve.go new file mode 100644 index 0000000..7118ef4 --- /dev/null +++ b/pkg/hypertext/status/serve.go @@ -0,0 +1,56 @@ +package status + +import ( + roadsign "code.smartsheep.studio/goatworks/roadsign/pkg" + "errors" + "fmt" + "github.com/gofiber/fiber/v2" + "github.com/spf13/viper" +) + +type ErrorPayload struct { + Title string `json:"title"` + Message string `json:"message"` + Version string `json:"version"` +} + +func StatusPageHandler(c *fiber.Ctx, err error) error { + var reqErr *fiber.Error + var status = fiber.StatusInternalServerError + if errors.As(err, &reqErr) { + status = reqErr.Code + } + + c.Status(status) + + payload := ErrorPayload{ + Version: roadsign.AppVersion, + } + + switch status { + case fiber.StatusNotFound: + payload.Title = "Not Found" + payload.Message = fmt.Sprintf("no resource for \"%s\"", c.OriginalURL()) + return c.Render("views/not-found", payload) + case fiber.StatusTooManyRequests: + payload.Title = "Request Too Fast" + payload.Message = fmt.Sprintf("you have sent over %d request(s) in a second", viper.GetInt("hypertext.limitation.max_qps")) + return c.Render("views/too-many-requests", payload) + case fiber.StatusRequestEntityTooLarge: + payload.Title = "Request Too Large" + payload.Message = fmt.Sprintf("you have sent a request over %d bytes", viper.GetInt("hypertext.limitation.max_body_size")) + return c.Render("views/request-too-large", payload) + case fiber.StatusBadGateway: + payload.Title = "Backend Down" + payload.Message = fmt.Sprintf("all destnations configured to handle your request are down: %s", err.Error()) + return c.Render("views/bad-gateway", payload) + case fiber.StatusGatewayTimeout: + payload.Title = "Backend Took Too Long To Response" + payload.Message = fmt.Sprintf("the destnation took too long to response your request: %s", err.Error()) + return c.Render("views/gateway-timeout", payload) + default: + payload.Title = "Oops" + payload.Message = err.Error() + return c.Render("views/fallback", payload) + } +} diff --git a/pkg/hypertext/status/views/bad-gateway.gohtml b/pkg/hypertext/status/views/bad-gateway.gohtml new file mode 100644 index 0000000..b12685b --- /dev/null +++ b/pkg/hypertext/status/views/bad-gateway.gohtml @@ -0,0 +1,6 @@ +

502

+

No one is standing...

+ +
+ {{ .Message }} +
\ No newline at end of file diff --git a/pkg/hypertext/status/views/fallback.gohtml b/pkg/hypertext/status/views/fallback.gohtml new file mode 100644 index 0000000..ab0ce6d --- /dev/null +++ b/pkg/hypertext/status/views/fallback.gohtml @@ -0,0 +1,6 @@ +

Oops

+

Something went wrong...

+ +
+ {{ .Message }} +
\ No newline at end of file diff --git a/pkg/hypertext/status/views/gateway-timeout.gohtml b/pkg/hypertext/status/views/gateway-timeout.gohtml new file mode 100644 index 0000000..cf5c4ef --- /dev/null +++ b/pkg/hypertext/status/views/gateway-timeout.gohtml @@ -0,0 +1,6 @@ +

504

+

Looks like the server in the back fell asleep

+ +
+ {{ .Message }} +
\ No newline at end of file diff --git a/pkg/hypertext/status/views/index.gohtml b/pkg/hypertext/status/views/index.gohtml new file mode 100644 index 0000000..ce4a402 --- /dev/null +++ b/pkg/hypertext/status/views/index.gohtml @@ -0,0 +1,83 @@ + + + + + + + + + + + + {{ .Title }} | RoadSign + + +
+
+ {{embed}} + + +
+
+ + \ No newline at end of file diff --git a/pkg/hypertext/status/views/not-found.gohtml b/pkg/hypertext/status/views/not-found.gohtml new file mode 100644 index 0000000..817e950 --- /dev/null +++ b/pkg/hypertext/status/views/not-found.gohtml @@ -0,0 +1,6 @@ +

404

+

Not Found

+ +
+ {{ .Message }} +
\ No newline at end of file diff --git a/pkg/hypertext/status/views/request-too-large.gohtml b/pkg/hypertext/status/views/request-too-large.gohtml new file mode 100644 index 0000000..f2c5e07 --- /dev/null +++ b/pkg/hypertext/status/views/request-too-large.gohtml @@ -0,0 +1,6 @@ +

413

+

Auh, you are too big.

+ +
+ {{ .Message }} +
\ No newline at end of file diff --git a/pkg/hypertext/status/views/too-many-requests.gohtml b/pkg/hypertext/status/views/too-many-requests.gohtml new file mode 100644 index 0000000..269d1d9 --- /dev/null +++ b/pkg/hypertext/status/views/too-many-requests.gohtml @@ -0,0 +1,6 @@ +

429

+

Stop it, you just to fast!

+ +
+ {{ .Message }} +
\ No newline at end of file diff --git a/pkg/sideload/view/tailwind.config.js b/pkg/sideload/view/tailwind.config.js index 4ddcaa7..2bddfc4 100644 --- a/pkg/sideload/view/tailwind.config.js +++ b/pkg/sideload/view/tailwind.config.js @@ -1,14 +1,13 @@ -/** @type {import('tailwindcss').Config} */ +/** @type {import("tailwindcss").Config} */ export default { content: ["./src/**/*.{js,jsx,ts,tsx}"], theme: { - extend: {}, + extend: {} }, daisyui: { themes: [ { light: { - ...require("daisyui/src/theming/themes")["light"], primary: "#4750a3", secondary: "#93c5fd", accent: "#0f766e", @@ -16,15 +15,22 @@ export default { success: "#15803d", warning: "#f97316", error: "#dc2626", + neutral: "#2B3440", + "secondary-content": "oklch(98.71% 0.0106 342.55)", + "neutral-content": "#D7DDE4", + "base-100": "oklch(100% 0 0)", + "base-200": "#F2F2F2", + "base-300": "#E5E6E6", + "base-content": "#1f2937", + "color-scheme": "light", "--rounded-box": "0", "--rounded-btn": "0", "--rounded-badge": "0", - "--tab-radius": "0", - }, + "--tab-radius": "0" + } }, { dark: { - ...require("daisyui/src/theming/themes")["dark"], primary: "#4750a3", secondary: "#93c5fd", accent: "#0f766e", @@ -32,13 +38,20 @@ export default { success: "#15803d", warning: "#f97316", error: "#dc2626", + neutral: "#2a323c", + "neutral-content": "#A6ADBB", + "base-100": "#1d232a", + "base-200": "#191e24", + "base-300": "#15191e", + "base-content": "#A6ADBB", + "color-scheme": "dark", "--rounded-box": "0", "--rounded-btn": "0", "--rounded-badge": "0", - "--tab-radius": "0", - }, - }, - ], + "--tab-radius": "0" + } + } + ] }, - plugins: [require("daisyui")], -}; + plugins: [require("daisyui")] +}