elastop

- Unnamed repository; edit this file 'description' to name the repository.
git clone git://git.acid.vegas/-c.git
Log | Files | Refs | Archive | README | LICENSE

commit 40057785124c4b72b98fe5388a63a3d9c8d45c02
parent 744fd5a57d78842b1ac3f2d8000d32ca3e20c0df
Author: acidvegas <acid.vegas@acid.vegas>
Date: Fri, 29 Nov 2024 02:42:54 -0500

Added dynamic node/index name padding & togglable boxes

Diffstat:
Melastop | 0
Melastop.go | 262++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------

2 files changed, 201 insertions(+), 61 deletions(-)

diff --git a/elastop b/elastop
Binary files differ.
diff --git a/elastop.go b/elastop.go
@@ -205,6 +205,23 @@ type CatNodesStats struct {
 	Name   string `json:"name"`
 }
 
+var (
+	showHeader  = true
+	showNodes   = true
+	showRoles   = true
+	showIndices = true
+	showMetrics = true
+)
+
+// Add these package level variables right after the existing var declarations
+var (
+	header       *tview.TextView
+	nodesPanel   *tview.TextView
+	rolesPanel   *tview.TextView
+	indicesPanel *tview.TextView
+	metricsPanel *tview.TextView
+)
+
 func bytesToHuman(bytes int64) string {
 	const unit = 1024
 	if bytes < unit {
@@ -456,6 +473,72 @@ type indexInfo struct {
 // Add startTime at package level
 var startTime = time.Now()
 
+// Update this helper function to recalculate the grid layout
+func updateGridLayout(grid *tview.Grid, showRoles, showIndices, showMetrics bool) {
+	// Start with clean grid
+	grid.Clear()
+
+	// Calculate visible panels for bottom row
+	visiblePanels := make([]struct {
+		panel *tview.TextView
+		show  bool
+	}, 0)
+
+	if showRoles {
+		visiblePanels = append(visiblePanels, struct {
+			panel *tview.TextView
+			show  bool
+		}{rolesPanel, true})
+	}
+	if showIndices {
+		visiblePanels = append(visiblePanels, struct {
+			panel *tview.TextView
+			show  bool
+		}{indicesPanel, true})
+	}
+	if showMetrics {
+		visiblePanels = append(visiblePanels, struct {
+			panel *tview.TextView
+			show  bool
+		}{metricsPanel, true})
+	}
+
+	// Adjust row configuration based on whether nodes panel is shown
+	if showNodes {
+		grid.SetRows(3, 0, 0) // Header, nodes, bottom panels
+	} else {
+		grid.SetRows(3, 0) // Just header and bottom panels
+	}
+
+	// Set up columns based on number of visible panels
+	switch len(visiblePanels) {
+	case 3:
+		grid.SetColumns(-1, -2, -1) // 1:2:1 ratio
+	case 2:
+		grid.SetColumns(-1, -1) // Two equal columns
+	case 1:
+		grid.SetColumns(-1) // Single column
+	}
+
+	// Always show header at top spanning all columns
+	grid.AddItem(header, 0, 0, 1, len(visiblePanels), 0, 0, false)
+
+	// Add nodes panel if visible, spanning all columns
+	if showNodes {
+		grid.AddItem(nodesPanel, 1, 0, 1, len(visiblePanels), 0, 0, false)
+
+		// Add bottom panels
+		for i, panel := range visiblePanels {
+			grid.AddItem(panel.panel, 2, i, 1, 1, 0, 0, false)
+		}
+	} else {
+		// Add bottom panels starting from row 1
+		for i, panel := range visiblePanels {
+			grid.AddItem(panel.panel, 1, i, 1, 1, 0, 0, false)
+		}
+	}
+}
+
 func main() {
 	host := flag.String("host", "localhost", "Elasticsearch host")
 	port := flag.Int("port", 9200, "Elasticsearch port")
@@ -471,23 +554,26 @@ func main() {
 		SetColumns(-1, -2, -1). // Three columns for bottom row: roles (1), indices (2), metrics (1)
 		SetBorders(true)
 
-	// Create the individual panels
-	header := tview.NewTextView().
+	// Initialize the panels (move initialization to package level)
+	header = tview.NewTextView().
 		SetDynamicColors(true).
 		SetTextAlign(tview.AlignLeft)
 
-	nodesPanel := tview.NewTextView().
+	nodesPanel = tview.NewTextView().
 		SetDynamicColors(true)
 
-	rolesPanel := tview.NewTextView(). // New panel for roles
+	rolesPanel = tview.NewTextView(). // New panel for roles
 						SetDynamicColors(true)
 
-	indicesPanel := tview.NewTextView().
+	indicesPanel = tview.NewTextView().
 		SetDynamicColors(true)
 
-	metricsPanel := tview.NewTextView().
+	metricsPanel = tview.NewTextView().
 		SetDynamicColors(true)
 
+	// Initial layout
+	updateGridLayout(grid, showRoles, showIndices, showMetrics)
+
 	// Add panels to grid
 	grid.AddItem(header, 0, 0, 1, 3, 0, 0, false). // Header spans all columns
 							AddItem(nodesPanel, 1, 0, 1, 3, 0, 0, false).   // Nodes panel spans all columns
@@ -554,6 +640,13 @@ func main() {
 			return
 		}
 
+		// Get index write stats
+		var indexWriteStats IndexWriteStats
+		if err := makeRequest("/_stats", &indexWriteStats); err != nil {
+			indicesPanel.SetText(fmt.Sprintf("[red]Error getting write stats: %v", err))
+			return
+		}
+
 		// Calculate aggregate metrics
 		var (
 			totalQueries       int64
@@ -592,45 +685,30 @@ func main() {
 			"red":    "red",
 		}[clusterStats.Status]
 
-		// Calculate maxNodeNameLen first
-		maxNodeNameLen := 20 // default minimum length
-		for _, nodeInfo := range nodesInfo.Nodes {
-			if len(nodeInfo.Name) > maxNodeNameLen {
-				maxNodeNameLen = len(nodeInfo.Name)
-			}
-		}
+		// Get max lengths after fetching node and index info
+		maxNodeNameLen, maxIndexNameLen := getMaxLengths(nodesInfo, indicesStats)
 
-		// Then use it in header formatting
+		// Update header with dynamic padding
 		header.Clear()
 		latestVer := getLatestVersion()
 		fmt.Fprintf(header, "[#00ffff]Cluster:[white] %s [%s]%s[-]%s[#00ffff]Latest: [white]%s\n",
 			clusterStats.ClusterName,
 			statusColor,
 			strings.ToUpper(clusterStats.Status),
-			strings.Repeat(" ", maxNodeNameLen-len(clusterStats.ClusterName)), // Add padding
+			strings.Repeat(" ", maxNodeNameLen-len(clusterStats.ClusterName)), // Dynamic padding
 			latestVer)
 		fmt.Fprintf(header, "[#00ffff]Nodes:[white] %d Total, [green]%d[white] Successful, [#ff5555]%d[white] Failed\n",
 			clusterStats.Nodes.Total,
 			clusterStats.Nodes.Successful,
 			clusterStats.Nodes.Failed)
+		fmt.Fprintf(header, "[#666666]Press 2-5 to toggle panels, 'q' to quit[white]\n")
 
-		// Update nodes panel
+		// Update nodes panel with dynamic width
 		nodesPanel.Clear()
-		fmt.Fprintf(nodesPanel, "[::b][#00ffff]Nodes Information[::-]\n\n")
-		fmt.Fprintf(nodesPanel, "[::b]%-*s [#444444]│[#00ffff] %-13s [#444444]│[#00ffff] %-20s [#444444]│[#00ffff] %-7s [#444444]│[#00ffff] %4s      [#444444]│[#00ffff] %4s [#444444]│[#00ffff] %-16s [#444444]│[#00ffff] %-16s [#444444]│[#00ffff] %-16s [#444444]│[#00ffff] %-25s[white]\n",
-			maxNodeNameLen,
-			"Node Name",
-			"Roles",
-			"Transport Address",
-			"Version",
-			"CPU",
-			"Load",
-			"Memory",
-			"Heap",
-			"Disk ",
-			"OS")
-
-		// Display nodes with resource usage
+		fmt.Fprintf(nodesPanel, "[::b][#00ffff][[#ff5555]2[#00ffff]] Nodes Information[::-]\n\n")
+		fmt.Fprint(nodesPanel, getNodesPanelHeader(maxNodeNameLen))
+
+		// Update node entries with dynamic width
 		for id, nodeInfo := range nodesInfo.Nodes {
 			nodeStats, exists := nodesStats.Nodes[id]
 			if !exists {
@@ -675,7 +753,7 @@ func main() {
 				nodeLoads[node.Name] = node.Load1m
 			}
 
-			fmt.Fprintf(nodesPanel, "[#5555ff]%-*s[white] [#444444]│[white] %s [#444444]│[white] [white]%-20s[white] [#444444]│[white] [%s]%-7s[white] [#444444]│[white] [%s]%3d%% [#444444](%d)[white] [#444444]│[white] %4s [#444444]│[white] %4s / %4s [%s]%3d%%[white] [#444444]│[white] %4s / %4s [%s]%3d%%[white] [#444444]│[white] %4s / %4s [%s]%3d%%[white] [#444444]│[white] %s [#bd93f9]%s[white] [#444444](%s)[white]\n",
+			fmt.Fprintf(nodesPanel, "[#5555ff]%-*s  [white] [#444444]│[white] %s [#444444]│[white] [white]%-20s[white] [#444444]│[white] [%s]%-7s[white] [#444444]│[white] [%s]%3d%% [#444444](%d)[white] [#444444]│[white] %4s [#444444]│[white] %4s / %4s [%s]%3d%%[white] [#444444]│[white] %4s / %4s [%s]%3d%%[white] [#444444]│[white] %4s / %4s [%s]%3d%%[white] [#444444]│[white] %s [#bd93f9]%s[white] [#444444](%s)[white]\n",
 				maxNodeNameLen,
 				nodeInfo.Name,
 				formatNodeRoles(nodeInfo.Roles),
@@ -703,33 +781,15 @@ func main() {
 				nodeInfo.OS.Arch)
 		}
 
-		// Update indices panel
+		// Update indices panel with dynamic width
 		indicesPanel.Clear()
-		fmt.Fprintf(indicesPanel, "[::b][#00ffff]Indices Information[::-]\n\n")
-		fmt.Fprintf(indicesPanel, "   [::b]%-20s  %15s  %12s  %8s  %8s  %-12s  %-10s[white]\n",
-			"Index Name",
-			"Documents",
-			"Size",
-			"Shards",
-			"Replicas",
-			"Ingested",
-			"Rate")
-
-		totalDocs := 0
-		totalSize := int64(0)
-		for _, node := range nodesStats.Nodes {
-			totalSize += node.FS.Total.TotalInBytes - node.FS.Total.AvailableInBytes
-		}
+		fmt.Fprintf(indicesPanel, "[::b][#00ffff][[#ff5555]4[#00ffff]] Indices Information[::-]\n\n")
+		fmt.Fprint(indicesPanel, getIndicesPanelHeader(maxIndexNameLen))
 
-		// Get detailed index stats for write operations
-		var indexWriteStats IndexWriteStats
-		if err := makeRequest("/_stats", &indexWriteStats); err != nil {
-			indicesPanel.SetText(fmt.Sprintf("[red]Error getting write stats: %v", err))
-			return
-		}
-
-		// Create a slice to hold indices for sorting
+		// Update index entries with dynamic width
 		var indices []indexInfo
+		var totalDocs int
+		var totalSize int64
 
 		// Collect index information
 		for _, index := range indicesStats {
@@ -777,12 +837,17 @@ func main() {
 			}
 		}
 
+		// Calculate total size
+		for _, node := range nodesStats.Nodes {
+			totalSize += node.FS.Total.TotalInBytes - node.FS.Total.AvailableInBytes
+		}
+
 		// Sort indices by document count (descending)
 		sort.Slice(indices, func(i, j int) bool {
 			return indices[i].docs > indices[j].docs
 		})
 
-		// Display sorted indices
+		// Update index entries with dynamic width
 		for _, idx := range indices {
 			// Only show purple dot if there's actual indexing happening
 			writeIcon := "[#444444]⚪"
@@ -815,9 +880,10 @@ func main() {
 			// Convert the size format before display
 			sizeStr := convertSizeFormat(idx.storeSize)
 
-			fmt.Fprintf(indicesPanel, "%s [%s]%-20s[white]  %15s  %12s  %8s  %8s  %s  %-10s\n",
+			fmt.Fprintf(indicesPanel, "%s [%s]%-*s  [white][#444444]│[white] %15s [#444444]│[white] %12s [#444444]│[white] %8s [#444444]│[white] %8s [#444444]│[white] %s [#444444]│[white] %-10s\n",
 				writeIcon,
 				getHealthColor(idx.health),
+				maxIndexNameLen,
 				idx.index,
 				formatNumber(idx.docs),
 				sizeStr,
@@ -864,7 +930,7 @@ func main() {
 
 		// Update metrics panel
 		metricsPanel.Clear()
-		fmt.Fprintf(metricsPanel, "[::b][#00ffff]Cluster Metrics[::-]\n\n")
+		fmt.Fprintf(metricsPanel, "[::b][#00ffff][[#ff5555]5[#00ffff]] Cluster Metrics[::-]\n\n")
 
 		// Helper function to format metric lines with consistent alignment
 		formatMetric := func(name string, value string) string {
@@ -934,7 +1000,7 @@ func main() {
 
 		// Update roles panel
 		rolesPanel.Clear()
-		fmt.Fprintf(rolesPanel, "[::b][#00ffff]Node Roles[::-]\n\n")
+		fmt.Fprintf(rolesPanel, "[::b][#00ffff][[#ff5555]3[#00ffff]] Node Roles[::-]\n\n")
 
 		// Create a map of used roles
 		usedRoles := make(map[string]bool)
@@ -987,8 +1053,26 @@ func main() {
 
 	// Handle quit
 	app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
-		if event.Key() == tcell.KeyEsc || event.Rune() == 'q' {
+		switch event.Key() {
+		case tcell.KeyEsc:
 			app.Stop()
+		case tcell.KeyRune:
+			switch event.Rune() {
+			case 'q':
+				app.Stop()
+			case '2':
+				showNodes = !showNodes
+				updateGridLayout(grid, showRoles, showIndices, showMetrics)
+			case '3':
+				showRoles = !showRoles
+				updateGridLayout(grid, showRoles, showIndices, showMetrics)
+			case '4':
+				showIndices = !showIndices
+				updateGridLayout(grid, showRoles, showIndices, showMetrics)
+			case '5':
+				showMetrics = !showMetrics
+				updateGridLayout(grid, showRoles, showIndices, showMetrics)
+			}
 		}
 		return event
 	})
@@ -1030,3 +1114,59 @@ func getTotalNetworkRX(stats NodesStats) int64 {
 	}
 	return total
 }
+
+// Update these helper functions at package level
+func getMaxLengths(nodesInfo NodesInfo, indicesStats IndexStats) (int, int) {
+	maxNodeNameLen := 0
+	maxIndexNameLen := 0
+
+	// Get max node name length
+	for _, nodeInfo := range nodesInfo.Nodes {
+		if len(nodeInfo.Name) > maxNodeNameLen {
+			maxNodeNameLen = len(nodeInfo.Name)
+		}
+	}
+
+	// Get max index name length
+	for _, index := range indicesStats {
+		if !strings.HasPrefix(index.Index, ".") && // Skip hidden indices
+			len(index.Index) > maxIndexNameLen {
+			maxIndexNameLen = len(index.Index)
+		}
+	}
+
+	// Add a small buffer to prevent tight spacing
+	maxNodeNameLen += 2
+	maxIndexNameLen += 2
+
+	return maxNodeNameLen, maxIndexNameLen
+}
+
+// Update the nodes panel header formatting
+func getNodesPanelHeader(maxNodeNameLen int) string {
+	return fmt.Sprintf("[::b]%-*s  [#444444]│[#00ffff] %-13s [#444444]│[#00ffff] %-20s [#444444]│[#00ffff] %-7s [#444444]│[#00ffff] %4s      [#444444]│[#00ffff] %4s [#444444]│[#00ffff] %-16s [#444444]│[#00ffff] %-16s [#444444]│[#00ffff] %-16s [#444444]│[#00ffff] %-25s[white]\n",
+		maxNodeNameLen,
+		"Node Name",
+		"Roles",
+		"Transport Address",
+		"Version",
+		"CPU",
+		"Load",
+		"Memory",
+		"Heap",
+		"Disk ",
+		"OS")
+}
+
+// Update the indices panel header formatting
+func getIndicesPanelHeader(maxIndexNameLen int) string {
+	return fmt.Sprintf("   [::b]%-*s  [#444444]│[#00ffff] %15s [#444444]│[#00ffff] %12s [#444444]│[#00ffff] %8s [#444444]│[#00ffff] %8s [#444444]│[#00ffff] %-12s [#444444]│[#00ffff] %-10s[white]\n",
+		maxIndexNameLen,
+		"Index Name",
+		"Documents",
+		"Size",
+		"Shards",
+		"Replicas",
+		"Ingested",
+		"Rate")
+}