app.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. /*
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. */
  15. var solrAdminApp = angular.module("solrAdminApp", [
  16. "ngResource",
  17. "ngRoute",
  18. "ngCookies",
  19. "ngtimeago",
  20. "solrAdminServices",
  21. "localytics.directives",
  22. "ab-base64"
  23. ]);
  24. solrAdminApp.config([
  25. '$routeProvider', function($routeProvider) {
  26. $routeProvider.
  27. when('/', {
  28. templateUrl: 'partials/index.html',
  29. controller: 'IndexController'
  30. }).
  31. when('/login', {
  32. templateUrl: 'partials/login.html',
  33. controller: 'LoginController'
  34. }).
  35. when('/~logging', {
  36. templateUrl: 'partials/logging.html',
  37. controller: 'LoggingController'
  38. }).
  39. when('/~logging/level', {
  40. templateUrl: 'partials/logging-levels.html',
  41. controller: 'LoggingLevelController'
  42. }).
  43. when('/~cloud', {
  44. templateUrl: 'partials/cloud.html',
  45. controller: 'CloudController'
  46. }).
  47. when('/~cores', {
  48. templateUrl: 'partials/cores.html',
  49. controller: 'CoreAdminController'
  50. }).
  51. when('/~cores/:corename', {
  52. templateUrl: 'partials/cores.html',
  53. controller: 'CoreAdminController'
  54. }).
  55. when('/~collections', {
  56. templateUrl: 'partials/collections.html',
  57. controller: 'CollectionsController'
  58. }).
  59. when('/~collections/:collection', {
  60. templateUrl: 'partials/collections.html',
  61. controller: 'CollectionsController'
  62. }).
  63. when('/~threads', {
  64. templateUrl: 'partials/threads.html',
  65. controller: 'ThreadsController'
  66. }).
  67. when('/~java-properties', {
  68. templateUrl: 'partials/java-properties.html',
  69. controller: 'JavaPropertiesController'
  70. }).
  71. when('/~cluster-suggestions', {
  72. templateUrl: 'partials/cluster_suggestions.html',
  73. controller: 'ClusterSuggestionsController'
  74. }).
  75. when('/:core/core-overview', {
  76. templateUrl: 'partials/core_overview.html',
  77. controller: 'CoreOverviewController'
  78. }).
  79. when('/:core/collection-overview', {
  80. templateUrl: 'partials/collection_overview.html',
  81. controller: 'CollectionOverviewController'
  82. }).
  83. when('/:core/analysis', {
  84. templateUrl: 'partials/analysis.html',
  85. controller: 'AnalysisController'
  86. }).
  87. when('/:core/dataimport', {
  88. templateUrl: 'partials/dataimport.html',
  89. controller: 'DataImportController'
  90. }).
  91. when('/:core/dataimport/:handler*', {
  92. templateUrl: 'partials/dataimport.html',
  93. controller: 'DataImportController'
  94. }).
  95. when('/:core/documents', {
  96. templateUrl: 'partials/documents.html',
  97. controller: 'DocumentsController'
  98. }).
  99. when('/:core/files', {
  100. templateUrl: 'partials/files.html',
  101. controller: 'FilesController'
  102. }).
  103. when('/:core/plugins', {
  104. templateUrl: 'partials/plugins.html',
  105. controller: 'PluginsController',
  106. reloadOnSearch: false
  107. }).
  108. when('/:core/plugins/:legacytype', {
  109. templateUrl: 'partials/plugins.html',
  110. controller: 'PluginsController',
  111. reloadOnSearch: false
  112. }).
  113. when('/:core/query', {
  114. templateUrl: 'partials/query.html',
  115. controller: 'QueryController'
  116. }).
  117. when('/:core/stream', {
  118. templateUrl: 'partials/stream.html',
  119. controller: 'StreamController'
  120. }).
  121. when('/:core/replication', {
  122. templateUrl: 'partials/replication.html',
  123. controller: 'ReplicationController'
  124. }).
  125. when('/:core/dataimport', {
  126. templateUrl: 'partials/dataimport.html',
  127. controller: 'DataImportController'
  128. }).
  129. when('/:core/dataimport/:handler*', {
  130. templateUrl: 'partials/dataimport.html',
  131. controller: 'DataImportController'
  132. }).
  133. when('/:core/schema', {
  134. templateUrl: 'partials/schema.html',
  135. controller: 'SchemaController'
  136. }).
  137. when('/:core/segments', {
  138. templateUrl: 'partials/segments.html',
  139. controller: 'SegmentsController'
  140. }).
  141. otherwise({
  142. redirectTo: '/'
  143. });
  144. }])
  145. .constant('Constants', {
  146. IS_ROOT_PAGE: 1,
  147. IS_CORE_PAGE: 2,
  148. IS_COLLECTION_PAGE: 3,
  149. ROOT_URL: "/"
  150. })
  151. .filter('uriencode', function() {
  152. return window.encodeURIComponent;
  153. })
  154. .filter('highlight', function($sce) {
  155. return function(input, lang) {
  156. if (lang && input && lang!="txt" && lang!="csv") return hljs.highlight(lang, input).value;
  157. return input;
  158. }
  159. })
  160. .filter('unsafe', function($sce) { return $sce.trustAsHtml; })
  161. .directive('loadingStatusMessage', function() {
  162. return {
  163. link: function($scope, $element, attrs) {
  164. var show = function() {$element.css('display', 'block')};
  165. var hide = function() {$element.css('display', 'none')};
  166. $scope.$on('loadingStatusActive', show);
  167. $scope.$on('loadingStatusInactive', hide);
  168. }
  169. };
  170. })
  171. .directive('escapePressed', function () {
  172. return function (scope, element, attrs) {
  173. element.bind("keydown keypress", function (event) {
  174. if(event.which === 27) {
  175. scope.$apply(function (){
  176. scope.$eval(attrs.escapePressed);
  177. });
  178. event.preventDefault();
  179. }
  180. });
  181. };
  182. })
  183. .directive('focusWhen', function($timeout) {
  184. return {
  185. link: function(scope, element, attrs) {
  186. scope.$watch(attrs.focusWhen, function(value) {
  187. if(value === true) {
  188. $timeout(function() {
  189. element[0].focus();
  190. }, 100);
  191. }
  192. });
  193. }
  194. };
  195. })
  196. .directive('scrollableWhenSmall', function($window) {
  197. return {
  198. link: function(scope, element, attrs) {
  199. var w = angular.element($window);
  200. var checkFixedMenu = function() {
  201. var shouldScroll = w.height() < (element.height() + $('#header').height() + 40);
  202. element.toggleClass( 'scroll', shouldScroll);
  203. };
  204. w.bind('resize', checkFixedMenu);
  205. w.bind('load', checkFixedMenu);
  206. }
  207. }
  208. })
  209. .filter('readableSeconds', function() {
  210. return function(input) {
  211. seconds = parseInt(input||0, 10);
  212. var minutes = Math.floor( seconds / 60 );
  213. var hours = Math.floor( minutes / 60 );
  214. var text = [];
  215. if( 0 !== hours ) {
  216. text.push( hours + 'h' );
  217. seconds -= hours * 60 * 60;
  218. minutes -= hours * 60;
  219. }
  220. if( 0 !== minutes ) {
  221. text.push( minutes + 'm' );
  222. seconds -= minutes * 60;
  223. }
  224. if( 0 !== seconds ) {
  225. text.push( ( '0' + seconds ).substr( -2 ) + 's' );
  226. }
  227. return text.join(' ');
  228. };
  229. })
  230. .filter('number', function($locale) {
  231. return function(input) {
  232. var sep = {
  233. 'de_CH' : '\'',
  234. 'de' : '.',
  235. 'en' : ',',
  236. 'es' : '.',
  237. 'it' : '.',
  238. 'ja' : ',',
  239. 'sv' : ' ',
  240. 'tr' : '.',
  241. '_' : '' // fallback
  242. };
  243. var browser = {};
  244. var match = $locale.id.match( /^(\w{2})([-_](\w{2}))?$/ );
  245. if (match[1]) {
  246. browser.language = match[1].toLowerCase();
  247. }
  248. if (match[1] && match[3]) {
  249. browser.locale = match[1] + '_' + match[3];
  250. }
  251. return ( input || 0 ).toString().replace(/\B(?=(\d{3})+(?!\d))/g,
  252. sep[ browser.locale ] || sep[ browser.language ] || sep['_']);
  253. };
  254. })
  255. .filter('orderObjectBy', function() {
  256. return function(items, field, reverse) {
  257. var filtered = [];
  258. angular.forEach(items, function(item) {
  259. filtered.push(item);
  260. });
  261. filtered.sort(function (a, b) {
  262. return (a[field] > b[field] ? 1 : -1);
  263. });
  264. if(reverse) filtered.reverse();
  265. return filtered;
  266. };
  267. })
  268. .directive('jstree', function($parse) {
  269. return {
  270. restrict: 'EA',
  271. scope: {
  272. data: '=',
  273. onSelect: '&'
  274. },
  275. link: function(scope, element, attrs) {
  276. scope.$watch("data", function(newValue, oldValue) {
  277. if (newValue) {
  278. var treeConfig = {
  279. "plugins" : [ "themes", "json_data", "ui" ],
  280. "json_data" : {
  281. "data" : scope.data,
  282. "progressive_render" : true
  283. },
  284. "core" : {
  285. "animation" : 0
  286. }
  287. };
  288. var tree = $(element).jstree(treeConfig);
  289. tree.jstree('open_node','li:first');
  290. if (tree) {
  291. element.bind("select_node.jstree", function (event, data) {
  292. scope.$apply(function() {
  293. scope.onSelect({url: data.args[0].href, data: data});
  294. });
  295. });
  296. }
  297. }
  298. }, true);
  299. }
  300. };
  301. })
  302. .directive('connectionMessage', function() {
  303. return {
  304. link: function($scope, $element, attrs) {
  305. var show = function() {$element.css('display', 'block')};
  306. var hide = function() {$element.css('display', 'none')};
  307. $scope.$on('connectionStatusActive', show);
  308. $scope.$on('connectionStatusInactive', hide);
  309. }
  310. };
  311. })
  312. .factory('httpInterceptor', function($q, $rootScope, $location, $timeout, $injector) {
  313. var activeRequests = 0;
  314. var started = function(config) {
  315. if (activeRequests == 0) {
  316. $rootScope.$broadcast('loadingStatusActive');
  317. }
  318. if ($rootScope.exceptions[config.url]) {
  319. delete $rootScope.exceptions[config.url];
  320. }
  321. activeRequests++;
  322. if (sessionStorage.getItem("auth.header")) {
  323. config.headers['Authorization'] = sessionStorage.getItem("auth.header");
  324. }
  325. config.timeout = 10000;
  326. return config || $q.when(config);
  327. };
  328. var ended = function(response) {
  329. activeRequests--;
  330. if (activeRequests == 0) {
  331. $rootScope.$broadcast('loadingStatusInactive');
  332. }
  333. if ($rootScope.retryCount>0) {
  334. $rootScope.connectionRecovered = true;
  335. $rootScope.retryCount=0;
  336. $timeout(function() {
  337. $rootScope.connectionRecovered=false;
  338. $rootScope.$broadcast('connectionStatusInactive');
  339. },2000);
  340. }
  341. if (!$location.path().startsWith('/login')) {
  342. sessionStorage.removeItem("http401");
  343. sessionStorage.removeItem("auth.state");
  344. sessionStorage.removeItem("auth.statusText");
  345. }
  346. return response || $q.when(response);
  347. };
  348. var failed = function(rejection) {
  349. activeRequests--;
  350. if (activeRequests == 0) {
  351. $rootScope.$broadcast('loadingStatusInactive');
  352. }
  353. if (rejection.config.headers.doNotIntercept) {
  354. return rejection;
  355. }
  356. if (rejection.status === 0) {
  357. $rootScope.$broadcast('connectionStatusActive');
  358. if (!$rootScope.retryCount) $rootScope.retryCount=0;
  359. $rootScope.retryCount ++;
  360. var $http = $injector.get('$http');
  361. var result = $http(rejection.config);
  362. return result;
  363. } else if (rejection.status === 401) {
  364. // Authentication redirect
  365. var headers = rejection.headers();
  366. var wwwAuthHeader = headers['www-authenticate'];
  367. sessionStorage.setItem("auth.wwwAuthHeader", wwwAuthHeader);
  368. sessionStorage.setItem("auth.statusText", rejection.statusText);
  369. sessionStorage.setItem("http401", "true");
  370. sessionStorage.removeItem("auth.scheme");
  371. sessionStorage.removeItem("auth.realm");
  372. sessionStorage.removeItem("auth.username");
  373. sessionStorage.removeItem("auth.header");
  374. sessionStorage.removeItem("auth.state");
  375. if ($location.path().includes('/login')) {
  376. if (!sessionStorage.getItem("auth.location")) {
  377. sessionStorage.setItem("auth.location", "/");
  378. }
  379. } else {
  380. sessionStorage.setItem("auth.location", $location.path());
  381. $location.path('/login');
  382. }
  383. } else {
  384. $rootScope.exceptions[rejection.config.url] = rejection.data.error;
  385. }
  386. return $q.reject(rejection);
  387. };
  388. return {request: started, response: ended, responseError: failed};
  389. })
  390. .config(function($httpProvider) {
  391. $httpProvider.interceptors.push("httpInterceptor");
  392. // Force BasicAuth plugin to serve us a 'Authorization: xBasic xxxx' header so browser will not pop up login dialogue
  393. $httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
  394. })
  395. .directive('fileModel', function ($parse) {
  396. return {
  397. restrict: 'A',
  398. link: function(scope, element, attrs) {
  399. var model = $parse(attrs.fileModel);
  400. var modelSetter = model.assign;
  401. element.bind('change', function(){
  402. scope.$apply(function(){
  403. modelSetter(scope, element[0].files[0]);
  404. });
  405. });
  406. }
  407. };
  408. });
  409. solrAdminApp.controller('MainController', function($scope, $route, $rootScope, $location, Cores, Collections, System, Ping, Constants) {
  410. $rootScope.exceptions={};
  411. $rootScope.toggleException = function() {
  412. $scope.showException=!$scope.showException;
  413. };
  414. $scope.refresh = function() {
  415. $scope.cores = [];
  416. $scope.collections = [];
  417. }
  418. $scope.refresh();
  419. $scope.resetMenu = function(page, pageType) {
  420. Cores.list(function(data) {
  421. $scope.cores = [];
  422. var currentCoreName = $route.current.params.core;
  423. delete $scope.currentCore;
  424. for (key in data.status) {
  425. var core = data.status[key];
  426. $scope.cores.push(core);
  427. if ((!$scope.isSolrCloud || pageType == Constants.IS_CORE_PAGE) && core.name == currentCoreName) {
  428. $scope.currentCore = core;
  429. }
  430. }
  431. $scope.showInitFailures = Object.keys(data.initFailures).length>0;
  432. $scope.initFailures = data.initFailures;
  433. });
  434. System.get(function(data) {
  435. $scope.isCloudEnabled = data.mode.match( /solrcloud/i );
  436. if ($scope.isCloudEnabled) {
  437. Collections.list(function (data) {
  438. $scope.collections = [];
  439. var currentCollectionName = $route.current.params.core;
  440. delete $scope.currentCollection;
  441. for (key in data.collections) {
  442. var collection = {name: data.collections[key]};
  443. $scope.collections.push(collection);
  444. if (pageType == Constants.IS_COLLECTION_PAGE && collection.name == currentCollectionName) {
  445. $scope.currentCollection = collection;
  446. }
  447. }
  448. })
  449. }
  450. });
  451. $scope.showingLogging = page.lastIndexOf("logging", 0) === 0;
  452. $scope.showingCloud = page.lastIndexOf("cloud", 0) === 0;
  453. $scope.page = page;
  454. $scope.currentUser = sessionStorage.getItem("auth.username");
  455. $scope.http401 = sessionStorage.getItem("http401");
  456. };
  457. $scope.ping = function() {
  458. Ping.ping({core: $scope.currentCore.name}, function(data) {
  459. $scope.showPing = true;
  460. $scope.pingMS = data.responseHeader.QTime;
  461. });
  462. // @todo .attr( 'title', '/admin/ping is not configured (' + xhr.status + ': ' + error_thrown + ')' );
  463. };
  464. $scope.dumpCloud = function() {
  465. $scope.$broadcast("cloud-dump");
  466. }
  467. $scope.showCore = function(core) {
  468. $location.url("/" + core.name + "/core-overview");
  469. }
  470. $scope.showCollection = function(collection) {
  471. $location.url("/" + collection.name + "/collection-overview")
  472. }
  473. $scope.$on('$routeChangeStart', function() {
  474. $rootScope.exceptions = {};
  475. });
  476. });