fd7361aa14
- 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
130 lines
2.3 KiB
Go
130 lines
2.3 KiB
Go
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
|
|
}
|