Keyboard Shortcuts
+About Hostkeeper V2
+Hostkeeper V2 is a free, open-source Termius alternative for managing SSH/SFTP connections.
+Built with GoFiber v2 + HTMX + Alpine.js + TailwindCSS v4.
+Version: 2.0.0-dev
+License: MIT
+diff --git a/AGENTS.md b/AGENTS.md index 6886f7d..9bdf4ed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ > **Purpose**: Memungkinkan AI agent berikutnya melanjutkan development tanpa mengulang atau merusak. > **Last Updated**: 2026-07-07 -> **Current Phase**: Sprint 1 + 2 selesai, Sprint 3 berikutnya +> **Current Phase**: Sprint 1 + 2 + 3 selesai, Sprint 4 berikutnya --- @@ -115,7 +115,7 @@ Alasan: ### ❌ Belum Dimulai / In Progress - **Sprint 2 (DONE)**: Snippets + Keychain -- **Sprint 3 (NEXT)**: Settings + Brief +- **Sprint 3 (DONE)**: Settings + Brief - Sprint 4: SFTP Dual-Pane - Sprint 5: Terminal + xterm.js - Sprint 6: Storage Integration diff --git a/app/backend/internal/handler/brief.go b/app/backend/internal/handler/brief.go new file mode 100644 index 0000000..5aec9bc --- /dev/null +++ b/app/backend/internal/handler/brief.go @@ -0,0 +1,11 @@ +package handler + +import "github.com/gofiber/fiber/v2" + +func Brief(c *fiber.Ctx) error { + return c.Render("brief", fiber.Map{ + "View": "brief", + "Title": "Quick Brief", + "NavItems": navItems, + }) +} diff --git a/app/backend/internal/handler/settings.go b/app/backend/internal/handler/settings.go new file mode 100644 index 0000000..6ea6415 --- /dev/null +++ b/app/backend/internal/handler/settings.go @@ -0,0 +1,44 @@ +package handler + +import ( + "github.com/gofiber/fiber/v2" + "git.tukangketik.id/swanadiva/hostkeeper/v2/internal/model" +) + +var deviceStore = model.NewDeviceStore() +var configStore = model.NewConfigStore() + +func Settings(c *fiber.Ctx) error { + return c.Render("settings", fiber.Map{ + "View": "settings", + "Title": "Settings", + "Config": configStore.Get(), + "Devices": deviceStore.All(), + "NavItems": navItems, + }) +} + +func UpdateConfig(c *fiber.Ctx) error { + cfg := configStore.Get() + if v := c.FormValue("theme"); v != "" { cfg.Theme = v } + if v := c.FormValue("fontSize"); v != "" { cfg.FontSize = parseInt(v, 14) } + if v := c.FormValue("scrollback"); v != "" { cfg.Scrollback = parseInt(v, 5000) } + if v := c.FormValue("autoLock"); v != "" { cfg.AutoLock = parseInt(v, 5) } + cfg.Keepalive = c.FormValue("keepalive") == "on" + cfg.CopyOnSelect = c.FormValue("copyOnSelect") == "on" + cfg.BellEnabled = c.FormValue("bellEnabled") == "on" + cfg.AutoReconnect = c.FormValue("autoReconnect") == "on" + cfg.BlinkCursor = c.FormValue("blinkCursor") == "on" + configStore.Update(cfg) + return c.Redirect("/settings", 303) +} + +func parseInt(s string, def int) int { + i := 0 + for _, c := range s { + if c < '0' || c > '9' { return def } + i = i*10 + int(c-'0') + } + if i == 0 { return def } + return i +} diff --git a/app/backend/internal/model/config.go b/app/backend/internal/model/config.go new file mode 100644 index 0000000..25ce594 --- /dev/null +++ b/app/backend/internal/model/config.go @@ -0,0 +1,39 @@ +package model + +type AppConfig struct { + Theme string `json:"theme"` // light | dark + FontSize int `json:"fontSize"` // 12-18 + Scrollback int `json:"scrollback"` // 1000-10000 + AutoLock int `json:"autoLock"` // minutes + Keepalive bool `json:"keepalive"` + CopyOnSelect bool `json:"copyOnSelect"` + BellEnabled bool `json:"bellEnabled"` + AutoReconnect bool `json:"autoReconnect"` + BlinkCursor bool `json:"blinkCursor"` +} + +type ConfigStore struct { + Config AppConfig +} + +func NewConfigStore() *ConfigStore { + return &ConfigStore{Config: defaultConfig()} +} + +func (s *ConfigStore) Get() AppConfig { return s.Config } + +func (s *ConfigStore) Update(cfg AppConfig) { s.Config = cfg } + +func defaultConfig() AppConfig { + return AppConfig{ + Theme: "light", + FontSize: 14, + Scrollback: 5000, + AutoLock: 5, + Keepalive: true, + CopyOnSelect: true, + BellEnabled: false, + AutoReconnect: true, + BlinkCursor: false, + } +} diff --git a/app/backend/internal/model/device.go b/app/backend/internal/model/device.go new file mode 100644 index 0000000..d195a58 --- /dev/null +++ b/app/backend/internal/model/device.go @@ -0,0 +1,28 @@ +package model + +type Device struct { + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` // desktop | mobile | tablet + OS string `json:"os"` + LastSeen string `json:"lastSeen"` + Current bool `json:"current"` +} + +type DeviceStore struct { + Devices []Device +} + +func NewDeviceStore() *DeviceStore { + return &DeviceStore{Devices: defaultDevices()} +} + +func (s *DeviceStore) All() []Device { return s.Devices } + +func defaultDevices() []Device { + return []Device{ + {ID: "d1", Name: "MacBook Pro M4", Type: "desktop", OS: "macOS 15 Sequoia", LastSeen: "Just now", Current: true}, + {ID: "d2", Name: "iPhone 17 Pro", Type: "mobile", OS: "iOS 20", LastSeen: "2 hours ago", Current: false}, + {ID: "d3", Name: "iPad Air M3", Type: "tablet", OS: "iPadOS 20", LastSeen: "Yesterday", Current: false}, + } +} diff --git a/app/backend/main.go b/app/backend/main.go index f1b6a63..00c40d9 100644 --- a/app/backend/main.go +++ b/app/backend/main.go @@ -40,6 +40,9 @@ func main() { app.Get("/keys/grid", handler.KeyGrid) app.Post("/keys", handler.CreateKey) app.Delete("/keys/:id", handler.DeleteKey) + app.Get("/settings", handler.Settings) + app.Post("/settings", handler.UpdateConfig) + app.Get("/brief", handler.Brief) app.Get("/api/health", handler.Health) log.Fatal(app.Listen(":1947")) diff --git a/app/backend/static/js/toggles.js b/app/backend/static/js/toggles.js new file mode 100644 index 0000000..d6be451 --- /dev/null +++ b/app/backend/static/js/toggles.js @@ -0,0 +1,13 @@ +document.addEventListener('alpine:init', () => { + Alpine.data('switchToggle', (initial) => ({ + on: initial, + toggle() { + this.on = !this.on; + this.$el.classList.toggle('active', this.on); + this.$nextTick(() => { + const hidden = this.$el.nextElementSibling; + if (hidden) hidden.value = this.on ? 'on' : ''; + }); + } + })); +}); diff --git a/app/backend/views/brief.html b/app/backend/views/brief.html new file mode 100644 index 0000000..f234a46 --- /dev/null +++ b/app/backend/views/brief.html @@ -0,0 +1,65 @@ + + +
+ + +Hostkeeper V2 is a free, open-source Termius alternative for managing SSH/SFTP connections.
+Built with GoFiber v2 + HTMX + Alpine.js + TailwindCSS v4.
+Version: 2.0.0-dev
+License: MIT
+User
+Local administrator
+{{.Name}}
+ {{if .Current}}Current{{end}} +{{.OS}} · {{.LastSeen}}
+This will permanently delete all your data, including hosts, keys, snippets, and settings. This action cannot be undone.
+Are you sure?
+ + +