feat: Sprint 1 — host model, mock store, dashboard CRUD, HTMX partials
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user