diff --git a/app/backend/internal/handler/dashboard.go b/app/backend/internal/handler/dashboard.go index 87105a0..754d45a 100644 --- a/app/backend/internal/handler/dashboard.go +++ b/app/backend/internal/handler/dashboard.go @@ -1,10 +1,20 @@ package handler -import "github.com/gofiber/fiber/v2" +import ( + "github.com/gofiber/fiber/v2" + "git.tukangketik.id/swanadiva/hostkeeper/v2/internal/model" +) + +var store = model.NewHostStore() func Dashboard(c *fiber.Ctx) error { + hosts := store.All() return c.Render("index", fiber.Map{ - "View": "hosts", + "View": "hosts", + "Title": "Hosts", + "Hosts": hosts, + "Active": countByStatus(hosts, "active"), + "Offline": countByStatus(hosts, "offline"), "NavItems": []fiber.Map{ {"ID": "hosts", "Label": "Hosts", "Icon": "server"}, {"ID": "terminal", "Label": "Terminal", "Icon": "terminal"}, @@ -13,6 +23,46 @@ func Dashboard(c *fiber.Ctx) error { {"ID": "keychain", "Label": "Keychain", "Icon": "key-round"}, {"ID": "settings", "Label": "Settings", "Icon": "settings2"}, }, - "Title": "Hosts", }) } + +func DashboardList(c *fiber.Ctx) error { + q := c.Query("q") + filter := c.Query("filter", "all") + hosts := store.Search(q, filter) + return c.Render("host_list", fiber.Map{ + "Hosts": hosts, + "Active": countByStatus(hosts, "active"), + "Offline": countByStatus(hosts, "offline"), + }) +} + +func CreateHost(c *fiber.Ctx) error { + name := c.FormValue("name") + ip := c.FormValue("ip") + os := c.FormValue("os") + provider := c.FormValue("provider") + hostType := c.FormValue("type") + + if name == "" || ip == "" { + return c.Status(400).SendString("name and ip required") + } + + store.Add(name, ip, os, provider, hostType) + hosts := store.All() + return c.Render("host_list", fiber.Map{ + "Hosts": hosts, + "Active": countByStatus(hosts, "active"), + "Offline": countByStatus(hosts, "offline"), + }) +} + +func countByStatus(hosts []model.Host, status string) int { + n := 0 + for _, h := range hosts { + if h.Status == status { + n++ + } + } + return n +} diff --git a/app/backend/internal/model/host.go b/app/backend/internal/model/host.go new file mode 100644 index 0000000..dc2842b --- /dev/null +++ b/app/backend/internal/model/host.go @@ -0,0 +1,87 @@ +package model + +import ( + "math/rand" + "strings" + "time" +) + +type Host struct { + ID string `json:"id"` + Name string `json:"name"` + IP string `json:"ip"` + OS string `json:"os"` + Provider string `json:"provider"` + Status string `json:"status"` // active | offline + LastSeen string `json:"lastSeen"` + Type string `json:"type"` // api | db | edge | web | desktop | server +} + +type HostStore struct { + Hosts []Host +} + +func NewHostStore() *HostStore { + return &HostStore{ + Hosts: defaultHosts(), + } +} + +func (s *HostStore) All() []Host { + return s.Hosts +} + +func (s *HostStore) Search(q, filter string) []Host { + var result []Host + for _, h := range s.Hosts { + if filter != "" && filter != "all" && h.Status != filter { + continue + } + if q == "" || strings.Contains(strings.ToLower(h.Name), strings.ToLower(q)) || + strings.Contains(strings.ToLower(h.IP), strings.ToLower(q)) || + strings.Contains(strings.ToLower(h.OS), strings.ToLower(q)) { + result = append(result, h) + } + } + if result == nil { + return []Host{} + } + return result +} + +func (s *HostStore) Add(name, ip, os, provider, hostType string) Host { + h := Host{ + ID: randString(8), + Name: name, + IP: ip, + OS: os, + Provider: provider, + Status: "active", + LastSeen: time.Now().Format("Jan 2, 2006"), + Type: hostType, + } + s.Hosts = append(s.Hosts, h) + return h +} + +func randString(n int) string { + const letters = "abcdefghijklmnopqrstuvwxyz0123456789" + b := make([]byte, n) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] + } + return string(b) +} + +func defaultHosts() []Host { + return []Host{ + {ID: "h1", Name: "api-prod-01", IP: "10.0.1.15", OS: "Ubuntu 22.04", Provider: "AWS US-East", Status: "active", LastSeen: "Online", Type: "api"}, + {ID: "h2", Name: "db-primary-01", IP: "10.0.2.5", OS: "Debian 12", Provider: "AWS US-East", Status: "active", LastSeen: "Online", Type: "db"}, + {ID: "h3", Name: "web-edge-02", IP: "192.168.1.20", OS: "Alpine 3.19", Provider: "DigitalOcean", Status: "offline", LastSeen: "2 hours ago", Type: "edge"}, + {ID: "h4", Name: "staging-api-01", IP: "10.0.3.10", OS: "Ubuntu 22.04", Provider: "AWS EU-West", Status: "active", LastSeen: "5 min ago", Type: "api"}, + {ID: "h5", Name: "dev-db-02", IP: "192.168.1.45", OS: "Fedora 39", Provider: "Hetzner", Status: "offline", LastSeen: "1 day ago", Type: "db"}, + {ID: "h6", Name: "monitor-01", IP: "10.0.0.5", OS: "Ubuntu 24.04", Provider: "AWS US-East", Status: "active", LastSeen: "Just now", Type: "server"}, + {ID: "h7", Name: "worker-pool-01", IP: "10.0.4.50", OS: "Debian 12", Provider: "GCP US-Central", Status: "active", LastSeen: "1 min ago", Type: "server"}, + {ID: "h8", Name: "bastion-host", IP: "54.85.12.7", OS: "Ubuntu 22.04", Provider: "AWS US-East", Status: "offline", LastSeen: "3 days ago", Type: "server"}, + } +} diff --git a/app/backend/main.go b/app/backend/main.go index 98d1673..616b53e 100644 --- a/app/backend/main.go +++ b/app/backend/main.go @@ -16,7 +16,7 @@ func main() { engine := html.New("./views", ".html") engine.Reload(true) engine.AddFunc("svg", func(name string) string { - return "" // TODO: inline SVG icons + return "" }) app := fiber.New(fiber.Config{ @@ -29,7 +29,8 @@ func main() { app.Static("/static", "./static") app.Get("/", handler.Dashboard) - app.Get("/hosts", handler.Dashboard) + app.Get("/hosts", handler.DashboardList) + app.Post("/hosts", handler.CreateHost) app.Get("/api/health", handler.Health) log.Fatal(app.Listen(":1947")) diff --git a/app/backend/views/host_list.html b/app/backend/views/host_list.html new file mode 100644 index 0000000..8804891 --- /dev/null +++ b/app/backend/views/host_list.html @@ -0,0 +1,53 @@ +{{define "host_list"}} +
{{.Name}}
+{{.IP}}
+No hosts found
+Try a different search or add a new host
+