This is an automated email from the ASF dual-hosted git repository.

epugh pushed a commit to branch branch_10_1
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/branch_10_1 by this push:
     new da2298fb2e8 SOLR-18317: Admin UI no longer sends nodes=all in 
standalone mode (#5001)
da2298fb2e8 is described below

commit da2298fb2e855bdad9f18263254c41abfab2302a
Author: Nick <[email protected]>
AuthorDate: Mon Oct 5 08:58:58 2026 -0600

    SOLR-18317: Admin UI no longer sends nodes=all in standalone mode (#5001)
    
    Co-authored-by: Eric Pugh <[email protected]>
    (cherry picked from commit e432df19c4a50b9d54d7fba545397b859cc54f98)
---
 changelog/unreleased/SOLR-18317.yml                |   7 ++
 .../solr/webapp/AdminUiLoggingStandaloneTest.java  | 103 +++++++++++++++++++++
 solr/webapp/web/js/angular/app.js                  |   9 +-
 solr/webapp/web/js/angular/controllers/logging.js  |  25 ++++-
 solr/webapp/web/js/angular/services.js             |  10 +-
 5 files changed, 146 insertions(+), 8 deletions(-)

diff --git a/changelog/unreleased/SOLR-18317.yml 
b/changelog/unreleased/SOLR-18317.yml
new file mode 100644
index 00000000000..03d74711b8e
--- /dev/null
+++ b/changelog/unreleased/SOLR-18317.yml
@@ -0,0 +1,7 @@
+title: Admin UI no longer sends nodes=all in standalone mode.
+type: fixed
+authors:
+  - name: Nick Shanin
+links:
+  - name: SOLR-18317
+    url: https://issues.apache.org/jira/browse/SOLR-18317
diff --git 
a/solr/webapp/src/test/org/apache/solr/webapp/AdminUiLoggingStandaloneTest.java 
b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiLoggingStandaloneTest.java
new file mode 100644
index 00000000000..548d2d5326d
--- /dev/null
+++ 
b/solr/webapp/src/test/org/apache/solr/webapp/AdminUiLoggingStandaloneTest.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.webapp;
+
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import org.apache.solr.common.util.NamedList;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+
+/**
+ * Tests the log-level editor on a standalone (user-managed) node.
+ *
+ * <p>SOLR-18317: the UI used to unconditionally send {@code nodes=all} to 
{@code
+ * /admin/info/logging}, a parameter that only makes sense in SolrCloud mode 
(it broadcasts the
+ * level change to every live node) and used to NPE server-side without a 
{@code ZkController}. This
+ * exercises the fixed {@code LoggingLevelController.setLevel} against a real 
standalone node, where
+ * the mapping test coverage ({@code AdminUiLoggingScreenTest}) only ever runs 
against a SolrCloud
+ * cluster.
+ */
+public class AdminUiLoggingStandaloneTest extends AdminUiStandaloneTestBase {
+
+  @BeforeClass
+  public static void startStandaloneNode() throws Exception {
+    Path home = buildStandaloneHome("collection1");
+    standaloneJetty = startStandaloneJetty(home);
+    baseUrl = standaloneJetty.getBaseUrl().toString();
+  }
+
+  @AfterClass
+  public static void stopStandaloneNode() throws Exception {
+    if (standaloneJetty != null) {
+      standaloneJetty.stop();
+      standaloneJetty = null;
+    }
+  }
+
+  @Test
+  public void testChangeLogLevelViaUi() throws Exception {
+    String logger = "org.apache.solr.core";
+    openPage("~logging/level", By.id("loggingtree"));
+
+    WebElement anchor =
+        waitFor(By.cssSelector("#loggingtree a.jstree-anchor[title='" + logger 
+ "']"));
+    anchor.click();
+    click(By.xpath("//li[a/@title='" + logger + 
"']//a[normalize-space()='WARN']"));
+    assertLoggerLevel(logger, "WARN");
+
+    // revert to unset; the logger then reports the inherited level with 
set=false
+    click(By.cssSelector("#loggingtree a.jstree-anchor[title='" + logger + 
"']"));
+    click(By.xpath("//li[a/@title='" + logger + 
"']//a[normalize-space()='UNSET']"));
+    assertLoggerLevel(logger, null);
+    assertNoSevereConsoleErrors();
+  }
+
+  /**
+   * Asserts the level a logger was explicitly set to, or with {@code 
expectedLevel} null, that the
+   * logger has no explicit level (it then reports the inherited effective 
level with set=false).
+   *
+   * <p>Before SOLR-18317, the hardcoded {@code nodes=all} param made this 
request NPE server-side
+   * in standalone mode, so the UI's success callback (and thus {@code 
$scope.refresh()}) never ran;
+   * this would time out here rather than observing the new level.
+   */
+  @SuppressWarnings("unchecked")
+  private void assertLoggerLevel(String logger, String expectedLevel) throws 
Exception {
+    waitUntil(
+        "logger " + logger + " has level " + (expectedLevel == null ? 
"(unset)" : expectedLevel),
+        () -> {
+          try {
+            NamedList<Object> response = adminApi("/admin/info/logging", 
params());
+            for (Map<?, ?> entry : (List<Map<?, ?>>) response.get("loggers")) {
+              if (logger.equals(entry.get("name"))) {
+                return expectedLevel == null
+                    ? Boolean.FALSE.equals(entry.get("set"))
+                    : expectedLevel.equals(entry.get("level"))
+                        && Boolean.TRUE.equals(entry.get("set"));
+              }
+            }
+            return false;
+          } catch (Exception e) {
+            throw new RuntimeException(e);
+          }
+        });
+  }
+}
diff --git a/solr/webapp/web/js/angular/app.js 
b/solr/webapp/web/js/angular/app.js
index 27a2f458682..1ff037daedb 100644
--- a/solr/webapp/web/js/angular/app.js
+++ b/solr/webapp/web/js/angular/app.js
@@ -554,7 +554,14 @@ solrAdminApp.controller('MainController', function($scope, 
$route, $rootScope, $
 
     SystemV2.getNodeSystemInfo({}, function(error, data, response) {
       $timeout(function() {
-        if (error) { ApiErrorHandler.handle(response); return; }
+        if (error) {
+          ApiErrorHandler.handle(response);
+          // The mode could not be determined. Treat the node as standalone so 
that
+          // pages waiting for isCloudEnabled to settle (e.g. the logging 
page) fall
+          // back to local-only behavior instead of waiting forever.
+          $scope.isCloudEnabled = false;
+          return;
+        }
         $scope.isCloudEnabled = data.mode.match( /solrcloud/i );
         $scope.usersPermissions = data.security.permissions;
         $scope.isSecurityEnabled = data.security.authenticationPlugin != null;
diff --git a/solr/webapp/web/js/angular/controllers/logging.js 
b/solr/webapp/web/js/angular/controllers/logging.js
index 2011bf9f7d5..a3529c3d420 100644
--- a/solr/webapp/web/js/angular/controllers/logging.js
+++ b/solr/webapp/web/js/angular/controllers/logging.js
@@ -165,9 +165,28 @@ solrAdminApp.controller('LoggingController',
       // broadcast the level change to every live node. The v2 NodeLoggingApis 
endpoint is
       // single-node only until SOLR-16738 wires it up to the new 
V2SolrRequestBasedProxy (see the
       // TODO in NodeLogging.java). Move this to LoggingV2.modifyLocalLogLevel 
once that lands.
-      Logging.setLevel({set: setString}, function(data) {
-        $scope.refresh();
-      });
+      var doSetLevel = function() {
+        var params = {set: setString};
+        // "nodes=all" only makes sense in SolrCloud mode; in standalone 
(user-managed) mode the
+        // request is handled locally, so the param is omitted entirely.
+        if ($scope.isCloudEnabled) {
+          params.nodes = "all";
+        }
+        Logging.setLevel(params, function(data) {
+          $scope.refresh();
+        });
+      };
+      // isCloudEnabled populates asynchronously via resetMenu(), so wait for 
it to settle
+      // before deciding whether to broadcast (same pattern as in 
paramsets.js).
+      if ($scope.isCloudEnabled === undefined) {
+        var unwatchCloudEnabled = $scope.$watch('isCloudEnabled', 
function(value) {
+          if (value === undefined) return;
+          unwatchCloudEnabled();
+          doSetLevel();
+        });
+      } else {
+        doSetLevel();
+      }
     };
 
     $scope.refresh();
diff --git a/solr/webapp/web/js/angular/services.js 
b/solr/webapp/web/js/angular/services.js
index 2119d3bd174..40768805c2f 100644
--- a/solr/webapp/web/js/angular/services.js
+++ b/solr/webapp/web/js/angular/services.js
@@ -189,11 +189,13 @@ solrAdminServices.factory('Metrics',
  }])
 .factory('Logging',
   ['$resource', function($resource) {
-    // This v1 factory only covers "setLevel", which needs the "nodes=all" 
broadcast-to-every-node
-    // behavior that the v2 NodeLoggingApis endpoint doesn't support yet (see 
SOLR-16738). Retire
-    // this factory once setLevel moves to LoggingV2.
+    // This v1 factory only covers "setLevel", which in SolrCloud mode needs 
the "nodes=all"
+    // broadcast-to-every-node behavior that the v2 NodeLoggingApis endpoint 
doesn't support yet
+    // (see SOLR-16738). The caller (LoggingLevelController) passes 
nodes:'all' only when
+    // SolrCloud is enabled; in standalone mode the param is omitted entirely. 
Retire this
+    // factory once setLevel moves to LoggingV2.
     return $resource('admin/info/logging', {'wt':'json', '_':Date.now()}, {
-      "setLevel": {params: {nodes:'all'}}
+      "setLevel": {}
       });
   }])
 .factory('Zookeeper',

Reply via email to