Files
HostKeeper/app/backend/internal/handler/dashboard.go
T
swanadiva a2412b7cd2 fix: host modal — single POST endpoint for create+edit, remove hx-put conflict
- hx-put="" (empty) was causing HTMX to send wrong requests
- Now use single hx-post="/hosts" with hidden hostId field
- Server checks hostId: empty → create, present → update
- UpdateHost reads ID from both URL param and form data
2026-07-07 15:56:54 +07:00

168 lines
3.8 KiB
Go

package handler
import (
"strconv"
"github.com/gofiber/fiber/v2"
"git.tukangketik.id/swanadiva/hostkeeper/v2/internal/model"
"git.tukangketik.id/swanadiva/hostkeeper/v2/internal/store"
)
var hostStore = store.NewHostStore()
var navItems = []fiber.Map{
{"ID": "hosts", "Label": "Hosts", "Icon": "server"},
{"ID": "terminal", "Label": "Terminal", "Icon": "terminal"},
{"ID": "sftp", "Label": "SFTP", "Icon": "folder-sync"},
{"ID": "snippets", "Label": "Snippets", "Icon": "code2"},
{"ID": "keychain", "Label": "Keychain", "Icon": "key-round"},
{"ID": "settings", "Label": "Settings", "Icon": "settings2"},
}
func Dashboard(c *fiber.Ctx) error {
hosts := hostStore.All()
return c.Render("index", fiber.Map{
"View": "hosts",
"Title": "Hosts",
"Hosts": hosts,
"Active": countByStatus(hosts, "active"),
"Offline": countByStatus(hosts, "offline"),
"NavItems": navItems,
})
}
func DashboardList(c *fiber.Ctx) error {
q := c.Query("q")
filter := c.Query("filter", "all")
hosts := hostStore.Search(q, filter)
return c.Render("host_list", fiber.Map{
"Hosts": hosts,
"Active": countByStatus(hosts, "active"),
"Offline": countByStatus(hosts, "offline"),
})
}
func parsePort(s string, def int) int {
i, err := strconv.Atoi(s)
if err != nil || i < 1 || i > 65535 {
return def
}
return i
}
func CreateHost(c *fiber.Ctx) error {
hostID := c.FormValue("hostId")
if hostID != "" {
return UpdateHost(c)
}
name := c.FormValue("name")
ip := c.FormValue("ip")
if name == "" || ip == "" {
return c.Status(400).SendString("name and ip required")
}
h := model.Host{
Name: name,
IP: ip,
OS: c.FormValue("os"),
Provider: c.FormValue("provider"),
Type: c.FormValue("type"),
Port: parsePort(c.FormValue("port"), 22),
Username: c.FormValue("username"),
AuthType: c.FormValue("authType"),
Password: c.FormValue("password"),
Hostname: c.FormValue("hostname"),
Notes: c.FormValue("notes"),
}
if h.Username == "" {
h.Username = "root"
}
if h.AuthType == "" {
h.AuthType = "key"
}
hostStore.AddFull(h)
hosts := hostStore.All()
return c.Render("host_list", fiber.Map{
"Hosts": hosts,
"Active": countByStatus(hosts, "active"),
"Offline": countByStatus(hosts, "offline"),
})
}
func UpdateHost(c *fiber.Ctx) error {
id := c.Params("id")
if id == "" {
id = c.FormValue("hostId")
}
existing, found := hostStore.Get(id)
if !found {
return c.Status(404).SendString("host not found")
}
if v := c.FormValue("name"); v != "" {
existing.Name = v
}
if v := c.FormValue("ip"); v != "" {
existing.IP = v
}
if v := c.FormValue("os"); v != "" {
existing.OS = v
}
if v := c.FormValue("provider"); v != "" {
existing.Provider = v
}
if v := c.FormValue("type"); v != "" {
existing.Type = v
}
if v := c.FormValue("port"); v != "" {
existing.Port = parsePort(v, 22)
}
if v := c.FormValue("username"); v != "" {
existing.Username = v
}
if v := c.FormValue("authType"); v != "" {
existing.AuthType = v
}
if v := c.FormValue("password"); v != "" {
existing.Password = v
}
if v := c.FormValue("hostname"); v != "" {
existing.Hostname = v
}
if v := c.FormValue("notes"); v != "" {
existing.Notes = v
}
hostStore.Update(existing)
hosts := hostStore.All()
return c.Render("host_list", fiber.Map{
"Hosts": hosts,
"Active": countByStatus(hosts, "active"),
"Offline": countByStatus(hosts, "offline"),
})
}
func DeleteHost(c *fiber.Ctx) error {
id := c.Params("id")
hostStore.Delete(id)
hosts := hostStore.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
}