orama/pkg/cli/cluster/watch.go
2026-02-14 14:14:04 +02:00

137 lines
3.0 KiB
Go

package cluster
import (
"fmt"
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
)
// HandleWatch handles the "orama cluster watch" command.
// It polls RQLite status and nodes at a configurable interval and reprints a summary.
func HandleWatch(args []string) {
interval := 10 * time.Second
// Parse --interval flag
intervalStr := getFlagValue(args, "--interval")
if intervalStr != "" {
secs, err := strconv.Atoi(intervalStr)
if err != nil || secs < 1 {
fmt.Fprintf(os.Stderr, "Error: --interval must be a positive integer (seconds)\n")
os.Exit(1)
}
interval = time.Duration(secs) * time.Second
}
// Set up signal handling for clean exit
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
fmt.Printf("Watching cluster status (interval: %s, Ctrl+C to exit)\n\n", interval)
// Initial render
renderWatchScreen()
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
renderWatchScreen()
case <-sigCh:
fmt.Printf("\nWatch stopped.\n")
return
}
}
}
// renderWatchScreen clears the terminal and prints a summary of cluster state.
func renderWatchScreen() {
// Clear screen using ANSI escape codes
fmt.Print("\033[2J\033[H")
now := time.Now().Format("2006-01-02 15:04:05")
fmt.Printf("Cluster Watch [%s]\n", now)
fmt.Printf("=======================================\n\n")
// Query RQLite status
status, err := queryRQLiteStatus()
if err != nil {
fmt.Printf("RQLite: UNREACHABLE (%v)\n\n", err)
} else {
fmt.Printf("Local Node: %s\n", status.Store.NodeID)
fmt.Printf(" State: %-10s Term: %-6d Applied: %-8d Commit: %-8d\n",
strings.ToUpper(status.Store.Raft.State),
status.Store.Raft.Term,
status.Store.Raft.AppliedIndex,
status.Store.Raft.CommitIndex)
fmt.Printf(" Leader: %s\n", status.Store.Raft.Leader)
if status.Node.Uptime != "" {
fmt.Printf(" Uptime: %s\n", status.Node.Uptime)
}
fmt.Println()
}
// Query nodes
nodes, err := queryRQLiteNodes(true)
if err != nil {
fmt.Printf("Nodes: UNAVAILABLE (%v)\n\n", err)
} else {
total := len(nodes)
voters := 0
reachable := 0
for _, n := range nodes {
if n.Voter {
voters++
}
if n.Reachable {
reachable++
}
}
fmt.Printf("Cluster: %d nodes (%d voters), %d/%d reachable\n\n",
total, voters, reachable, total)
// Compact table
fmt.Printf("%-18s %-28s %-7s %-7s %-7s\n",
"ID", "ADDRESS", "VOTER", "LEADER", "UP")
fmt.Printf("%-18s %-28s %-7s %-7s %-7s\n",
strings.Repeat("-", 18),
strings.Repeat("-", 28),
strings.Repeat("-", 7),
strings.Repeat("-", 7),
strings.Repeat("-", 7))
for id, node := range nodes {
nodeID := id
if len(nodeID) > 18 {
nodeID = nodeID[:15] + "..."
}
voter := " "
if node.Voter {
voter = "yes"
}
leader := " "
if node.Leader {
leader = "yes"
}
up := "no"
if node.Reachable {
up = "yes"
}
fmt.Printf("%-18s %-28s %-7s %-7s %-7s\n",
nodeID, node.Address, voter, leader, up)
}
}
fmt.Printf("\nPress Ctrl+C to exit\n")
}