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

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

commit aec4a3fbf2064a208cff36031b1ac13983d29c9c
Author: Andrzej BiaƂecki <[email protected]>
AuthorDate: Tue Oct 20 12:18:55 2026 +0200

    Revert " SOLR-18450: Relocate GET /api/cluster under /api/collections and 
return only the collections tree (#4952)"
    
    This reverts commit 2db17cf25860c6ed5ae20e18fa9eaa3ab9311f08.
---
 .../SOLR-18450-v2-collections-detailed.yml         |  14 --
 .../client/api/endpoint/ListCollectionsApi.java    |  36 +---
 .../client/api/model/ListCollectionsResponse.java  | 100 ---------
 .../java/org/apache/solr/handler/ClusterAPI.java   |   9 +
 .../apache/solr/handler/admin/ClusterStatus.java   | 127 ++++-------
 .../solr/handler/admin/CollectionsHandler.java     |   3 +-
 .../solr/handler/admin/api/ListCollections.java    |  48 +----
 .../solr/handler/V2ClusterAPIMappingTest.java      |   8 +
 .../api/ListCollectionsDetailedStandaloneTest.java |  45 ----
 .../admin/api/ListCollectionsDetailedTest.java     | 236 ---------------------
 .../modules/configuration-guide/pages/v2-api.adoc  |  10 +-
 .../pages/cluster-node-management.adoc             |  25 ++-
 .../pages/collection-management.adoc               | 200 -----------------
 solr/webapp/web/js/angular/app.js                  |   2 +-
 .../web/js/angular/controllers/alias-overview.js   |   2 +-
 solr/webapp/web/js/angular/controllers/cloud.js    | 124 ++++++-----
 .../js/angular/controllers/collection-overview.js  |  13 +-
 .../web/js/angular/controllers/collections.js      |  11 +-
 solr/webapp/web/js/angular/services.js             |  14 +-
 19 files changed, 164 insertions(+), 863 deletions(-)

diff --git a/changelog/unreleased/SOLR-18450-v2-collections-detailed.yml 
b/changelog/unreleased/SOLR-18450-v2-collections-detailed.yml
deleted file mode 100644
index 3406f380b72..00000000000
--- a/changelog/unreleased/SOLR-18450-v2-collections-detailed.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-title:
-  V2 GET /api/collections gains a `detailed=true` param returning the 
collections, shards, and
-  replicas tree (in `collectionsDetail`), without live nodes, aliases, or 
cluster properties. This
-  tree previously lived at GET /api/cluster in an earlier draft of this 
change; it was relocated to
-  /api/collections since it's collection data, not cluster-wide data -- 
`/api/cluster/nodes`,
-  `/api/aliases`, and `/api/cluster/properties` already cover the cluster-wide 
pieces.
-type: changed
-authors:
-  - name: Prithvi S
-    nick: iprithv
-  - name: Eric Pugh
-links:
-  - name: SOLR-18450
-    url: https://issues.apache.org/jira/browse/SOLR-18450
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/ListCollectionsApi.java 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ListCollectionsApi.java
index 468b79d52bf..2f20f4b4366 100644
--- 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/ListCollectionsApi.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/ListCollectionsApi.java
@@ -17,10 +17,8 @@
 package org.apache.solr.client.api.endpoint;
 
 import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
 import jakarta.ws.rs.GET;
 import jakarta.ws.rs.Path;
-import jakarta.ws.rs.QueryParam;
 import org.apache.solr.client.api.model.ListCollectionsResponse;
 
 @Path("/collections")
@@ -29,37 +27,5 @@ public interface ListCollectionsApi {
   @Operation(
       summary = "List all collections in this Solr cluster",
       tags = {"collections"})
-  ListCollectionsResponse listCollections(
-      @Parameter(
-              description =
-                  "When true, return the collections, shards, and replicas 
tree (in"
-                      + " 'collectionsDetail') instead of the plain collection 
name list.")
-          @QueryParam("detailed")
-          Boolean detailed,
-      @Parameter(
-              description =
-                  "Only used when 'detailed' is true. Collection or alias to 
return. Omit to"
-                      + " return every collection. An alias returns the 
collections it points"
-                      + " at.")
-          @QueryParam("collection")
-          String collection,
-      @Parameter(
-              description =
-                  "Only used when 'detailed' is true. Shard or comma-separated 
shards to return."
-                      + " Applied to each selected collection.")
-          @QueryParam("shard")
-          String shard,
-      @Parameter(
-              description =
-                  "Only used when 'detailed' is true. Route key of a document. 
Limits the tree to"
-                      + " the shard that would hold that document.")
-          @QueryParam("_route_")
-          String routeKey,
-      @Parameter(
-              description =
-                  "Only used when 'detailed' is true. Include per-replica 
state when the"
-                      + " collection uses it.")
-          @QueryParam("prs")
-          Boolean prs)
-      throws Exception;
+  ListCollectionsResponse listCollections();
 }
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/ListCollectionsResponse.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/ListCollectionsResponse.java
index 996b6e70729..17bf44c44ec 100644
--- 
a/solr/api/src/java/org/apache/solr/client/api/model/ListCollectionsResponse.java
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/ListCollectionsResponse.java
@@ -16,110 +16,10 @@
  */
 package org.apache.solr.client.api.model;
 
-import com.fasterxml.jackson.annotation.JsonAnyGetter;
-import com.fasterxml.jackson.annotation.JsonAnySetter;
 import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.media.Schema;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 
 public class ListCollectionsResponse extends SolrJerseyResponse {
   @JsonProperty("collections")
   public List<String> collections;
-
-  @Schema(description = "Collections, shards, and replicas, present only when 
detailed=true.")
-  @JsonProperty("collectionsDetail")
-  public Map<String, CollectionState> collectionsDetail;
-
-  /** State of one collection, including its shards and replicas. */
-  public static class CollectionState {
-    @JsonProperty public Map<String, ShardState> shards;
-
-    @Schema(description = "Worst shard health in this collection: GREEN, 
YELLOW, ORANGE, or RED.")
-    @JsonProperty
-    public String health;
-
-    @JsonProperty public String configName;
-    @JsonProperty public Integer znodeVersion;
-    @JsonProperty public Long creationTimeMillis;
-
-    @Schema(description = "Aliases that point at this collection.")
-    @JsonProperty
-    public List<String> aliases;
-
-    @Schema(description = "Document router for this collection, e.g. 
name=compositeId.")
-    @JsonProperty
-    public Map<String, String> router;
-
-    @JsonProperty public Integer replicationFactor;
-
-    private final Map<String, Object> additionalProperties = new 
LinkedHashMap<>();
-
-    @JsonAnyGetter
-    public Map<String, Object> unknownProperties() {
-      return additionalProperties;
-    }
-
-    @JsonAnySetter
-    public void setUnknownProperty(String field, Object value) {
-      additionalProperties.put(field, value);
-    }
-  }
-
-  /** State of one shard. */
-  public static class ShardState {
-    @JsonProperty public String state;
-    @JsonProperty public String range;
-
-    @Schema(description = "Shard health: GREEN, YELLOW, ORANGE, or RED.")
-    @JsonProperty
-    public String health;
-
-    @JsonProperty public Map<String, ReplicaState> replicas;
-
-    private final Map<String, Object> additionalProperties = new 
LinkedHashMap<>();
-
-    @JsonAnyGetter
-    public Map<String, Object> unknownProperties() {
-      return additionalProperties;
-    }
-
-    @JsonAnySetter
-    public void setUnknownProperty(String field, Object value) {
-      additionalProperties.put(field, value);
-    }
-  }
-
-  /** State of one replica. */
-  public static class ReplicaState {
-    @JsonProperty public String state;
-    @JsonProperty public String core;
-
-    @JsonProperty("node_name")
-    public String nodeName;
-
-    @JsonProperty("base_url")
-    public String baseUrl;
-
-    /**
-     * {@code "true"} when this replica is the leader. Absent otherwise. 
state.json stores this as a
-     * string.
-     */
-    @JsonProperty public String leader;
-
-    @JsonProperty public String type;
-
-    private final Map<String, Object> additionalProperties = new 
LinkedHashMap<>();
-
-    @JsonAnyGetter
-    public Map<String, Object> unknownProperties() {
-      return additionalProperties;
-    }
-
-    @JsonAnySetter
-    public void setUnknownProperty(String field, Object value) {
-      additionalProperties.put(field, value);
-    }
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java 
b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
index c73f10f2ec6..41dd007d955 100644
--- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java
@@ -41,6 +41,8 @@ import org.apache.solr.common.SolrException;
 import org.apache.solr.common.annotation.JsonProperty;
 import org.apache.solr.common.cloud.ClusterProperties;
 import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.params.CollectionParams.CollectionAction;
+import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.DefaultSolrParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.ReflectMapWriter;
@@ -213,6 +215,13 @@ public class ClusterAPI {
     return req;
   }
 
+  @EndPoint(method = GET, path = "/cluster", permission = COLL_READ_PERM)
+  public void getClusterStatus(SolrQueryRequest req, SolrQueryResponse rsp) 
throws Exception {
+    final Map<String, Object> v1Params =
+        Map.of(CommonParams.ACTION, CollectionAction.CLUSTERSTATUS.toLower());
+    collectionsHandler.handleRequestBody(wrapParams(req, v1Params), rsp);
+  }
+
   private CoreContainer getCoreContainer() {
     return collectionsHandler.getCoreContainer();
   }
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/ClusterStatus.java 
b/solr/core/src/java/org/apache/solr/handler/admin/ClusterStatus.java
index aaa1d6f5383..c0852ad94de 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ClusterStatus.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ClusterStatus.java
@@ -20,12 +20,10 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-import java.util.function.BiConsumer;
 import java.util.stream.Stream;
 import org.apache.solr.client.api.util.SolrVersion;
 import org.apache.solr.common.MapWriter;
@@ -122,10 +120,9 @@ public class ClusterStatus {
     boolean withAliases = solrParams.getBool(ALIASES_PROP, includeAll);
 
     List<String> liveNodes = null;
-    // Replica state is cross-checked against live nodes whenever the 
collection tree is built,
-    // including when the caller asked for that tree without the live-node 
list itself.
-    if (withLiveNodes || withCollection) {
+    if (withLiveNodes || collection != null) {
       liveNodes = 
zkStateReader.getZkClient().getChildren(ZkStateReader.LIVE_NODES_ZKNODE, null);
+      // add live_nodes
       if (withLiveNodes) clusterStatus.add("live_nodes", liveNodes);
     }
 
@@ -136,7 +133,7 @@ public class ClusterStatus {
 
     if (withCollection) {
       assert liveNodes != null;
-      addCollectionStatuses(clusterStatus, liveNodes, aliases, solrVersion);
+      fetchClusterStatusForCollOrAlias(clusterStatus, liveNodes, aliases, 
solrVersion);
     }
 
     if (withAliases) {
@@ -166,49 +163,13 @@ public class ClusterStatus {
     results.add("cluster", clusterStatus);
   }
 
-  /**
-   * Collections, shards, and replicas selected by this request's {@code 
collection}, {@code shard},
-   * and {@code _route_} parameters.
-   *
-   * <p>Live nodes are read so replica state can be cross-checked, and aliases 
are read so a
-   * collection alias in {@code collection} can be resolved. Each collection 
still lists the aliases
-   * that point at it. The live-node list, the alias map, and cluster 
properties are not returned.
-   */
-  public Map<String, Object> getCollectionStatuses() throws KeeperException, 
InterruptedException {
-    List<String> liveNodes =
-        
zkStateReader.getZkClient().getChildren(ZkStateReader.LIVE_NODES_ZKNODE, null);
-    Aliases aliases = zkStateReader.getAliases();
-    Map<String, Object> collections = new LinkedHashMap<>();
-    forEachCollectionStatus(
-        liveNodes, aliases, (name, status) -> collections.put(name, 
copyForJackson(status)));
-    return collections;
-  }
-
-  private void addCollectionStatuses(
+  private void fetchClusterStatusForCollOrAlias(
       NamedList<Object> clusterStatus,
       List<String> liveNodes,
       Aliases aliases,
       SolrVersion solrVersion) {
-    // Resolve the collection list now so a missing name fails the request, 
rather than during
-    // response writing. The per-collection JSON is still built while the 
response is written.
-    PreparedCollections prepared = prepareCollections(aliases);
-    if (solrVersion == null || 
solrVersion.greaterThanOrEqualTo(SolrVersion.valueOf("9.9.0"))) {
-      MapWriter collectionPropsWriter =
-          ew -> prepared.forEach(liveNodes, (name, status) -> ew.putNoEx(name, 
status));
-      clusterStatus.add("collections", collectionPropsWriter);
-    } else {
-      NamedList<Object> collectionProps = new SimpleOrderedMap<>();
-      prepared.forEach(liveNodes, collectionProps::add);
-      clusterStatus.add("collections", collectionProps);
-    }
-  }
-
-  private void forEachCollectionStatus(
-      List<String> liveNodes, Aliases aliases, BiConsumer<String, Map<String, 
Object>> consumer) {
-    prepareCollections(aliases).forEach(liveNodes, consumer);
-  }
 
-  private PreparedCollections prepareCollections(Aliases aliases) {
+    // read aliases
     Map<String, List<String>> collectionVsAliases = new HashMap<>();
     Map<String, List<String>> aliasVsCollections = 
aliases.getCollectionAliasListMap();
     for (Map.Entry<String, List<String>> entry : 
aliasVsCollections.entrySet()) {
@@ -223,6 +184,12 @@ public class ClusterStatus {
     }
 
     ClusterState clusterState = zkStateReader.getClusterState();
+
+    String routeKey = solrParams.get(ShardParams._ROUTE_);
+    String shard = solrParams.get(ZkStateReader.SHARD_ID_PROP);
+
+    Set<String> requestedShards = (shard != null) ? Set.of(shard.split(",")) : 
null;
+
     Stream<DocCollection> collectionStream;
     if (collection == null) {
       collectionStream = clusterState.collectionStream();
@@ -248,53 +215,33 @@ public class ClusterStatus {
       }
     }
 
-    String routeKey = solrParams.get(ShardParams._ROUTE_);
-    String shard = solrParams.get(ZkStateReader.SHARD_ID_PROP);
-    Set<String> requestedShards = (shard != null) ? Set.of(shard.split(",")) : 
null;
-    return new PreparedCollections(
-        collectionStream.toList(), collectionVsAliases, routeKey, 
requestedShards);
-  }
-
-  private final class PreparedCollections {
-    private final List<DocCollection> collections;
-    private final Map<String, List<String>> collectionVsAliases;
-    private final String routeKey;
-    private final Set<String> requestedShards;
-
-    private PreparedCollections(
-        List<DocCollection> collections,
-        Map<String, List<String>> collectionVsAliases,
-        String routeKey,
-        Set<String> requestedShards) {
-      this.collections = collections;
-      this.collectionVsAliases = collectionVsAliases;
-      this.routeKey = routeKey;
-      this.requestedShards = requestedShards;
-    }
-
-    private void forEach(List<String> liveNodes, BiConsumer<String, 
Map<String, Object>> consumer) {
-      for (DocCollection collectionState : collections) {
-        consumer.accept(
-            collectionState.getName(),
-            buildResponseForCollection(
-                collectionState, collectionVsAliases, routeKey, liveNodes, 
requestedShards));
-      }
-    }
-  }
-
-  /**
-   * Jersey serializes the v2 response with Jackson, which does not write Solr 
{@link MapWriter}s.
-   * Per-replica state is the one value still attached as a writer; turn it 
into plain JSON objects.
-   * Other fields are already maps and scalars.
-   */
-  private static Map<String, Object> copyForJackson(Map<String, Object> 
status) {
-    Object prs = status.get("PRS");
-    if (!(prs instanceof MapWriter)) {
-      return status;
+    if (solrVersion == null || 
solrVersion.greaterThanOrEqualTo(SolrVersion.valueOf("9.9.0"))) {
+      MapWriter collectionPropsWriter =
+          ew -> {
+            collectionStream.forEach(
+                (collectionState) -> {
+                  ew.putNoEx(
+                      collectionState.getName(),
+                      buildResponseForCollection(
+                          collectionState,
+                          collectionVsAliases,
+                          routeKey,
+                          liveNodes,
+                          requestedShards));
+                });
+          };
+      clusterStatus.add("collections", collectionPropsWriter);
+    } else {
+      NamedList<Object> collectionProps = new SimpleOrderedMap<>();
+      collectionStream.forEach(
+          collectionState -> {
+            collectionProps.add(
+                collectionState.getName(),
+                buildResponseForCollection(
+                    collectionState, collectionVsAliases, routeKey, liveNodes, 
requestedShards));
+          });
+      clusterStatus.add("collections", collectionProps);
     }
-    Map<String, Object> copy = new LinkedHashMap<>(status);
-    copy.put("PRS", Utils.fromJSON(Utils.toJSON(prs)));
-    return copy;
   }
 
   private void addAliasMap(Aliases aliases, NamedList<Object> clusterStatus) {
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
index 12055863ea6..6fac52efbf5 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
@@ -832,8 +832,7 @@ public class CollectionsHandler extends RequestHandlerBase 
implements Permission
         LIST,
         (req, rsp, h) -> {
           final ListCollections listCollectionsAPI = new 
ListCollections(h.coreContainer, req, rsp);
-          final SolrJerseyResponse listCollectionsResponse =
-              listCollectionsAPI.listCollections(null, null, null, null, null);
+          final SolrJerseyResponse listCollectionsResponse = 
listCollectionsAPI.listCollections();
           V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, 
listCollectionsResponse);
           return null;
         }),
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollections.java 
b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollections.java
index 831e5929b25..81c5fac32c5 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollections.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollections.java
@@ -21,18 +21,11 @@ import static 
org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PER
 
 import jakarta.inject.Inject;
 import java.util.List;
-import java.util.Map;
 import org.apache.solr.client.api.endpoint.ListCollectionsApi;
 import org.apache.solr.client.api.model.ListCollectionsResponse;
-import 
org.apache.solr.client.api.model.ListCollectionsResponse.CollectionState;
 import org.apache.solr.common.cloud.DocCollection;
-import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.common.params.ShardParams;
-import org.apache.solr.common.util.CollectionUtil;
 import org.apache.solr.core.CoreContainer;
-import org.apache.solr.handler.admin.ClusterStatus;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJacksonMapper;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
@@ -40,12 +33,6 @@ import org.apache.solr.response.SolrQueryResponse;
  * V2 API for listing collections.
  *
  * <p>This API (GET /v2/collections) is equivalent to the v1 
/admin/collections?action=LIST command
- *
- * <p>With {@code detailed=true}, it instead returns the collections, shards, 
and replicas tree (in
- * {@code collectionsDetail}) -- the same tree v1 {@code CLUSTERSTATUS} 
returns under {@code
- * cluster.collections}, without the live-node list, alias map, or cluster 
properties that accompany
- * it there. Live nodes, aliases, and cluster properties have their own v2 
endpoints: {@code GET
- * /api/cluster/nodes}, {@code GET /api/aliases}, and {@code GET 
/api/cluster/properties}.
  */
 public class ListCollections extends AdminAPIBase implements 
ListCollectionsApi {
 
@@ -56,31 +43,11 @@ public class ListCollections extends AdminAPIBase 
implements ListCollectionsApi
 
   @Override
   @PermissionName(COLL_READ_PERM)
-  public ListCollectionsResponse listCollections(
-      Boolean detailed, String collection, String shard, String routeKey, 
Boolean prs)
-      throws Exception {
+  public ListCollectionsResponse listCollections() {
     final ListCollectionsResponse response =
         instantiateJerseyResponse(ListCollectionsResponse.class);
     validateZooKeeperAwareCoreContainer(coreContainer);
 
-    if (Boolean.TRUE.equals(detailed)) {
-      if (collection != null) {
-        recordCollectionForLogAndTracing(collection, solrQueryRequest);
-      }
-      // Bind only the documented query params; anything else on the request 
is ignored.
-      final ModifiableSolrParams params = new ModifiableSolrParams();
-      params.setNonNull("collection", collection);
-      params.setNonNull("shard", shard);
-      params.setNonNull(ShardParams._ROUTE_, routeKey);
-      if (prs != null) {
-        params.set("prs", prs);
-      }
-      final ClusterStatus clusterStatus =
-          new 
ClusterStatus(coreContainer.getZkController().getZkStateReader(), params);
-      response.collectionsDetail = 
typedCollections(clusterStatus.getCollectionStatuses());
-      return response;
-    }
-
     // resolve each name to ensure it exists.
     //  TODO https://issues.apache.org/jira/browse/SOLR-16909 to go direct to 
ZK?
     List<String> collectionList =
@@ -96,17 +63,4 @@ public class ListCollections extends AdminAPIBase implements 
ListCollectionsApi
 
     return response;
   }
-
-  /**
-   * Bind the state maps onto the response types. Fields the model knows about 
are typed; every
-   * other entry stays on the object through its catch-all.
-   */
-  private static Map<String, CollectionState> typedCollections(Map<String, 
Object> raw) {
-    var mapper = SolrJacksonMapper.getObjectMapper();
-    Map<String, CollectionState> collections = 
CollectionUtil.newLinkedHashMap(raw.size());
-    for (Map.Entry<String, Object> entry : raw.entrySet()) {
-      collections.put(entry.getKey(), mapper.convertValue(entry.getValue(), 
CollectionState.class));
-    }
-    return collections;
-  }
 }
diff --git 
a/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java 
b/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
index 3974894386d..4fc458bc589 100644
--- a/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/V2ClusterAPIMappingTest.java
@@ -29,6 +29,7 @@ import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.api.Api;
 import org.apache.solr.api.ApiBag;
 import org.apache.solr.common.params.CollectionParams;
+import org.apache.solr.common.params.CollectionParams.CollectionAction;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.CommandOperation;
@@ -74,6 +75,13 @@ public class V2ClusterAPIMappingTest extends SolrTestCaseJ4 {
     assertEquals(CollectionParams.CollectionAction.OVERSEERSTATUS.lowerName, 
v1Params.get(ACTION));
   }
 
+  @Test
+  public void testClusterStatusAllParams() throws Exception {
+    final SolrParams v1Params = captureConvertedV1Params("/cluster", "GET", 
null);
+
+    assertEquals(CollectionAction.CLUSTERSTATUS.lowerName, 
v1Params.get(ACTION));
+  }
+
   @Test
   public void testAddRoleAllParams() throws Exception {
     final SolrParams v1Params =
diff --git 
a/solr/core/src/test/org/apache/solr/handler/admin/api/ListCollectionsDetailedStandaloneTest.java
 
b/solr/core/src/test/org/apache/solr/handler/admin/api/ListCollectionsDetailedStandaloneTest.java
deleted file mode 100644
index 48a08b7a9e3..00000000000
--- 
a/solr/core/src/test/org/apache/solr/handler/admin/api/ListCollectionsDetailedStandaloneTest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.handler.admin.api;
-
-import org.apache.solr.SolrTestCase;
-import org.apache.solr.client.solrj.RemoteSolrException;
-import org.apache.solr.client.solrj.request.CollectionsApi;
-import org.apache.solr.util.SolrJettyTestRule;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-
-/** Standalone coverage for {@code GET /api/collections?detailed=true}. */
-public class ListCollectionsDetailedStandaloneTest extends SolrTestCase {
-
-  @ClassRule public static final SolrJettyTestRule solrTestRule = new 
SolrJettyTestRule();
-
-  @BeforeClass
-  public static void setupSolr() throws Exception {
-    solrTestRule.startSolr(createTempDir());
-  }
-
-  @Test
-  public void testRequiresSolrCloud() {
-    var req = new CollectionsApi.ListCollections();
-    req.setDetailed(true);
-    final RemoteSolrException ex =
-        expectThrows(RemoteSolrException.class, () -> 
req.process(solrTestRule.getAdminClient()));
-    assertEquals(400, ex.code());
-  }
-}
diff --git 
a/solr/core/src/test/org/apache/solr/handler/admin/api/ListCollectionsDetailedTest.java
 
b/solr/core/src/test/org/apache/solr/handler/admin/api/ListCollectionsDetailedTest.java
deleted file mode 100644
index 93808b8a27a..00000000000
--- 
a/solr/core/src/test/org/apache/solr/handler/admin/api/ListCollectionsDetailedTest.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * 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.handler.admin.api;
-
-import static org.apache.solr.client.solrj.SolrRequest.METHOD.GET;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import org.apache.solr.client.api.model.ListCollectionsResponse;
-import 
org.apache.solr.client.api.model.ListCollectionsResponse.CollectionState;
-import org.apache.solr.client.api.model.ListCollectionsResponse.ReplicaState;
-import org.apache.solr.client.solrj.SolrRequest.SolrRequestType;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.CollectionsApi;
-import org.apache.solr.client.solrj.request.GenericSolrRequest;
-import org.apache.solr.cloud.SolrCloudTestCase;
-import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.Utils;
-import org.eclipse.jetty.client.ContentResponse;
-import org.eclipse.jetty.client.HttpClient;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-/**
- * HTTP tests for {@code GET /api/collections?detailed=true}.
- *
- * <p>The response is the collections, shards, and replicas tree. Live nodes, 
the alias map, and
- * cluster properties stay on their own endpoints (not this one). v1 {@code 
CLUSTERSTATUS} still
- * returns them.
- */
-public class ListCollectionsDetailedTest extends SolrCloudTestCase {
-
-  private static final String COLLECTION = "clusterstatuscoll";
-  private static final String ALIAS = "clusterstatusalias";
-
-  @BeforeClass
-  public static void setupCluster() throws Exception {
-    configureCluster(1).addConfig("conf", 
configset("cloud-minimal")).configure();
-    CollectionAdminRequest.createCollection(COLLECTION, "conf", 1, 1)
-        .process(cluster.getSolrClient());
-    CollectionAdminRequest.createAlias(ALIAS, 
COLLECTION).process(cluster.getSolrClient());
-  }
-
-  @Test
-  public void testReturnsCollectionTreeWithoutClusterLevelExtras() throws 
Exception {
-    Map<String, Object> body = getCollections("?detailed=true");
-    assertNull(body.get("cluster"));
-    assertNull(body.get("live_nodes"));
-    assertNull(body.get("aliases"));
-    assertNull(body.get("properties"));
-
-    Map<String, Object> collection = collection(body, COLLECTION);
-    assertNotNull(collection.get("health"));
-    assertEquals("conf", collection.get("configName"));
-    assertNotNull(collection.get("router"));
-    assertEquals(Set.of("shard1"), shards(collection).keySet());
-    assertNotNull(replica(collection).get("node_name"));
-    assertNotNull(replica(collection).get("state"));
-    assertTrue(aliasesOf(collection).contains(ALIAS));
-  }
-
-  @Test
-  @SuppressWarnings("unchecked")
-  public void testGeneratedClientReadsTheTree() throws Exception {
-    var req = new CollectionsApi.ListCollections();
-    req.setDetailed(true);
-    ListCollectionsResponse response = req.process(cluster.getSolrClient());
-    assertNull(response.error);
-    assertNull(response.collections);
-    CollectionState collection = response.collectionsDetail.get(COLLECTION);
-    assertNotNull(collection);
-    assertEquals("conf", collection.configName);
-    assertNotNull(collection.health);
-    assertTrue(collection.aliases.contains(ALIAS));
-    assertNotNull(collection.router);
-    assertEquals("compositeId", collection.router.get("name"));
-    assertEquals(Integer.valueOf(1), collection.replicationFactor);
-
-    ReplicaState replica = 
collection.shards.get("shard1").replicas.values().iterator().next();
-    assertNotNull(replica.nodeName);
-    assertNotNull(replica.state);
-    assertEquals("true", replica.leader);
-  }
-
-  @Test
-  public void testWithoutDetailedReturnsPlainNameList() throws Exception {
-    var req = new CollectionsApi.ListCollections();
-    ListCollectionsResponse response = req.process(cluster.getSolrClient());
-    assertNull(response.error);
-    assertNull(response.collectionsDetail);
-    assertTrue(response.collections.contains(COLLECTION));
-  }
-
-  @Test
-  @SuppressWarnings("unchecked")
-  public void testPerReplicaState() throws Exception {
-    final String prsCollection = "prsclusterstatus";
-    CollectionAdminRequest.createCollection(prsCollection, "conf", 1, 1)
-        .setPerReplicaState(Boolean.TRUE)
-        .process(cluster.getSolrClient());
-
-    Map<String, Object> prs =
-        (Map<String, Object>)
-            collection(
-                    getCollections("?detailed=true&collection=" + 
prsCollection + "&prs=true"),
-                    prsCollection)
-                .get("PRS");
-    assertNotNull(prs);
-    assertNotNull(prs.get("states"));
-
-    var request = new CollectionsApi.ListCollections();
-    request.setDetailed(true);
-    request.setCollection(prsCollection);
-    request.setPrs(true);
-    CollectionState typed =
-        
request.process(cluster.getSolrClient()).collectionsDetail.get(prsCollection);
-    assertNotNull(typed.unknownProperties().get("PRS"));
-  }
-
-  @Test
-  public void testCollectionShardAndAliasFilters() throws Exception {
-    Map<String, Object> byCollection =
-        collections(getCollections("?detailed=true&collection=" + COLLECTION));
-    assertEquals(Set.of(COLLECTION), byCollection.keySet());
-
-    Map<String, Object> oneShard =
-        shards(
-            collection(
-                getCollections("?detailed=true&collection=" + COLLECTION + 
"&shard=shard1"),
-                COLLECTION));
-    assertEquals(Set.of("shard1"), oneShard.keySet());
-
-    ContentResponse missingShard =
-        httpGet("?detailed=true&collection=" + COLLECTION + 
"&shard=nosuchshard");
-    assertEquals(400, missingShard.getStatus());
-
-    assertNotNull(
-        collections(getCollections("?detailed=true&collection=" + 
ALIAS)).get(COLLECTION));
-  }
-
-  @Test
-  public void testUnknownCollectionIsRejected() throws Exception {
-    ContentResponse response = 
httpGet("?detailed=true&collection=not-a-collection");
-    assertEquals(400, response.getStatus());
-    assertTrue(response.getContentAsString().contains("not found"));
-  }
-
-  @Test
-  @SuppressWarnings("unchecked")
-  public void testV1ClusterStatusStillReturnsLiveNodes() throws Exception {
-    NamedList<Object> response =
-        
CollectionAdminRequest.getClusterStatus().process(cluster.getSolrClient()).getResponse();
-    Map<String, Object> clusterState = (Map<String, Object>) 
response.get("cluster");
-    assertNotNull(clusterState.get("live_nodes"));
-    assertNotNull(clusterState.get("collections"));
-
-    ModifiableSolrParams params = new ModifiableSolrParams();
-    params.set("action", "CLUSTERSTATUS");
-    params.set("liveNodes", false);
-    params.set("aliases", false);
-    params.set("clusterProperties", false);
-    NamedList<Object> collectionsOnly =
-        cluster
-            .getSolrClient()
-            .request(
-                new GenericSolrRequest(GET, "/admin/collections", 
SolrRequestType.ADMIN, params));
-    Map<String, Object> filtered = (Map<String, Object>) 
collectionsOnly.get("cluster");
-    assertNull(filtered.get("live_nodes"));
-    assertNull(filtered.get("aliases"));
-    assertNull(filtered.get("properties"));
-    assertNotNull(((Map<String, Object>) 
filtered.get("collections")).get(COLLECTION));
-  }
-
-  private static Map<String, Object> getCollections(String query) throws 
Exception {
-    ContentResponse response = httpGet(query);
-    assertEquals(response.getContentAsString(), 200, response.getStatus());
-    return parsed(response);
-  }
-
-  private static ContentResponse httpGet(String query) throws Exception {
-    HttpClient httpClient = 
cluster.getJettySolrRunner(0).getSolrClient().getHttpClient();
-    String url = cluster.getJettySolrRunner(0).getBaseURLV2().toString() + 
"/collections" + query;
-    return httpClient.GET(url);
-  }
-
-  @SuppressWarnings("unchecked")
-  private static Map<String, Object> parsed(ContentResponse response) {
-    return (Map<String, Object>) 
Utils.fromJSONString(response.getContentAsString());
-  }
-
-  @SuppressWarnings("unchecked")
-  private static Map<String, Object> collections(Map<String, Object> body) {
-    return (Map<String, Object>) body.get("collectionsDetail");
-  }
-
-  @SuppressWarnings("unchecked")
-  private static Map<String, Object> collection(Map<String, Object> body, 
String name) {
-    Map<String, Object> collection = (Map<String, Object>) 
collections(body).get(name);
-    assertNotNull(collection);
-    return collection;
-  }
-
-  @SuppressWarnings("unchecked")
-  private static Map<String, Object> shards(Map<String, Object> collection) {
-    return (Map<String, Object>) collection.get("shards");
-  }
-
-  @SuppressWarnings("unchecked")
-  private static List<String> aliasesOf(Map<String, Object> collection) {
-    return (List<String>) collection.get("aliases");
-  }
-
-  @SuppressWarnings("unchecked")
-  private static Map<String, Object> replica(Map<String, Object> collection) {
-    Map<String, Object> shard = (Map<String, Object>) 
shards(collection).get("shard1");
-    Map<String, Object> replicas = (Map<String, Object>) shard.get("replicas");
-    assertFalse(replicas.isEmpty());
-    return (Map<String, Object>) replicas.values().iterator().next();
-  }
-}
diff --git a/solr/solr-ref-guide/modules/configuration-guide/pages/v2-api.adoc 
b/solr/solr-ref-guide/modules/configuration-guide/pages/v2-api.adoc
index 6d0e6c0e60d..26390f38c30 100644
--- a/solr/solr-ref-guide/modules/configuration-guide/pages/v2-api.adoc
+++ b/solr/solr-ref-guide/modules/configuration-guide/pages/v2-api.adoc
@@ -38,7 +38,7 @@ Following are some v2 API URL paths and path prefixes, along 
with some of the op
 [width="100%",options="header",]
 |===
 |Path prefix |Some Supported Operations
-|`/api/collections` |Create, alias, backup, and restore a collection. Fetch 
the collections, shards, and replicas tree (`?detailed=true`).
+|`/api/collections` |Create, alias, backup, and restore a collection.
 |`/api/c/\{collectionName}/update` |Update requests.
 |`/api/c/\{collectionName}/config` |Configuration requests.
 |`/api/c/\{collectionName}/schema` |Schema requests.
@@ -49,7 +49,7 @@ Following are some v2 API URL paths and path prefixes, along 
with some of the op
 |`/api/cores` |Create a core.
 |`/api/cores/\{coreName}` |Reload, rename, delete, and unload a core.
 |`/api/node` |Perform overseer operation, rejoin leader election.
-|`/api/cluster` |List live nodes, manage node roles, set cluster property. 
`POST` sets the cluster rate limiter.
+|`/api/cluster` |List live nodes, add role, remove role, set cluster property.
 |===
 
 == Introspect
@@ -149,13 +149,13 @@ curl http://localhost:8983/api/c/gettingstarted -H 
'Content-type:application/jso
 {"responseHeader":{"status":0,"QTime":842}}
 ----
 
-See the collections, shards, and replicas in the cluster:
+See the state of the cluster:
 
 [source,bash]
 ----
-curl http://localhost:8983/api/collections?detailed=true
+curl http://localhost:8983/api/cluster
 
-{"responseHeader":{"status":0,"QTime":1},"collectionsDetail":{"gettingstarted":{"health":"GREEN","shards":{"shard1":{"state":"active"}}}}}
+{"responseHeader":{"status":0,"QTime":0},"collections":["gettingstarted"]}
 ----
 
 Set a cluster property:
diff --git 
a/solr/solr-ref-guide/modules/deployment-guide/pages/cluster-node-management.adoc
 
b/solr/solr-ref-guide/modules/deployment-guide/pages/cluster-node-management.adoc
index 8028a4d7d5e..22ee652954a 100644
--- 
a/solr/solr-ref-guide/modules/deployment-guide/pages/cluster-node-management.adoc
+++ 
b/solr/solr-ref-guide/modules/deployment-guide/pages/cluster-node-management.adoc
@@ -27,10 +27,6 @@ These API commands work with a SolrCloud cluster at the 
entire cluster level, or
 
 Fetch the cluster status including collections, shards, replicas, 
configuration name as well as collection aliases and cluster properties.
 
-NOTE: This is a v1-only command.
-The v2 API splits this information up: see 
xref:collection-management.adoc#collectionsdetail[Detailed Collections Tree] 
for the collections, shards, and replicas tree (`detailed=true`), and `GET 
/api/cluster/nodes`, `GET /api/aliases`, and `GET /api/cluster/properties` for 
live nodes, the alias map, and cluster properties, respectively.
-The two collection-tree responses overlap in content -- same `health` states, 
same shard/replica fields -- because they cover the same underlying data; 
they're documented separately because the parameters and response shape 
otherwise differ between v1 and v2 here.
-
 Additionally, this command reports a `health` status of each collection and 
shard, in
 order to make it easier to monitor the operational state of the collections.
 The
@@ -48,10 +44,28 @@ No active replicas *OR* there's no shard leader.
 
 The collection health state is reported as the worst state of any shard, e.g., 
for a collection with all shards GREEN except for one YELLOW the collection 
health will be reported as YELLOW.
 
+[tabs#clusterstatus-request]
+======
+V1 API::
++
+====
 [source,bash]
 ----
 http://localhost:8983/solr/admin/collections?action=CLUSTERSTATUS
+
 ----
+====
+
+V2 API::
++
+====
+[source,bash]
+----
+curl http://localhost:8983/api/cluster
+
+----
+====
+======
 
 === CLUSTERSTATUS Parameters
 
@@ -92,7 +106,6 @@ This can be used if you need the details of the shard where 
a particular documen
 |Optional |Default: will default to the default value of `includeAll` 
parameter specified below
 |===
 +
-If set to true, returns the cluster alias map.
 
 `liveNodes`::
 +
@@ -101,7 +114,7 @@ If set to true, returns the cluster alias map.
 |Optional |Default: will default to the default value of `includeAll` 
parameter specified below
 |===
 +
-If set to true, returns the live nodes in the cluster.
+If set to true, returns the status of live nodes in the cluster.
 
 `clusterProperties`::
 +
diff --git 
a/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc 
b/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc
index 8dbc01b365a..8023f72bcb1 100644
--- 
a/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc
+++ 
b/solr/solr-ref-guide/modules/deployment-guide/pages/collection-management.adoc
@@ -510,206 +510,6 @@ curl http://localhost:8983/api/collections
     "example2"]}
 ----
 
-[[collectionsdetail]]
-=== Detailed Collections Tree (V2 Only)
-
-With `detailed=true`, the V2 API returns the collections, shards, and replicas 
tree, in `collectionsDetail`, including each collection's configuration name 
and health, instead of the plain collection name list `LIST` returns by default.
-
-NOTE: This is a v2-only variant of `LIST`.
-See xref:cluster-node-management.adoc#clusterstatus[CLUSTERSTATUS] for the v1 
command that returns similar information (plus live nodes, the alias map, and 
cluster properties).
-The two overlap in content -- same `health` states, same shard/replica fields 
-- because they cover the same underlying data; they're documented separately 
because the parameters and response shape otherwise differ between v1 and v2 
here.
-
-This command reports a `health` status of each collection and shard, in order 
to make
-it easier to monitor the operational state of the collections.
-The following health
-state values are defined, ordered from the best to worst, based on the 
percentage of
-active replicas (`active`):
-
-`GREEN`::
-`active == 100%`, all replicas are active and there's a shard leader.
-`YELLOW`::
-`100% > active > 50%`, AND there's a shard leader.
-`ORANGE`::
-`50% >= active > 0%`, AND there's a shard leader.
-`RED`::
-No active replicas *OR* there's no shard leader.
-
-The collection health state is reported as the worst state of any shard, e.g., 
for a collection with all shards GREEN except for one YELLOW the collection 
health will be reported as YELLOW.
-
-[source,bash]
-----
-curl -X GET "http://localhost:8983/api/collections?detailed=true";
-----
-
-==== Detailed Collections Tree Parameters
-
-`detailed`::
-+
-[%autowidth,frame=none]
-|===
-|Optional |Default: `false`
-|===
-+
-Return the collections, shards, and replicas tree in `collectionsDetail` 
instead of the plain collection name list in `collections`.
-
-`collection`::
-+
-[%autowidth,frame=none]
-|===
-|Optional |Default: none
-|===
-+
-Only used when `detailed=true`.
-The collection or alias name for which information is requested.
-If omitted, information on all collections in the cluster will be returned.
-If an alias is supplied, information on the collections in the alias will be 
returned.
-
-`shard`::
-+
-[%autowidth,frame=none]
-|===
-|Optional |Default: none
-|===
-+
-Only used when `detailed=true`.
-The shard(s) for which information is requested.
-Multiple shard names can be specified as a comma-separated list.
-
-`\_route_`::
-+
-[%autowidth,frame=none]
-|===
-|Optional |Default: none
-|===
-+
-Only used when `detailed=true`.
-This can be used if you need the details of the shard where a particular 
document belongs to and you don't know which shard it falls under.
-
-`prs`::
-+
-[%autowidth,frame=none]
-|===
-|Optional |Default: `false`
-|===
-+
-Only used when `detailed=true`.
-Include per-replica state when the collection uses it.
-
-==== Detailed Collections Tree Response
-
-The response will include the status of the request and `collectionsDetail`, a 
map of collection name to collection state.
-
-==== Example
-
-*Input*
-
-[source,text]
-----
-http://localhost:8983/api/collections?detailed=true
-----
-
-*Output*
-
-[source,json]
-----
-{
-  "responseHeader":{
-    "status":0,
-    "QTime":333},
-  "collectionsDetail":{
-    "collection1":{
-      "shards":{
-        "shard1":{
-          "range":"80000000-ffffffff",
-          "state":"active",
-          "health": "GREEN",
-          "replicas":{
-            "core_node1":{
-              "state":"active",
-              "core":"collection1",
-              "node_name":"127.0.1.1:8983_solr",
-              "base_url":"http://127.0.1.1:8983/solr";,
-              "leader":"true"},
-            "core_node3":{
-              "state":"active",
-              "core":"collection1",
-              "node_name":"127.0.1.1:8900_solr",
-              "base_url":"http://127.0.1.1:8900/solr"}}},
-        "shard2":{
-          "range":"0-7fffffff",
-          "state":"active",
-          "health": "GREEN",
-          "replicas":{
-            "core_node2":{
-              "state":"active",
-              "core":"collection1",
-              "node_name":"127.0.1.1:7574_solr",
-              "base_url":"http://127.0.1.1:7574/solr";,
-              "leader":"true"},
-            "core_node4":{
-              "state":"active",
-              "core":"collection1",
-              "node_name":"127.0.1.1:7500_solr",
-              "base_url":"http://127.0.1.1:7500/solr"}}}},
-      "router":{"name":"compositeId"},
-      "replicationFactor":1,
-      "znodeVersion": 11,
-      "configName" : "my_config",
-      "health": "GREEN",
-      "aliases":["both_collections"]
-    },
-    "collection2":{"key": "value"}
-  }
-}
-----
-
-Adding `collection` and `shard` narrows the tree to just that shard of that 
collection:
-
-*Input*
-
-[source,text]
-----
-http://localhost:8983/api/collections?detailed=true&collection=collection1&shard=shard1
-----
-
-*Output*
-
-[source,json]
-----
-{
-  "responseHeader":{
-    "status":0,
-    "QTime":12},
-  "collectionsDetail":{
-    "collection1":{
-      "shards":{
-        "shard1":{
-          "range":"80000000-ffffffff",
-          "state":"active",
-          "health": "GREEN",
-          "replicas":{
-            "core_node1":{
-              "state":"active",
-              "core":"collection1",
-              "node_name":"127.0.1.1:8983_solr",
-              "base_url":"http://127.0.1.1:8983/solr";,
-              "leader":"true"},
-            "core_node3":{
-              "state":"active",
-              "core":"collection1",
-              "node_name":"127.0.1.1:8900_solr",
-              "base_url":"http://127.0.1.1:8900/solr"}}}},
-      "router":{"name":"compositeId"},
-      "replicationFactor":1,
-      "znodeVersion": 11,
-      "configName" : "my_config",
-      "health": "GREEN",
-      "aliases":["both_collections"]
-    }
-  }
-}
-----
-
 [[rename]]
 == RENAME: Rename a Collection
 
diff --git a/solr/webapp/web/js/angular/app.js 
b/solr/webapp/web/js/angular/app.js
index aba91049975..93b57bde5fb 100644
--- a/solr/webapp/web/js/angular/app.js
+++ b/solr/webapp/web/js/angular/app.js
@@ -579,7 +579,7 @@ solrAdminApp.controller('MainController', function($scope, 
$route, $rootScope, $
         var currentCollectionName = $route.current.params.core;
         delete $scope.currentCollection;
         if ($scope.isCloudEnabled) {
-          CollectionsV2.listCollections({}, function (error, cdata, response) {
+          CollectionsV2.listCollections(function (error, cdata, response) {
             $timeout(function() {
               if (error) { ApiErrorHandler.handle(response); return; }
               AliasesV2.getAliases(function (error, adata, response) {
diff --git a/solr/webapp/web/js/angular/controllers/alias-overview.js 
b/solr/webapp/web/js/angular/controllers/alias-overview.js
index 212493e8444..6fbca386fe2 100644
--- a/solr/webapp/web/js/angular/controllers/alias-overview.js
+++ b/solr/webapp/web/js/angular/controllers/alias-overview.js
@@ -16,7 +16,7 @@
 */
 
 solrAdminApp.controller('AliasOverviewController',
-function($scope, $routeParams, Constants) {
+function($scope, $routeParams, Collections, Constants) {
   $scope.resetMenu("collection-overview", Constants.IS_COLLECTION_PAGE);
 
   $scope.refresh = function() {
diff --git a/solr/webapp/web/js/angular/controllers/cloud.js 
b/solr/webapp/web/js/angular/controllers/cloud.js
index 87ae9eecf31..2ce9a392d1c 100644
--- a/solr/webapp/web/js/angular/controllers/cloud.js
+++ b/solr/webapp/web/js/angular/controllers/cloud.js
@@ -16,7 +16,7 @@
 */
 
 solrAdminApp.controller('CloudController',
-    function($scope, $location, $timeout, $q, Zookeeper, ZookeeperReadV2, 
Constants, ClusterV2, CollectionsV2, SystemV2, Metrics, MetricsExtractor, 
ZookeeperStatus, ApiErrorHandler) {
+    function($scope, $location, $timeout, $q, Zookeeper, ZookeeperReadV2, 
Constants, Collections, ClusterV2, SystemV2, Metrics, MetricsExtractor, 
ZookeeperStatus, ApiErrorHandler) {
 
         $scope.showDebug = false;
 
@@ -37,7 +37,7 @@ solrAdminApp.controller('CloudController',
             graphSubController($scope, $timeout, Zookeeper, ClusterV2, 
ApiErrorHandler);
         } else if (view === "nodes") {
             $scope.resetMenu("cloud-nodes", Constants.IS_ROOT_PAGE);
-            nodesSubController($scope, $timeout, ClusterV2, CollectionsV2, 
SystemV2, Metrics, MetricsExtractor, ApiErrorHandler);
+            nodesSubController($scope, $timeout, Collections, ClusterV2, 
SystemV2, Metrics, MetricsExtractor, ApiErrorHandler);
         } else if (view === "zkstatus") {
             $scope.resetMenu("cloud-zkstatus", Constants.IS_ROOT_PAGE);
             zkStatusSubController($scope, ZookeeperStatus, false);
@@ -107,7 +107,7 @@ function isNumeric(n) {
   return !isNaN(parseFloat(n)) && isFinite(n);
 }
 
-var nodesSubController = function($scope, $timeout, ClusterV2, CollectionsV2, 
SystemV2, Metrics, MetricsExtractor, ApiErrorHandler) {
+var nodesSubController = function($scope, $timeout, Collections, ClusterV2, 
SystemV2, Metrics, MetricsExtractor, ApiErrorHandler) {
   $scope.pageSize = 10;
   $scope.showNodes = true;
   $scope.showTree = false;
@@ -178,74 +178,70 @@ var nodesSubController = function($scope, $timeout, 
ClusterV2, CollectionsV2, Sy
     var live_nodes = [];
 
     // We build a node-centric view of the cluster state which we can easily 
consume to render the table
-    CollectionsV2.listCollections({detailed: true}, function (error, data, 
response) {
-      $timeout(function() {
-        if (error) { ApiErrorHandler.handle(response); return; }
-
-        // Fetch cluster state from CollectionsApi and invert to a nodes 
structure
-        for (var name in data.collectionsDetail) {
-          var collection = data.collectionsDetail[name];
-          collection.name = name;
-          var shards = collection.shards;
-          collection.shards = [];
-          for (var shardName in shards) {
-            var shard = shards[shardName];
-            shard.name = shardName;
-            shard.collection = collection.name;
-            var replicas = shard.replicas;
-            shard.replicas = [];
-            for (var replicaName in replicas) {
-              var core = replicas[replicaName];
-              core.name = replicaName;
-              core.replica = core['core'].replace(/.*_(replica_.*)$/, '\$1');
-              core.collection = collection.name;
-              core.shard = shard.name;
-              core.shard_state = shard.state;
-              core.label = core['collection'] + "_"
-                + (core['shard'] + "_").replace(/shard(\d+)_/, 's\$1')
-                + core['replica'].replace(/replica_?[ntp]?(\d+)/, 'r\$1');
-
-              var node_name = core['node_name'];
-              var node = getOrCreateObj(node_name, nodes);
-              var cores = getOrCreateList("cores", node);
-              cores.push(core);
-              node['base_url'] = core.base_url;
-              node['id'] = core.base_url.replace(/[^\w\d]/g, '');
-              node['host'] = node_name.split(":")[0];
-              var collections = getOrCreateList("collections", node);
-              ensureInList(core.collection, collections);
-              ensureNodeInHosts(node_name, hosts);
-            }
+    Collections.status(function (data) {
+      // Fetch cluster state from collections API and invert to a nodes 
structure
+      for (var name in data.cluster.collections) {
+        var collection = data.cluster.collections[name];
+        collection.name = name;
+        var shards = collection.shards;
+        collection.shards = [];
+        for (var shardName in shards) {
+          var shard = shards[shardName];
+          shard.name = shardName;
+          shard.collection = collection.name;
+          var replicas = shard.replicas;
+          shard.replicas = [];
+          for (var replicaName in replicas) {
+            var core = replicas[replicaName];
+            core.name = replicaName;
+            core.replica = core['core'].replace(/.*_(replica_.*)$/, '\$1');
+            core.collection = collection.name;
+            core.shard = shard.name;
+            core.shard_state = shard.state;
+            core.label = core['collection'] + "_"
+              + (core['shard'] + "_").replace(/shard(\d+)_/, 's\$1')
+              + core['replica'].replace(/replica_?[ntp]?(\d+)/, 'r\$1');
+
+            var node_name = core['node_name'];
+            var node = getOrCreateObj(node_name, nodes);
+            var cores = getOrCreateList("cores", node);
+            cores.push(core);
+            node['base_url'] = core.base_url;
+            node['id'] = core.base_url.replace(/[^\w\d]/g, '');
+            node['host'] = node_name.split(":")[0];
+            var collections = getOrCreateList("collections", node);
+            ensureInList(core.collection, collections);
+            ensureNodeInHosts(node_name, hosts);
           }
         }
+      }
 
-        ClusterV2.listClusterNodes(function (error, nodesData, response) {
-          $timeout(function() {
-            if (error) { ApiErrorHandler.handle(response); return; }
-
-            live_nodes = nodesData.nodes;
-            for (n in live_nodes) {
-              node = live_nodes[n];
-              if (!(node in nodes)) {
-                var hostName = node.split(":")[0];
-                nodes[node] = {};
-                nodes[node]['host'] = hostName;
-              }
-              ensureNodeInHosts(node, hosts);
+      ClusterV2.listClusterNodes(function (error, nodesData, response) {
+        $timeout(function() {
+          if (error) { ApiErrorHandler.handle(response); return; }
+
+          live_nodes = nodesData.nodes;
+          for (n in live_nodes) {
+            node = live_nodes[n];
+            if (!(node in nodes)) {
+              var hostName = node.split(":")[0];
+              nodes[node] = {};
+              nodes[node]['host'] = hostName;
             }
+            ensureNodeInHosts(node, hosts);
+          }
 
-            // Make sure nodes are sorted alphabetically to align with rowspan 
in table
-            for (var host in hosts) {
-              hosts[host].nodes.sort();
-            }
+          // Make sure nodes are sorted alphabetically to align with rowspan 
in table
+          for (var host in hosts) {
+            hosts[host].nodes.sort();
+          }
 
-            $scope.nodes = nodes;
-            $scope.hosts = hosts;
-            $scope.live_nodes = live_nodes;
+          $scope.nodes = nodes;
+          $scope.hosts = hosts;
+          $scope.live_nodes = live_nodes;
 
-            $scope.Math = window.Math;
-            $scope.reload();
-          });
+          $scope.Math = window.Math;
+          $scope.reload();
         });
       });
     });
diff --git a/solr/webapp/web/js/angular/controllers/collection-overview.js 
b/solr/webapp/web/js/angular/controllers/collection-overview.js
index 9292da4f7c6..d1834b2bd1e 100644
--- a/solr/webapp/web/js/angular/controllers/collection-overview.js
+++ b/solr/webapp/web/js/angular/controllers/collection-overview.js
@@ -16,17 +16,14 @@
 */
 
 solrAdminApp.controller('CollectionOverviewController',
-function($scope, $routeParams, $timeout, CollectionsV2, Constants, 
ApiErrorHandler) {
+function($scope, $routeParams, Collections, Constants) {
   $scope.resetMenu("collection-overview", Constants.IS_COLLECTION_PAGE);
 
   $scope.refresh = function() {
-    CollectionsV2.listCollections({detailed: true, collection: 
$routeParams.core}, function(error, data, response) {
-      $timeout(function() {
-        if (error) { ApiErrorHandler.handle(response); return; }
-        $scope.selectedCollection = data.collectionsDetail[$routeParams.core];
-        $scope.selectedCollection.name = $routeParams.core;
-        $scope.rootUrl = Constants.ROOT_URL;
-      });
+    Collections.status({}, function(data) {
+      $scope.selectedCollection = data.cluster.collections[$routeParams.core];
+      $scope.selectedCollection.name = $routeParams.core;
+      $scope.rootUrl = Constants.ROOT_URL;
     });
   };
 
diff --git a/solr/webapp/web/js/angular/controllers/collections.js 
b/solr/webapp/web/js/angular/controllers/collections.js
index 17707f34243..1c93b088b5e 100644
--- a/solr/webapp/web/js/angular/controllers/collections.js
+++ b/solr/webapp/web/js/angular/controllers/collections.js
@@ -16,7 +16,7 @@
 */
 
 solrAdminApp.controller('CollectionsController',
-    function($scope, $routeParams, $location, $timeout, CollectionsV2, 
AliasesV2, ShardsV2, ReplicasV2, ConfigSetsV2, ClusterV2, Constants, 
ApiErrorHandler){
+    function($scope, $routeParams, $location, $timeout, Collections, 
CollectionsV2, AliasesV2, ShardsV2, ReplicasV2, ConfigSetsV2, ClusterV2, 
Constants, ApiErrorHandler){
       $scope.resetMenu("collections", Constants.IS_ROOT_PAGE);
 
       $scope.refresh = function() {
@@ -30,15 +30,13 @@ solrAdminApp.controller('CollectionsController',
             });
           });
 
-          CollectionsV2.listCollections({detailed: true}, function (error, 
data, response) {
-            $timeout(function() {
-              if (error) { ApiErrorHandler.handle(response); return; }
+          Collections.status(function (data) {
               $scope.collections = [];
-              for (var name in data.collectionsDetail) {
+              for (var name in data.cluster.collections) {
                   if (name.startsWith("._designer_")) {
                       continue;
                   }
-                  var collection = data.collectionsDetail[name];
+                  var collection = data.cluster.collections[name];
                   collection.name = name;
                   collection.type = 'collection';
                   var shards = collection.shards;
@@ -87,7 +85,6 @@ solrAdminApp.controller('CollectionsController',
                       }
                   });
               });
-            });
           });
           ConfigSetsV2.listConfigSet(function(error, data, response) {
               $timeout(function() {
diff --git a/solr/webapp/web/js/angular/services.js 
b/solr/webapp/web/js/angular/services.js
index 462d684121e..c0b525b1698 100644
--- a/solr/webapp/web/js/angular/services.js
+++ b/solr/webapp/web/js/angular/services.js
@@ -166,6 +166,16 @@ solrAdminServices.factory('Metrics',
       delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"];
       return new solrApi.ConfigApi();
     })
+.factory('Collections',
+  ['$resource', function ($resource) {
+    // v2 ClusterAPI (/api/cluster) delegates straight through to the same v1 
CollectionsHandler
+    // that v1's CLUSTERSTATUS action used, so the response shape is 
byte-identical -- no
+    // generated solrApi client class exists for it (old-style @EndPoint API, 
predates the
+    // OpenAPI-based v2 framework), so this stays a plain $resource, like 
ParamSet.
+    return $resource('/api/cluster', {'wt':'json', '_':Date.now()}, {
+      "status": {}
+    });
+  }])
 .factory('ConfigSetFiles',
  ['$http', function ($http) {
     // Fetches a single file from a configset via V2 
/api/configsets/{name}/files/{path}.
@@ -266,7 +276,7 @@ solrAdminServices.factory('Metrics',
     // v2 GetConfigAPI/ModifyParamSetAPI 
(/api/(cores|collections)/:core/config/params) still
     // delegate straight through to the same v1 SolrConfigHandler, so the 
response shape is
     // byte-identical -- no generated solrApi client class exists for it 
(old-style @EndPoint API,
-    // predates the OpenAPI-based v2 framework), so this stays a plain 
$resource.
+    // predates the OpenAPI-based v2 framework), so this stays a plain 
$resource, like Collections.
     // NB: unlike v1's flexible routing, the v2 API requires knowing up front 
whether ":core" is a
     // collection name (SolrCloud) or an actual core name 
(standalone/user-managed) --
     // /api/collections/... 500s in standalone mode (it tries to resolve 
aliases, which needs ZK),
@@ -385,7 +395,7 @@ solrAdminServices.factory('Metrics',
      // body (the server deliberately reads the raw content stream, dispatched 
by Content-Type,
      // rather than a formal parameter) and query() takes no query params at 
all (the server
      // forwards arbitrary SolrParams straight through). Both stay on this 
plain $resource, like
-     // ParamSet. Every other Schema Designer endpoint uses SchemaDesignerV2.
+     // Collections/ParamSet. Every other Schema Designer endpoint uses 
SchemaDesignerV2.
      return $resource('/api/schema-designer/:configSet/:path', {wt: 'json', 
path: '@path', configSet: '@configSet', filePath: '@filePath', _:Date.now()}, {
        get: {method: "GET"},
        post: {method: "POST", timeout: 90000},

Reply via email to