This is an automated email from the ASF dual-hosted git repository.
epugh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/main by this push:
new e432df19c4a SOLR-18317: Admin UI no longer sends nodes=all in
standalone mode (#5001)
e432df19c4a is described below
commit e432df19c4a50b9d54d7fba545397b859cc54f98
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]>
---
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 1bbaa4b9163..f61b4e8d51c 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 2f635c17f1e..c0b525b1698 100644
--- a/solr/webapp/web/js/angular/services.js
+++ b/solr/webapp/web/js/angular/services.js
@@ -195,11 +195,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',