diff --git a/internal/ui/views_indices.go b/internal/ui/views_indices.go index c899ef6..5d0df4c 100644 --- a/internal/ui/views_indices.go +++ b/internal/ui/views_indices.go @@ -91,19 +91,63 @@ func (a *App) renderShardsView() string { primaryCount := 0 replicaCount := 0 + // Group shards by index for this node + shardsByIndex := make(map[string]struct { + primaryShards int + replicaShards int + }) + for _, shard := range nodeShards { + entry := shardsByIndex[shard.Index] if shard.Prirep == "p" { primaryCount++ + entry.primaryShards++ } else { replicaCount++ + entry.replicaShards++ } + shardsByIndex[shard.Index] = entry + } + + // Node header with IP if available + nodeDisplay := node.Name + if node.IP != "" { + nodeDisplay = fmt.Sprintf("%s (%s)", node.Name, node.IP) } - b.WriteString(valueStyle.Render(node.Name)) + b.WriteString(valueStyle.Render(nodeDisplay)) b.WriteString(fmt.Sprintf(" - %d shards ", len(nodeShards))) b.WriteString(fmt.Sprintf("(%s %d / %s %d)\n", statusGreen.Render("P:"), primaryCount, statusYellow.Render("R:"), replicaCount)) + + // Show indices on this node + if len(shardsByIndex) > 0 { + // Get sorted list of indices + indices := make([]string, 0, len(shardsByIndex)) + for index := range shardsByIndex { + indices = append(indices, index) + } + // Simple bubble sort + for i := 0; i < len(indices); i++ { + for j := i + 1; j < len(indices); j++ { + if indices[j] < indices[i] { + indices[i], indices[j] = indices[j], indices[i] + } + } + } + + // Display each index with its shard counts, one per line + for _, index := range indices { + entry := shardsByIndex[index] + b.WriteString(fmt.Sprintf(" %s %s (P:%d/R:%d)\n", + labelStyle.Render("•"), + index, + entry.primaryShards, + entry.replicaShards)) + } + } + b.WriteString("\n") } // Show unassigned shards if any diff --git a/internal/ui/views_indices_test.go b/internal/ui/views_indices_test.go index 892c154..7c97e4d 100644 --- a/internal/ui/views_indices_test.go +++ b/internal/ui/views_indices_test.go @@ -306,3 +306,89 @@ func TestApp_RenderShardsView_PrimaryAndReplicaCounting(t *testing.T) { t.Error("renderShardsView() should show replica count of 2") } } + +func TestApp_RenderShardsView_IndicesPerNode(t *testing.T) { + app := &App{ + nodes: []NodeInfo{ + {Name: "node-1", IP: "10.0.1.1"}, + {Name: "node-2", IP: "10.0.1.2"}, + }, + shards: []ShardInfo{ + {Index: "index-a", Shard: "0", Prirep: "p", State: "STARTED", Node: "node-1"}, + {Index: "index-a", Shard: "1", Prirep: "r", State: "STARTED", Node: "node-1"}, + {Index: "index-b", Shard: "0", Prirep: "p", State: "STARTED", Node: "node-1"}, + {Index: "index-c", Shard: "0", Prirep: "p", State: "STARTED", Node: "node-2"}, + {Index: "index-c", Shard: "1", Prirep: "p", State: "STARTED", Node: "node-2"}, + {Index: "index-c", Shard: "2", Prirep: "r", State: "STARTED", Node: "node-2"}, + }, + } + + result := app.renderShardsView() + + // Check that node IP addresses are shown + if !strings.Contains(result, "10.0.1.1") { + t.Error("renderShardsView() should show node IP addresses") + } + + // Check that specific indices appear with their counts + if !strings.Contains(result, "index-a") { + t.Error("renderShardsView() should show index-a") + } + if !strings.Contains(result, "index-b") { + t.Error("renderShardsView() should show index-b") + } + if !strings.Contains(result, "index-c") { + t.Error("renderShardsView() should show index-c") + } + + // Check for shard count format (P:X/R:Y) + if !strings.Contains(result, "P:") || !strings.Contains(result, "/R:") { + t.Error("renderShardsView() should show shard counts in P:X/R:Y format") + } + + // Check for bullet point formatting + if !strings.Contains(result, "•") { + t.Error("renderShardsView() should show bullet points for indices") + } +} + +func TestApp_RenderShardsView_MultipleIndicesOnNode(t *testing.T) { + app := &App{ + nodes: []NodeInfo{ + {Name: "node-1"}, + }, + shards: []ShardInfo{ + {Index: "users", Shard: "0", Prirep: "p", State: "STARTED", Node: "node-1"}, + {Index: "users", Shard: "1", Prirep: "p", State: "STARTED", Node: "node-1"}, + {Index: "orders", Shard: "0", Prirep: "p", State: "STARTED", Node: "node-1"}, + {Index: "products", Shard: "0", Prirep: "r", State: "STARTED", Node: "node-1"}, + }, + } + + result := app.renderShardsView() + + // Check that all indices are shown + if !strings.Contains(result, "users") { + t.Error("renderShardsView() should show users index") + } + if !strings.Contains(result, "orders") { + t.Error("renderShardsView() should show orders index") + } + if !strings.Contains(result, "products") { + t.Error("renderShardsView() should show products index") + } + + // Check that indices are sorted alphabetically + usersPos := strings.Index(result, "users") + ordersPos := strings.Index(result, "orders") + productsPos := strings.Index(result, "products") + + if ordersPos < 0 || productsPos < 0 || usersPos < 0 { + t.Error("renderShardsView() should contain all index names") + } + + // orders < products < users (alphabetically) + if ordersPos >= productsPos || productsPos >= usersPos { + t.Error("renderShardsView() should sort indices alphabetically") + } +}