Files
HostKeeper/cmd/hostkeeper/list.go
T
swanadiva 93957e3989 feat: auto-detect encrypted files + CLI --password flag
- Storage.IsDataEncrypted() checks if hosts.json is encrypted
- TUI auto-detects encrypted files, prompts password automatically
- Root command: --password flag for all CLI commands
- newStorage() helper applies password flag to storage
- add/list/edit/delete commands now support encrypted storage
- passwordSetMsg loads hosts after password is set
2026-06-25 14:34:41 +07:00

193 lines
4.2 KiB
Go

package main
import (
"context"
"fmt"
"os"
"sort"
"strings"
"text/tabwriter"
"github.com/spf13/cobra"
"git.tukangketik.id/swanadiva/hostkeeper/internal/models"
"git.tukangketik.id/swanadiva/hostkeeper/pkg/config"
)
var (
listGroup string
listTag string
listFormat string
listSort string
)
// listCmd represents the list command
var listCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List all saved SSH hosts",
Long: `List all SSH hosts saved in HostKeeper.
You can filter by group or tag, and format the output as table or json.
Examples:
# List all hosts
hostkeeper list
# List hosts in a specific group
hostkeeper list --group production
# List hosts with a specific tag
hostkeeper list --tag web
# Output in JSON format
hostkeeper list --format json
# Sort by name
hostkeeper list --sort name`,
RunE: runListHosts,
}
func init() {
listCmd.Flags().StringVar(&listGroup, "group", "", "filter hosts by group")
listCmd.Flags().StringVar(&listTag, "tag", "", "filter hosts by tag")
listCmd.Flags().StringVar(&listFormat, "format", "table", "output format: table or json")
listCmd.Flags().StringVar(&listSort, "sort", "name", "sort by: name, hostname, or group")
rootCmd.AddCommand(listCmd)
}
func runListHosts(cmd *cobra.Command, args []string) error {
cfg := appCfg
if cfg == nil {
var err error
cfg, err = config.New()
if err != nil {
return fmt.Errorf("failed to initialize config: %w", err)
}
}
// Initialize storage
store, err := newStorage(cfg)
if err != nil {
return fmt.Errorf("failed to initialize storage: %w", err)
}
// Get all hosts
ctx := context.Background()
hosts, err := store.ListHosts(ctx)
if err != nil {
return fmt.Errorf("failed to list hosts: %w", err)
}
// Apply filters
if listGroup != "" {
hosts = filterByGroup(hosts, listGroup)
}
if listTag != "" {
hosts = filterByTag(hosts, listTag)
}
// Apply sorting
sortHosts(hosts, listSort)
// Output
if len(hosts) == 0 {
fmt.Println("No hosts found.")
fmt.Println()
fmt.Println("Add a host with: hostkeeper add [name] --host <hostname> --user <username>")
return nil
}
switch listFormat {
case "json":
return outputJSON(hosts)
case "table":
return outputTable(hosts)
default:
return fmt.Errorf("unsupported format: %s (use 'table' or 'json')", listFormat)
}
}
func filterByGroup(hosts []*models.Host, group string) []*models.Host {
var filtered []*models.Host
for _, h := range hosts {
if strings.EqualFold(h.Group, group) {
filtered = append(filtered, h)
}
}
return filtered
}
func filterByTag(hosts []*models.Host, tag string) []*models.Host {
var filtered []*models.Host
for _, h := range hosts {
for _, t := range h.Tags {
if strings.EqualFold(t, tag) {
filtered = append(filtered, h)
break
}
}
}
return filtered
}
func sortHosts(hosts []*models.Host, sortBy string) {
switch sortBy {
case "hostname":
sort.Slice(hosts, func(i, j int) bool {
return hosts[i].Hostname < hosts[j].Hostname
})
case "group":
sort.Slice(hosts, func(i, j int) bool {
return hosts[i].Group < hosts[j].Group
})
default: // "name"
sort.Slice(hosts, func(i, j int) bool {
return hosts[i].Name < hosts[j].Name
})
}
}
func shortID(id string) string {
if len(id) >= 8 {
return id[:8]
}
return id
}
func outputTable(hosts []*models.Host) error {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "ID\tNAME\tHOSTNAME\tPORT\tUSER\tGROUP\tAUTH\tTAGS")
fmt.Fprintln(w, "--\t────\t────────\t────\t────\t─────\t────\t────")
for _, h := range hosts {
tags := strings.Join(h.Tags, ", ")
fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\n",
shortID(h.ID),
h.Name,
h.Hostname,
h.Port,
h.Username,
h.Group,
h.Auth.Type,
tags,
)
}
return w.Flush()
}
func outputJSON(hosts []*models.Host) error {
fmt.Print("[")
for i, h := range hosts {
if i > 0 {
fmt.Print(",")
}
fmt.Printf(`{"id":"%s","short_id":"%s","name":"%s","hostname":"%s","port":%d,"username":"%s","group":"%s","auth_type":"%s"}`,
h.ID, shortID(h.ID), h.Name, h.Hostname, h.Port, h.Username, h.Group, h.Auth.Type)
}
fmt.Println("]")
return nil
}