Skip to content

Commit

Permalink
client dashboard initial
Browse files Browse the repository at this point in the history
  • Loading branch information
amalshaji committed Mar 20, 2024
1 parent d2189bf commit 9a4645c
Show file tree
Hide file tree
Showing 52 changed files with 2,970 additions and 38 deletions.
10 changes: 9 additions & 1 deletion tunnel/cmd/portr/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ import (

"github.com/amalshaji/portr/internal/client/client"
"github.com/amalshaji/portr/internal/client/config"
"github.com/amalshaji/portr/internal/client/dashboard"
"github.com/amalshaji/portr/internal/client/db"
"github.com/urfave/cli/v2"
)

func startTunnels(c *cli.Context, tunnelFromCli *config.Tunnel) error {
_c := client.NewClient(c.String("config"))
db := db.New()

_c := client.NewClient(c.String("config"), db)

var err error

Expand All @@ -27,11 +31,15 @@ func startTunnels(c *cli.Context, tunnelFromCli *config.Tunnel) error {
return err
}

dash := dashboard.New(db, _c.GetConfig())
dash.Start()

signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM)
<-signalCh

_c.Shutdown(c.Context)
dash.Shutdown()
return nil
}

Expand Down
1 change: 1 addition & 0 deletions tunnel/configs/client.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ tunnel_url: localhost:8001
secret_key: Zq5Zk5gP5qHGg28nixeqRIOMM9Zo3EUgWnFAEnJnwC
use_localhost: true
debug: true
use_vite: true
tunnels:
- name: portr
subdomain: portr
Expand Down
12 changes: 6 additions & 6 deletions tunnel/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ module github.com/amalshaji/portr
go 1.22.0

require (
github.com/briandowns/spinner v1.23.0
github.com/gliderlabs/ssh v0.3.6
github.com/go-resty/resty/v2 v2.11.0
github.com/gofiber/fiber/v2 v2.52.1
github.com/gofiber/fiber/v2 v2.52.2
github.com/gofiber/template/django/v3 v3.1.10
github.com/gookit/validate v1.5.2
github.com/labstack/gommon v0.4.2
github.com/matoous/go-nanoid/v2 v2.0.0
Expand All @@ -26,8 +26,10 @@ require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/flosch/pongo2/v6 v6.0.0 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/gofiber/template v1.8.3 // indirect
github.com/gofiber/utils v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gookit/filter v1.2.1 // indirect
github.com/gookit/goutil v0.6.15 // indirect
Expand All @@ -38,7 +40,6 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/kr/text v0.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
Expand All @@ -51,8 +52,7 @@ require (
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/term v0.17.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
gorm.io/driver/mysql v1.5.4 // indirect
)
21 changes: 12 additions & 9 deletions tunnel/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,28 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A=
github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/flosch/pongo2/v6 v6.0.0 h1:lsGru8IAzHgIAw6H2m4PCyleO58I40ow6apih0WprMU=
github.com/flosch/pongo2/v6 v6.0.0/go.mod h1:CuDpFm47R0uGGE7z13/tTlt1Y6zdxvr2RLT5LJhsHEU=
github.com/gliderlabs/ssh v0.3.6 h1:ZzjlDa05TcFRICb3anf/dSPN3ewz1Zx6CMLPWgkm3b8=
github.com/gliderlabs/ssh v0.3.6/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/gofiber/fiber/v2 v2.52.1 h1:1RoU2NS+b98o1L77sdl5mboGPiW+0Ypsi5oLmcYlgHI=
github.com/gofiber/fiber/v2 v2.52.1/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/gofiber/fiber/v2 v2.52.2 h1:b0rYH6b06Df+4NyrbdptQL8ifuxw/Tf2DgfkZkDaxEo=
github.com/gofiber/fiber/v2 v2.52.2/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc=
github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
github.com/gofiber/template/django/v3 v3.1.10 h1:U5z65ihGqd774tO4mq3+xVva4o57zTnMUOu8t4BVAfM=
github.com/gofiber/template/django/v3 v3.1.10/go.mod h1:MRY/CU3zOJr0AXLY7z3z9fkjU+DNCf6TQ+vpD7gyIT0=
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/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
Expand Down Expand Up @@ -50,7 +54,6 @@ github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLA
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
Expand Down Expand Up @@ -132,8 +135,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
Expand Down
12 changes: 8 additions & 4 deletions tunnel/internal/client/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import (
type Client struct {
config *config.Config
sshcs []*ssh.SshClient
db *db.Db
}

func NewClient(configFile string) *Client {
func NewClient(configFile string, db *db.Db) *Client {
config, err := config.Load(configFile)

if err != nil {
Expand All @@ -26,14 +27,17 @@ func NewClient(configFile string) *Client {
return &Client{
config: &config,
sshcs: make([]*ssh.SshClient, 0),
db: db,
}
}

func (c *Client) GetConfig() *config.Config {
return c.config
}

func (c *Client) Start(ctx context.Context, services ...string) error {
var clientConfigs []config.ClientConfig

db := db.New()

for _, tunnel := range c.config.Tunnels {
if len(services) > 0 && !slices.Contains(services, tunnel.Name) {
continue
Expand All @@ -54,7 +58,7 @@ func (c *Client) Start(ctx context.Context, services ...string) error {
}

for _, clientConfig := range clientConfigs {
sshc := ssh.New(clientConfig, db)
sshc := ssh.New(clientConfig, c.db)
c.Add(sshc)
go sshc.Start(ctx)
}
Expand Down
1 change: 1 addition & 0 deletions tunnel/internal/client/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type Config struct {
Tunnels []Tunnel `yaml:"tunnels"`
UseLocalHost bool `yaml:"use_localhost"`
Debug bool `yaml:"debug"`
UseVite bool `yaml:"use_vite"`
}

func (c *Config) SetDefaults() {
Expand Down
105 changes: 105 additions & 0 deletions tunnel/internal/client/dashboard/dashboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package dashboard

import (
"context"
"errors"
"fmt"
"log/slog"
"net/http"
"os"
"time"

"github.com/amalshaji/portr/internal/client/config"
"github.com/amalshaji/portr/internal/client/dashboard/handler"
"github.com/amalshaji/portr/internal/client/dashboard/service"
"github.com/amalshaji/portr/internal/client/dashboard/ui/dist"
"github.com/amalshaji/portr/internal/client/db"
"github.com/amalshaji/portr/internal/client/vite"
"github.com/amalshaji/portr/internal/constants"
"github.com/amalshaji/portr/internal/utils"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/filesystem"
"github.com/gofiber/fiber/v2/middleware/recover"
"github.com/gofiber/template/django/v3"
)

type Dashboard struct {
app *fiber.App
config *config.Config
db *db.Db
logger *slog.Logger
port int
}

func New(db *db.Db, config *config.Config) *Dashboard {
engine := django.New("./internal/client/dashboard/templates", ".html")
engine.SetAutoEscape(false)

app := fiber.New(fiber.Config{
DisableStartupMessage: true,
Views: engine,
})

app.Use(recover.New())

if config.UseVite {
app.Static("/", "./internal/server/admin/web/dist")
app.Static("/static", "./internal/client/dashboard/static")
} else {
app.Use("/static", filesystem.New(filesystem.Config{
Root: http.FS(dist.EmbededDirStatic),
PathPrefix: "static",
}))
}

rootTemplateView := func(c *fiber.Ctx) error {
return c.Render("index", fiber.Map{
"UseVite": config.UseVite,
"ViteTags": vite.GenerateViteTags(constants.ClientUiViteDistDir),
})
}

service := service.New(db, config)
handler := handler.New(config, service)

app.Get("/", rootTemplateView)
app.Get("/:id", rootTemplateView)

tunnelsGroup := app.Group("/api/tunnels")
handler.RegisterTunnelRoutes(tunnelsGroup)

return &Dashboard{
app: app,
config: config,
db: db,
logger: utils.GetLogger(),
port: 7777,
}
}

func (d *Dashboard) Start() {
fmt.Println("Dashboard running on http://localhost:7777")

if err := d.app.Listen(":" + fmt.Sprint(d.port)); err != nil && !errors.Is(err, http.ErrServerClosed) {
if d.config.Debug {
d.logger.Error("failed to start dashboard server", "error", err)
}
os.Exit(1)
}
}

func (d *Dashboard) Shutdown() {
if d.config.Debug {
d.logger.Info("stopping dashboard server")
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)

defer func() { cancel() }()

if err := d.app.ShutdownWithContext(ctx); err != nil {
if d.config.Debug {
d.logger.Error("failed to stop dashboard server", "error", err)
}
os.Exit(1)
}
}
30 changes: 30 additions & 0 deletions tunnel/internal/client/dashboard/handler/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package handler

import (
"log/slog"

"github.com/amalshaji/portr/internal/client/config"
"github.com/amalshaji/portr/internal/client/dashboard/service"
"github.com/amalshaji/portr/internal/utils"
"github.com/gofiber/fiber/v2"
)

type Handler struct {
config *config.Config
service *service.Service
log *slog.Logger
}

func New(config *config.Config, service *service.Service) *Handler {
return &Handler{
config: config,
service: service,
log: utils.GetLogger(),
}
}

func (h *Handler) RegisterTunnelRoutes(group fiber.Router) {
group.Get("/", h.GetTunnels)
group.Get("/render/:id", h.RenderResponse)
group.Get("/:subdomain/:port", h.GetRequests)
}
58 changes: 58 additions & 0 deletions tunnel/internal/client/dashboard/handler/tunnels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package handler

import (
"encoding/json"
"fmt"

"github.com/gofiber/fiber/v2"
)

func (h *Handler) GetTunnels(c *fiber.Ctx) error {
tunnels, err := h.service.GetTunnels()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": "failed to get tunnels"})
}

return c.JSON(fiber.Map{"tunnels": tunnels})
}

func (h *Handler) GetRequests(c *fiber.Ctx) error {
subdomain := c.Params("subdomain")
port := c.Params("port")
tunnels, err := h.service.GetRequests(subdomain, port)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": "failed to get requests"})
}

return c.JSON(fiber.Map{"requests": tunnels})
}

func (h *Handler) RenderResponse(c *fiber.Ctx) error {
requestId := c.Params("id")
request, err := h.service.GetRequestById(requestId)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": "failed to get requests"})
}

headersMap := make(map[string][]string)
err = json.Unmarshal([]byte(request.ResponseHeaders), &headersMap)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": "failed to parse response headers"})
}

contentType := headersMap["Content-Type"]
if len(contentType) == 0 {
contentType = []string{"text/html; charset=utf-8"}
}

contentLength := headersMap["Content-Length"]
if len(contentLength) == 0 {
contentLength = []string{fmt.Sprintf("%d", len(request.ResponseBody))}
}

c.Response().Header.Set("Content-Type", contentType[0])
c.Response().Header.Set("Content-Length", contentLength[0])

c.Response().BodyWriter().Write(request.ResponseBody)
return nil
}
Loading

0 comments on commit 9a4645c

Please sign in to comment.