mirror of
https://github.com/Gu1llaum-3/sshm.git
synced 2026-01-27 03:04:21 +01:00
feat: add SSH connection history with TUI sorting by last login
This commit is contained in:
208
internal/history/history.go
Normal file
208
internal/history/history.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package history
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"sshm/internal/config"
|
||||
)
|
||||
|
||||
// ConnectionHistory represents the history of SSH connections
|
||||
type ConnectionHistory struct {
|
||||
Connections map[string]ConnectionInfo `json:"connections"`
|
||||
}
|
||||
|
||||
// ConnectionInfo stores information about a specific connection
|
||||
type ConnectionInfo struct {
|
||||
HostName string `json:"host_name"`
|
||||
LastConnect time.Time `json:"last_connect"`
|
||||
ConnectCount int `json:"connect_count"`
|
||||
}
|
||||
|
||||
// HistoryManager manages the connection history
|
||||
type HistoryManager struct {
|
||||
historyPath string
|
||||
history *ConnectionHistory
|
||||
}
|
||||
|
||||
// NewHistoryManager creates a new history manager
|
||||
func NewHistoryManager() (*HistoryManager, error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
historyPath := filepath.Join(homeDir, ".ssh", "sshm_history.json")
|
||||
|
||||
hm := &HistoryManager{
|
||||
historyPath: historyPath,
|
||||
history: &ConnectionHistory{Connections: make(map[string]ConnectionInfo)},
|
||||
}
|
||||
|
||||
// Load existing history if it exists
|
||||
err = hm.loadHistory()
|
||||
if err != nil {
|
||||
// If file doesn't exist, that's okay - we'll create it when needed
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return hm, nil
|
||||
}
|
||||
|
||||
// loadHistory loads the connection history from the JSON file
|
||||
func (hm *HistoryManager) loadHistory() error {
|
||||
data, err := os.ReadFile(hm.historyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal(data, hm.history)
|
||||
}
|
||||
|
||||
// saveHistory saves the connection history to the JSON file
|
||||
func (hm *HistoryManager) saveHistory() error {
|
||||
// Ensure the directory exists
|
||||
dir := filepath.Dir(hm.historyPath)
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(hm.history, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(hm.historyPath, data, 0600)
|
||||
}
|
||||
|
||||
// RecordConnection records a new connection for the specified host
|
||||
func (hm *HistoryManager) RecordConnection(hostName string) error {
|
||||
now := time.Now()
|
||||
|
||||
if conn, exists := hm.history.Connections[hostName]; exists {
|
||||
// Update existing connection
|
||||
conn.LastConnect = now
|
||||
conn.ConnectCount++
|
||||
hm.history.Connections[hostName] = conn
|
||||
} else {
|
||||
// Create new connection record
|
||||
hm.history.Connections[hostName] = ConnectionInfo{
|
||||
HostName: hostName,
|
||||
LastConnect: now,
|
||||
ConnectCount: 1,
|
||||
}
|
||||
}
|
||||
|
||||
return hm.saveHistory()
|
||||
}
|
||||
|
||||
// GetLastConnectionTime returns the last connection time for a host
|
||||
func (hm *HistoryManager) GetLastConnectionTime(hostName string) (time.Time, bool) {
|
||||
if conn, exists := hm.history.Connections[hostName]; exists {
|
||||
return conn.LastConnect, true
|
||||
}
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
// GetConnectionCount returns the total number of connections for a host
|
||||
func (hm *HistoryManager) GetConnectionCount(hostName string) int {
|
||||
if conn, exists := hm.history.Connections[hostName]; exists {
|
||||
return conn.ConnectCount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// SortHostsByLastUsed sorts hosts by their last connection time (most recent first)
|
||||
func (hm *HistoryManager) SortHostsByLastUsed(hosts []config.SSHHost) []config.SSHHost {
|
||||
sorted := make([]config.SSHHost, len(hosts))
|
||||
copy(sorted, hosts)
|
||||
|
||||
sort.Slice(sorted, func(i, j int) bool {
|
||||
timeI, existsI := hm.GetLastConnectionTime(sorted[i].Name)
|
||||
timeJ, existsJ := hm.GetLastConnectionTime(sorted[j].Name)
|
||||
|
||||
// If both have history, sort by most recent first
|
||||
if existsI && existsJ {
|
||||
return timeI.After(timeJ)
|
||||
}
|
||||
|
||||
// Hosts with history come before hosts without history
|
||||
if existsI && !existsJ {
|
||||
return true
|
||||
}
|
||||
if !existsI && existsJ {
|
||||
return false
|
||||
}
|
||||
|
||||
// If neither has history, sort alphabetically
|
||||
return sorted[i].Name < sorted[j].Name
|
||||
})
|
||||
|
||||
return sorted
|
||||
}
|
||||
|
||||
// SortHostsByMostUsed sorts hosts by their connection count (most used first)
|
||||
func (hm *HistoryManager) SortHostsByMostUsed(hosts []config.SSHHost) []config.SSHHost {
|
||||
sorted := make([]config.SSHHost, len(hosts))
|
||||
copy(sorted, hosts)
|
||||
|
||||
sort.Slice(sorted, func(i, j int) bool {
|
||||
countI := hm.GetConnectionCount(sorted[i].Name)
|
||||
countJ := hm.GetConnectionCount(sorted[j].Name)
|
||||
|
||||
// If counts are different, sort by count (highest first)
|
||||
if countI != countJ {
|
||||
return countI > countJ
|
||||
}
|
||||
|
||||
// If counts are equal, sort by most recent
|
||||
timeI, existsI := hm.GetLastConnectionTime(sorted[i].Name)
|
||||
timeJ, existsJ := hm.GetLastConnectionTime(sorted[j].Name)
|
||||
|
||||
if existsI && existsJ {
|
||||
return timeI.After(timeJ)
|
||||
}
|
||||
|
||||
// If neither has history, sort alphabetically
|
||||
return sorted[i].Name < sorted[j].Name
|
||||
})
|
||||
|
||||
return sorted
|
||||
}
|
||||
|
||||
// CleanupOldEntries removes connection history for hosts that no longer exist
|
||||
func (hm *HistoryManager) CleanupOldEntries(currentHosts []config.SSHHost) error {
|
||||
// Create a set of current host names
|
||||
currentHostNames := make(map[string]bool)
|
||||
for _, host := range currentHosts {
|
||||
currentHostNames[host.Name] = true
|
||||
}
|
||||
|
||||
// Remove entries for hosts that no longer exist
|
||||
for hostName := range hm.history.Connections {
|
||||
if !currentHostNames[hostName] {
|
||||
delete(hm.history.Connections, hostName)
|
||||
}
|
||||
}
|
||||
|
||||
return hm.saveHistory()
|
||||
}
|
||||
|
||||
// GetAllConnectionsInfo returns all connection information sorted by last connection time
|
||||
func (hm *HistoryManager) GetAllConnectionsInfo() []ConnectionInfo {
|
||||
var connections []ConnectionInfo
|
||||
for _, conn := range hm.history.Connections {
|
||||
connections = append(connections, conn)
|
||||
}
|
||||
|
||||
sort.Slice(connections, func(i, j int) bool {
|
||||
return connections[i].LastConnect.After(connections[j].LastConnect)
|
||||
})
|
||||
|
||||
return connections
|
||||
}
|
||||
Reference in New Issue
Block a user