Sprint 6: JSON file persistence + real SSH terminal + V1 crypto/knownhosts integration
- go.mod replace directive for git.tukangketik.id/swanadiva/hostkeeper → ../../v1 - store/ package: JSON file persistence for hosts, snippets, keys, settings (~/Library/Application Support/hostkeeper/v2/) - model SSH fields: Hostname, Port, Username, AuthType, Password, KeyID - sshconn/ package: real SSH client via golang.org/x/crypto/ssh - Terminal WS: real SSH connection with fallback to mock shell - Dynamic host list in terminal (server-rendered JSON script tag) - All mock data defaults removed from model packages - Settings persist via store/settings.go
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
package store
|
||||
|
||||
import "git.tukangketik.id/swanadiva/hostkeeper/v2/internal/model"
|
||||
|
||||
type HostStore struct {
|
||||
hosts []model.Host
|
||||
}
|
||||
|
||||
func NewHostStore() *HostStore {
|
||||
s := &HostStore{}
|
||||
mu.RLock()
|
||||
readFile("hosts.json", &s.hosts)
|
||||
mu.RUnlock()
|
||||
if s.hosts == nil {
|
||||
s.hosts = []model.Host{}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *HostStore) All() []model.Host {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
return s.hosts
|
||||
}
|
||||
|
||||
func (s *HostStore) Search(q, filter string) []model.Host {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
var result []model.Host
|
||||
for _, h := range s.hosts {
|
||||
if filter != "" && filter != "all" && h.Status != filter {
|
||||
continue
|
||||
}
|
||||
if q == "" || contains(h.Name, q) || contains(h.IP, q) || contains(h.OS, q) ||
|
||||
contains(h.Hostname, q) || contains(h.Username, q) {
|
||||
result = append(result, h)
|
||||
}
|
||||
}
|
||||
if result == nil {
|
||||
return []model.Host{}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *HostStore) Get(id string) (model.Host, bool) {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
for _, h := range s.hosts {
|
||||
if h.ID == id {
|
||||
return h, true
|
||||
}
|
||||
}
|
||||
return model.Host{}, false
|
||||
}
|
||||
|
||||
func (s *HostStore) Add(name, ip, os, provider, hostType string) model.Host {
|
||||
return s.AddFull(model.Host{
|
||||
Name: name,
|
||||
IP: ip,
|
||||
OS: os,
|
||||
Provider: provider,
|
||||
Type: hostType,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *HostStore) AddFull(h model.Host) model.Host {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if h.ID == "" {
|
||||
h.ID = randID()
|
||||
}
|
||||
if h.Status == "" {
|
||||
h.Status = "active"
|
||||
}
|
||||
s.hosts = append(s.hosts, h)
|
||||
writeFile("hosts.json", &s.hosts)
|
||||
return h
|
||||
}
|
||||
|
||||
func (s *HostStore) Update(h model.Host) bool {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
for i, existing := range s.hosts {
|
||||
if existing.ID == h.ID {
|
||||
s.hosts[i] = h
|
||||
writeFile("hosts.json", &s.hosts)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *HostStore) Delete(id string) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
for i, h := range s.hosts {
|
||||
if h.ID == id {
|
||||
s.hosts = append(s.hosts[:i], s.hosts[i+1:]...)
|
||||
writeFile("hosts.json", &s.hosts)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func contains(s, q string) bool {
|
||||
if len(s) < len(q) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i <= len(s)-len(q); i++ {
|
||||
match := true
|
||||
for j := 0; j < len(q); j++ {
|
||||
sc, qc := s[i+j], q[j]
|
||||
if sc >= 'A' && sc <= 'Z' {
|
||||
sc += 32
|
||||
}
|
||||
if qc >= 'A' && qc <= 'Z' {
|
||||
qc += 32
|
||||
}
|
||||
if sc != qc {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if match {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user