From 91a14b4af05c7179827b195e0be3440034f9eff8 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Tue, 6 Oct 2026 15:33:17 -0400 Subject: [PATCH 1/2] Introduce coloured dots that update every ten seconds for health. --- .../solr-15395-admin-ui-collection-health.yml | 8 +++ .../webapp/AdminUiCollectionsScreenTest.java | 38 ++++++++++++ solr/webapp/web/css/angular/collections.css | 61 +++++++++++++++++++ .../web/js/angular/controllers/collections.js | 36 +++++++++++ solr/webapp/web/partials/collections.html | 6 +- 5 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 changelog/unreleased/solr-15395-admin-ui-collection-health.yml diff --git a/changelog/unreleased/solr-15395-admin-ui-collection-health.yml b/changelog/unreleased/solr-15395-admin-ui-collection-health.yml new file mode 100644 index 000000000000..5e0b010fc2cc --- /dev/null +++ b/changelog/unreleased/solr-15395-admin-ui-collection-health.yml @@ -0,0 +1,8 @@ +# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc +title: Show collection and shard health (GREEN/YELLOW/ORANGE/RED, from SOLR-15300) as a colored dot in the Admin UI's Collections screen. +type: added +authors: + - name: Eric Pugh +links: + - name: SOLR-15395 + url: https://issues.apache.org/jira/browse/SOLR-15395 diff --git a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiCollectionsScreenTest.java b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiCollectionsScreenTest.java index e8e44ad6ebd7..8cb17c4a8014 100644 --- a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiCollectionsScreenTest.java +++ b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiCollectionsScreenTest.java @@ -46,6 +46,44 @@ public void testCollectionDetailDisplay() { assertNoSevereConsoleErrors(); } + @Test + public void testHealthStatusDisplay() { + openPage("~collections/" + COLLECTION, By.id("collections")); + waitForPageContains("shard1"); + + Map healthDots = + Map.of( + "sidebar", + By.xpath( + "//div[@id='navigation']//li[a[text()='" + + COLLECTION + + "']]//span[contains(@class,'health-dot')]"), + "collection header", By.cssSelector("#collection-data h2 .health-dot"), + "shard header", By.cssSelector("#shard-data h2 .health-dot")); + healthDots.forEach( + (label, locator) -> { + WebElement dot = waitFor(locator); + assertTrue( + label + " health dot should be GREEN: " + dot.getAttribute("class"), + dot.getAttribute("class").contains("health-GREEN")); + }); + + // dot and name must stay on the same line (not wrap) in the narrow sidebar + WebElement sidebarDot = waitFor(healthDots.get("sidebar")); + WebElement sidebarLink = + waitFor(By.xpath("//div[@id='navigation']//li[a[text()='" + COLLECTION + "']]/a")); + int dotCenterY = sidebarDot.getLocation().y + sidebarDot.getSize().height / 2; + int linkCenterY = sidebarLink.getLocation().y + sidebarLink.getSize().height / 2; + assertTrue( + "sidebar health dot and collection name should be on the same line (dot center y=" + + dotCenterY + + ", link center y=" + + linkCenterY + + ")", + Math.abs(dotCenterY - linkCenterY) <= 5); + assertNoSevereConsoleErrors(); + } + @Test public void testCreateAndDeleteCollectionViaUi() throws Exception { String name = "uicreated"; diff --git a/solr/webapp/web/css/angular/collections.css b/solr/webapp/web/css/angular/collections.css index 417074936677..757655d50d30 100644 --- a/solr/webapp/web/css/angular/collections.css +++ b/solr/webapp/web/css/angular/collections.css @@ -53,6 +53,24 @@ limitations under the License. padding-top: 20px; } +#content #collections #navigation ul li +{ + display: flex; + align-items: center; +} + +#content #collections #navigation ul li .health-dot +{ + flex: 0 0 auto; +} + +#content #collections #navigation ul li a +{ + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + #content #collections #navigation hr { margin-top: 20px; @@ -395,3 +413,46 @@ button.disabled /* styles when the element is disabled */ color: #c0c0c0; } + +.health-dot { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 50%; + margin: 0 6px; + vertical-align: middle; +} + +#content #collections #data #collection-data h2 +{ + display: flex; + align-items: center; +} + +#content #collections #data #shard-data h2 .shard-name +{ + display: inline-flex; + align-items: center; +} + +#content #collections #data #collection-data h2 .health-dot, +#content #collections #data #shard-data h2 .shard-name .health-dot +{ + flex: 0 0 auto; +} + +.health-dot.health-GREEN { + background-color: #57A957; +} + +.health-dot.health-YELLOW { + background-color: #d5dd00; +} + +.health-dot.health-ORANGE { + background-color: #c48f00; +} + +.health-dot.health-RED { + background-color: #C43C35; +} diff --git a/solr/webapp/web/js/angular/controllers/collections.js b/solr/webapp/web/js/angular/controllers/collections.js index 17707f34243f..00f7608f132c 100644 --- a/solr/webapp/web/js/angular/controllers/collections.js +++ b/solr/webapp/web/js/angular/controllers/collections.js @@ -99,8 +99,43 @@ solrAdminApp.controller('CollectionsController', } }); }); + + $timeout.cancel($scope.timeout); + $scope.timeout = $timeout($scope.refreshHealth, 10000); }; + // Patches health onto existing objects in place, so it never disturbs expanded/open UI state. + $scope.refreshHealth = function() { + CollectionsV2.listCollections({detailed: true}, function(error, data, response) { + $timeout(function() { + if (error) { + ApiErrorHandler.handle(response); + } else { + for (var name in data.collectionsDetail) { + var existingCollection = ($scope.collections || []).find(function(c) { return c.name === name; }); + if (!existingCollection) { + continue; + } + var freshCollection = data.collectionsDetail[name]; + existingCollection.health = freshCollection.health; + for (var shardName in freshCollection.shards) { + var existingShard = existingCollection.shards.find(function(s) { return s.name === shardName; }); + if (existingShard) { + existingShard.health = freshCollection.shards[shardName].health; + } + } + } + } + $scope.timeout = $timeout($scope.refreshHealth, 10000); + }); + }); + }; + + var onRouteChangeOff = $scope.$on('$routeChangeStart', function() { + $timeout.cancel($scope.timeout); + onRouteChangeOff(); + }); + $scope.hideAll = function() { $scope.showRename = false; $scope.showAdd = false; @@ -242,6 +277,7 @@ solrAdminApp.controller('CollectionsController', ApiErrorHandler.handle(response); } else { $scope.reloadSuccess = true; + $scope.refreshHealth(); } }); }); diff --git a/solr/webapp/web/partials/collections.html b/solr/webapp/web/partials/collections.html index 8973d31562c6..1e9591679a40 100644 --- a/solr/webapp/web/partials/collections.html +++ b/solr/webapp/web/partials/collections.html @@ -205,7 +205,7 @@

Alias: {{collection.name}}

-

Collection: {{collection.name}}

+

Collection: {{collection.name}}

@@ -253,7 +253,7 @@

Collection: {{collection.name}}

- Shard: {{shard.name}} + Shard: {{shard.name}}

@@ -401,7 +401,7 @@