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

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

commit f96c4b3cfd3cc40f3f4e5754ae3338ad58616df8
Author: Eric Pugh <[email protected]>
AuthorDate: Wed Aug 26 14:42:23 2026 -0400

    SOLR-15752: Migrate admin UI to v2 apis (#4659)
    
    Co-authored-by: Claude Sonnet 5 <[email protected]>
    (cherry picked from commit 6df6cb732725b247e4e039778164f55163c4e91c)
---
 solr/webapp/web/js/angular/app.js                  | 173 ++++++++++--------
 solr/webapp/web/js/angular/controllers/cloud.js    |  88 +++++----
 .../web/js/angular/controllers/collections.js      | 203 ++++++++++++---------
 solr/webapp/web/js/angular/controllers/cores.js    | 144 +++++++++------
 solr/webapp/web/js/angular/controllers/index.js    | 195 ++++++++++----------
 solr/webapp/web/js/angular/controllers/logging.js  | 104 ++++++-----
 solr/webapp/web/js/angular/controllers/login.js    |   2 +
 .../webapp/web/js/angular/controllers/paramsets.js |  36 +++-
 solr/webapp/web/js/angular/controllers/query.js    |  12 +-
 solr/webapp/web/js/angular/controllers/security.js |  40 ++--
 solr/webapp/web/js/angular/controllers/segments.js |  52 +++---
 solr/webapp/web/js/angular/services.js             | 182 ++++++++++++------
 12 files changed, 729 insertions(+), 502 deletions(-)

diff --git a/solr/webapp/web/js/angular/app.js 
b/solr/webapp/web/js/angular/app.js
index 1ff0cb824f2..27a2f458682 100644
--- a/solr/webapp/web/js/angular/app.js
+++ b/solr/webapp/web/js/angular/app.js
@@ -478,6 +478,19 @@ solrAdminApp.config([
   $httpProvider.interceptors.push("httpInterceptor");
   // Force BasicAuth plugin to serve us a 'Authorization: xBasic xxxx' header 
so browser will not pop up login dialogue
   $httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
+  // The V2 solrApi client (superagent-based) never goes through 
$httpProvider, so X-Requested-With
+  // (for the same popup-suppression trick) needs its own copy here.
+  solrApi.ApiClient.instance.defaultHeaders['X-Requested-With'] = 
'XMLHttpRequest';
+  // Authorization is set via a superagent plugin instead of defaultHeaders, 
so it's re-read from
+  // sessionStorage on every request -- same as $http gets via 
httpInterceptor.started above -- and
+  // there's a single place to keep in sync (nowhere else should touch
+  // solrApi.ApiClient.instance.defaultHeaders['Authorization']).
+  solrApi.ApiClient.instance.plugins = [function(request) {
+    if (sessionStorage.getItem("auth.header")) {
+      request.set('Authorization', sessionStorage.getItem("auth.header"));
+    }
+    return request;
+  }];
   // Suppress AngularJS 1.6+ "Possibly unhandled rejection" console noise; 
errors are handled via callbacks and the security/schema-designer error dialogs
   $qProvider.errorOnUnhandledRejections(false);
 })
@@ -497,7 +510,7 @@ solrAdminApp.config([
     };
 });
 
-solrAdminApp.controller('MainController', function($scope, $route, $rootScope, 
$location, Cores, Collections, System, Ping, Constants, SchemaDesigner) {
+solrAdminApp.controller('MainController', function($scope, $route, $rootScope, 
$location, $timeout, CoresV2, CollectionsV2, AliasesV2, SystemV2, Ping, 
Constants, SchemaDesigner, ApiErrorHandler) {
 
   $rootScope.exceptions={};
 
@@ -518,86 +531,98 @@ solrAdminApp.controller('MainController', 
function($scope, $route, $rootScope, $
 
   $scope.refresh();
   $scope.resetMenu = function(page, pageType) {
-    Cores.list(function(data) {
-      $scope.cores = [];
-      var currentCoreName = $route.current.params.core;
-      delete $scope.currentCore;
-      for (key in data.status) {
-        var core = data.status[key];
-        if (core.name.startsWith("._designer_")) {
-          continue;
-        }
-        $scope.cores.push(core);
-        if ((!$scope.isSolrCloud || pageType == Constants.IS_CORE_PAGE) && 
core.name == currentCoreName) {
-            $scope.currentCore = core;
+    CoresV2.getAllCoreStatus({indexInfo: false}, function(error, data, 
response) {
+      $timeout(function() {
+        if (error) { ApiErrorHandler.handle(response); return; }
+        $scope.cores = [];
+        var currentCoreName = $route.current.params.core;
+        delete $scope.currentCore;
+        for (var key in data.status) {
+          var core = data.status[key];
+          if (core.name.startsWith("._designer_")) {
+            continue;
+          }
+          $scope.cores.push(core);
+          if ((!$scope.isSolrCloud || pageType == Constants.IS_CORE_PAGE) && 
core.name == currentCoreName) {
+              $scope.currentCore = core;
+          }
         }
-      }
-      $scope.showInitFailures = Object.keys(data.initFailures).length>0;
-      $scope.initFailures = data.initFailures;
+        $scope.showInitFailures = Object.keys(data.initFailures).length>0;
+        $scope.initFailures = data.initFailures;
+      });
     });
 
-    System.get(function(data) {
-      $scope.isCloudEnabled = data.mode.match( /solrcloud/i );
-      $scope.usersPermissions = data.security.permissions;
-      $scope.isSecurityEnabled = $scope.authenticationPlugin != null;
-
-      $scope.isSchemaDesignerEnabled = $scope.isPermitted([
-        permissions.CONFIG_EDIT_PERM,
-        permissions.SCHEMA_EDIT_PERM,
-        permissions.READ_PERM,
-        permissions.UPDATE_PERM
-      ]);
-
-      var currentCollectionName = $route.current.params.core;
-      delete $scope.currentCollection;
-      if ($scope.isCloudEnabled) {
-        Collections.list(function (cdata) {
-          Collections.listaliases(function (adata) {
-            $scope.aliases = [];
-            for (var key in adata.aliases) {
-              props = {};
-              if (key in adata.properties) {
-                props = adata.properties[key];
-              }
-              var alias = {name: key, collections: adata.aliases[key], type: 
'alias', properties: props};
-              $scope.aliases.push(alias);
-              if (pageType == Constants.IS_COLLECTION_PAGE && alias.name == 
currentCollectionName) {
-                $scope.currentCollection = alias;
-              }
-            }
-            $scope.collections = [];
-            for (key in cdata.collections) {
-              if (cdata.collections[key].startsWith("._designer_")) {
-                continue; // ignore temp designer collections
-              }
-              var collection = {name: cdata.collections[key], type: 
'collection'};
-              $scope.collections.push(collection);
-              if (pageType == Constants.IS_COLLECTION_PAGE && collection.name 
== currentCollectionName) {
-                $scope.currentCollection = collection;
-              }
-            }
+    SystemV2.getNodeSystemInfo({}, function(error, data, response) {
+      $timeout(function() {
+        if (error) { ApiErrorHandler.handle(response); return; }
+        $scope.isCloudEnabled = data.mode.match( /solrcloud/i );
+        $scope.usersPermissions = data.security.permissions;
+        $scope.isSecurityEnabled = data.security.authenticationPlugin != null;
+
+        $scope.isSchemaDesignerEnabled = $scope.isPermitted([
+          permissions.CONFIG_EDIT_PERM,
+          permissions.SCHEMA_EDIT_PERM,
+          permissions.READ_PERM,
+          permissions.UPDATE_PERM
+        ]);
+
+        var currentCollectionName = $route.current.params.core;
+        delete $scope.currentCollection;
+        if ($scope.isCloudEnabled) {
+          CollectionsV2.listCollections(function (error, cdata, response) {
+            $timeout(function() {
+              if (error) { ApiErrorHandler.handle(response); return; }
+              AliasesV2.getAliases(function (error, adata, response) {
+                $timeout(function() {
+                  if (error) { ApiErrorHandler.handle(response); return; }
+                  $scope.aliases = [];
+                  for (var key in adata.aliases) {
+                    var props = {};
+                    if (key in adata.properties) {
+                      props = adata.properties[key];
+                    }
+                    var alias = {name: key, collections: adata.aliases[key], 
type: 'alias', properties: props};
+                    $scope.aliases.push(alias);
+                    if (pageType == Constants.IS_COLLECTION_PAGE && alias.name 
== currentCollectionName) {
+                      $scope.currentCollection = alias;
+                    }
+                  }
+                  $scope.collections = [];
+                  for (var key in cdata.collections) {
+                    if (cdata.collections[key].startsWith("._designer_")) {
+                      continue; // ignore temp designer collections
+                    }
+                    var collection = {name: cdata.collections[key], type: 
'collection'};
+                    $scope.collections.push(collection);
+                    if (pageType == Constants.IS_COLLECTION_PAGE && 
collection.name == currentCollectionName) {
+                      $scope.currentCollection = collection;
+                    }
+                  }
 
-            $scope.aliases_and_collections = $scope.aliases;
-            if ($scope.aliases.length > 0) {
-              $scope.aliases_and_collections = 
$scope.aliases_and_collections.concat({name:'-----'});
-            }
-            $scope.aliases_and_collections = 
$scope.aliases_and_collections.concat($scope.collections);
+                  $scope.aliases_and_collections = $scope.aliases;
+                  if ($scope.aliases.length > 0) {
+                    $scope.aliases_and_collections = 
$scope.aliases_and_collections.concat({name:'-----'});
+                  }
+                  $scope.aliases_and_collections = 
$scope.aliases_and_collections.concat($scope.collections);
+                });
+              });
+            });
           });
-        });
-      }
-
-      $scope.showEnvironment = data.environment !== undefined;
-      if (data.environment) {
-        $scope.environment = data.environment;
-        var env_labels = {'prod': 'Production', 'stage': 'Staging', 'test': 
'Test', 'dev': 'Development'};
-        $scope.environment_label = env_labels[data.environment];
-        if (data.environment_label) {
-          $scope.environment_label = data.environment_label;
         }
-        if (data.environment_color) {
-          $scope.environment_color = data.environment_color;
+
+        $scope.showEnvironment = data.environment !== undefined;
+        if (data.environment) {
+          $scope.environment = data.environment;
+          var env_labels = {'prod': 'Production', 'stage': 'Staging', 'test': 
'Test', 'dev': 'Development'};
+          $scope.environment_label = env_labels[data.environment];
+          if (data.environment_label) {
+            $scope.environment_label = data.environment_label;
+          }
+          if (data.environment_color) {
+            $scope.environment_color = data.environment_color;
+          }
         }
-      }
+      });
     });
 
     $scope.showingLogging = page.lastIndexOf("logging", 0) === 0;
diff --git a/solr/webapp/web/js/angular/controllers/cloud.js 
b/solr/webapp/web/js/angular/controllers/cloud.js
index 1a3fb2d26c9..4dcc1324fbd 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, Zookeeper, Constants, Collections, System, 
Metrics, MetricsExtractor, ZookeeperStatus) {
+    function($scope, $location, $timeout, Zookeeper, Constants, Collections, 
SystemV2, Metrics, MetricsExtractor, ZookeeperStatus, ApiErrorHandler) {
 
         $scope.showDebug = false;
 
@@ -37,7 +37,7 @@ solrAdminApp.controller('CloudController',
             graphSubController($scope, Zookeeper, false);
         } else if (view === "nodes") {
             $scope.resetMenu("cloud-nodes", Constants.IS_ROOT_PAGE);
-            nodesSubController($scope, Collections, System, Metrics, 
MetricsExtractor);
+            nodesSubController($scope, $timeout, Collections, 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, Collections, System, Metrics, 
MetricsExtractor) {
+var nodesSubController = function($scope, $timeout, Collections, SystemV2, 
Metrics, MetricsExtractor, ApiErrorHandler) {
   $scope.pageSize = 10;
   $scope.showNodes = true;
   $scope.showTree = false;
@@ -353,43 +353,51 @@ var nodesSubController = function($scope, Collections, 
System, Metrics, MetricsE
      Fetch system info for all selected nodes
      Pick the data we want to display and add it to the node-centric data 
structure
       */
-    System.get({"nodes": liveNodesToShow.join(',')}, function (systemResponse) 
{
-      for (var node in systemResponse) {
-        if (node in nodes) {
-          var s = systemResponse[node];
-          nodes[node]['system'] = s;
-          var memTotal = s.system.totalPhysicalMemorySize;
-          var memFree = s.system.freePhysicalMemorySize;
-          var memPercentage = Math.floor((memTotal - memFree) / memTotal * 
100);
-          nodes[node]['memUsedPct'] = memPercentage;
-          nodes[node]['memUsedPctStyle'] = styleForPct(memPercentage);
-          nodes[node]['memTotal'] = bytesToSize(memTotal);
-          nodes[node]['memFree'] = bytesToSize(memFree);
-          nodes[node]['memUsed'] = bytesToSize(memTotal - memFree);
-
-          var heapMax = s.jvm.memory.raw.max;
-          var heapTotal = s.jvm.memory.raw.total;
-          var heapFree = s.jvm.memory.raw.free;
-          var heapPercentage = Math.floor((heapTotal - heapFree) / heapMax * 
100);
-          nodes[node]['heapUsed'] = bytesToSize(heapTotal - heapFree);
-          nodes[node]['heapUsedPct'] = heapPercentage;
-          nodes[node]['heapUsedPctStyle'] = styleForPct(heapPercentage);
-          nodes[node]['heapMax'] = bytesToSize(heapMax);
-          nodes[node]['heapTotal'] = bytesToSize(heapTotal);
-          nodes[node]['heapFree'] = bytesToSize(heapFree);
-
-          var jvmUptime = s.jvm.jmx.upTimeMS / 1000; // Seconds
-          nodes[node]['jvmUptime'] = secondsForHumans(jvmUptime);
-          nodes[node]['jvmUptimeSec'] = jvmUptime;
-
-          nodes[node]['uptime'] = (s.system.uptime || "unknown").replace(/.*up 
(.*?,.*?),.*/, "$1");
-          nodes[node]['loadAvg'] = Math.round(s.system.systemLoadAverage * 
100) / 100;
-          nodes[node]['cpuPct'] = Math.ceil(s.system.processCpuLoad * 100);
-          nodes[node]['cpuPctStyle'] = 
styleForPct(Math.ceil(s.system.processCpuLoad));
-          nodes[node]['maxFileDescriptorCount'] = 
s.system.maxFileDescriptorCount;
-          nodes[node]['openFileDescriptorCount'] = 
s.system.openFileDescriptorCount;
-        }
+    SystemV2.getNodeSystemInfo({"nodes": liveNodesToShow.join(',')}, function 
(error, data, response) {
+      if (error) {
+        console.error('Failed to fetch node system info:', error);
+        ApiErrorHandler.handle(response);
+        return;
       }
+      $timeout(function() {
+        var systemResponse = response.body;
+        for (var node in systemResponse) {
+          if (node in nodes) {
+            var s = systemResponse[node];
+            nodes[node]['system'] = s;
+            var memTotal = s.system.totalPhysicalMemorySize;
+            var memFree = s.system.freePhysicalMemorySize;
+            var memPercentage = Math.floor((memTotal - memFree) / memTotal * 
100);
+            nodes[node]['memUsedPct'] = memPercentage;
+            nodes[node]['memUsedPctStyle'] = styleForPct(memPercentage);
+            nodes[node]['memTotal'] = bytesToSize(memTotal);
+            nodes[node]['memFree'] = bytesToSize(memFree);
+            nodes[node]['memUsed'] = bytesToSize(memTotal - memFree);
+
+            var heapMax = s.jvm.memory.raw.max;
+            var heapTotal = s.jvm.memory.raw.total;
+            var heapFree = s.jvm.memory.raw.free;
+            var heapPercentage = Math.floor((heapTotal - heapFree) / heapMax * 
100);
+            nodes[node]['heapUsed'] = bytesToSize(heapTotal - heapFree);
+            nodes[node]['heapUsedPct'] = heapPercentage;
+            nodes[node]['heapUsedPctStyle'] = styleForPct(heapPercentage);
+            nodes[node]['heapMax'] = bytesToSize(heapMax);
+            nodes[node]['heapTotal'] = bytesToSize(heapTotal);
+            nodes[node]['heapFree'] = bytesToSize(heapFree);
+
+            var jvmUptime = s.jvm.jmx.upTimeMS / 1000; // Seconds
+            nodes[node]['jvmUptime'] = secondsForHumans(jvmUptime);
+            nodes[node]['jvmUptimeSec'] = jvmUptime;
+
+            nodes[node]['uptime'] = (s.system.uptime || 
"unknown").replace(/.*up (.*?,.*?),.*/, "$1");
+            nodes[node]['loadAvg'] = Math.round(s.system.systemLoadAverage * 
100) / 100;
+            nodes[node]['cpuPct'] = Math.ceil(s.system.processCpuLoad * 100);
+            nodes[node]['cpuPctStyle'] = 
styleForPct(Math.ceil(s.system.processCpuLoad));
+            nodes[node]['maxFileDescriptorCount'] = 
s.system.maxFileDescriptorCount;
+            nodes[node]['openFileDescriptorCount'] = 
s.system.openFileDescriptorCount;
+          }
+        }
+      });
     });
 
     /*
@@ -792,7 +800,7 @@ var graphSubController = function ($scope, Zookeeper) {
                 params.filter = filter;
             }
 
-          Zookeeper.clusterState(params, function (data) { 
+          Zookeeper.clusterState(params, function (data) {
                     var state = data.znode.data;
                     var leaf_count = 0;
                     var graph_data = {
diff --git a/solr/webapp/web/js/angular/controllers/collections.js 
b/solr/webapp/web/js/angular/controllers/collections.js
index 830290a1806..93fd5fb1b6d 100644
--- a/solr/webapp/web/js/angular/controllers/collections.js
+++ b/solr/webapp/web/js/angular/controllers/collections.js
@@ -16,19 +16,18 @@
 */
 
 solrAdminApp.controller('CollectionsController',
-    function($scope, $routeParams, $location, $timeout, Collections, 
CollectionsV2, Zookeeper, Constants, ConfigSets){
+    function($scope, $routeParams, $location, $timeout, Collections, 
CollectionsV2, AliasesV2, ShardsV2, ReplicasV2, ConfigSetsV2, ClusterV2, 
Constants, ApiErrorHandler){
       $scope.resetMenu("collections", Constants.IS_ROOT_PAGE);
 
       $scope.refresh = function() {
 
           $scope.rootUrl = Constants.ROOT_URL + "#/~collections/" + 
$routeParams.collection;
 
-          Zookeeper.liveNodes({}, function(data) {
-            $scope.availableNodeSet = [];
-            var children = data.tree[0].children;
-            for (var child in children) {
-              $scope.availableNodeSet.push(children[child].text);
-            }
+          ClusterV2.listClusterNodes(function(error, data, response) {
+            $timeout(function() {
+              if (error) { ApiErrorHandler.handle(response); return; }
+              $scope.availableNodeSet = data.nodes;
+            });
           });
 
           Collections.status(function (data) {
@@ -62,36 +61,42 @@ solrAdminApp.controller('CollectionsController',
                       $scope.collection = collection;
                   }
               }
-              // Fetch aliases using LISTALIASES to get properties
-              Collections.listaliases(function (adata) {
-                  // TODO: Population of aliases array duplicated in app.js
-                  $scope.aliases = [];
-                  for (var key in adata.aliases) {
-                      props = {};
-                      if (key in adata.properties) {
-                          props = adata.properties[key];
+              // Fetch aliases using getAliases to get properties
+              AliasesV2.getAliases(function (error, adata, response) {
+                  $timeout(function() {
+                      if (error) { ApiErrorHandler.handle(response); return; }
+                      // TODO: Population of aliases array duplicated in app.js
+                      $scope.aliases = [];
+                      for (var key in adata.aliases) {
+                          var props = {};
+                          if (key in adata.properties) {
+                              props = adata.properties[key];
+                          }
+                          var alias = {name: key, collections: 
adata.aliases[key], type: 'alias', properties: props};
+                          $scope.aliases.push(alias);
+                          if ($routeParams.collection == 'alias_' + key) {
+                              $scope.collection = alias;
+                          }
                       }
-                      var alias = {name: key, collections: adata.aliases[key], 
type: 'alias', properties: props};
-                      $scope.aliases.push(alias);
-                      if ($routeParams.collection == 'alias_' + key) {
-                          $scope.collection = alias;
+                      // Decide what is selected in list
+                      if ($routeParams.collection && !$scope.collection) {
+                          alert("No collection or alias called " + 
$routeParams.collection);
+                          $location.path("/~collections");
                       }
-                  }
-                  // Decide what is selected in list
-                  if ($routeParams.collection && !$scope.collection) {
-                      alert("No collection or alias called " + 
$routeParams.collection);
-                      $location.path("/~collections");
-                  }
+                  });
               });
 
               $scope.liveNodes = data.cluster.liveNodes;
           });
-          ConfigSets.configs(function(data) {
-              $scope.configs = [];
-              var items = data.configSets;
-              for (var i in items) {
-                  $scope.configs.push({name: items[i]});
-              }
+          ConfigSetsV2.listConfigSet(function(error, data, response) {
+              $timeout(function() {
+                  if (error) { ApiErrorHandler.handle(response); return; }
+                  $scope.configs = [];
+                  var items = data.configSets;
+                  for (var i in items) {
+                      $scope.configs.push({name: items[i]});
+                  }
+              });
           });
       };
 
@@ -138,17 +143,23 @@ solrAdminApp.controller('CollectionsController',
         for (var i in $scope.aliasCollections) {
           collections.push($scope.aliasCollections[i].name);
         }
-        Collections.createAlias({name: $scope.aliasToCreate, collections: 
collections.join(",")}, function(data) {
-          $scope.cancelCreateAlias();
-          $scope.resetMenu("collections", Constants.IS_ROOT_PAGE);
-          $location.path("/~collections/alias_" + $scope.aliasToCreate);
+        AliasesV2.createAlias({createAliasRequestBody: {name: 
$scope.aliasToCreate, collections: collections}}, function(error, data, 
response) {
+          $timeout(function() {
+            if (error) { ApiErrorHandler.handle(response); return; }
+            $scope.cancelCreateAlias();
+            $scope.resetMenu("collections", Constants.IS_ROOT_PAGE);
+            $location.path("/~collections/alias_" + $scope.aliasToCreate);
+          });
         });
       }
       $scope.deleteAlias = function() {
-        Collections.deleteAlias({name: $scope.collection.name}, function(data) 
{
-          $scope.hideAll();
-          $scope.resetMenu("collections", Constants.IS_ROOT_PAGE);
-          $location.path("/~collections/");
+        AliasesV2.deleteAlias($scope.collection.name, {}, function(error, 
data, response) {
+          $timeout(function() {
+            if (error) { ApiErrorHandler.handle(response); return; }
+            $scope.hideAll();
+            $scope.resetMenu("collections", Constants.IS_ROOT_PAGE);
+            $location.path("/~collections/");
+          });
         });
 
       };
@@ -162,26 +173,29 @@ solrAdminApp.controller('CollectionsController',
           $scope.addMessage = "A collection can't be made up of just PULL 
replicas";
         } else {
             var coll = $scope.newCollection;
-            var params = {
+            var createCollectionParams = {
               name: coll.name,
-              "router.name": coll.routerName,
+              router: {name: coll.routerName},
               numShards: coll.numShards,
-              "collection.configName": coll.configName
+              config: coll.configName
             };
-            if (coll.shards) params.shards = coll.shards;
-            if (coll.routerField) params["router.field"] = coll.routerField;
-            if (coll.createNodeSet) params.createNodeSet = 
coll.createNodeSet.join(",");
+            if (coll.shards) createCollectionParams.shardNames = 
coll.shards.split(",").map(function(s) { return s.trim(); });
+            if (coll.routerField) createCollectionParams.router.field = 
coll.routerField;
+            if (coll.createNodeSet) createCollectionParams.nodeSet = 
coll.createNodeSet;
             if ($scope.replicaTypesChosen()) {
-              params["nrtReplicas"] = coll.nrtReplicas;
-              params["tlogReplicas"] = coll.tlogReplicas;
-              params["pullReplicas"] = coll.pullReplicas;
+              createCollectionParams.nrtReplicas = coll.nrtReplicas;
+              createCollectionParams.tlogReplicas = coll.tlogReplicas;
+              createCollectionParams.pullReplicas = coll.pullReplicas;
             } else {
-              params["replicationFactor"] = coll.replicationFactor;
+              createCollectionParams.replicationFactor = 
coll.replicationFactor;
             }
-            Collections.add(params, function(data) {
-              $scope.cancelAddCollection();
-              $scope.resetMenu("collections", Constants.IS_ROOT_PAGE);
-              $location.path("/~collections/" + $scope.newCollection.name);
+            CollectionsV2.createCollection({createCollectionRequestBody: 
createCollectionParams}, function(error, data, response) {
+              $timeout(function() {
+                if (error) { ApiErrorHandler.handle(response); return; }
+                $scope.cancelAddCollection();
+                $scope.resetMenu("collections", Constants.IS_ROOT_PAGE);
+                $location.path("/~collections/" + $scope.newCollection.name);
+              });
             });
         }
       };
@@ -202,8 +216,11 @@ solrAdminApp.controller('CollectionsController',
 
       $scope.deleteCollection = function() {
         if ($scope.collection.name == $scope.collectionDeleteConfirm) {
-            Collections.delete({name: $scope.collection.name}, function (data) 
{
-                $location.path("/~collections");
+            CollectionsV2.deleteCollection($scope.collection.name, {}, 
function (error, data, response) {
+                $timeout(function() {
+                    if (error) { ApiErrorHandler.handle(response); return; }
+                    $location.path("/~collections");
+                });
             });
         } else {
             $scope.deleteMessage = "Collection names do not match.";
@@ -215,15 +232,17 @@ solrAdminApp.controller('CollectionsController',
             alert("No collection selected.");
             return;
         }
-        CollectionsV2.reloadCollection($scope.collection.name, function(error, 
data,response) {
-           if (error) {
-               $scope.reloadFailure = true;
-               $timeout(function() {$scope.reloadFailure=false}, 1000);
-               $location.path("/~collections");
-           } else {
-               $scope.reloadSuccess = true;
-               $timeout(function() {$scope.reloadSuccess=false}, 1000);
-           }
+        CollectionsV2.reloadCollection($scope.collection.name, {}, 
function(error, data,response) {
+           $timeout(function() {
+             if (error) {
+                 $scope.reloadFailure = true;
+                 $timeout(function() {$scope.reloadFailure=false}, 1000);
+                 $location.path("/~collections");
+             } else {
+                 $scope.reloadSuccess = true;
+                 $timeout(function() {$scope.reloadSuccess=false}, 1000);
+             }
+           });
         });
       };
 
@@ -232,12 +251,11 @@ solrAdminApp.controller('CollectionsController',
           shard.showAdd = !shard.showAdd;
           delete $scope.addReplicaMessage;
 
-          Zookeeper.liveNodes({}, function(data) {
-            $scope.nodes = [];
-            var children = data.tree[0].children;
-            for (var child in children) {
-              $scope.nodes.push(children[child].text);
-            }
+          ClusterV2.listClusterNodes(function(error, data, response) {
+            $timeout(function() {
+              if (error) { ApiErrorHandler.handle(response); return; }
+              $scope.nodes = data.nodes;
+            });
           });
       };
 
@@ -252,38 +270,45 @@ solrAdminApp.controller('CollectionsController',
       };
 
       $scope.deleteShard = function(shard) {
-          Collections.deleteShard({collection: shard.collection, 
shard:shard.name}, function(data) {
-            shard.deleted = true;
+          ShardsV2.deleteShard(shard.collection, shard.name, {}, 
function(error, data, response) {
             $timeout(function() {
-              $scope.refresh();
-            }, 2000);
+              if (error) { ApiErrorHandler.handle(response); return; }
+              shard.deleted = true;
+              $timeout(function() {
+                $scope.refresh();
+              }, 2000);
+            });
           });
         }
 
       $scope.deleteReplica = function(replica) {
-        Collections.deleteReplica({collection: replica.collection, 
shard:replica.shard, replica:replica.name}, function(data) {
-          replica.deleted = true;
+        ReplicasV2.deleteReplicaByName(replica.collection, replica.shard, 
replica.name, {}, function(error, data, response) {
           $timeout(function() {
-            $scope.refresh();
-          }, 2000);
+            if (error) { ApiErrorHandler.handle(response); return; }
+            replica.deleted = true;
+            $timeout(function() {
+              $scope.refresh();
+            }, 2000);
+          });
         });
       }
       $scope.addReplica = function(shard) {
-        var params = {
-          collection: shard.collection,
-          shard: shard.name,
+        var createReplicaParams = {
           type: shard.replicaType
         }
         if (shard.replicaNodeName && shard.replicaNodeName != "") {
-          params.node = shard.replicaNodeName;
+          createReplicaParams.node = shard.replicaNodeName;
         }
-        Collections.addReplica(params, function(data) {
-          shard.replicaAdded = true;
-          $timeout(function () {
-            shard.replicaAdded = false;
-            shard.showAdd = false;
-            $scope.refresh();
-          }, 2000);
+        ReplicasV2.createReplica(shard.collection, shard.name, 
{createReplicaRequestBody: createReplicaParams}, function(error, data, 
response) {
+          $timeout(function() {
+            if (error) { ApiErrorHandler.handle(response); return; }
+            shard.replicaAdded = true;
+            $timeout(function () {
+              shard.replicaAdded = false;
+              shard.showAdd = false;
+              $scope.refresh();
+            }, 2000);
+          });
         });
       };
 
diff --git a/solr/webapp/web/js/angular/controllers/cores.js 
b/solr/webapp/web/js/angular/controllers/cores.js
index 202b6224ca1..c6c6b311488 100644
--- a/solr/webapp/web/js/angular/controllers/cores.js
+++ b/solr/webapp/web/js/angular/controllers/cores.js
@@ -16,35 +16,39 @@
 */
 
 solrAdminApp.controller('CoreAdminController',
-    function($scope, $routeParams, $location, $timeout, $route, Cores, Update, 
Constants){
+    function($scope, $routeParams, $location, $timeout, $route, CoresV2, 
Update, Constants, ApiErrorHandler){
       $scope.resetMenu("cores", Constants.IS_ROOT_PAGE);
       $scope.selectedCore = $routeParams.corename; // use 'corename' not 
'core' to distinguish from /solr/:core/
       $scope.refresh = function() {
-        Cores.get(function(data) {
-          var coreCount = 0;
-          var cores = data.status;
-          for (_obj in cores) coreCount++;
-          $scope.hasCores = coreCount >0;
-          if (!$scope.selectedCore && coreCount==0) {
-            $scope.showAddCore();
-            return;
-          } else if (!$scope.selectedCore) {
-            for (firstCore in cores) break;
-            $scope.selectedCore = firstCore;
-            $location.path("/~cores/" + $scope.selectedCore).replace();
-          }
-          $scope.core = cores[$scope.selectedCore];
-          $scope.corelist = [];
-          $scope.swapCorelist = [];
-          for (var core in cores) {
-             $scope.corelist.push(cores[core]);
-            if (cores[core] != $scope.core) {
-              $scope.swapCorelist.push(cores[core]);
+        CoresV2.getAllCoreStatus({}, function(error, data, response) {
+          $timeout(function() {
+            if (error) { ApiErrorHandler.handle(response); return; }
+            var coreCount = 0;
+            var cores = data.status;
+            for (var _obj in cores) coreCount++;
+            $scope.hasCores = coreCount >0;
+            if (!$scope.selectedCore && coreCount==0) {
+              $scope.showAddCore();
+              return;
+            } else if (!$scope.selectedCore) {
+              var firstCore;
+              for (firstCore in cores) break;
+              $scope.selectedCore = firstCore;
+              $location.path("/~cores/" + $scope.selectedCore).replace();
             }
-          }
-          if ($scope.swapCorelist.length>0) {
-            $scope.swapOther = $scope.swapCorelist[0].name;
-          }
+            $scope.core = cores[$scope.selectedCore];
+            $scope.corelist = [];
+            $scope.swapCorelist = [];
+            for (var core in cores) {
+               $scope.corelist.push(cores[core]);
+              if (cores[core] != $scope.core) {
+                $scope.swapCorelist.push(cores[core]);
+              }
+            }
+            if ($scope.swapCorelist.length>0) {
+              $scope.swapOther = $scope.swapCorelist[0].name;
+            }
+          });
         });
       };
       $scope.showAddCore = function() {
@@ -65,9 +69,9 @@ solrAdminApp.controller('CoreAdminController',
         if (!$scope.newCore.name) {
           $scope.addMessage = "Please provide a core name";
         } else if (false) { //@todo detect whether core exists
-          $scope.AddMessage = "A core with that name already exists";
+          $scope.addMessage = "A core with that name already exists";
         } else {
-          var params = {
+          var createCoreParams = {
             name: $scope.newCore.name,
             instanceDir: $scope.newCore.instanceDir,
             config: $scope.newCore.config,
@@ -75,12 +79,19 @@ solrAdminApp.controller('CoreAdminController',
             dataDir: $scope.newCore.dataDir
           };
           if ($scope.isCloud) {
-            params.collection = $scope.newCore.collection;
-            params.shard = $scope.newCore.shard;
+            createCoreParams.collection = $scope.newCore.collection;
+            createCoreParams.shard = $scope.newCore.shard;
           }
-          Cores.add(params, function(data) {
-            $location.path("/~cores/" + $scope.newCore.name);
-            $scope.cancelAddCore();
+          CoresV2.createCore({createCoreParams: createCoreParams}, 
function(error, data, response) {
+            $timeout(function() {
+              if (error) {
+                ApiErrorHandler.handle(response);
+                $scope.addMessage = "Error creating core: " + error;
+                return;
+              }
+              $location.path("/~cores/" + $scope.newCore.name);
+              $scope.cancelAddCore();
+            });
           });
         }
       };
@@ -93,8 +104,11 @@ solrAdminApp.controller('CoreAdminController',
       $scope.unloadCore = function() {
         var answer = confirm( 'Do you really want to unload Core "' + 
$scope.selectedCore + '"?' );
         if( !answer ) return;
-        Cores.unload({core: $scope.selectedCore}, function(data) {
-          $location.path("/~cores");
+        CoresV2.unloadCore($scope.selectedCore, {}, function(error, data, 
response) {
+          $timeout(function() {
+            if (error) { ApiErrorHandler.handle(response); return; }
+            $location.path("/~cores");
+          });
         });
       };
 
@@ -109,9 +123,16 @@ solrAdminApp.controller('CoreAdminController',
         } else if ($scope.other == $scope.selectedCore) {
           $scope.renameMessage = "New name must be different from the current 
one";
         } else {
-          Cores.rename({core:$scope.selectedCore, other: $scope.other}, 
function(data) {
-            $location.path("/~cores/" + $scope.other);
-            $scope.cancelRename();
+          CoresV2.renameCore($scope.selectedCore, {renameCoreRequestBody: {to: 
$scope.other}}, function(error, data, response) {
+            $timeout(function() {
+              if (error) {
+                ApiErrorHandler.handle(response);
+                $scope.renameMessage = "Error renaming core: " + error;
+                return;
+              }
+              $location.path("/~cores/" + $scope.other);
+              $scope.cancelRenameCore();
+            });
           });
         }
       };
@@ -133,10 +154,17 @@ solrAdminApp.controller('CoreAdminController',
         } else if ($scope.swapOther == $scope.selectedCore) {
           $scope.swapMessage = "Cannot swap with the same core";
         } else {
-          Cores.swap({core: $scope.selectedCore, other: $scope.swapOther}, 
function(data) {
-            $location.path("/~cores/" + $scope.swapOther);
-            delete $scope.swapOther;
-            $scope.cancelSwapCores();
+          CoresV2.swapCores($scope.selectedCore, {swapCoresRequestBody: {with: 
$scope.swapOther}}, function(error, data, response) {
+            $timeout(function() {
+              if (error) {
+                ApiErrorHandler.handle(response);
+                $scope.swapMessage = "Error swapping cores: " + error;
+                return;
+              }
+              $location.path("/~cores/" + $scope.swapOther);
+              delete $scope.swapOther;
+              $scope.cancelSwapCores();
+            });
           });
         }
       };
@@ -149,23 +177,25 @@ solrAdminApp.controller('CoreAdminController',
       $scope.reloadCore = function() {
         if ($scope.initFailures[$scope.selectedCore]) {
           delete $scope.initFailures[$scope.selectedCore];
-          $scope.showInitFailures = Object.keys(data.initFailures).length>0;
+          $scope.showInitFailures = Object.keys($scope.initFailures).length>0;
         }
-        Cores.reload({core: $scope.selectedCore},
-          function(data) {
-            if (data.error) {
-              $scope.reloadFailure = true;
-              $timeout(function() {
-                $scope.reloadFailure = false;
-                $route.reload();
-              }, 1000);
-            } else {
-              $scope.reloadSuccess = true;
-              $timeout(function () {
-                $scope.reloadSuccess = false;
-                $route.reload();
-              }, 1000);
-            }
+        CoresV2.reloadCore($scope.selectedCore,
+          function(error, data, response) {
+            $timeout(function() {
+              if (error) {
+                $scope.reloadFailure = true;
+                $timeout(function() {
+                  $scope.reloadFailure = false;
+                  $route.reload();
+                }, 1000);
+              } else {
+                $scope.reloadSuccess = true;
+                $timeout(function () {
+                  $scope.reloadSuccess = false;
+                  $route.reload();
+                }, 1000);
+              }
+            });
           });
       };
 
diff --git a/solr/webapp/web/js/angular/controllers/index.js 
b/solr/webapp/web/js/angular/controllers/index.js
index eda5ea86c28..553724f1f4a 100644
--- a/solr/webapp/web/js/angular/controllers/index.js
+++ b/solr/webapp/web/js/angular/controllers/index.js
@@ -15,111 +15,114 @@ See the License for the specific language governing 
permissions and
 limitations under the License.
 */
 
-solrAdminApp.controller('IndexController', function($scope, System, Cores, 
Constants) {
+solrAdminApp.controller('IndexController', function($scope, $timeout, 
SystemV2, Constants, ApiErrorHandler) {
   $scope.resetMenu("index", Constants.IS_ROOT_PAGE);
   $scope.reload = function() {
-    System.get(function(data) {
-      $scope.system = data;
-      const releaseDate = 
parse_release_date($scope.system.lucene['solr-impl-version'])
-      $scope.releaseDaysOld = (new Date() - releaseDate)/1000/60/60/24;
-
-      if (data.security.authenticationPlugin) {
-        $scope.isSecurityEnabled = true
-      }
-
-      // load average, unless its negative (means n/a on windows, etc)
-      if (data.system.systemLoadAverage >= 0) {
-        $scope.load_average = data.system.systemLoadAverage.toFixed(2);
-      }
-
-      // physical memory
-      var memoryMax = parse_memory_value(data.system.totalPhysicalMemorySize);
-      $scope.memoryTotal = 
parse_memory_value(data.system.totalPhysicalMemorySize - 
data.system.freePhysicalMemorySize);
-      $scope.memoryPercentage = ($scope.memoryTotal / memoryMax * 
100).toFixed(1)+ "%";
-      $scope.memoryMax = pretty_print_bytes(memoryMax);
-      $scope.memoryTotalDisplay = pretty_print_bytes($scope.memoryTotal);
-
-      // swap space
-      var swapMax = parse_memory_value(data.system.totalSwapSpaceSize);
-      $scope.swapTotal = parse_memory_value(data.system.totalSwapSpaceSize - 
data.system.freeSwapSpaceSize);
-      $scope.swapPercentage = ($scope.swapTotal / swapMax * 100).toFixed(1)+ 
"%";
-      $scope.swapMax = pretty_print_bytes(swapMax);
-      $scope.swapTotalDisplay = pretty_print_bytes($scope.swapTotal);
-
-      // file handles
-      $scope.fileDescriptorPercentage = (data.system.openFileDescriptorCount / 
data.system.maxFileDescriptorCount *100).toFixed(1) + "%";
-
-      // java memory
-      var javaMemoryMax = parse_memory_value(data.jvm.memory.raw.max || 
data.jvm.memory.max);
-      $scope.javaMemoryTotal = parse_memory_value(data.jvm.memory.raw.total || 
data.jvm.memory.total);
-      $scope.javaMemoryUsed = parse_memory_value(data.jvm.memory.raw.used || 
data.jvm.memory.used);
-      $scope.javaMemoryTotalPercentage = ($scope.javaMemoryTotal / 
javaMemoryMax *100).toFixed(1) + "%";
-      $scope.javaMemoryUsedPercentage = ($scope.javaMemoryUsed / 
$scope.javaMemoryTotal *100).toFixed(1) + "%";
-      $scope.javaMemoryPercentage = ($scope.javaMemoryUsed / javaMemoryMax * 
100).toFixed(1) + "%";
-      $scope.javaMemoryTotalDisplay = 
pretty_print_bytes($scope.javaMemoryTotal);
-      $scope.javaMemoryUsedDisplay = 
pretty_print_bytes($scope.javaMemoryUsed);  // @todo These should really be an 
AngularJS Filter: {{ javaMemoryUsed | bytes }}
-      $scope.javaMemoryMax = pretty_print_bytes(javaMemoryMax);
-
-      // GPU
-      $scope.gpuAvailable = data.gpu && data.gpu.available;
-      if ($scope.gpuAvailable) {
-        $scope.gpuCount = data.gpu.count;
-
-        var devices = data.gpu.devices;
-        $scope.gpuDevices = [];
-        if (devices && Object.keys(devices).length > 0) {
-          var deviceKeys = Object.keys(devices);
-          var firstDevice = devices[deviceKeys[0]];
-          $scope.gpuName = firstDevice.name;
-          $scope.gpuId = firstDevice.id;
-          $scope.gpuCompute = firstDevice.computeCapability;
-
-          if (deviceKeys.length > 1) {
-            $scope.gpuName += " (+" + (deviceKeys.length - 1) + " more)";
-          }
+    SystemV2.getNodeSystemInfo({}, function(error, data, response) {
+      $timeout(function() {
+        if (error) { ApiErrorHandler.handle(response); return; }
+        $scope.system = data;
+        const releaseDate = 
parse_release_date($scope.system.lucene['solr-impl-version'])
+        $scope.releaseDaysOld = (new Date() - releaseDate)/1000/60/60/24;
+
+        if (data.security.authenticationPlugin) {
+          $scope.isSecurityEnabled = true
+        }
 
-          for (var i = 0; i < deviceKeys.length; i++) {
-            var device = devices[deviceKeys[i]];
-            var gpuData = {
-              id: device.id,
-              name: device.name,
-              computeCapability: device.computeCapability,
-              totalMemory: device.totalMemory,
-              usedMemory: device.usedMemory,
-              freeMemory: device.freeMemory,
-              active: device.active
-            };
-
-            // Add "(active)" indicator to the name for active GPUs
-            if (gpuData.active) {
-              gpuData.name += " (active)";
+        // load average, unless its negative (means n/a on windows, etc)
+        if (data.system.systemLoadAverage >= 0) {
+          $scope.load_average = data.system.systemLoadAverage.toFixed(2);
+        }
+
+        // physical memory
+        var memoryMax = 
parse_memory_value(data.system.totalPhysicalMemorySize);
+        $scope.memoryTotal = 
parse_memory_value(data.system.totalPhysicalMemorySize - 
data.system.freePhysicalMemorySize);
+        $scope.memoryPercentage = ($scope.memoryTotal / memoryMax * 
100).toFixed(1)+ "%";
+        $scope.memoryMax = pretty_print_bytes(memoryMax);
+        $scope.memoryTotalDisplay = pretty_print_bytes($scope.memoryTotal);
+
+        // swap space
+        var swapMax = parse_memory_value(data.system.totalSwapSpaceSize);
+        $scope.swapTotal = parse_memory_value(data.system.totalSwapSpaceSize - 
data.system.freeSwapSpaceSize);
+        $scope.swapPercentage = ($scope.swapTotal / swapMax * 100).toFixed(1)+ 
"%";
+        $scope.swapMax = pretty_print_bytes(swapMax);
+        $scope.swapTotalDisplay = pretty_print_bytes($scope.swapTotal);
+
+        // file handles
+        $scope.fileDescriptorPercentage = (data.system.openFileDescriptorCount 
/ data.system.maxFileDescriptorCount *100).toFixed(1) + "%";
+
+        // java memory
+        var javaMemoryMax = parse_memory_value(data.jvm.memory.raw.max || 
data.jvm.memory.max);
+        $scope.javaMemoryTotal = parse_memory_value(data.jvm.memory.raw.total 
|| data.jvm.memory.total);
+        $scope.javaMemoryUsed = parse_memory_value(data.jvm.memory.raw.used || 
data.jvm.memory.used);
+        $scope.javaMemoryTotalPercentage = ($scope.javaMemoryTotal / 
javaMemoryMax *100).toFixed(1) + "%";
+        $scope.javaMemoryUsedPercentage = ($scope.javaMemoryUsed / 
$scope.javaMemoryTotal *100).toFixed(1) + "%";
+        $scope.javaMemoryPercentage = ($scope.javaMemoryUsed / javaMemoryMax * 
100).toFixed(1) + "%";
+        $scope.javaMemoryTotalDisplay = 
pretty_print_bytes($scope.javaMemoryTotal);
+        $scope.javaMemoryUsedDisplay = 
pretty_print_bytes($scope.javaMemoryUsed);  // @todo These should really be an 
AngularJS Filter: {{ javaMemoryUsed | bytes }}
+        $scope.javaMemoryMax = pretty_print_bytes(javaMemoryMax);
+
+        // GPU
+        $scope.gpuAvailable = data.gpu && data.gpu.available;
+        if ($scope.gpuAvailable) {
+          $scope.gpuCount = data.gpu.count;
+
+          var devices = data.gpu.devices;
+          $scope.gpuDevices = [];
+          if (devices && Object.keys(devices).length > 0) {
+            var deviceKeys = Object.keys(devices);
+            var firstDevice = devices[deviceKeys[0]];
+            $scope.gpuName = firstDevice.name;
+            $scope.gpuId = firstDevice.id;
+            $scope.gpuCompute = firstDevice.computeCapability;
+
+            if (deviceKeys.length > 1) {
+              $scope.gpuName += " (+" + (deviceKeys.length - 1) + " more)";
             }
 
-            // Only calculate memory display for active GPUs
-            if (gpuData.active && gpuData.totalMemory && gpuData.usedMemory) {
-              var total = parse_memory_value(gpuData.totalMemory);
-              var used = parse_memory_value(gpuData.usedMemory);
-              gpuData.memoryPercentage = (used / total * 100).toFixed(1) + "%";
-              gpuData.totalMemoryDisplay = pretty_print_bytes(total);
-              gpuData.usedMemoryDisplay = pretty_print_bytes(used);
+            for (var i = 0; i < deviceKeys.length; i++) {
+              var device = devices[deviceKeys[i]];
+              var gpuData = {
+                id: device.id,
+                name: device.name,
+                computeCapability: device.computeCapability,
+                totalMemory: device.totalMemory,
+                usedMemory: device.usedMemory,
+                freeMemory: device.freeMemory,
+                active: device.active
+              };
+
+              // Add "(active)" indicator to the name for active GPUs
+              if (gpuData.active) {
+                gpuData.name += " (active)";
+              }
+
+              // Only calculate memory display for active GPUs
+              if (gpuData.active && gpuData.totalMemory && gpuData.usedMemory) 
{
+                var total = parse_memory_value(gpuData.totalMemory);
+                var used = parse_memory_value(gpuData.usedMemory);
+                gpuData.memoryPercentage = (used / total * 100).toFixed(1) + 
"%";
+                gpuData.totalMemoryDisplay = pretty_print_bytes(total);
+                gpuData.usedMemoryDisplay = pretty_print_bytes(used);
+              }
+              $scope.gpuDevices.push(gpuData);
             }
-            $scope.gpuDevices.push(gpuData);
           }
         }
-      }
-
-      // no info bar:
-      $scope.noInfo = !(
-        data.system.totalPhysicalMemorySize && 
data.system.freePhysicalMemorySize &&
-        data.system.totalSwapSpaceSize && data.system.freeSwapSpaceSize &&
-        data.system.openFileDescriptorCount && 
data.system.maxFileDescriptorCount);
-
-      // save a copy of the original commandline args
-      $scope.commandLineArgsUnsorted = [...data.jvm.jmx.commandLineArgs];
-      // get commandline args latest orderby or defaults to "Unsorted"
-      $scope.commandLineOrderBy = 
sessionStorage.getItem("commandline.orderby") || "Unsorted";
-      $scope.showCommandLineArgs();
+
+        // no info bar:
+        $scope.noInfo = !(
+          data.system.totalPhysicalMemorySize && 
data.system.freePhysicalMemorySize &&
+          data.system.totalSwapSpaceSize && data.system.freeSwapSpaceSize &&
+          data.system.openFileDescriptorCount && 
data.system.maxFileDescriptorCount);
+
+        // save a copy of the original commandline args
+        $scope.commandLineArgsUnsorted = [...data.jvm.jmx.commandLineArgs];
+        // get commandline args latest orderby or defaults to "Unsorted"
+        $scope.commandLineOrderBy = 
sessionStorage.getItem("commandline.orderby") || "Unsorted";
+        $scope.showCommandLineArgs();
       });
+    });
   };
   $scope.toggleCommandLineOrder = function() {
     $scope.commandLineOrderBy = ($scope.commandLineOrderBy=="Sorted") ? 
"Unsorted":"Sorted";
diff --git a/solr/webapp/web/js/angular/controllers/logging.js 
b/solr/webapp/web/js/angular/controllers/logging.js
index 6b38afe06ce..2011bf9f7d5 100644
--- a/solr/webapp/web/js/angular/controllers/logging.js
+++ b/solr/webapp/web/js/angular/controllers/logging.js
@@ -24,52 +24,55 @@ var format_time_content = function( time, timeZone ) {
 }
 
 solrAdminApp.controller('LoggingController',
-  function($scope, $timeout, $cookies, Logging, Constants){
+  function($scope, $timeout, $cookies, LoggingV2, Constants, ApiErrorHandler){
     $scope.resetMenu("logging", Constants.IS_ROOT_PAGE);
     $scope.timezone = $cookies.logging_timezone || "Local";
     $scope.refresh = function() {
-      Logging.events(function(data) {
-        $scope.since = new Date();
-        $scope.sinceDisplay = format_time_content($scope.since, "Local");
-        var events = data.history.docs;
-        for (var i=0; i<events.length; i++) {
-          var event = events[i];
-          var time = new Date(event.time);
-          event.local_time = format_time_content(time, "Local");
-          event.utc_time = format_time_content(time, "UTC");
-          event.loggerBase = event.logger.split( '.' ).pop();
+      LoggingV2.fetchLocalLogMessages({since: 0}, function(error, data, 
response) {
+        $timeout(function() {
+          if (error) { ApiErrorHandler.handle(response); return; }
+          $scope.since = new Date();
+          $scope.sinceDisplay = format_time_content($scope.since, "Local");
+          var events = data.history;
+          for (var i=0; i<events.length; i++) {
+            var event = events[i];
+            var time = new Date(event.time);
+            event.local_time = format_time_content(time, "Local");
+            event.utc_time = format_time_content(time, "UTC");
+            event.loggerBase = event.logger.split( '.' ).pop();
 
-          if( !event.trace ) {
-            var lines = event.message.split( "\n" );
-            if( lines.length > 1) {
-              event.trace = event.message;
-              event.message = lines[0];
+            if( !event.trace ) {
+              var lines = event.message.split( "\n" );
+              if( lines.length > 1) {
+                event.trace = event.message;
+                event.message = lines[0];
+              }
             }
+            event.message = event.message.replace(/,/g, ',&#8203;');
+            event.showTrace = false;
           }
-          event.message = event.message.replace(/,/g, ',&#8203;');
-          event.showTrace = false;
-        }
-        $scope.events = events;
-        $scope.watcher = data.watcher;
-        /* @todo sticky_mode
-        // state element is in viewport
-        sticky_mode = ( state.position().top <= $( window ).scrollTop() + $( 
window ).height() - ( $( 'body' ).height() - state.position().top ) );
-        // initial request
-        if( 0 === since ) {
-          sticky_mode = true;
-        }
-        $scope.loggingEvents = events;
+          $scope.events = events;
+          $scope.watcher = data.watcher;
+          /* @todo sticky_mode
+          // state element is in viewport
+          sticky_mode = ( state.position().top <= $( window ).scrollTop() + $( 
window ).height() - ( $( 'body' ).height() - state.position().top ) );
+          // initial request
+          if( 0 === since ) {
+            sticky_mode = true;
+          }
+          $scope.loggingEvents = events;
 
-        if( sticky_mode )
-        {
-          $( 'body' )
-            .animate
-            (
-                { scrollTop: state.position().top },
-                1000
-            );
-        }
-      */
+          if( sticky_mode )
+          {
+            $( 'body' )
+              .animate
+              (
+                  { scrollTop: state.position().top },
+                  1000
+              );
+          }
+        */
+        });
       });
       $scope.timeout = $timeout($scope.refresh, 10000);
       var onRouteChangeOff = $scope.$on('$routeChangeStart', function() {
@@ -98,7 +101,7 @@ solrAdminApp.controller('LoggingController',
 )
 
 .controller('LoggingLevelController',
-  function($scope, Logging) {
+  function($scope, $timeout, Logging, LoggingV2, ApiErrorHandler) {
     $scope.resetMenu("logging-levels");
 
     var packageOf = function(logger) {
@@ -123,13 +126,16 @@ solrAdminApp.controller('LoggingController',
     };
 
     $scope.refresh = function() {
-      Logging.levels(function(data) {
-        $scope.logging = makeTree(data.loggers, "");
-        $scope.watcher = data.watcher;
-        $scope.levels = [];
-        for (level in data.levels) {
-          $scope.levels.push({name:data.levels[level], pos:level});
-        }
+      LoggingV2.listAllLoggersAndLevels(function(error, data, response) {
+        $timeout(function() {
+          if (error) { ApiErrorHandler.handle(response); return; }
+          $scope.logging = makeTree(data.loggers, "");
+          $scope.watcher = data.watcher;
+          $scope.levels = [];
+          for (var level in data.levels) {
+            $scope.levels.push({name:data.levels[level], pos:level});
+          }
+        });
       });
     };
 
@@ -155,6 +161,10 @@ solrAdminApp.controller('LoggingController',
       }
       var setString = logger.name + ":" + newLevel;
       logger.showOptions = false;
+      // Intentionally still v1 (Logging, not LoggingV2): this relies on the 
"nodes=all" param to
+      // 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();
       });
diff --git a/solr/webapp/web/js/angular/controllers/login.js 
b/solr/webapp/web/js/angular/controllers/login.js
index aead23a4e37..02fa6c3b6d6 100644
--- a/solr/webapp/web/js/angular/controllers/login.js
+++ b/solr/webapp/web/js/angular/controllers/login.js
@@ -147,6 +147,8 @@ solrAdminApp.controller('LoginController',
               }
 
               if (errorText === "") {
+                // The V2 solrApi client picks these up automatically via the 
superagent plugin
+                // registered in app.js .config()                
                 sessionStorage.setItem("auth.username", payload['sub']);
                 sessionStorage.setItem("auth.header", "Bearer " + accessToken);
                 sessionStorage.removeItem("auth.statusText");
diff --git a/solr/webapp/web/js/angular/controllers/paramsets.js 
b/solr/webapp/web/js/angular/controllers/paramsets.js
index e18870b3ce3..d466c31a8a6 100644
--- a/solr/webapp/web/js/angular/controllers/paramsets.js
+++ b/solr/webapp/web/js/angular/controllers/paramsets.js
@@ -33,6 +33,13 @@ solrAdminApp.controller('ParamSetsController',
       }
     }
 
+    // The v2 config/params API needs to know up front whether ":core" is a 
collection name
+    // (SolrCloud) or an actual core name (standalone/user-managed) -- there's 
no equivalent of
+    // v1's flexible core-or-collection routing.
+    $scope.paramSetIndexType = function() {
+      return $scope.isCloudEnabled ? "collections" : "cores";
+    }
+
     $scope.selectParamset = function() {
       $location.search("paramset", $scope.name);
       $scope.getParamset($scope.name);
@@ -43,6 +50,7 @@ solrAdminApp.controller('ParamSetsController',
 
       var params = {};
       params.core = $routeParams.core;
+      params.indexType = $scope.paramSetIndexType();
       params.wt = "json";
       params.name = paramset;
 
@@ -76,6 +84,7 @@ solrAdminApp.controller('ParamSetsController',
 
       var params = {};
       params.core = $routeParams.core;
+      params.indexType = $scope.paramSetIndexType();
       params.wt = "json";
 
       ParamSet.get(params, callback, failure);
@@ -95,11 +104,18 @@ solrAdminApp.controller('ParamSetsController',
       }
     }
 
-    $scope.getParamsets();
-    if ($routeParams.paramset){
-      $scope.name = $routeParams.paramset;
-      $scope.getParamset($routeParams.paramset);
-    }
+    // resetMenu() above populates $scope.isCloudEnabled asynchronously, so 
calling
+    // getParamsets() synchronously here would race it and fall back to 
indexType "cores"
+    // even in SolrCloud -- wait for isCloudEnabled to settle before the first 
fetch.
+    var unwatchCloudEnabled = $scope.$watch('isCloudEnabled', function(value) {
+      if (value === undefined) return;
+      unwatchCloudEnabled();
+      $scope.getParamsets();
+      if ($routeParams.paramset){
+        $scope.name = $routeParams.paramset;
+        $scope.getParamset($routeParams.paramset);
+      }
+    });
 
     $scope.refresh = function () {
       $scope.paramsetContent = "";
@@ -108,9 +124,17 @@ solrAdminApp.controller('ParamSetsController',
     $scope.refresh();
 
     $scope.submit = function () {
+      if (!$scope.paramsetContent || !$scope.paramsetContent.trim()) {
+        $scope.responseStatus = "error";
+        $scope.response = "Please enter a Paramset(s) JSON payload before 
submitting. " +
+          "(Checking \"Sample Paramset\" only shows an example -- copy it into 
the text area to use it.)";
+        return;
+      }
+
       var params = {};
 
       params.core = $routeParams.core;
+      params.indexType = $scope.paramSetIndexType();
       params.wt = "json";
 
       ParamSet.submit(params, $scope.paramsetContent, callback, failure);
@@ -133,8 +157,8 @@ solrAdminApp.controller('ParamSetsController',
       var params = {};
 
       params.core = $routeParams.core;
+      params.indexType = $scope.paramSetIndexType();
       params.wt = "json";
-      params.name = $scope.name;
 
       var apiPayload = {
         "delete": [$scope.name]
diff --git a/solr/webapp/web/js/angular/controllers/query.js 
b/solr/webapp/web/js/angular/controllers/query.js
index 33294b81097..1d135e2069c 100644
--- a/solr/webapp/web/js/angular/controllers/query.js
+++ b/solr/webapp/web/js/angular/controllers/query.js
@@ -30,12 +30,22 @@ solrAdminApp.controller('QueryController',
     $scope.val['indent'] = true;
     $scope.useParams = [];
 
-    getParamsets();
+    // isCloudEnabled populates asynchronously via resetMenu(), so calling 
getParamsets()
+    // synchronously here would race it and fall back to indexType "cores" 
even in SolrCloud --
+    // wait for isCloudEnabled to settle before the first fetch (see 
paramsets.js for the same fix).
+    var unwatchCloudEnabled = $scope.$watch('isCloudEnabled', function(value) {
+      if (value === undefined) return;
+      unwatchCloudEnabled();
+      getParamsets();
+    });
 
     function getParamsets() {
 
       var params = {};
       params.core = $routeParams.core;
+      // The v2 config/params API needs to know up front whether ":core" is a 
collection name
+      // (SolrCloud) or an actual core name (standalone/user-managed); see 
paramsets.js for details.
+      params.indexType = $scope.isCloudEnabled ? "collections" : "cores";
       params.wt = "json";
 
       ParamSet.get(params, callback, failure);
diff --git a/solr/webapp/web/js/angular/controllers/security.js 
b/solr/webapp/web/js/angular/controllers/security.js
index 9dec02f8d02..1bbfd3511ab 100644
--- a/solr/webapp/web/js/angular/controllers/security.js
+++ b/solr/webapp/web/js/angular/controllers/security.js
@@ -15,7 +15,7 @@
  limitations under the License.
 */
 
-solrAdminApp.controller('SecurityController', function ($scope, $timeout, 
$cookies, $window, Constants, System, Security) {
+solrAdminApp.controller('SecurityController', function ($scope, $timeout, 
$cookies, $window, Constants, SystemV2, Security, ApiErrorHandler) {
   $scope.resetMenu("security", Constants.IS_ROOT_PAGE);
 
   $scope.params = [];
@@ -324,21 +324,27 @@ solrAdminApp.controller('SecurityController', function 
($scope, $timeout, $cooki
     $scope.permFilterOptions = [];
     $scope.permFilterTypes = ["", "name", "role", "path", "collection"];
 
-    System.get(function(data) {
-      $scope.tls = data.security ? data.security["tls"] : false;
-      $scope.authenticationPlugin = data.security ? 
data.security["authenticationPlugin"] : null;
-      $scope.authorizationPlugin = data.security ? 
data.security["authorizationPlugin"] : null;
-      $scope.isSecurityAdminEnabled = $scope.authenticationPlugin != null;
-      $scope.isCloudMode = data.mode.match( /solrcloud/i ) != null;
-      $scope.zkHost = $scope.isCloudMode ? data["zkHost"] : "";
-      $scope.solrHome = data["solr_home"];
-      $scope.refreshSecurityPanel();
-    }, function(e) {
-      if (e.status === 401 || e.status === 403) {
-        $scope.isSecurityAdminEnabled = true;
-        $scope.hasSecurityEditPerm = false;
-        $scope.hideAll();
-      }
+    SystemV2.getNodeSystemInfo({}, function(error, data, response) {
+      $timeout(function() {
+        if (error) {
+          if (response && (response.status === 401 || response.status === 
403)) {
+            $scope.isSecurityAdminEnabled = true;
+            $scope.hasSecurityEditPerm = false;
+            $scope.hideAll();
+          } else {
+            ApiErrorHandler.handle(response);
+          }
+          return;
+        }
+        $scope.tls = data.security ? data.security["tls"] : false;
+        $scope.authenticationPlugin = data.security ? 
data.security["authenticationPlugin"] : null;
+        $scope.authorizationPlugin = data.security ? 
data.security["authorizationPlugin"] : null;
+        $scope.isSecurityAdminEnabled = $scope.authenticationPlugin != null;
+        $scope.isCloudMode = data.mode.match( /solrcloud/i ) != null;
+        $scope.zkHost = $scope.isCloudMode ? data["zkHost"] : "";
+        $scope.solrHome = data["solr_home"];
+        $scope.refreshSecurityPanel();
+      });
     });
   };
 
@@ -403,7 +409,7 @@ solrAdminApp.controller('SecurityController', function 
($scope, $timeout, $cooki
       // check for issues with perm config
       $scope.validatePermConfig();
 
-      // use the current user's roles (obtained from System.get) to check if 
they have the security permissions
+      // use the current user's roles (obtained from 
SystemV2.getNodeSystemInfo) to check if they have the security permissions
       // Note: the backend will check too so this is only for display purposes
       $scope.hasSecurityEditPerm = 
$scope.isPermitted(permissions.SECURITY_EDIT_PERM);
       $scope.hasSecurityReadPerm = $scope.hasSecurityEditPerm || 
$scope.isPermitted(permissions.SECURITY_READ_PERM);
diff --git a/solr/webapp/web/js/angular/controllers/segments.js 
b/solr/webapp/web/js/angular/controllers/segments.js
index e835cc08496..7043bae5a98 100644
--- a/solr/webapp/web/js/angular/controllers/segments.js
+++ b/solr/webapp/web/js/angular/controllers/segments.js
@@ -17,41 +17,47 @@
 
 var MB_FACTOR = 1024*1024;
 
-solrAdminApp.controller('SegmentsController', function($scope, $routeParams, 
$interval, Segments, Constants) {
+solrAdminApp.controller('SegmentsController', function($scope, $routeParams, 
$interval, $timeout, SegmentsV2, Constants, ApiErrorHandler) {
     $scope.resetMenu("segments", Constants.IS_CORE_PAGE);
 
     $scope.refresh = function() {
 
-        Segments.get({core: $routeParams.core}, function(data) {
-            var segments = data.segments;
-
-            var segmentSizeInBytesMax = getLargestSegmentSize(segments);
-            $scope.segmentMB = Math.floor(segmentSizeInBytesMax / MB_FACTOR);
-            $scope.xaxis = calculateXAxis(segmentSizeInBytesMax);
+        SegmentsV2.getSegmentData($routeParams.core, {}, function(error, data, 
response) {
+            if (error) {
+              console.error('Failed to fetch segment data:', error);
+              ApiErrorHandler.handle(response);
+              return;
+            }
+            $timeout(function() {
+              var segments = data.segments;
 
-            $scope.documentCount = 0;
-            $scope.deletionCount = 0;
+              var segmentSizeInBytesMax = getLargestSegmentSize(segments);
+              $scope.segmentMB = Math.floor(segmentSizeInBytesMax / MB_FACTOR);
+              $scope.xaxis = calculateXAxis(segmentSizeInBytesMax);
 
-            $scope.segments = [];
-            for (var name in segments) {
-                var segment = segments[name];
+              $scope.documentCount = 0;
+              $scope.deletionCount = 0;
 
-                var segmentSizeInBytesLog = Math.log(segment.sizeInBytes);
-                var segmentSizeInBytesMaxLog = Math.log(segmentSizeInBytesMax);
+              $scope.segments = [];
+              for (var name in segments) {
+                  var segment = segments[name];
 
-                segment.totalSize = Math.floor((segmentSizeInBytesLog / 
segmentSizeInBytesMaxLog ) * 100);
+                  var segmentSizeInBytesLog = Math.log(segment.sizeInBytes);
+                  var segmentSizeInBytesMaxLog = 
Math.log(segmentSizeInBytesMax);
 
-                segment.deletedDocSize = Math.floor((segment.delCount / 
segment.size) * segment.totalSize);
-                if (segment.delDocSize <= 0.001) delete segment.deletedDocSize;
+                  segment.totalSize = Math.floor((segmentSizeInBytesLog / 
segmentSizeInBytesMaxLog ) * 100);
 
-                segment.aliveDocSize = segment.totalSize - 
segment.deletedDocSize;
+                  segment.deletedDocSize = Math.floor((segment.delCount / 
segment.size) * segment.totalSize);
+                  segment.aliveDocSize = segment.totalSize - 
segment.deletedDocSize;
+                  if (segment.deletedDocSize <= 0.001) delete 
segment.deletedDocSize;
 
-                $scope.segments.push(segment);
+                  $scope.segments.push(segment);
 
-                $scope.documentCount += segment.size;
-                $scope.deletionCount += segment.delCount;
-            }
-            $scope.deletionsPercentage = 
calculateDeletionsPercentage($scope.documentCount, $scope.deletionCount);
+                  $scope.documentCount += segment.size;
+                  $scope.deletionCount += segment.delCount;
+              }
+              $scope.deletionsPercentage = 
calculateDeletionsPercentage($scope.documentCount, $scope.deletionCount);
+            });
         });
     };
 
diff --git a/solr/webapp/web/js/angular/services.js 
b/solr/webapp/web/js/angular/services.js
index 47c5ad2fa6e..f75a2f9d3c6 100644
--- a/solr/webapp/web/js/angular/services.js
+++ b/solr/webapp/web/js/angular/services.js
@@ -17,11 +17,7 @@
 
 var solrAdminServices = angular.module('solrAdminServices', ['ngResource']);
 
-solrAdminServices.factory('System',
-  ['$resource', function($resource) {
-    return $resource('admin/info/system', {"wt":"json", "nodes": "@nodes", 
"_":Date.now()});
-  }])
-.factory('Metrics',
+solrAdminServices.factory('Metrics',
   ['$resource', 'PrometheusParser', function($resource, PrometheusParser) {
     return $resource('admin/metrics', {"wt":"prometheus", "node": "@node", 
"_":Date.now()}, {
       get: {
@@ -44,35 +40,124 @@ solrAdminServices.factory('System',
       }
     });
   }])
+.factory('ApiErrorHandler',
+    ['$rootScope', '$location', '$timeout', function($rootScope, $location, 
$timeout) {
+      // v2 API calls go through the generated OpenAPI client, which uses 
superagent directly and
+      // so never passes through Angular's $http -- meaning httpInterceptor in 
app.js (the global
+      // error banner, 401 redirect, 403 handling) never fires for them. This 
mirrors
+      // httpInterceptor's responseError handling for v2's superagent 
responses, so a failure looks
+      // the same to the user regardless of which API generation served it. 
Call from any v2
+      // callback's error branch: `if (error) { 
ApiErrorHandler.handle(response); return; }`
+      function handle(response) {
+        if (!response) {
+          return;
+        }
+        // superagent callbacks fire outside Angular's digest cycle, so 
changes here are invisible
+        // until the next digest -- $timeout both defers and triggers one.
+        $timeout(function() {
+          if (response.status === 401) {
+            var headers = response.headers || {};
+            sessionStorage.setItem("auth.wwwAuthHeader", 
headers['www-authenticate']);
+            sessionStorage.setItem("auth.authDataHeader", 
headers['x-solr-authdata']);
+            sessionStorage.setItem("auth.statusText", response.statusText);
+            sessionStorage.setItem("http401", "true");
+            sessionStorage.removeItem("auth.scheme");
+            sessionStorage.removeItem("auth.realm");
+            sessionStorage.removeItem("auth.username");
+            sessionStorage.removeItem("auth.header");
+            sessionStorage.removeItem("auth.state");
+            if ($location.path().includes('/login')) {
+              if (!sessionStorage.getItem("auth.location")) {
+                sessionStorage.setItem("auth.location", "/");
+              }
+            } else {
+              sessionStorage.setItem("auth.location", $location.path());
+              $location.path('/login');
+            }
+          } else if (response.status === 403) {
+            $rootScope.showAuthzFailures = true;
+          } else {
+            var url = (response.req && response.req.url) || (response.status + 
' ' + $location.url());
+            var body = response.body || {};
+            var msg = (body.error && body.error.msg) || response.statusText || 
"Unknown error";
+            // MainController normally sets this up first, but don't assume 
that ordering here.
+            $rootScope.exceptions = $rootScope.exceptions || {};
+            $rootScope.exceptions[url] = {msg: msg};
+          }
+        });
+      }
+      return {handle: handle};
+    }])
 .factory('CollectionsV2',
     function() {
       solrApi.ApiClient.instance.basePath = '/api';
       delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"];
       return new solrApi.CollectionsApi();
     })
+.factory('CoresV2',
+    function() {
+      solrApi.ApiClient.instance.basePath = '/api';
+      delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"];
+      return new solrApi.CoresApi();
+    })
+.factory('LoggingV2',
+    function() {
+      solrApi.ApiClient.instance.basePath = '/api';
+      delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"];
+      return new solrApi.LoggingApi();
+    })
+.factory('SystemV2',
+    function() {
+      solrApi.ApiClient.instance.basePath = '/api';
+      delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"];
+      return new solrApi.SystemApi();
+    })
+.factory('AliasesV2',
+    function() {
+      solrApi.ApiClient.instance.basePath = '/api';
+      delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"];
+      return new solrApi.AliasesApi();
+    })
+.factory('ShardsV2',
+    function() {
+      solrApi.ApiClient.instance.basePath = '/api';
+      delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"];
+      return new solrApi.ShardsApi();
+    })
+.factory('ReplicasV2',
+    function() {
+      solrApi.ApiClient.instance.basePath = '/api';
+      delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"];
+      return new solrApi.ReplicasApi();
+    })
+.factory('ConfigSetsV2',
+    function() {
+      solrApi.ApiClient.instance.basePath = '/api';
+      delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"];
+      return new solrApi.ConfigsetsApi();
+    })
+.factory('ClusterV2',
+    function() {
+      solrApi.ApiClient.instance.basePath = '/api';
+      delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"];
+      return new solrApi.ClusterApi();
+    })
+.factory('SegmentsV2',
+    function() {
+      solrApi.ApiClient.instance.basePath = '/api';
+      delete solrApi.ApiClient.instance.defaultHeaders["User-Agent"];
+      return new solrApi.SegmentsApi();
+    })
 .factory('Collections',
-  ['$resource', function($resource) {
-    return $resource('admin/collections',
-    {'wt':'json', '_':Date.now()}, {
-    "list": {params:{action: "LIST"}},
-    "listaliases": {params:{action: "LISTALIASES"}},
-    "status": {params:{action: "CLUSTERSTATUS"}},
-    "add": {params:{action: "CREATE"}},
-    "delete": {params:{action: "DELETE"}},
-    "rename": {params:{action: "RENAME"}},
-    "createAlias": {params:{action: "CREATEALIAS"}},
-    "deleteAlias": {params:{action: "DELETEALIAS"}},
-    "deleteReplica": {params:{action: "DELETEREPLICA"}},
-    "addReplica": {params:{action: "ADDREPLICA"}},
-    "deleteShard": {params:{action: "DELETESHARD"}},
-    "reload": {method: "GET", params:{action:"RELOAD", core: "@core"}}
+  ['$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 
Threads/ParamSet.
+    return $resource('/api/cluster', {'wt':'json', '_':Date.now()}, {
+      "status": {}
     });
   }])
-.factory('ConfigSets',
- ['$resource', function ($resource) {
-    return $resource('admin/configs', {'wt': 'json', '_': Date.now()}, 
{"configs": {params: {action: "LIST"}}
-    });
- }])
 .factory('ConfigSetFiles',
  ['$http', function ($http) {
     // Fetches a single file from a configset via V2 
/api/configsets/{name}/files/{path}.
@@ -90,24 +175,12 @@ solrAdminServices.factory('System',
       }
     };
  }])
-.factory('Cores',
-  ['$resource', function($resource) {
-    return $resource('admin/cores',
-    {'wt':'json', '_':Date.now()}, {
-    "query": {},
-    "list": {params:{indexInfo: false}},
-    "add": {params:{action: "CREATE"}},
-    "unload": {params:{action: "UNLOAD", core: "@core"}},
-    "rename": {params:{action: "RENAME"}},
-    "swap": {params:{action: "SWAP"}},
-    "reload": {method: "GET", params:{action:"RELOAD", core: "@core"}, 
headers:{doNotIntercept: "true"}}
-    });
-  }])
 .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.
     return $resource('admin/info/logging', {'wt':'json', '_':Date.now()}, {
-      "events": {params: {since:'0'}},
-      "levels": {},
       "setLevel": {params: {nodes:'all'}}
       });
   }])
@@ -132,11 +205,11 @@ solrAdminServices.factory('System',
   }])
 .factory('Threads',
   ['$resource', function($resource) {
-    return $resource('admin/info/threads', {'wt':'json', '_':Date.now()});
-  }])
-.factory('Properties',
-  ['$resource', function($resource) {
-    return $resource('admin/info/properties', {'wt':'json', '_':Date.now()});
+    // v2 NodeThreadsAPI (/api/node/threads) still just delegates straight 
through to the same v1
+    // ThreadDumpHandler, so the response shape is byte-identical -- no 
generated solrApi client
+    // class exists for it (it predates the OpenAPI-based v2 API framework), 
so this stays a plain
+    // $resource, like SchemaDesigner/Security.
+    return $resource('/api/node/threads', {'wt':'json', '_':Date.now()});
   }])
 .factory('Replication',
   ['$resource', function($resource) {
@@ -161,7 +234,16 @@ solrAdminServices.factory('System',
   }])
 .factory('ParamSet',
   ['$resource', function($resource) {
-    return $resource(':core/config/params/:name', {core: '@core', wt:'json', 
_:Date.now()}, {
+    // 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, like Threads.
+    // 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),
+    // and there's no such thing as a "collection" there anyway. Callers must 
pass "indexType" as
+    // "collections" or "cores" (see paramsets.js, driven by 
$scope.isCloudEnabled).
+    return $resource('/api/:indexType/:core/config/params/:name', {core: 
'@core', indexType: '@indexType', wt:'json', _:Date.now()}, {
       "submit": {headers: {'Content-type': 'application/json'}, method: 
"POST"},
       "get": {headers: {'Content-type': 'application/json'}, method: "GET"}
     });
@@ -266,12 +348,6 @@ solrAdminServices.factory('System',
        }
        return resource;
 }])
-.factory('Segments',
-   ['$resource', function($resource) {
-       return $resource(':core/admin/segments', {'wt':'json', core: '@core', 
_:Date.now()}, {
-           get: {}
-       });
-}])
 .factory('Schema',
    ['$resource', function($resource) {
      return $resource(':core/schema', {wt: 'json', core: '@core', 
_:Date.now()}, {
@@ -378,6 +454,8 @@ solrAdminServices.factory('System',
         service.SetCredentials = function (username, password) {
           var authdata = base64.encode(username + ':' + password);
 
+          // The V2 solrApi client picks this up automatically via the 
superagent plugin
+          // registered in app.js .config().
           sessionStorage.setItem("auth.header", "Basic " + authdata);
           sessionStorage.setItem("auth.username", username);
         };

Reply via email to