├── .gitignore ├── src ├── kopf │ ├── css │ │ ├── benchmark.css │ │ ├── hotthreads.css │ │ ├── repository.css │ │ ├── analysis.css │ │ ├── explain.css │ │ ├── cluster_health.css │ │ ├── aliases.css │ │ ├── nodes.css │ │ ├── rest_client.css │ │ └── navbar.css │ ├── elastic │ │ ├── elastic_client.js │ │ ├── index_template.js │ │ ├── node_stats.js │ │ ├── hot_thread.js │ │ ├── shard_stats.js │ │ ├── warmer.js │ │ ├── hot_threads.js │ │ ├── token.js │ │ ├── shard.js │ │ ├── cluster_mapping.js │ │ ├── snapshot.js │ │ ├── cat_result.js │ │ ├── cluster_health.js │ │ ├── es_connection.js │ │ ├── node_hot_threads.js │ │ ├── version.js │ │ ├── cluster_settings.js │ │ ├── broken_cluster.js │ │ ├── percolator.js │ │ ├── node.js │ │ ├── index.js │ │ ├── editable_index_settings.js │ │ ├── cluster_changes.js │ │ ├── index_metadata.js │ │ ├── repository.js │ │ └── alias.js │ ├── models │ │ ├── modal_controls.js │ │ ├── gist.js │ │ ├── snapshot_filter.js │ │ ├── warmer_filter.js │ │ ├── request.js │ │ ├── index_template_filter.js │ │ ├── alias_filter.js │ │ ├── ace_editor.js │ │ ├── node_filter.js │ │ ├── paginator.js │ │ └── index_filter.js │ ├── services │ │ ├── aceeditor.js │ │ ├── state.js │ │ ├── debug.js │ │ ├── clipboard.js │ │ ├── host_history.js │ │ ├── explain.js │ │ ├── page.js │ │ └── alerts.js │ ├── directives │ │ ├── static_include.js │ │ ├── navbar_section.js │ │ ├── json_tree.js │ │ ├── pagination.js │ │ └── sort_table.js │ ├── controllers │ │ ├── confirm_dialog.js │ │ ├── cluster_stats.js │ │ ├── alerts.js │ │ ├── debug.js │ │ ├── cat.js │ │ ├── cluster_settings.js │ │ ├── hotthreads.js │ │ ├── nodes.js │ │ ├── index_settings.js │ │ ├── benchmark.js │ │ ├── create_index.js │ │ ├── navbar.js │ │ ├── global.js │ │ └── warmers.js │ ├── filters │ │ ├── starts_with.js │ │ ├── bytes.js │ │ └── time_interval.js │ ├── util.js │ ├── theme-kopf.js │ └── kopf.js └── lib │ ├── bootstrap │ └── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ ├── jsontree │ └── jsontree.min.js │ ├── csv │ └── csv.js │ └── angular-tree-dnd │ └── ng-tree-dnd.css ├── imgs ├── warmer.png ├── aliases.png ├── analysis.png ├── snapshot.png ├── percolator.png ├── rest_client.png ├── cluster_state.png └── cluster_view.png ├── _site ├── favicon.ico ├── es-plugin.properties ├── font-awesome │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── less │ │ ├── fixed-width.less │ │ ├── bordered-pulled.less │ │ ├── larger.less │ │ ├── list.less │ │ ├── font-awesome.less │ │ ├── core.less │ │ ├── stacked.less │ │ ├── rotated-flipped.less │ │ ├── path.less │ │ ├── animated.less │ │ └── mixins.less │ └── scss │ │ ├── _fixed-width.scss │ │ ├── _bordered-pulled.scss │ │ ├── _larger.scss │ │ ├── _list.scss │ │ ├── font-awesome.scss │ │ ├── _core.scss │ │ ├── _stacked.scss │ │ ├── _rotated-flipped.scss │ │ ├── _path.scss │ │ ├── _animated.scss │ │ └── _mixins.scss ├── kopf_external_settings.json ├── partials │ ├── analysis │ │ └── analysis_token.html │ ├── snapshot │ │ ├── url_repository.html │ │ ├── azure_repository.html │ │ ├── fs_repository.html │ │ ├── hdfs_repository.html │ │ ├── s3_repository.html │ │ └── repositories_table.html │ ├── cluster_overview │ │ ├── index_unassigned.html │ │ ├── filters.html │ │ └── index_body.html │ ├── snapshot.html │ ├── debug.html │ ├── main_alerts.html │ ├── directives │ │ └── pagination.html │ ├── index_settings │ │ ├── cache.html │ │ ├── routing.html │ │ ├── blocks.html │ │ └── translog.html │ ├── aliases │ │ └── alias_details.html │ ├── cluster_settings.html │ ├── create_index.html │ ├── index_settings.html │ ├── cat.html │ ├── cluster_settings │ │ ├── cluster.html │ │ └── recovery.html │ └── cluster_stats.html ├── modals │ ├── modal_info.html │ └── confirm_dialog.html ├── index.html └── dist │ └── theme-kopf.js ├── plugin-descriptor.properties ├── CHANGELOG.md ├── tests ├── jasmine │ ├── filters │ │ ├── starts_with.js │ │ ├── time_interval.tests.js │ │ └── bytes.tests.js │ ├── directives │ │ └── navbar_section.tests.js │ ├── alerts.tests.js │ ├── services │ │ ├── debug.tests.js │ │ ├── explain.tests.js │ │ ├── page.tests.js │ │ └── external_settings.tests.js │ ├── cat.tests.js │ └── host_history.tests.js ├── es_connection.js ├── models │ ├── cat_result.js │ ├── url_autocomplete.js │ └── node_filter.js ├── all.html └── karma.config.js ├── docker ├── Dockerfile ├── run.sh ├── nginx.conf.tpl └── README.md ├── LICENSE └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /src/kopf/css/benchmark.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/kopf/elastic/elastic_client.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/kopf/css/hotthreads.css: -------------------------------------------------------------------------------- 1 | .hot-threads-output { 2 | white-space: pre; 3 | } -------------------------------------------------------------------------------- /imgs/warmer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmenezes/elasticsearch-kopf/HEAD/imgs/warmer.png -------------------------------------------------------------------------------- /_site/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmenezes/elasticsearch-kopf/HEAD/_site/favicon.ico -------------------------------------------------------------------------------- /imgs/aliases.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmenezes/elasticsearch-kopf/HEAD/imgs/aliases.png -------------------------------------------------------------------------------- /imgs/analysis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmenezes/elasticsearch-kopf/HEAD/imgs/analysis.png -------------------------------------------------------------------------------- /imgs/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmenezes/elasticsearch-kopf/HEAD/imgs/snapshot.png -------------------------------------------------------------------------------- /imgs/percolator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmenezes/elasticsearch-kopf/HEAD/imgs/percolator.png -------------------------------------------------------------------------------- /imgs/rest_client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmenezes/elasticsearch-kopf/HEAD/imgs/rest_client.png -------------------------------------------------------------------------------- /imgs/cluster_state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmenezes/elasticsearch-kopf/HEAD/imgs/cluster_state.png -------------------------------------------------------------------------------- /imgs/cluster_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmenezes/elasticsearch-kopf/HEAD/imgs/cluster_view.png -------------------------------------------------------------------------------- /src/kopf/css/repository.css: -------------------------------------------------------------------------------- 1 | select.input-sm-twoxheight { 2 | height: 100px; 3 | line-height: 30px; 4 | } -------------------------------------------------------------------------------- /_site/es-plugin.properties: -------------------------------------------------------------------------------- 1 | description=kopf - simple web administration tool for ElasticSearch 2 | version=1.5.7-SNAPSHOT -------------------------------------------------------------------------------- /src/kopf/elastic/index_template.js: -------------------------------------------------------------------------------- 1 | function IndexTemplate(name, body) { 2 | this.name = name; 3 | this.body = body; 4 | } 5 | -------------------------------------------------------------------------------- /_site/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmenezes/elasticsearch-kopf/HEAD/_site/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /plugin-descriptor.properties: -------------------------------------------------------------------------------- 1 | description=kopf - simple web administration tool for Elasticsearch 2 | version=2.0.1 3 | site=true 4 | name=kopf 5 | -------------------------------------------------------------------------------- /src/kopf/elastic/node_stats.js: -------------------------------------------------------------------------------- 1 | function NodeStats(id, stats) { 2 | this.id = id; 3 | this.name = stats.name; 4 | this.stats = stats; 5 | } 6 | -------------------------------------------------------------------------------- /src/kopf/elastic/hot_thread.js: -------------------------------------------------------------------------------- 1 | function HotThread(header) { 2 | this.header = header; 3 | this.subHeader = undefined; 4 | this.stack = []; 5 | } 6 | -------------------------------------------------------------------------------- /_site/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmenezes/elasticsearch-kopf/HEAD/_site/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /_site/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmenezes/elasticsearch-kopf/HEAD/_site/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /_site/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmenezes/elasticsearch-kopf/HEAD/_site/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /_site/font-awesome/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmenezes/elasticsearch-kopf/HEAD/_site/font-awesome/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/kopf/elastic/shard_stats.js: -------------------------------------------------------------------------------- 1 | function ShardStats(shard, index, stats) { 2 | this.shard = shard; 3 | this.index = index; 4 | this.stats = stats; 5 | } 6 | -------------------------------------------------------------------------------- /_site/kopf_external_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "elasticsearch_root_path": "", 3 | "with_credentials": false, 4 | "theme": "dark", 5 | "refresh_rate": 5000 6 | } -------------------------------------------------------------------------------- /src/kopf/models/modal_controls.js: -------------------------------------------------------------------------------- 1 | function ModalControls() { 2 | this.alert = null; 3 | this.active = false; 4 | this.title = ''; 5 | this.info = ''; 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmenezes/elasticsearch-kopf/HEAD/src/lib/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmenezes/elasticsearch-kopf/HEAD/src/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/lib/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmenezes/elasticsearch-kopf/HEAD/src/lib/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /_site/font-awesome/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /src/kopf/elastic/warmer.js: -------------------------------------------------------------------------------- 1 | function Warmer(id, index, body) { 2 | this.id = id; 3 | this.index = index; 4 | this.source = body.source; 5 | this.types = body.types; 6 | } 7 | -------------------------------------------------------------------------------- /_site/font-awesome/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /src/kopf/services/aceeditor.js: -------------------------------------------------------------------------------- 1 | kopf.factory('AceEditorService', function() { 2 | 3 | this.init = function(name) { 4 | return new AceEditor(name); 5 | }; 6 | 7 | return this; 8 | }); 9 | -------------------------------------------------------------------------------- /src/kopf/elastic/hot_threads.js: -------------------------------------------------------------------------------- 1 | function HotThreads(data) { 2 | this.nodes_hot_threads = data.split(':::').slice(1).map(function(data) { 3 | return new NodeHotThreads(data); 4 | }); 5 | } 6 | -------------------------------------------------------------------------------- /src/kopf/directives/static_include.js: -------------------------------------------------------------------------------- 1 | kopf.directive('ngStaticInclude', function() { 2 | return { 3 | templateUrl: function(elem, attr) { 4 | return './partials/' + attr.file + '.html'; 5 | } 6 | }; 7 | }); 8 | -------------------------------------------------------------------------------- /_site/partials/analysis/analysis_token.html: -------------------------------------------------------------------------------- 1 |
2 | {{token.token}} 3 |
4 |
5 | pos: {{token.position}} start: {{token.start_offset}} end: {{token.end_offset}} 6 |
-------------------------------------------------------------------------------- /src/kopf/elastic/token.js: -------------------------------------------------------------------------------- 1 | /** TYPES **/ 2 | function Token(token, startOffset, endOffset, position) { 3 | this.token = token; 4 | this.start_offset = startOffset; 5 | this.end_offset = endOffset; 6 | this.position = position; 7 | } 8 | -------------------------------------------------------------------------------- /_site/partials/snapshot/url_repository.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
-------------------------------------------------------------------------------- /_site/partials/cluster_overview/index_unassigned.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{shard.shard}} 4 | 5 |
-------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # 1.4.3 (2014-12-08) 3 | 4 | 5 | ## Bug Fixes 6 | 7 | - **SNAPSHOT:** Display list of indices from snapshot instead of list of current indices when restoring a snapshot 8 | ([https://github.com/lmenezes/elasticsearch-kopf/issues/211]) 9 | -------------------------------------------------------------------------------- /src/kopf/elastic/shard.js: -------------------------------------------------------------------------------- 1 | function Shard(routing) { 2 | this.primary = routing.primary; 3 | this.shard = routing.shard; 4 | this.state = routing.state; 5 | this.node = routing.node; 6 | this.index = routing.index; 7 | this.id = this.node + '_' + this.shard + '_' + this.index; 8 | } 9 | -------------------------------------------------------------------------------- /_site/partials/snapshot.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | -------------------------------------------------------------------------------- /src/kopf/elastic/cluster_mapping.js: -------------------------------------------------------------------------------- 1 | function ClusterMapping(data) { 2 | 3 | this.getIndices = function() { 4 | return Object.keys(data); 5 | }; 6 | 7 | this.getTypes = function(index) { 8 | var indexMapping = getProperty(data, index + '.mappings', {}); 9 | return Object.keys(indexMapping); 10 | }; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /_site/partials/debug.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{visible ? 'hide' : 'show'}} log 4 | 5 |
6 | {{msg}} 7 |
8 |
-------------------------------------------------------------------------------- /src/kopf/models/gist.js: -------------------------------------------------------------------------------- 1 | function Gist(title, url) { 2 | this.timestamp = getTimeString(new Date()); 3 | this.title = title; 4 | this.url = url; 5 | 6 | this.loadFromJSON = function(json) { 7 | this.title = json.title; 8 | this.url = json.url; 9 | this.timestamp = json.timestamp; 10 | return this; 11 | }; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tests/jasmine/filters/starts_with.js: -------------------------------------------------------------------------------- 1 | describe('filter', function() { 2 | 3 | beforeEach(module('kopf')); 4 | 5 | describe('startsWithFilter', function() { 6 | 7 | it('should convert boolean values to unicode checkmark or cross', 8 | inject(function(startsWith) { 9 | expect(startsWith(['abc', 'acd', 'abd'], 'a')).toBe(['fabc', 'acd', 10 | 'abd']); 11 | })); 12 | }); 13 | }); -------------------------------------------------------------------------------- /src/kopf/controllers/confirm_dialog.js: -------------------------------------------------------------------------------- 1 | kopf.controller('ConfirmDialogController', ['$scope', 'ConfirmDialogService', 2 | function($scope, ConfirmDialogService) { 3 | 4 | $scope.dialog_service = ConfirmDialogService; 5 | 6 | $scope.close = function() { 7 | $scope.dialog_service.close(); 8 | }; 9 | 10 | $scope.confirm = function() { 11 | $scope.dialog_service.confirm(); 12 | }; 13 | 14 | } 15 | ]); 16 | -------------------------------------------------------------------------------- /_site/font-awesome/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .pull-right { float: right; } 11 | .pull-left { float: left; } 12 | 13 | .@{fa-css-prefix} { 14 | &.pull-left { margin-right: .3em; } 15 | &.pull-right { margin-left: .3em; } 16 | } 17 | -------------------------------------------------------------------------------- /_site/font-awesome/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .pull-right { float: right; } 11 | .pull-left { float: left; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.pull-left { margin-right: .3em; } 15 | &.pull-right { margin-left: .3em; } 16 | } 17 | -------------------------------------------------------------------------------- /src/kopf/controllers/cluster_stats.js: -------------------------------------------------------------------------------- 1 | kopf.controller('ClusterStatsController', ['$scope', 'ElasticService', 2 | function($scope, ElasticService) { 3 | 4 | $scope.cluster = undefined; 5 | 6 | $scope.$watch( 7 | function() { 8 | return ElasticService.cluster; 9 | }, 10 | function(newValue, oldValue) { 11 | $scope.cluster = ElasticService.cluster; 12 | } 13 | ); 14 | 15 | } 16 | ]); 17 | -------------------------------------------------------------------------------- /_site/font-awesome/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /_site/font-awesome/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /src/kopf/css/analysis.css: -------------------------------------------------------------------------------- 1 | .analysis-token { 2 | border-radius: 1px 1px 1px 1px; 3 | margin-right: 10px; 4 | padding-left: 2px; 5 | padding-right: 2px; 6 | margin-top: 5px; 7 | float: left; 8 | } 9 | .analysis-token-token { 10 | font-size:11px; 11 | text-align: center; 12 | font-weight: 500; 13 | } 14 | .analysis-token-info { 15 | font-size:9px; 16 | display: inline; 17 | } 18 | .analysis-text-input { 19 | overflow: hidden; 20 | display: block; 21 | } -------------------------------------------------------------------------------- /src/kopf/css/explain.css: -------------------------------------------------------------------------------- 1 | .explanation-result { 2 | border: 1px solid #444; 3 | border-radius: 4px; 4 | margin-bottom: 10px; 5 | margin-top: 10px; 6 | } 7 | .explanation-result-title { 8 | font-size: 14px; 9 | padding: 5px; 10 | } 11 | .explanation-result table { 12 | border-top: none; 13 | border-left: none; 14 | border-right: none; 15 | } 16 | 17 | .explanation-result .table thead > tr > th { 18 | border-bottom: 1px solid #444; 19 | } 20 | -------------------------------------------------------------------------------- /src/kopf/filters/starts_with.js: -------------------------------------------------------------------------------- 1 | kopf.filter('startsWith', function() { 2 | 3 | function strStartsWith(str, prefix) { 4 | return (str + '').indexOf(prefix) === 0; 5 | } 6 | 7 | return function(elements, prefix) { 8 | var filtered = []; 9 | angular.forEach(elements, function(element) { 10 | if (strStartsWith(element, prefix)) { 11 | filtered.push(element); 12 | } 13 | }); 14 | 15 | return filtered; 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /src/kopf/css/cluster_health.css: -------------------------------------------------------------------------------- 1 | .cluster-health-top-actions { 2 | text-align: right; 3 | } 4 | .cluster-health-content { 5 | font-family: monospace; 6 | white-space: pre; 7 | } 8 | 9 | .cluster-health-form-group { 10 | padding-left: 5px; 11 | float: right; 12 | } 13 | .gist-timestamp-col { 14 | width: 70px; 15 | } 16 | .gist-link-col { 17 | width: 240px; 18 | } 19 | .gist-title-col { 20 | width: auto; 21 | } 22 | .cluster-health-filters { 23 | padding-top: 6px; 24 | } -------------------------------------------------------------------------------- /src/kopf/models/snapshot_filter.js: -------------------------------------------------------------------------------- 1 | function SnapshotFilter() { 2 | 3 | this.clone = function() { 4 | return new SnapshotFilter(); 5 | }; 6 | 7 | this.getSorting = function() { 8 | return undefined; 9 | }; 10 | 11 | this.equals = function(other) { 12 | return other !== null; 13 | }; 14 | 15 | this.isBlank = function() { 16 | return true; 17 | }; 18 | 19 | this.matches = function(snapshot) { 20 | return true; 21 | }; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/kopf/filters/bytes.js: -------------------------------------------------------------------------------- 1 | kopf.filter('bytes', function() { 2 | 3 | var UNITS = ['b', 'KB', 'MB', 'GB', 'TB', 'PB']; 4 | 5 | function stringify(bytes) { 6 | if (bytes > 0) { 7 | var e = Math.floor(Math.log(bytes) / Math.log(1024)); 8 | return (bytes / Math.pow(1024, e)).toFixed(2) + UNITS[e]; 9 | } else { 10 | return 0 + UNITS[0]; 11 | } 12 | } 13 | 14 | return function(bytes) { 15 | return stringify(bytes); 16 | }; 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /src/kopf/elastic/snapshot.js: -------------------------------------------------------------------------------- 1 | function Snapshot(info) { 2 | this.name = info.snapshot; 3 | this.indices = info.indices; 4 | this.state = info.state; 5 | this.start_time = info.start_time; 6 | this.start_time_in_millis = info.start_time_in_millis; 7 | this.end_time = info.end_time; 8 | this.end_time_in_millis = info.end_time_in_millis; 9 | this.duration_in_millis = info.duration_in_millis; 10 | this.failures = info.failures; 11 | this.shards = info.shards; 12 | } 13 | -------------------------------------------------------------------------------- /_site/font-awesome/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: (-@fa-li-width + (4em / 14)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /_site/font-awesome/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/kopf/css/aliases.css: -------------------------------------------------------------------------------- 1 | .aliases-index-alias { 2 | border: 1px solid; 3 | border-radius: 2px 2px 2px 2px; 4 | margin-left: 5px; 5 | padding-left: 3px; 6 | padding-right: 2px; 7 | line-height: 14px; 8 | margin-bottom: 2px; 9 | height: 16px; 10 | } 11 | 12 | .aliases-alias-cell { 13 | width: 200px; 14 | max-width: 200px; 15 | min-width: 200px; 16 | } 17 | .aliases-new-alias-input { 18 | overflow: hidden; 19 | } 20 | .aliases-new-alias-input > a { 21 | text-decoration: none; 22 | color: inherit; 23 | } -------------------------------------------------------------------------------- /_site/font-awesome/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "animated"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | -------------------------------------------------------------------------------- /src/kopf/controllers/alerts.js: -------------------------------------------------------------------------------- 1 | kopf.controller('AlertsController', ['$scope', 'AlertService', 2 | function($scope, AlertService) { 3 | 4 | $scope.alerts = []; 5 | 6 | $scope.$watch( 7 | function() { 8 | return AlertService.alerts; 9 | }, 10 | function(newValue, oldValue) { 11 | $scope.alerts = AlertService.alerts; 12 | } 13 | ); 14 | 15 | $scope.remove = function(id) { 16 | AlertService.remove(id); 17 | }; 18 | 19 | } 20 | 21 | ]); 22 | -------------------------------------------------------------------------------- /src/kopf/controllers/debug.js: -------------------------------------------------------------------------------- 1 | kopf.controller('DebugController', ['$scope', 'DebugService', 2 | function($scope, DebugService) { 3 | 4 | $scope.messages = []; 5 | 6 | $scope.visible = false; 7 | 8 | $scope.$watch( 9 | function() { 10 | return $scope.visible ? DebugService.getUpdatedAt() : 0; 11 | }, 12 | function(newValue, oldValue) { 13 | $scope.messages = $scope.visible ? DebugService.getMessages() : []; 14 | } 15 | ); 16 | 17 | } 18 | 19 | ]); 20 | -------------------------------------------------------------------------------- /src/kopf/services/state.js: -------------------------------------------------------------------------------- 1 | kopf.factory('AppState', function() { 2 | 3 | this.properties = {}; 4 | 5 | this.getProperty = function(controller, property, defaultValue) { 6 | if (this.properties[controller] === undefined) { 7 | this.properties[controller] = {}; 8 | } 9 | if (this.properties[controller][property] === undefined) { 10 | this.properties[controller][property] = defaultValue; 11 | } 12 | return this.properties[controller][property]; 13 | }; 14 | 15 | return this; 16 | 17 | }); 18 | -------------------------------------------------------------------------------- /_site/modals/modal_info.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_site/font-awesome/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables.less"; 7 | @import "mixins.less"; 8 | @import "path.less"; 9 | @import "core.less"; 10 | @import "larger.less"; 11 | @import "fixed-width.less"; 12 | @import "list.less"; 13 | @import "bordered-pulled.less"; 14 | @import "animated.less"; 15 | @import "rotated-flipped.less"; 16 | @import "stacked.less"; 17 | @import "icons.less"; 18 | -------------------------------------------------------------------------------- /_site/font-awesome/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/1 FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | transform: translate(0, 0); // ensures no half-pixel rendering in firefox 12 | 13 | } 14 | -------------------------------------------------------------------------------- /_site/font-awesome/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /_site/font-awesome/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/1 FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | transform: translate(0, 0); // ensures no half-pixel rendering in firefox 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/kopf/elastic/cat_result.js: -------------------------------------------------------------------------------- 1 | function CatResult(result) { 2 | var lines = result.split('\n'); 3 | var header = lines[0]; 4 | var columns = header.match(/\S+/g); 5 | var values = lines.slice(1, -1).map(function(line) { 6 | return columns.map(function(column, i) { 7 | var start = header.indexOf(column); 8 | var lastColumn = i < columns.length - 1; 9 | var end = lastColumn ? header.indexOf(columns[i + 1]) : undefined; 10 | return line.substring(start, end).trim(); 11 | }); 12 | }); 13 | 14 | this.columns = columns; 15 | this.lines = values; 16 | } 17 | -------------------------------------------------------------------------------- /_site/font-awesome/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /src/kopf/css/nodes.css: -------------------------------------------------------------------------------- 1 | .node-stat { 2 | font-size: 36px; 3 | font-weight: 300; 4 | float: left; 5 | line-height: 40px; 6 | min-width: 70px; 7 | margin-right: 15px; 8 | } 9 | 10 | .node-stat-detail { 11 | float: left; 12 | line-height: 20px; 13 | } 14 | 15 | .node-labels { 16 | padding-left: 20px; 17 | } 18 | 19 | .node-label { 20 | display: inline; 21 | padding: .1em .3em .1em; 22 | font-size: 8px; 23 | line-height: 1; 24 | color: #ffffff; 25 | text-align: center; 26 | white-space: nowrap; 27 | vertical-align: baseline; 28 | border-radius: .25em; 29 | } 30 | -------------------------------------------------------------------------------- /src/kopf/css/rest_client.css: -------------------------------------------------------------------------------- 1 | .rest-client-request-url { 2 | overflow: hidden; 3 | display: block; 4 | } 5 | .rest-client-request-method { 6 | width: 80px; 7 | float: right; 8 | margin-left: 10px; 9 | } 10 | .rest-client-execute { 11 | float: right; 12 | margin-left: 10px; 13 | line-height: 12px; 14 | } 15 | .rest-client-request-body { 16 | font-size: 11px; 17 | margin-top: 20px; 18 | } 19 | .request-client-request-body-options { 20 | overflow: hidden; 21 | display: block; 22 | } 23 | .history-icon { 24 | float: right; 25 | padding-left: 10px; 26 | font-size: 22px; 27 | } 28 | 29 | .history-icon > a:hover { 30 | text-decoration: none; 31 | } -------------------------------------------------------------------------------- /src/kopf/models/warmer_filter.js: -------------------------------------------------------------------------------- 1 | function WarmerFilter(id) { 2 | 3 | this.id = id; 4 | 5 | this.clone = function() { 6 | return new WarmerFilter(this.id); 7 | }; 8 | 9 | this.getSorting = function() { 10 | return undefined; 11 | }; 12 | 13 | this.equals = function(other) { 14 | return other !== null && this.id == other.id; 15 | }; 16 | 17 | this.isBlank = function() { 18 | return !notEmpty(this.id); 19 | }; 20 | 21 | this.matches = function(warmer) { 22 | if (this.isBlank()) { 23 | return true; 24 | } else { 25 | return warmer.id.indexOf(this.id) != -1; 26 | } 27 | }; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/kopf/directives/navbar_section.js: -------------------------------------------------------------------------------- 1 | kopf.directive('ngNavbarSection', ['$location', 'ElasticService', 2 | function($location, ElasticService) { 3 | 4 | return { 5 | template: function(elem, attrs) { 6 | if (!attrs.version || ElasticService.versionCheck(attrs.version)) { 7 | var target = attrs.target; 8 | var text = attrs.text; 9 | var icon = attrs.icon; 10 | return '' + 11 | ' ' + text + 12 | ''; 13 | } else { 14 | return ''; 15 | } 16 | } 17 | }; 18 | } 19 | 20 | ]); 21 | -------------------------------------------------------------------------------- /tests/es_connection.js: -------------------------------------------------------------------------------- 1 | test("Creating regular ES connection", function() { 2 | var con = new ESConnection("http://localhost:9200"); 3 | ok(con.host == "http://localhost:9200", "Checking host"); 4 | }) 5 | 6 | test("Creating HTTPS ES connection", function() { 7 | var con = new ESConnection("https://localhost:9200"); 8 | ok(con.host == "https://localhost:9200", "Checking host"); 9 | }) 10 | 11 | test("Creating ES connection with username + password", function() { 12 | var con = new ESConnection("http://foo:bar@localhost:9200"); 13 | ok(con.host == "http://localhost:9200", "Checking host"); 14 | ok(con.username == "foo", "Checking username"); 15 | ok(con.password == "bar", "Checking password"); 16 | }) -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.9.4 2 | 3 | # upgrade 4 | RUN apt-get update && \ 5 | apt-get upgrade -y && \ 6 | apt-get install -y --no-install-recommends python-pip curl && \ 7 | rm -rf /var/lib/apt/lists/* && \ 8 | pip install envtpl 9 | 10 | # nginx 11 | ADD nginx.conf.tpl /etc/nginx/nginx.conf.tpl 12 | 13 | # run script 14 | ADD ./run.sh ./run.sh 15 | 16 | # kopf 17 | ENV KOPF_VERSION 2.0.1 18 | RUN curl -s -L "https://github.com/lmenezes/elasticsearch-kopf/archive/v${KOPF_VERSION}.tar.gz" | \ 19 | tar xz -C /tmp && mv "/tmp/elasticsearch-kopf-${KOPF_VERSION}" /kopf 20 | 21 | # logs 22 | VOLUME ["/var/log/nginx"] 23 | 24 | # ports 25 | EXPOSE 80 443 26 | 27 | ENTRYPOINT ["/run.sh"] 28 | -------------------------------------------------------------------------------- /tests/models/cat_result.js: -------------------------------------------------------------------------------- 1 | QUnit.test( "cat result when column starts after value(docs)", function( assert ) { 2 | var response = 3 | 'id host ip node \n' + 4 | 'FDr3acnmQkaaz-9m2YZxHw foobarfoobarfooba 10.8.36.70 foobarfoobarfoobarfoobarfooba \n'; 5 | var result = new CatResult(response); 6 | var expectedColumns = ['id', 'host', 'ip', 'node']; 7 | var expectedValues = ['FDr3acnmQkaaz-9m2YZxHw', 'foobarfoobarfooba', '10.8.36.70', 'foobarfoobarfoobarfoobarfooba']; 8 | assert.deepEqual(result.columns, expectedColumns, "correctly parses columns"); 9 | assert.deepEqual(result.lines[0], expectedValues, "correctly parses values"); 10 | }); 11 | -------------------------------------------------------------------------------- /_site/font-awesome/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .@{fa-css-prefix}-rotate-90, 15 | :root .@{fa-css-prefix}-rotate-180, 16 | :root .@{fa-css-prefix}-rotate-270, 17 | :root .@{fa-css-prefix}-flip-horizontal, 18 | :root .@{fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /docker/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | envtpl --keep-template /etc/nginx/nginx.conf.tpl 6 | 7 | if [ ! -z "${KOPF_BASIC_AUTH_LOGIN}" ]; then 8 | echo "${KOPF_BASIC_AUTH_LOGIN}:${KOPF_BASIC_AUTH_PASSWORD}" > /etc/nginx/kopf.htpasswd 9 | fi 10 | 11 | KOPF_REFRESH_RATE="${KOPF_REFRESH_RATE:-5000}" 12 | KOPF_THEME="${KOPF_THEME:-dark}" 13 | KOPF_WITH_CREDENTIALS="${KOPF_WITH_CREDENTIALS:-false}" 14 | KOPF_ES_ROOT_PATH="${KOPF_ES_ROOT_PATH:-/es}" 15 | 16 | cat < /kopf/_site/kopf_external_settings.json 17 | { 18 | "elasticsearch_root_path": "${KOPF_ES_ROOT_PATH}", 19 | "with_credentials": ${KOPF_WITH_CREDENTIALS}, 20 | "theme": "${KOPF_THEME}", 21 | "refresh_rate": ${KOPF_REFRESH_RATE} 22 | } 23 | EOF 24 | 25 | exec nginx 26 | -------------------------------------------------------------------------------- /_site/partials/main_alerts.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 |   7 | 8 | {{a.message}} 9 | 10 | {{a.message}} 11 |
12 |
{{a.getResponse()}}
13 |
14 |
15 |
-------------------------------------------------------------------------------- /src/kopf/elastic/cluster_health.js: -------------------------------------------------------------------------------- 1 | function ClusterHealth(health) { 2 | this.status = health.status; 3 | this.cluster_name = health.cluster_name; 4 | this.initializing_shards = health.initializing_shards; 5 | this.active_primary_shards = health.active_primary_shards; 6 | this.active_shards = health.active_shards; 7 | this.relocating_shards = health.relocating_shards; 8 | this.unassigned_shards = health.unassigned_shards; 9 | this.number_of_nodes = health.number_of_nodes; 10 | this.number_of_data_nodes = health.number_of_data_nodes; 11 | this.timed_out = health.timed_out; 12 | this.shards = this.active_shards + this.relocating_shards + 13 | this.unassigned_shards + this.initializing_shards; 14 | this.fetched_at = getTimeString(new Date()); 15 | } 16 | -------------------------------------------------------------------------------- /src/kopf/filters/time_interval.js: -------------------------------------------------------------------------------- 1 | kopf.filter('timeInterval', function() { 2 | 3 | var UNITS = ['yr', 'mo', 'd', 'h', 'min']; 4 | 5 | var UNIT_MEASURE = { 6 | yr: 31536000000, 7 | mo: 2678400000, 8 | wk: 604800000, 9 | d: 86400000, 10 | h: 3600000, 11 | min: 60000 12 | }; 13 | 14 | function stringify(seconds) { 15 | 16 | var result = 'less than a minute'; 17 | 18 | for (var idx = 0; idx < UNITS.length; idx++) { 19 | var amount = Math.floor(seconds / UNIT_MEASURE[UNITS[idx]]); 20 | if (amount) { 21 | result = amount + UNITS[idx] + '.'; 22 | break; 23 | } 24 | } 25 | 26 | return result; 27 | } 28 | 29 | return function(seconds) { 30 | return stringify(seconds); 31 | }; 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /tests/jasmine/filters/time_interval.tests.js: -------------------------------------------------------------------------------- 1 | describe('filter', function() { 2 | 3 | beforeEach(module('kopf')); 4 | 5 | describe('timeInterval', function() { 6 | 7 | it('should convert boolean values to unicode checkmark or cross', 8 | inject(function(timeIntervalFilter) { 9 | expect(timeIntervalFilter(100000)).toBe('1min.'); 10 | expect(timeIntervalFilter(1000000)).toBe('16min.'); 11 | expect(timeIntervalFilter(10000000)).toBe('2h.'); 12 | expect(timeIntervalFilter(100000000)).toBe('1d.'); 13 | expect(timeIntervalFilter(1000000000)).toBe('11d.'); 14 | expect(timeIntervalFilter(10000000000)).toBe('3mo.'); 15 | expect(timeIntervalFilter(100000000000)).toBe('3yr.'); 16 | })); 17 | }); 18 | }); -------------------------------------------------------------------------------- /_site/font-awesome/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .#{$fa-css-prefix}-rotate-90, 15 | :root .#{$fa-css-prefix}-rotate-180, 16 | :root .#{$fa-css-prefix}-rotate-270, 17 | :root .#{$fa-css-prefix}-flip-horizontal, 18 | :root .#{$fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /src/kopf/services/debug.js: -------------------------------------------------------------------------------- 1 | kopf.factory('DebugService', ['$filter', function($filter) { 2 | 3 | var MaxMessages = 1000; 4 | 5 | var messages = []; 6 | 7 | var updatedAt = 0; 8 | 9 | var addMessage = function(message) { 10 | var date = new Date(); 11 | messages.push($filter('date')(date, '[yyyy-MM-dd HH:mm:ss] ') + message); 12 | if (messages.length > MaxMessages) { 13 | messages.shift(); 14 | } 15 | updatedAt = date.getTime(); 16 | }; 17 | 18 | this.debug = function(message, data) { 19 | addMessage(message); 20 | if (data) { 21 | addMessage(JSON.stringify(data)); 22 | } 23 | }; 24 | 25 | this.getUpdatedAt = function() { 26 | return updatedAt; 27 | }; 28 | 29 | this.getMessages = function() { 30 | return messages; 31 | }; 32 | 33 | return this; 34 | 35 | }]); 36 | -------------------------------------------------------------------------------- /_site/font-awesome/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'), 9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /_site/font-awesome/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), 9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /src/kopf/services/clipboard.js: -------------------------------------------------------------------------------- 1 | kopf.factory('ClipboardService', ['AlertService', '$document', '$window', 2 | function(AlertService, $document, $window) { 3 | var textarea = angular.element($document[0].createElement('textarea')); 4 | textarea.css({ 5 | position: 'absolute', 6 | left: '-9999px', 7 | top: ( 8 | $window.pageYOffset || $document[0].documentElement.scrollTop 9 | ) + 'px' 10 | }); 11 | textarea.attr({readonly: ''}); 12 | angular.element($document[0].body).append(textarea); 13 | 14 | this.copy = function(value, success, failure) { 15 | try { 16 | textarea.val(value); 17 | textarea.select(); 18 | $document[0].execCommand('copy'); 19 | success(); 20 | } catch (error) { 21 | failure(); 22 | } 23 | }; 24 | 25 | return this; 26 | }]); 27 | -------------------------------------------------------------------------------- /_site/font-awesome/less/animated.less: -------------------------------------------------------------------------------- 1 | // Animated Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .@{fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /_site/font-awesome/scss/_animated.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .#{$fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/kopf/directives/json_tree.js: -------------------------------------------------------------------------------- 1 | (function(kopf, JSONTree) { 2 | 'use strict'; 3 | kopf.directive('kopfJsonTree', function($sce) { 4 | var directive = { 5 | restrict: 'E', 6 | template:'
', 7 | scope: { 8 | kopfBind: '=' 9 | }, 10 | link: function(scope, element, attrs, requires) { 11 | scope.$watch('kopfBind', function(value) { 12 | var result; 13 | if (value) { 14 | try { 15 | result = JSONTree.create(value); 16 | } catch (invalidJsonError) { 17 | result = invalidJsonError; 18 | } 19 | } else { 20 | result = ''; 21 | } 22 | 23 | scope.result = $sce.trustAsHtml(result); 24 | }); 25 | } 26 | }; 27 | return directive; 28 | }); 29 | })(kopf, JSONTree); 30 | -------------------------------------------------------------------------------- /_site/partials/directives/pagination.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {{page.first | number:0}}-{{page.last | number:0}} of {{page.total | number:0}} {{label || ''}} 16 | 17 |
-------------------------------------------------------------------------------- /tests/jasmine/filters/bytes.tests.js: -------------------------------------------------------------------------------- 1 | describe('filter', function() { 2 | 3 | beforeEach(module('kopf')); 4 | 5 | describe('bytes', function() { 6 | 7 | it('should convert boolean values to unicode checkmark or cross', 8 | inject(function(bytesFilter) { 9 | expect(bytesFilter(1)).toBe('1.00b'); 10 | expect(bytesFilter(12)).toBe('12.00b'); 11 | expect(bytesFilter(123)).toBe('123.00b'); 12 | expect(bytesFilter(1234)).toBe('1.21KB'); 13 | expect(bytesFilter(12345)).toBe('12.06KB'); 14 | expect(bytesFilter(123456)).toBe('120.56KB'); 15 | expect(bytesFilter(1234567)).toBe('1.18MB'); 16 | expect(bytesFilter(12345678)).toBe('11.77MB'); 17 | expect(bytesFilter(123456789)).toBe('117.74MB'); 18 | expect(bytesFilter(1234567890)).toBe('1.15GB'); 19 | expect(bytesFilter(1234567890000000)).toBe('1.10PB'); 20 | })); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/kopf/models/request.js: -------------------------------------------------------------------------------- 1 | function Request(path, method, body) { 2 | this.timestamp = getTimeString(new Date()); 3 | this.path = path; 4 | this.method = method; 5 | this.body = body; 6 | 7 | this.clear = function() { 8 | this.path = ''; 9 | this.method = ''; 10 | this.body = ''; 11 | }; 12 | 13 | this.loadFromJSON = function(json) { 14 | if (isDefined(json.url)) { 15 | var url = json.url.substring(7); 16 | var path = url.substring(url.indexOf('/')); 17 | this.path = path; 18 | } else { 19 | this.path = json.path; 20 | } 21 | this.method = json.method; 22 | this.body = json.body; 23 | this.timestamp = json.timestamp; 24 | return this; 25 | }; 26 | 27 | this.equals = function(request) { 28 | return ( 29 | this.path === request.path && 30 | this.method.toUpperCase() === request.method.toUpperCase() && 31 | this.body === request.body 32 | ); 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/kopf/elastic/es_connection.js: -------------------------------------------------------------------------------- 1 | // Expects URL according to /^(https|http):\/\/(\w+):(\w+)@(.*)/i; 2 | // Examples: 3 | // http://localhost:9200 4 | // http://user:password@localhost:9200 5 | // https://localhost:9200 6 | function ESConnection(url, withCredentials) { 7 | if (url.indexOf('http://') !== 0 && url.indexOf('https://') !== 0) { 8 | url = 'http://' + url; 9 | } 10 | var protectedUrl = /^(https|http):\/\/(\w+):(\w+)@(.*)/i; 11 | this.host = 'http://localhost:9200'; // default 12 | this.withCredentials = withCredentials; 13 | if (notEmpty(url)) { 14 | var connectionParts = protectedUrl.exec(url); 15 | if (isDefined(connectionParts)) { 16 | this.host = connectionParts[1] + '://' + connectionParts[4]; 17 | this.username = connectionParts[2]; 18 | this.password = connectionParts[3]; 19 | this.auth = 'Basic ' + window.btoa(this.username + ':' + this.password); 20 | } else { 21 | this.host = url; 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /_site/modals/confirm_dialog.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/jasmine/directives/navbar_section.tests.js: -------------------------------------------------------------------------------- 1 | describe('ngNavbarSection', function() { 2 | var $compile, $rootScope, $location; 3 | 4 | beforeEach(module('kopf')); 5 | 6 | beforeEach(function() { 7 | module('kopf'); 8 | module(function($provide) { 9 | $provide.value('ElasticService', { 10 | versionCheck: function() { 11 | return true; 12 | } 13 | }); 14 | }); 15 | }); 16 | 17 | beforeEach(inject(function(_$compile_, _$rootScope_, _$location_) { 18 | $compile = _$compile_; 19 | $rootScope = _$rootScope_; 20 | $location = _$location_; 21 | this.scope = $rootScope.$new(); 22 | })); 23 | 24 | it('Creates correct HTML', function() { 25 | var element = $compile('
  • ')($rootScope); 26 | $rootScope.$digest(); 27 | expect(element.html()).toContain(' snapshot text'); 28 | }); 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /_site/partials/index_settings/cache.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 | 6 | 7 | 8 | 9 |
    10 |
    11 |
    12 |
    13 | 14 | 15 | 16 | 17 | 18 |
    19 |
    20 |
    -------------------------------------------------------------------------------- /_site/font-awesome/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/1 FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | transform: translate(0, 0); // ensures no half-pixel rendering in firefox 12 | 13 | } 14 | 15 | .fa-icon-rotate(@degrees, @rotation) { 16 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation); 17 | -webkit-transform: rotate(@degrees); 18 | -ms-transform: rotate(@degrees); 19 | transform: rotate(@degrees); 20 | } 21 | 22 | .fa-icon-flip(@horiz, @vert, @rotation) { 23 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1); 24 | -webkit-transform: scale(@horiz, @vert); 25 | -ms-transform: scale(@horiz, @vert); 26 | transform: scale(@horiz, @vert); 27 | } 28 | -------------------------------------------------------------------------------- /src/kopf/elastic/node_hot_threads.js: -------------------------------------------------------------------------------- 1 | function NodeHotThreads(data) { 2 | var lines = data.split('\n'); 3 | this.header = lines[0]; 4 | // pre 4859ce5d79a786b58b1cd2fb131614677efd6b91 5 | var BackwardCompatible = lines[1].indexOf('Hot threads at') == -1; 6 | var HeaderLines = BackwardCompatible ? 2 : 3; 7 | this.subHeader = BackwardCompatible ? undefined : lines[1]; 8 | this.node = this.header.substring( 9 | this.header.indexOf('[') + 1, 10 | this.header.indexOf(']') 11 | ); 12 | var threads = []; 13 | var thread; 14 | if (lines.length > HeaderLines) { 15 | lines.slice(HeaderLines).forEach(function(line) { 16 | var blankLine = line.trim().length === 0; 17 | if (thread) { 18 | if (thread.subHeader) { 19 | thread.stack.push(line); 20 | if (blankLine) { 21 | thread = undefined; 22 | } 23 | } else { 24 | thread.subHeader = line; 25 | } 26 | } else { 27 | thread = new HotThread(line); 28 | threads.push(thread); 29 | } 30 | }); 31 | } 32 | this.threads = threads; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/kopf/models/index_template_filter.js: -------------------------------------------------------------------------------- 1 | function IndexTemplateFilter(name, template) { 2 | 3 | this.name = name; 4 | this.template = template; 5 | 6 | this.clone = function() { 7 | return new IndexTemplateFilter(name, template); 8 | }; 9 | 10 | this.getSorting = function() { 11 | return function(a, b) { 12 | return a.name.localeCompare(b.name); 13 | }; 14 | }; 15 | 16 | this.equals = function(other) { 17 | return (other !== null && 18 | this.name === other.name && 19 | this.template === other.template); 20 | }; 21 | 22 | this.isBlank = function() { 23 | return !notEmpty(this.name) && !notEmpty(this.template); 24 | }; 25 | 26 | this.matches = function(template) { 27 | if (this.isBlank()) { 28 | return true; 29 | } else { 30 | var matches = true; 31 | if (notEmpty(this.name)) { 32 | matches = template.name.indexOf(this.name) != -1; 33 | } 34 | if (matches && notEmpty(this.template)) { 35 | matches = template.body.template.indexOf(this.template) != -1; 36 | } 37 | return matches; 38 | } 39 | }; 40 | 41 | } 42 | -------------------------------------------------------------------------------- /_site/font-awesome/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/1 FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | transform: translate(0, 0); // ensures no half-pixel rendering in firefox 12 | 13 | } 14 | 15 | @mixin fa-icon-rotate($degrees, $rotation) { 16 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 17 | -webkit-transform: rotate($degrees); 18 | -ms-transform: rotate($degrees); 19 | transform: rotate($degrees); 20 | } 21 | 22 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 23 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 24 | -webkit-transform: scale($horiz, $vert); 25 | -ms-transform: scale($horiz, $vert); 26 | transform: scale($horiz, $vert); 27 | } 28 | -------------------------------------------------------------------------------- /src/kopf/directives/pagination.js: -------------------------------------------------------------------------------- 1 | kopf.directive('ngPagination', ['$document', function($document) { 2 | 3 | return { 4 | scope: { 5 | paginator: '=paginator', 6 | page: '=page', 7 | label: '=label' 8 | }, 9 | templateUrl: './partials/directives/pagination.html', 10 | link: function(scope, element, attrs) { 11 | var handler = function(event) { 12 | var $target = $(event.target); 13 | if ($target.is('input, textarea')) { 14 | return; 15 | } 16 | if (event.keyCode == 39 && scope.page.next) { 17 | scope.$apply(function() { 18 | scope.paginator.nextPage(); 19 | event.preventDefault(); 20 | }); 21 | } 22 | if (event.keyCode == 37 && scope.page.previous) { 23 | scope.$apply(function() { 24 | scope.paginator.previousPage(); 25 | event.preventDefault(); 26 | }); 27 | } 28 | }; 29 | 30 | $document.bind('keydown', handler); 31 | element.on('$destroy', function() { 32 | $document.unbind('keydown', handler); 33 | }); 34 | } 35 | }; 36 | }]); 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Leonardo Menezes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/kopf/controllers/cat.js: -------------------------------------------------------------------------------- 1 | kopf.controller('CatController', ['$scope', 'ElasticService', 'AlertService', 2 | function($scope, ElasticService, AlertService) { 3 | 4 | $scope.apis = [ 5 | 'aliases', 6 | //'allocation', 7 | 'count', 8 | //'fielddata', 9 | //'health', 10 | //'indices', 11 | 'master', 12 | //'nodes', 13 | //'pending_tasks', 14 | 'plugins', 15 | 'recovery', 16 | //'thread_pool', 17 | //'shards', 18 | //'segments' 19 | ]; 20 | 21 | $scope.api = ''; 22 | 23 | $scope.result = undefined; 24 | 25 | $scope.execute = function() { 26 | if ($scope.api.length > 0) { 27 | ElasticService.executeCatRequest( 28 | $scope.api, 29 | function(result) { 30 | $scope.result = result; 31 | }, 32 | function(error) { 33 | AlertService.error('Error while fetching data', error); 34 | $scope.result = undefined; 35 | } 36 | ); 37 | } else { 38 | AlertService.error('You must select an API'); 39 | } 40 | }; 41 | } 42 | 43 | ]); 44 | -------------------------------------------------------------------------------- /src/kopf/controllers/cluster_settings.js: -------------------------------------------------------------------------------- 1 | kopf.controller('ClusterSettingsController', ['$scope', '$location', '$timeout', 2 | 'AlertService', 'ElasticService', 3 | function($scope, $location, $timeout, AlertService, ElasticService) { 4 | 5 | $scope.initializeController = function() { 6 | $('#cluster_settings_option a').tab('show'); 7 | $('#cluster_settings_tabs a:first').tab('show'); 8 | $('.setting-info').popover(); 9 | $scope.active_settings = 'transient'; // remember last active? 10 | $scope.settings = new ClusterSettings(ElasticService.cluster.settings); 11 | }; 12 | 13 | $scope.save = function() { 14 | var settings = JSON.stringify($scope.settings, undefined, ''); 15 | ElasticService.updateClusterSettings(settings, 16 | function(response) { 17 | AlertService.success('Cluster settings were successfully updated', 18 | response); 19 | ElasticService.refresh(); 20 | }, 21 | function(error) { 22 | AlertService.error('Error while updating cluster settings', error); 23 | } 24 | ); 25 | }; 26 | } 27 | ]); 28 | -------------------------------------------------------------------------------- /src/kopf/models/alias_filter.js: -------------------------------------------------------------------------------- 1 | function AliasFilter(index, alias) { 2 | 3 | this.index = index; 4 | this.alias = alias; 5 | 6 | this.clone = function() { 7 | return new AliasFilter(this.index, this.alias); 8 | }; 9 | 10 | this.getSorting = function() { 11 | return undefined; 12 | }; 13 | 14 | this.equals = function(other) { 15 | return (other !== null && 16 | this.index == other.index && 17 | this.alias == other.alias); 18 | }; 19 | 20 | this.isBlank = function() { 21 | return !notEmpty(this.index) && !notEmpty(this.alias); 22 | }; 23 | 24 | this.matches = function(indexAlias) { 25 | if (this.isBlank()) { 26 | return true; 27 | } else { 28 | var matches = true; 29 | if (notEmpty(this.index)) { 30 | matches = indexAlias.index.indexOf(this.index) != -1; 31 | } 32 | if (matches && notEmpty(this.alias)) { 33 | matches = false; 34 | var aliases = indexAlias.aliases; 35 | for (var i = 0; !matches && i < aliases.length; i++) { 36 | var alias = aliases[i]; 37 | matches = alias.alias.indexOf(this.alias) != -1; 38 | } 39 | } 40 | return matches; 41 | } 42 | }; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /tests/jasmine/alerts.tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('AlertsController', function() { 4 | var scope, createController; 5 | 6 | beforeEach(angular.mock.module('kopf')); 7 | 8 | beforeEach(angular.mock.inject(function($rootScope, $controller, $injector) { 9 | this.scope = $rootScope.$new(); 10 | this.AlertService = $injector.get('AlertService'); 11 | this.createController = function() { 12 | return $controller('AlertsController', {$scope: this.scope}, 13 | this.AlertService); 14 | }; 15 | this._controller = this.createController(); 16 | })); 17 | 18 | it('init : values are set', function() { 19 | expect(this.scope.alerts).toEqual([]); 20 | }); 21 | 22 | it('updates alerts when alert service changes', function() { 23 | expect(this.scope.alerts.length).toEqual(0); 24 | this.AlertService.info("test"); 25 | this.scope.$digest(); 26 | expect(this.scope.alerts.length).toEqual(1); 27 | }); 28 | 29 | it('removes an alert when remove method is called', function() { 30 | spyOn(this.AlertService, 'remove').andReturn(true); 31 | this.scope.remove('hello'); 32 | expect(this.AlertService.remove).toHaveBeenCalledWith('hello'); 33 | }); 34 | 35 | }); 36 | 37 | -------------------------------------------------------------------------------- /src/kopf/elastic/version.js: -------------------------------------------------------------------------------- 1 | function Version(version) { 2 | var checkVersion = new RegExp('(\\d)\\.(\\d)\\.(\\d)\\.*'); 3 | var major; 4 | var minor; 5 | var patch; 6 | var value = version; 7 | var valid = false; 8 | 9 | if (checkVersion.test(value)) { 10 | valid = true; 11 | var parts = checkVersion.exec(version); 12 | major = parseInt(parts[1]); 13 | minor = parseInt(parts[2]); 14 | patch = parseInt(parts[3]); 15 | } 16 | 17 | this.isValid = function() { 18 | return valid; 19 | }; 20 | 21 | this.getMajor = function() { 22 | return major; 23 | }; 24 | 25 | this.getMinor = function() { 26 | return minor; 27 | }; 28 | 29 | this.getPatch = function() { 30 | return patch; 31 | }; 32 | 33 | this.getValue = function() { 34 | return value; 35 | }; 36 | 37 | this.isGreater = function(other) { 38 | var higherMajor = major > other.getMajor(); 39 | var higherMinor = major == other.getMajor() && minor > other.getMinor(); 40 | var higherPatch = ( 41 | major == other.getMajor() && 42 | minor == other.getMinor() && 43 | patch >= other.getPatch() 44 | ); 45 | return (higherMajor || higherMinor || higherPatch); 46 | }; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/kopf/services/host_history.js: -------------------------------------------------------------------------------- 1 | kopf.factory('HostHistoryService', function() { 2 | 3 | this.getHostHistory = function() { 4 | var history = localStorage.getItem('kopfHostHistory'); 5 | history = isDefined(history) ? history : '[]'; 6 | return JSON.parse(history); 7 | }; 8 | 9 | this.addToHistory = function(connection) { 10 | var host = connection.host.toLowerCase(); 11 | var username = connection.username; 12 | var password = connection.password; 13 | if (username && password) { 14 | host = host.replace(/^(https|http):\/\//gi, function addAuth(prefix) { 15 | return prefix + username + ':' + password + '@'; 16 | }); 17 | } 18 | var entry = {host: host}; 19 | var history = this.getHostHistory(); 20 | for (var i = 0; i < history.length; i++) { 21 | if (history[i].host === host) { 22 | history.splice(i, 1); 23 | break; 24 | } 25 | } 26 | history.splice(0, 0, entry); 27 | if (history.length > 10) { 28 | history.length = 10; 29 | } 30 | localStorage.setItem('kopfHostHistory', JSON.stringify(history)); 31 | }; 32 | 33 | this.clearHistory = function() { 34 | localStorage.removeItem('kopfHostHistory'); 35 | }; 36 | 37 | return this; 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /src/kopf/elastic/cluster_settings.js: -------------------------------------------------------------------------------- 1 | function ClusterSettings(settings) { 2 | // FIXME: 0.90/1.0 check 3 | var valid = [ 4 | // cluster 5 | 'cluster.blocks.read_only', 6 | 'indices.ttl.interval', 7 | 'indices.cache.filter.size', 8 | 'discovery.zen.minimum_master_nodes', 9 | // recovery 10 | 'indices.recovery.concurrent_streams', 11 | 'indices.recovery.compress', 12 | 'indices.recovery.file_chunk_size', 13 | 'indices.recovery.translog_ops', 14 | 'indices.recovery.translog_size', 15 | 'indices.recovery.max_bytes_per_sec', 16 | // routing 17 | 'cluster.routing.allocation.node_initial_primaries_recoveries', 18 | 'cluster.routing.allocation.cluster_concurrent_rebalance', 19 | 'cluster.routing.allocation.awareness.attributes', 20 | 'cluster.routing.allocation.node_concurrent_recoveries', 21 | 'cluster.routing.allocation.disable_allocation', 22 | 'cluster.routing.allocation.disable_replica_allocation' 23 | ]; 24 | var instance = this; 25 | ['persistent', 'transient'].forEach(function(type) { 26 | instance[type] = {}; 27 | var currentSettings = settings[type]; 28 | valid.forEach(function(setting) { 29 | instance[type][setting] = getProperty(currentSettings, setting); 30 | }); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /src/kopf/directives/sort_table.js: -------------------------------------------------------------------------------- 1 | kopf.directive('ngSortBy', 2 | function() { 3 | 4 | function updateSortingIcon(scope, elem, attrs) { 5 | var sorts = scope.sortBy === attrs.property; 6 | var sortIcon = elem.find('i'); 7 | sortIcon.removeClass('fa-sort-asc fa-sort-desc'); 8 | if (sorts) { 9 | if (scope.reverse) { 10 | sortIcon.addClass('fa-sort-desc'); 11 | } else { 12 | sortIcon.addClass('fa-sort-asc'); 13 | } 14 | } 15 | } 16 | 17 | function link(scope, elem, attrs) { 18 | scope.$watch( 19 | function() { 20 | return scope.sortBy; 21 | }, 22 | function() { 23 | updateSortingIcon(scope, elem, attrs); 24 | }); 25 | 26 | scope.$watch( 27 | function() { 28 | return scope.reverse; 29 | }, 30 | function() { 31 | updateSortingIcon(scope, elem, attrs); 32 | } 33 | ); 34 | } 35 | 36 | return { 37 | link: link, 38 | template: function(elem, attrs) { 39 | return '' + attrs.text + 41 | ''; 42 | } 43 | }; 44 | } 45 | ); 46 | -------------------------------------------------------------------------------- /src/kopf/controllers/hotthreads.js: -------------------------------------------------------------------------------- 1 | kopf.controller('HotThreadsController', ['$scope', 'ElasticService', 2 | 'AlertService', 3 | function($scope, ElasticService, AlertService) { 4 | 5 | $scope.node = undefined; 6 | 7 | $scope.nodes = []; 8 | 9 | $scope.type = 'cpu'; 10 | 11 | $scope.types = ['cpu', 'wait', 'block']; 12 | 13 | $scope.interval = '500ms'; 14 | 15 | $scope.threads = 3; 16 | 17 | $scope.ignoreIdleThreads = true; 18 | 19 | $scope.nodesHotThreads = undefined; 20 | 21 | $scope.execute = function() { 22 | ElasticService.getHotThreads($scope.node, $scope.type, $scope.threads, 23 | $scope.interval, $scope.ignoreIdleThreads, 24 | function(result) { 25 | $scope.nodesHotThreads = result; 26 | }, 27 | function(error) { 28 | AlertService.error('Error while fetching hot threads', error); 29 | $scope.nodesHotThreads = undefined; 30 | } 31 | ); 32 | }; 33 | 34 | $scope.$watch( 35 | function() { 36 | return ElasticService.cluster; 37 | }, 38 | function(current, previous) { 39 | $scope.nodes = ElasticService.getNodes(); 40 | }, 41 | true 42 | ); 43 | 44 | $scope.initializeController = function() { 45 | $scope.nodes = ElasticService.getNodes(); 46 | }; 47 | 48 | } 49 | 50 | ]); 51 | -------------------------------------------------------------------------------- /src/kopf/models/ace_editor.js: -------------------------------------------------------------------------------- 1 | function AceEditor(target) { 2 | // ace editor 3 | ace.config.set('basePath', 'dist/'); 4 | this.editor = ace.edit(target); 5 | this.editor.setFontSize('10px'); 6 | this.editor.setTheme('ace/theme/kopf'); 7 | this.editor.getSession().setMode('ace/mode/json'); 8 | this.editor.setOptions({ 9 | fontFamily: 'Monaco, Menlo, Consolas, "Courier New", monospace', 10 | fontSize: '12px', 11 | fontWeight: '400' 12 | }); 13 | 14 | // validation error 15 | this.error = null; 16 | 17 | // sets value and moves cursor to beggining 18 | this.setValue = function(value) { 19 | this.editor.setValue(value, 1); 20 | this.editor.gotoLine(0, 0, false); 21 | }; 22 | 23 | this.getValue = function() { 24 | return this.editor.getValue(); 25 | }; 26 | 27 | // formats the json content 28 | this.format = function() { 29 | var content = this.editor.getValue(); 30 | try { 31 | if (isDefined(content) && content.trim().length > 0) { 32 | this.error = null; 33 | content = JSON.stringify(JSON.parse(content), undefined, 2); 34 | this.editor.setValue(content, 0); 35 | this.editor.gotoLine(0, 0, false); 36 | } 37 | } catch (error) { 38 | this.error = error.toString(); 39 | } 40 | return content; 41 | }; 42 | 43 | this.hasContent = function() { 44 | return this.editor.getValue().trim().length > 0; 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elasticsearch-kopf", 3 | "version": "2.0.1", 4 | "description": "kopf - simple web administration tool for ElasticSearch", 5 | "main": "index.html", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/lmenezes/elasticsearch-kopf.git" 12 | }, 13 | "author": "Leonardo Menezes", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/lmenezes/elasticsearch-kopf/issues" 17 | }, 18 | "devDependencies": { 19 | "grunt": "^0.4.5", 20 | "grunt-contrib-clean": "~0.5.0", 21 | "grunt-contrib-concat": "~0.3.0", 22 | "grunt-contrib-connect": "^0.5.0", 23 | "grunt-contrib-copy": "~0.4.1", 24 | "grunt-contrib-jshint": "~0.8.0", 25 | "grunt-contrib-qunit": "~0.4.0", 26 | "grunt-contrib-watch": "~0.5.3", 27 | "grunt-jscs": "^1.0.0", 28 | "grunt-karma": "~0.6.2", 29 | "karma": "~0.10.9", 30 | "karma-chrome-launcher": "~0.1.2", 31 | "karma-coffee-preprocessor": "~0.1.2", 32 | "karma-firefox-launcher": "~0.1.3", 33 | "karma-html2js-preprocessor": "~0.1.0", 34 | "karma-jasmine": "~0.1.5", 35 | "karma-phantomjs-launcher": "~0.1.2", 36 | "karma-requirejs": "~0.2.1", 37 | "karma-script-launcher": "~0.1.0", 38 | "requirejs": "~2.1.10" 39 | }, 40 | "directories": { 41 | "test": "tests" 42 | }, 43 | "dependencies": { 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/kopf/elastic/broken_cluster.js: -------------------------------------------------------------------------------- 1 | function BrokenCluster(health, state, nodesStats, settings, nodes) { 2 | 3 | this.status = health.status; 4 | this.initializing_shards = health.initializing_shards; 5 | this.active_primary_shards = health.active_primary_shards; 6 | this.active_shards = health.active_shards; 7 | this.relocating_shards = health.relocating_shards; 8 | this.unassigned_shards = health.unassigned_shards; 9 | this.number_of_nodes = health.number_of_nodes; 10 | this.number_of_data_nodes = health.number_of_data_nodes; 11 | this.timed_out = health.timed_out; 12 | this.shards = this.active_shards + this.relocating_shards + 13 | this.unassigned_shards + this.initializing_shards; 14 | this.fetched_at = getTimeString(new Date()); 15 | 16 | this.name = state.cluster_name; 17 | this.master_node = state.master_node; 18 | 19 | this.settings = settings; 20 | 21 | var totalSize = 0; 22 | 23 | this.nodes = Object.keys(nodes.nodes).map(function(nodeId) { 24 | var nodeStats = nodesStats.nodes[nodeId]; 25 | var nodeInfo = nodes.nodes[nodeId]; 26 | var node = new Node(nodeId, nodeStats, nodeInfo); 27 | if (nodeId === state.master_node) { 28 | node.setCurrentMaster(); 29 | } 30 | return node; 31 | }); 32 | 33 | this.getNodes = function() { 34 | return this.nodes; 35 | }; 36 | 37 | this.total_size = readablizeBytes(totalSize); 38 | this.total_size_in_bytes = totalSize; 39 | this.indices = []; 40 | } 41 | -------------------------------------------------------------------------------- /src/kopf/models/node_filter.js: -------------------------------------------------------------------------------- 1 | function NodeFilter(name, data, master, client, timestamp) { 2 | this.name = name; 3 | this.data = data; 4 | this.master = master; 5 | this.client = client; 6 | this.timestamp = timestamp; 7 | 8 | this.clone = function() { 9 | return new NodeFilter(this.name, this.data, this.master, this.client); 10 | }; 11 | 12 | this.getSorting = function() { 13 | return undefined; 14 | }; 15 | 16 | this.equals = function(other) { 17 | return ( 18 | other !== null && 19 | this.name == other.name && 20 | this.data == other.data && 21 | this.master == other.master && 22 | this.client == other.client && 23 | this.timestamp == other.timestamp 24 | ); 25 | }; 26 | 27 | this.isBlank = function() { 28 | return !notEmpty(this.name) && (this.data && this.master && this.client); 29 | }; 30 | 31 | this.matches = function(node) { 32 | if (this.isBlank()) { 33 | return true; 34 | } else { 35 | return this.matchesName(node.name) && this.matchesType(node); 36 | } 37 | }; 38 | 39 | this.matchesType = function(node) { 40 | return ( 41 | node.data && this.data || 42 | node.master && this.master || 43 | node.client && this.client 44 | ); 45 | }; 46 | 47 | this.matchesName = function(name) { 48 | if (notEmpty(this.name)) { 49 | return name.toLowerCase().indexOf(this.name.toLowerCase()) != -1; 50 | } else { 51 | return true; 52 | } 53 | }; 54 | 55 | } 56 | -------------------------------------------------------------------------------- /tests/jasmine/services/debug.tests.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("DebugService", function() { 4 | 5 | var service, filter; 6 | 7 | beforeEach(module("kopf")); 8 | 9 | beforeEach(function() { 10 | module('kopf'); 11 | 12 | module(function($provide) { 13 | $provide.value('$filter', 14 | function() { 15 | return function() { 16 | return 'prefix> '; 17 | } 18 | } 19 | ); 20 | }); 21 | }); 22 | 23 | beforeEach(inject(function($injector) { 24 | service = $injector.get('DebugService'); 25 | filter = $injector.get('$filter'); 26 | filter.date = function() { 27 | return 'xxx'; 28 | }; 29 | })); 30 | 31 | it("should correctly add a formatted message to the messages list", function() { 32 | service.debug("hello"); 33 | expect(service.getMessages().length).toEqual(1); 34 | expect(service.getMessages()).toEqual(['prefix> hello']); 35 | }); 36 | 37 | it("should limit message to 100", function() { 38 | for (var i = 0; i < 1003; i++) { 39 | service.debug('message ' + i); 40 | } 41 | expect(service.getMessages().length).toEqual(1000); 42 | expect(service.getMessages()[999]).toEqual('prefix> message 1002'); 43 | 44 | }); 45 | 46 | it("adding a message should change internal updatedAt value", function() { 47 | expect(service.getUpdatedAt()).toEqual(0); 48 | service.debug('message'); 49 | expect(service.getUpdatedAt()).not.toEqual(0); 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /_site/partials/cluster_overview/filters.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 | 8 |
    9 |
    11 | closed 14 | ({{cluster.closedIndices}}) 15 |
    16 |
    18 | special 21 | ({{cluster.special_indices}}) 22 |
    23 |
    24 |
    25 |
    26 |
    27 |
    28 | 31 |
    32 |
    33 |
    34 |
    35 | 40 | -------------------------------------------------------------------------------- /src/kopf/elastic/percolator.js: -------------------------------------------------------------------------------- 1 | function PercolateQuery(queryInfo) { 2 | this.index = queryInfo._index; 3 | this.id = queryInfo._id; 4 | this.source = queryInfo._source; 5 | this.filter = {}; 6 | 7 | this.sourceAsJSON = function() { 8 | try { 9 | return JSON.stringify(this.source, undefined, 2); 10 | } catch (error) { 11 | 12 | } 13 | }; 14 | 15 | this.equals = function(other) { 16 | return (other instanceof PercolateQuery && 17 | this.index == other.index && 18 | this.id == other.id && 19 | this.source == other.source); 20 | }; 21 | } 22 | 23 | function PercolatorsPage(from, size, total, percolators) { 24 | this.from = from; 25 | this.size = size; 26 | this.total = total; 27 | this.percolators = percolators; 28 | 29 | this.hasNextPage = function() { 30 | return from + size < total; 31 | }; 32 | 33 | this.hasPreviousPage = function() { 34 | return from > 0; 35 | }; 36 | 37 | this.firstResult = function() { 38 | return total > 0 ? from + 1 : 0; 39 | }; 40 | 41 | this.lastResult = function() { 42 | return this.hasNextPage() ? from + size : total; 43 | }; 44 | 45 | this.nextOffset = function() { 46 | return this.hasNextPage() ? from + size : from; 47 | }; 48 | 49 | this.previousOffset = function() { 50 | return this.hasPreviousPage() ? from - size : from; 51 | }; 52 | 53 | this.getPage = function() { 54 | return percolators; 55 | }; 56 | 57 | this.total = function() { 58 | return total; 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /_site/partials/snapshot/azure_repository.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 |
    5 |
    6 | 7 | 8 |
    9 |
    10 | 11 | 12 |
    13 |
    14 | 15 | 16 |
    17 |
    18 | 19 | 20 |
    21 |
    22 | 25 | 28 |
    29 |
    30 |
    -------------------------------------------------------------------------------- /_site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
    19 |
    20 |
    21 |
    22 |
    23 |
    24 | 25 |
    26 |
    27 |
    28 |
    29 |
    30 |
    31 | 32 | 33 | -------------------------------------------------------------------------------- /_site/partials/aliases/alias_details.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_site/partials/snapshot/fs_repository.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 |
    5 |
    6 | 7 | 8 |
    9 |
    10 | 11 | 12 |
    13 |
    14 | 15 | 16 |
    17 |
    18 | 19 | 20 |
    21 |
    22 | 25 | 28 |
    29 |
    30 |
    -------------------------------------------------------------------------------- /src/kopf/css/navbar.css: -------------------------------------------------------------------------------- 1 | .dropdown-menu { 2 | margin-top: 0px; 3 | margin-left: 25px; 4 | } 5 | 6 | .navbar-nav { 7 | margin-top: 0px; 8 | font-size: 13px; 9 | } 10 | 11 | .navbar-collapse { 12 | border-top: 0px; 13 | } 14 | 15 | .navbar-toggle { 16 | padding: 6px 9px; 17 | margin-right: 25px; 18 | height: 22px; 19 | width: 30px; 20 | margin-top: 4px; 21 | margin-bottom: 4px; 22 | } 23 | .navbar-toggle .icon-bar { 24 | width: 10px; 25 | height: 1px; 26 | } 27 | .navbar-fixed-top { 28 | margin-right: 0px !important; 29 | } 30 | .navbar { 31 | height: 30px !important; 32 | min-height: 30px !important; 33 | } 34 | .navbar-logo { 35 | font-size: 38px; 36 | font-variant: small-caps; 37 | line-height: 32px !important; 38 | padding-top: 0px !important; 39 | padding-bottom: 0px !important; 40 | font-weight: 100; 41 | vertical-align: middle; 42 | } 43 | .navbar-logo-action { 44 | font-size: 24px; 45 | font-weight: 100; 46 | vertical-align: middle; 47 | font-variant: normal; 48 | } 49 | .navbar-menu-section { 50 | vertical-align: middle; 51 | padding-top: 4px; 52 | padding-bottom: 4px; 53 | width: 220px !important; 54 | } 55 | .navbar-nav > li { 56 | height: 30px !important; 57 | } 58 | .navbar-nav > li > a { 59 | height: 30px !important; 60 | line-height: 30px; 61 | padding-top: 0px; 62 | padding-bottom:0px; 63 | padding-left: 13px; 64 | padding-right: 13px; 65 | } 66 | .nav-pills > li > a { 67 | font-weight: 400; 68 | font-size: 13px; 69 | } 70 | .navbar-app-settings { 71 | width: auto; 72 | padding-top: 6px; 73 | padding-left: 10px; 74 | padding-right: 10px; 75 | } 76 | .navbar-app-setting { 77 | line-height: 30px; 78 | font-weight: 300; 79 | font-size: 12px; 80 | } 81 | -------------------------------------------------------------------------------- /src/kopf/controllers/nodes.js: -------------------------------------------------------------------------------- 1 | kopf.controller('NodesController', ['$scope', 'ConfirmDialogService', 2 | 'AlertService', 'ElasticService', 'AppState', 3 | function($scope, ConfirmDialogService, AlertService, ElasticService, 4 | AppState) { 5 | 6 | $scope.sortBy = 'name'; 7 | $scope.reverse = false; 8 | 9 | $scope.setSortBy = function(field) { 10 | if ($scope.sortBy === field) { 11 | $scope.reverse = !$scope.reverse; 12 | } 13 | $scope.sortBy = field; 14 | }; 15 | 16 | $scope.filter = AppState.getProperty( 17 | 'NodesController', 18 | 'filter', 19 | new NodeFilter('', true, true, true, 0) 20 | ); 21 | 22 | $scope.nodes = []; 23 | 24 | $scope.$watch('filter', 25 | function(newValue, oldValue) { 26 | $scope.refresh(); 27 | }, 28 | true); 29 | 30 | $scope.$watch( 31 | function() { 32 | return ElasticService.cluster; 33 | }, 34 | function(newValue, oldValue) { 35 | $scope.refresh(); 36 | } 37 | ); 38 | 39 | $scope.refresh = function() { 40 | var nodes = ElasticService.getNodes(); 41 | $scope.nodes = nodes.filter(function(node) { 42 | return $scope.filter.matches(node); 43 | }); 44 | }; 45 | 46 | $scope.showNodeStats = function(nodeId) { 47 | ElasticService.getNodeStats(nodeId, 48 | function(nodeStats) { 49 | $scope.displayInfo('stats for ' + nodeStats.name, nodeStats.stats); 50 | }, 51 | function(error) { 52 | AlertService.error('Error while loading node stats', error); 53 | } 54 | ); 55 | }; 56 | 57 | } 58 | 59 | ]); 60 | -------------------------------------------------------------------------------- /src/kopf/services/explain.js: -------------------------------------------------------------------------------- 1 | kopf.factory('ExplainService', ['$TreeDnDConvert', 2 | function($TreeDnDConvert) { 3 | function containsString(value, searched) { 4 | return value.indexOf(searched) >= 0; 5 | } 6 | this.isExplainPath = function(path) { 7 | return path && 8 | (containsString(path, '_explain') || 9 | containsString(path, '?explain') || 10 | containsString(path, 'explain=true')); 11 | }; 12 | /** 13 | * Normalize Get document by id and Document search responses. 14 | * Build explanation tree for TreeDnd directive. 15 | */ 16 | this.normalizeExplainResponse = function(response) { 17 | var lHits; 18 | if (response.hits) { 19 | // Explain query 20 | lHits = response.hits.hits; 21 | // Remove hits from main response 22 | delete response.hits.hits; 23 | } else { 24 | // Explain document 25 | lHits = [response]; 26 | } 27 | lHits.forEach(function(lHit) { 28 | // Sometimes ._explanation, .sometimes explanation, let's normalize it 29 | if (lHit.explanation) { 30 | var lExplanation = lHit.explanation; 31 | delete response.explanation; 32 | response._explanation = lExplanation; 33 | } 34 | lHit.documentId = lHit._index + '/' + lHit._type + '/' + lHit._id; 35 | if (lHit._explanation) { 36 | if (!lHit._score) { 37 | lHit._score = lHit._explanation.value; 38 | } 39 | lHit.explanationTreeData = 40 | $TreeDnDConvert.tree2tree([lHit._explanation], 'details'); 41 | } 42 | }); 43 | return lHits; 44 | }; 45 | 46 | return this; 47 | }]); 48 | -------------------------------------------------------------------------------- /src/kopf/util.js: -------------------------------------------------------------------------------- 1 | function readablizeBytes(bytes) { 2 | if (bytes > 0) { 3 | var s = ['b', 'KB', 'MB', 'GB', 'TB', 'PB']; 4 | var e = Math.floor(Math.log(bytes) / Math.log(1024)); 5 | return (bytes / Math.pow(1024, e)).toFixed(2) + s[e]; 6 | } else { 7 | return 0; 8 | } 9 | } 10 | 11 | // Gets the value of a nested property from an object if it exists. 12 | // Otherwise returns the default_value given. 13 | // Example: get the value of object[a][b][c][d] 14 | // where property_path is [a,b,c,d] 15 | function getProperty(object, propertyPath, defaultValue) { 16 | if (isDefined(object)) { 17 | if (isDefined(object[propertyPath])) { 18 | return object[propertyPath]; 19 | } 20 | var pathParts = propertyPath.split('.'); // path as nested properties 21 | for (var i = 0; i < pathParts.length && isDefined(object); i++) { 22 | object = object[pathParts[i]]; 23 | } 24 | } 25 | return isDefined(object) ? object : defaultValue; 26 | } 27 | 28 | // Checks if value is both non null and undefined 29 | function isDefined(value) { 30 | return value !== null && typeof value != 'undefined'; 31 | } 32 | 33 | // Checks if the String representation of value is a non empty string 34 | // string.trim().length is grater than 0 35 | function notEmpty(value) { 36 | return isDefined(value) && value.toString().trim().length > 0; 37 | } 38 | 39 | function isNumber(value) { 40 | var exp = /\d+/; 41 | return exp.test(value); 42 | } 43 | 44 | // Returns the given date as a String formatted as hh:MM:ss 45 | function getTimeString(date) { 46 | var hh = ('0' + date.getHours()).slice(-2); 47 | var mm = ('0' + date.getMinutes()).slice(-2); 48 | var ss = ('0' + date.getSeconds()).slice(-2); 49 | return hh + ':' + mm + ':' + ss; 50 | } 51 | -------------------------------------------------------------------------------- /docker/nginx.conf.tpl: -------------------------------------------------------------------------------- 1 | daemon off; 2 | user www-data; 3 | worker_processes 4; 4 | pid /run/nginx.pid; 5 | 6 | events { 7 | worker_connections 1024; 8 | } 9 | 10 | http { 11 | sendfile on; 12 | tcp_nopush on; 13 | tcp_nodelay on; 14 | keepalive_timeout 65; 15 | types_hash_max_size 2048; 16 | 17 | include /etc/nginx/mime.types; 18 | default_type application/octet-stream; 19 | 20 | access_log /dev/stdout; 21 | error_log /dev/stderr; 22 | 23 | upstream es { 24 | {% for server in KOPF_ES_SERVERS.split(",") %} 25 | server {{ server }}; 26 | {% endfor %} 27 | } 28 | 29 | {% if KOPF_SSL_CERT is defined %} 30 | server { 31 | listen 80; 32 | server_name {{ KOPF_SERVER_NAME }}; 33 | return 301 https://{{ KOPF_SERVER_NAME }}$request_uri; 34 | } 35 | {% endif %} 36 | 37 | server { 38 | {% if KOPF_SSL_CERT is defined %} 39 | listen 443 ssl; 40 | 41 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 42 | ssl_certificate {{ KOPF_SSL_CERT }}; 43 | ssl_certificate_key {{ KOPF_SSL_KEY }}; 44 | {% else %} 45 | listen 80; 46 | {% endif %} 47 | 48 | server_name {{ KOPF_SERVER_NAME }}; 49 | 50 | satisfy any; 51 | 52 | {% if KOPF_BASIC_AUTH_LOGIN is defined %} 53 | auth_basic "Access restricted"; 54 | auth_basic_user_file /etc/nginx/kopf.htpasswd; 55 | {% endif %} 56 | 57 | {% if KOPF_NGINX_INCLUDE_FILE is defined %} 58 | include {{ KOPF_NGINX_INCLUDE_FILE }}; 59 | {% endif %} 60 | 61 | # suppress passing basic auth to upstreams 62 | proxy_set_header Authorization ""; 63 | 64 | # everybody loves caching bugs after upgrade 65 | expires -1; 66 | 67 | location / { 68 | root /kopf/_site; 69 | } 70 | 71 | location /es/ { 72 | rewrite ^/es/(.*)$ /$1 break; 73 | proxy_pass http://es; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/lib/jsontree/jsontree.min.js: -------------------------------------------------------------------------------- 1 | /*! json-tree - v0.2.0 - 2015-06-23 */ 2 | var JSONTree=function(){var a={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"},b=0,c=0;this.create=function(a,b){return c+=1,p(f(a,0,!1),{"class":"jstValue"})};var d=function(b){return b.replace(/[&<>'"]/g,function(b){return a[b]})},e=function(){return c+"_"+b++},f=function(a,b,c){if(null===a)return l(c?b:0);var d=typeof a;switch(d){case"boolean":return k(a,c?b:0);case"number":return j(a,c?b:0);case"string":return i(a,c?b:0);default:return a instanceof Array?h(a,b,c):g(a,b,c)}},g=function(a,b,c){var d=e(),f=Object.keys(a).map(function(c){return m(c,a[c],b+1,!0)}).join(o()),g=[r("{",c?b:0,d),p(f,{id:d}),s("}",b)].join("\n");return p(g,{})},h=function(a,b,c){var d=e(),g=a.map(function(a){return f(a,b+1,!0)}).join(o()),h=[r("[",c?b:0,d),p(g,{id:d}),s("]",b)].join("\n");return h},i=function(a,b){return p(t(n(d(a)),b),{"class":"jstStr"})},j=function(a,b){return p(t(a,b),{"class":"jstNum"})},k=function(a,b){return p(t(a,b),{"class":"jstBool"})},l=function(a){return p(t("null",a),{"class":"jstNull"})},m=function(a,b,c){var e=t(n(d(a))+": ",c),g=p(f(b,c,!1),{});return p(e+g,{"class":"jstProperty"})},n=function(a){return'"'+a+'"'},o=function(){return p(",\n",{"class":"jstComma"})},p=function(a,b){return q("span",b,a)},q=function(a,b,c){return"<"+a+Object.keys(b).map(function(a){return" "+a+'="'+b[a]+'"'}).join("")+">"+c+""},r=function(a,b,c){return p(t(a,b),{"class":"jstBracket"})+p("",{"class":"jstFold",onclick:"JSONTree.toggle('"+c+"')"})};this.toggle=function(a){var b=document.getElementById(a),c=b.parentNode,d=b.previousElementSibling;""===b.className?(b.className="jstHiddenBlock",c.className="jstFolded",d.className="jstExpand"):(b.className="",c.className="",d.className="jstFold")};var s=function(a,b){return p(t(a,b),{})},t=function(a,b){return Array(2*b+1).join(" ")+a};return this}(); -------------------------------------------------------------------------------- /tests/models/url_autocomplete.js: -------------------------------------------------------------------------------- 1 | 2 | var mappings = { 3 | getIndices: function() { 4 | return ['foo', 'bar', 'qux']; 5 | }, 6 | getTypes: function(index) { 7 | return { 8 | foo: ['foobar'], 9 | bar: ['baz', 'qux'], 10 | qux: ['bar'] 11 | }[index]; 12 | } 13 | }; 14 | 15 | test("Autocomplete first part of path", function() { 16 | var suggest = new URLAutocomplete(mappings); 17 | deepEqual(suggest.getAlternatives('f'), [ "_msearch", "_search", "_suggest", "bar", "foo", "qux" ], 'First part suggest'); 18 | }); 19 | 20 | test("Autocomplete second part of path", function() { 21 | var suggest = new URLAutocomplete(mappings); 22 | deepEqual(suggest.getAlternatives('foo/'), [ "foo/_msearch", "foo/_search", "foo/_suggest", "foo/foobar" ], 'Second prt suggest foo/'); 23 | deepEqual(suggest.getAlternatives('foo/f'), [ "foo/_msearch", "foo/_search", "foo/_suggest", "foo/foobar" ], 'Second part suggest foo/f'); 24 | deepEqual(suggest.getAlternatives('_search/'), [ "_search/exists", "_search/template" ], 'Second part suggest _search/'); 25 | }); 26 | 27 | test("Autocomplete third part of path", function() { 28 | var suggest = new URLAutocomplete(mappings); 29 | deepEqual(suggest.getAlternatives('foo/foobar/'), [ "foo/foobar/_msearch", "foo/foobar/_search" ], 'Third part suggest foo/foobar/'); 30 | deepEqual(suggest.getAlternatives('foo/_search/'), [ "foo/_search/exists", "foo/_search/template" ], 'Third part suggest foo/_search/'); 31 | }); 32 | 33 | test("Autocomplete fourth part of path", function() { 34 | var suggest = new URLAutocomplete(mappings); 35 | deepEqual(suggest.getAlternatives('bar/baz/_search/'), [ "bar/baz/_search/exists", "bar/baz/_search/template" ], 'Third part suggest bar/baz/_search'); 36 | deepEqual(suggest.getAlternatives('qux/bar/_msearch/'), [ "qux/bar/_msearch/template" ], 'Third part suggest qux/bar/_msearch'); 37 | }); -------------------------------------------------------------------------------- /src/kopf/controllers/index_settings.js: -------------------------------------------------------------------------------- 1 | kopf.controller('IndexSettingsController', ['$scope', '$location', 2 | 'AlertService', 'ElasticService', 3 | function($scope, $location, AlertService, ElasticService) { 4 | 5 | $scope.index = null; 6 | $scope.settings = null; 7 | $scope.editable_settings = null; 8 | 9 | $scope.save = function() { 10 | var index = $scope.index; 11 | var settings = $scope.settings; 12 | var newSettings = {}; 13 | var editableSettings = $scope.editable_settings; 14 | // TODO: could move that to editable_index_settings model 15 | editableSettings.valid_settings.forEach(function(setting) { 16 | if (notEmpty(editableSettings[setting])) { 17 | newSettings[setting] = editableSettings[setting]; 18 | } 19 | }); 20 | ElasticService.updateIndexSettings(index, 21 | JSON.stringify(newSettings, undefined, ''), 22 | function(response) { 23 | AlertService.success('Index settings were successfully updated', 24 | response); 25 | ElasticService.refresh(); 26 | }, 27 | function(error) { 28 | AlertService.error('Error while updating index settings', error); 29 | } 30 | ); 31 | }; 32 | 33 | $scope.initializeController = function() { 34 | var index = $location.search().index; 35 | ElasticService.getIndexMetadata(index, 36 | function(metadata) { 37 | $scope.index = index; 38 | $scope.settings = metadata.settings; 39 | $scope.editable_settings = new EditableIndexSettings( 40 | $scope.settings 41 | ); 42 | }, 43 | function(error) { 44 | AlertService.error('Error while loading index settings for [' + 45 | index + ']', 46 | error); 47 | } 48 | ); 49 | }; 50 | 51 | } 52 | ]); 53 | -------------------------------------------------------------------------------- /_site/partials/cluster_overview/index_body.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{shard.shard}} 12 | 13 | 14 | 31 | 32 | 33 |
    -------------------------------------------------------------------------------- /_site/partials/cluster_settings.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 | 6 | 10 | 11 |
    12 |
    13 |
    14 |
    15 |
    16 | 21 |
    22 |
    23 |
    24 |
    25 |
    26 |
    27 |
    28 |
    29 |
    30 |
    31 |
    32 | 33 |
    34 |
    35 |
    36 | back 37 | 38 |
    39 |
    40 |
    41 |
    42 |
    43 |
    44 |
    45 | 46 | -------------------------------------------------------------------------------- /src/kopf/controllers/benchmark.js: -------------------------------------------------------------------------------- 1 | kopf.controller('BenchmarkController', ['$scope', '$location', '$timeout', 2 | 'AlertService', 'ElasticService', 3 | function($scope, $location, $timeout, AlertService, ElasticService) { 4 | 5 | $scope.bench = new Benchmark(); 6 | $scope.competitor = new Competitor(); 7 | $scope.indices = []; 8 | $scope.types = []; 9 | 10 | $scope.initializeController = function() { 11 | $scope.indices = ElasticService.getIndices(); 12 | }; 13 | 14 | $scope.addCompetitor = function() { 15 | if (notEmpty($scope.competitor.name)) { 16 | this.bench.addCompetitor($scope.competitor); 17 | $scope.competitor = new Competitor(); 18 | } else { 19 | AlertService.error('Competitor needs a name'); 20 | } 21 | }; 22 | 23 | $scope.removeCompetitor = function(index) { 24 | $scope.bench.competitors.splice(index, 1); 25 | }; 26 | 27 | $scope.editCompetitor = function(index) { 28 | var edit = $scope.bench.competitors.splice(index, 1); 29 | $scope.competitor = edit[0]; 30 | }; 31 | 32 | $scope.runBenchmark = function() { 33 | $('#benchmark-result').html(''); 34 | try { 35 | var json = $scope.bench.toJson(); 36 | ElasticService.executeBenchmark(json, 37 | function(response) { 38 | $scope.result = JSONTree.create(response); 39 | $('#benchmark-result').html($scope.result); 40 | }, 41 | function(error, status) { 42 | if (status == 503) { 43 | AlertService.info('No available nodes for benchmarking. ' + 44 | 'At least one node must be started with ' + 45 | '\'--node.bench true\' option.'); 46 | } else { 47 | AlertService.error(error.error); 48 | } 49 | } 50 | ); 51 | } catch (error) { 52 | AlertService.error(error); 53 | } 54 | }; 55 | 56 | } 57 | ]); 58 | -------------------------------------------------------------------------------- /src/kopf/elastic/node.js: -------------------------------------------------------------------------------- 1 | function Node(nodeId, nodeStats, nodeInfo) { 2 | this.id = nodeId; 3 | this.name = nodeInfo.name; 4 | this.elasticVersion = nodeInfo.version; 5 | this.jvmVersion = nodeInfo.jvm.version; 6 | this.availableProcessors = nodeInfo.os.available_processors; 7 | this.transportAddress = nodeInfo.transport_address; 8 | this.host = nodeInfo.host; 9 | 10 | var attributes = getProperty(nodeInfo, 'attributes', {}); 11 | var master = attributes.master === 'false' ? false : true; 12 | var data = attributes.data === 'false' ? false : true; 13 | var client = attributes.client === 'true' ? true : false; 14 | this.master = master && !client; 15 | this.data = data && !client; 16 | this.client = client || !master && !data; 17 | this.current_master = false; 18 | 19 | this.stats = nodeStats; 20 | this.uptime = nodeStats.jvm.uptime_in_millis; 21 | 22 | this.heap_used = readablizeBytes(getProperty(this.stats, 23 | 'jvm.mem.heap_used_in_bytes')); 24 | 25 | this.heap_committed = readablizeBytes(getProperty(this.stats, 26 | 'jvm.mem.heap_committed_in_bytes')); 27 | 28 | this.heap_used_percent = getProperty(this.stats, 'jvm.mem.heap_used_percent'); 29 | 30 | this.heap_max = readablizeBytes(getProperty(this.stats, 31 | 'jvm.mem.heap_max_in_bytes')); 32 | 33 | this.disk_total_in_bytes = getProperty(this.stats, 'fs.total.total_in_bytes'); 34 | this.disk_free_in_bytes = getProperty(this.stats, 'fs.total.free_in_bytes'); 35 | var diskUsedInBytes = (this.disk_total_in_bytes - this.disk_free_in_bytes); 36 | var usedRatio = (diskUsedInBytes / this.disk_total_in_bytes); 37 | this.disk_used_percent = Math.round(100 * usedRatio); 38 | 39 | this.cpu = getProperty(this.stats, 'process.cpu.percent'); 40 | 41 | this.load_average = getProperty(this.stats, 'os.load_average'); 42 | 43 | this.setCurrentMaster = function() { 44 | this.current_master = true; 45 | }; 46 | 47 | this.equals = function(node) { 48 | return node.id === this.id; 49 | }; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/kopf/elastic/index.js: -------------------------------------------------------------------------------- 1 | function Index(indexName, clusterState, indexStats, aliases) { 2 | this.name = indexName; 3 | this.shards = null; 4 | this.metadata = {}; 5 | this.state = 'close'; 6 | this.num_of_shards = 0; 7 | this.num_of_replicas = 0; 8 | this.aliases = []; 9 | if (isDefined(aliases)) { 10 | var indexAliases = aliases.aliases; 11 | if (isDefined(indexAliases)) { 12 | this.aliases = Object.keys(aliases.aliases); 13 | } 14 | } 15 | 16 | if (isDefined(clusterState)) { 17 | var routing = getProperty(clusterState, 'routing_table.indices'); 18 | this.state = 'open'; 19 | if (isDefined(routing)) { 20 | var shards = Object.keys(routing[indexName].shards); 21 | this.num_of_shards = shards.length; 22 | var shardMap = routing[indexName].shards; 23 | this.num_of_replicas = shardMap[0].length - 1; 24 | } 25 | } 26 | this.num_docs = getProperty(indexStats, 'primaries.docs.count', 0); 27 | this.deleted_docs = getProperty(indexStats, 'primaries.docs.deleted', 0); 28 | this.size_in_bytes = getProperty(indexStats, 29 | 'primaries.store.size_in_bytes', 0); 30 | this.total_size_in_bytes = getProperty(indexStats, 31 | 'total.store.size_in_bytes', 0); 32 | 33 | this.unassigned = []; 34 | this.unhealthy = false; 35 | 36 | if (isDefined(clusterState) && isDefined(clusterState.routing_table)) { 37 | var instance = this; 38 | var shardsMap = clusterState.routing_table.indices[this.name].shards; 39 | Object.keys(shardsMap).forEach(function(shardNum) { 40 | shardsMap[shardNum].forEach(function(shard) { 41 | if (shard.state != 'STARTED') { 42 | instance.unhealthy = true; 43 | } 44 | }); 45 | }); 46 | } 47 | 48 | this.special = this.name.indexOf('.') === 0 || this.name.indexOf('_') === 0; 49 | 50 | this.equals = function(index) { 51 | return index !== null && index.name == this.name; 52 | }; 53 | 54 | this.closed = this.state === 'close'; 55 | 56 | this.open = this.state === 'open'; 57 | 58 | } 59 | -------------------------------------------------------------------------------- /_site/partials/create_index.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 |
    6 |
    7 |
    8 | 9 | 10 |
    11 |
    12 |
    13 |
    14 |
    15 |
    16 | 17 | 18 |
    19 |
    20 |
    21 |
    22 | 23 | 24 |
    25 |
    26 |
    27 |
    28 |
    29 |
    30 | 31 | 33 |
    34 |
    35 |
    36 |
    37 |
    38 |
    39 | 40 |
    41 |
    42 |
    43 |
    44 |
    45 |
    46 | 47 | back 48 | 49 | 50 |
    51 |
    52 |
    53 |
    -------------------------------------------------------------------------------- /_site/partials/index_settings.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 |

    settings for {{index}}

    6 |
    7 |
    8 |
    9 |
    10 | 18 |
    19 |
    20 |
    21 |
    22 |
    23 |
    24 |
    25 |
    26 |
    27 |
    28 |
    29 |
    30 |
    31 |
    32 | 33 | back 34 | 35 | 36 |
    37 |
    38 |
    39 |
    -------------------------------------------------------------------------------- /src/kopf/elastic/editable_index_settings.js: -------------------------------------------------------------------------------- 1 | function EditableIndexSettings(settings) { 2 | // FIXME: 0.90/1.0 check 3 | this.valid_settings = [ 4 | // blocks 5 | 'index.blocks.read_only', 6 | 'index.blocks.read', 7 | 'index.blocks.write', 8 | 'index.blocks.metadata', 9 | // cache 10 | 'index.cache.filter.max_size', 11 | 'index.cache.filter.expire', 12 | // index 13 | 'index.number_of_replicas', 14 | 'index.index_concurrency', 15 | 'index.warmer.enabled', 16 | 'index.refresh_interval', 17 | 'index.term_index_divisor', 18 | 'index.ttl.disable_purge', 19 | 'index.fail_on_merge_failure', 20 | 'index.gc_deletes', 21 | 'index.codec', 22 | 'index.compound_on_flush', 23 | 'index.term_index_interval', 24 | 'index.auto_expand_replicas', 25 | 'index.recovery.initial_shards', 26 | 'index.compound_format', 27 | // routing 28 | 'index.routing.allocation.disable_allocation', 29 | 'index.routing.allocation.disable_new_allocation', 30 | 'index.routing.allocation.disable_replica_allocation', 31 | 'index.routing.allocation.total_shards_per_node', 32 | // slowlog 33 | 'index.search.slowlog.threshold.query.warn', 34 | 'index.search.slowlog.threshold.query.info', 35 | 'index.search.slowlog.threshold.query.debug', 36 | 'index.search.slowlog.threshold.query.trace', 37 | 'index.search.slowlog.threshold.fetch.warn', 38 | 'index.search.slowlog.threshold.fetch.info', 39 | 'index.search.slowlog.threshold.fetch.debug', 40 | 'index.search.slowlog.threshold.fetch.trace', 41 | 'index.indexing.slowlog.threshold.index.warn', 42 | 'index.indexing.slowlog.threshold.index.info', 43 | 'index.indexing.slowlog.threshold.index.debug', 44 | 'index.indexing.slowlog.threshold.index.trace', 45 | // translog 46 | 'index.translog.flush_threshold_ops', 47 | 'index.translog.flush_threshold_size', 48 | 'index.translog.flush_threshold_period', 49 | 'index.translog.disable_flush', 50 | 'index.translog.fs.type' 51 | ]; 52 | var instance = this; 53 | this.valid_settings.forEach(function(setting) { 54 | instance[setting] = getProperty(settings, setting); 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /_site/dist/theme-kopf.js: -------------------------------------------------------------------------------- 1 | define("ace/theme/kopf",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-kopf",t.cssText='/* CSS slightly modified from github theme. */.ace-kopf .ace_gutter {background: #e8e8e8;color: #AAA;}.ace-kopf {background: #fff;color: #666;}.ace-kopf .ace_keyword {font-weight: bold;}.ace-kopf .ace_string {color: #2DB669;}.ace-kopf .ace_variable.ace_class {color: teal;}.ace-kopf .ace_constant.ace_numeric {color: #B627B6;}.ace-kopf .ace_constant.ace_buildin {color: #0086B3;}.ace-kopf .ace_support.ace_function {color: #0086B3;}.ace-kopf .ace_comment {color: #998;font-style: italic;}.ace-kopf .ace_variable.ace_language {color: #0086B3;}.ace-kopf .ace_paren {font-weight: bold;}.ace-kopf .ace_boolean {font-weight: bold; color: #2525CC;}.ace-kopf .ace_string.ace_regexp {color: #009926;font-weight: normal;}.ace-kopf .ace_variable.ace_instance {color: teal;}.ace-kopf .ace_constant.ace_language {font-weight: bold;}.ace-kopf .ace_cursor {border-left: 2px solid black;}.ace-kopf .ace_overwrite-cursors .ace_cursor {border-left: 0px;border-bottom: 1px solid black;}.ace-kopf .ace_marker-layer .ace_active-line {background: rgb(255, 255, 204);}.ace-kopf .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-kopf.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px white;border-radius: 2px;}/* bold keywords cause cursor issues for some fonts *//* this disables bold style for editor and keeps for static highlighter */.ace-kopf.ace_nobold .ace_line > span {font-weight: normal !important;}.ace-kopf .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-kopf .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-kopf .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-kopf .ace_gutter-active-line {background-color : rgba(0, 0, 0, 0.07);}.ace-kopf .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-kopf .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-kopf .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}';var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)}) -------------------------------------------------------------------------------- /src/kopf/theme-kopf.js: -------------------------------------------------------------------------------- 1 | define("ace/theme/kopf",["require","exports","module","ace/lib/dom"],function(e,t,n){t.isDark=!1,t.cssClass="ace-kopf",t.cssText='/* CSS slightly modified from github theme. */.ace-kopf .ace_gutter {background: #e8e8e8;color: #AAA;}.ace-kopf {background: #fff;color: #666;}.ace-kopf .ace_keyword {font-weight: bold;}.ace-kopf .ace_string {color: #2DB669;}.ace-kopf .ace_variable.ace_class {color: teal;}.ace-kopf .ace_constant.ace_numeric {color: #B627B6;}.ace-kopf .ace_constant.ace_buildin {color: #0086B3;}.ace-kopf .ace_support.ace_function {color: #0086B3;}.ace-kopf .ace_comment {color: #998;font-style: italic;}.ace-kopf .ace_variable.ace_language {color: #0086B3;}.ace-kopf .ace_paren {font-weight: bold;}.ace-kopf .ace_boolean {font-weight: bold; color: #2525CC;}.ace-kopf .ace_string.ace_regexp {color: #009926;font-weight: normal;}.ace-kopf .ace_variable.ace_instance {color: teal;}.ace-kopf .ace_constant.ace_language {font-weight: bold;}.ace-kopf .ace_cursor {border-left: 2px solid black;}.ace-kopf .ace_overwrite-cursors .ace_cursor {border-left: 0px;border-bottom: 1px solid black;}.ace-kopf .ace_marker-layer .ace_active-line {background: rgb(255, 255, 204);}.ace-kopf .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-kopf.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px white;border-radius: 2px;}/* bold keywords cause cursor issues for some fonts *//* this disables bold style for editor and keeps for static highlighter */.ace-kopf.ace_nobold .ace_line > span {font-weight: normal !important;}.ace-kopf .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-kopf .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-kopf .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-kopf .ace_gutter-active-line {background-color : rgba(0, 0, 0, 0.07);}.ace-kopf .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-kopf .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-kopf .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}';var r=e("../lib/dom");r.importCssString(t.cssText,t.cssClass)}) -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | # Kopf in docker 2 | 3 | Tagged docker images for kopf, `lmenezes/elasticsearch-kopf` on docker hub. 4 | 5 | ## Usage 6 | 7 | Use `docker run` as you always do. You need to publish port `80` 8 | (and `443` if you use ssl) in order to have access to kopf. 9 | 10 | Container should have access to elasticsearch. You don't 11 | need to expose elasticsearch to end users of kopf. 12 | 13 | It is strongly recommended to use https and basic auth 14 | if you don't want to get hacked. 15 | 16 | ### Env variables. 17 | 18 | * `KOPF_SERVER_NAME` server name for your grafana, for example `kopf.example.com` 19 | * `KOPF_ES_SERVERS` elasticsearch servers in `host:port[,host:port]` format 20 | * `KOPF_ES_ROOT_PATH` elasticsearch root path 21 | * `KOPF_SSL_CERT` path to ssl `.crt` file, enables http-to-https redirect, should be bind-mounted 22 | * `KOPF_SSL_KEY` path to ssl `.key` file, should be bind-mounted 23 | * `KOPF_BASIC_AUTH_LOGIN` basic auth login, if needed 24 | * `KOPF_BASIC_AUTH_PASSWORD` hashed basic auth password, if needed 25 | * `KOPF_NGINX_INCLUDE_FILE` file to include into main server of nginx (place allowed ips here) 26 | * `KOPF_WITH_CREDENTIALS` set the external setting with_credentials. Default: false 27 | * `KOPF_THEME` set the theme in external settings. Default: dark 28 | * `KOPF_REFRESH_RATE` set the external setting refresh_rate. Default: 5000 29 | 30 | ### Example 31 | 32 | #### pure docker run 33 | 34 | Running kopf with elasticsearch on `es.dev:9200`, 35 | exposing it on `kopf.dev` with ip address `10.10.10.10`: 36 | 37 | ``` 38 | docker run -d -p 10.10.10.10:80:80 -e KOPF_SERVER_NAME=grafana.dev \ 39 | -e KOPF_ES_SERVERS=es.dev:9200 --name kopf lmenezes/elasticsearch-kopf 40 | ``` 41 | #### fig 42 | 43 | An easy way to orchestrate a local docker run is fig 44 | Install fig by fireing up ```pip install fig```. 45 | After create a fig file and off you go. 46 | ``` 47 | $ cat << EOF > fig.yml 48 | kopf: 49 | image: lmenezes/elasticsearch-kopf 50 | ports: 51 | - 8080:80 52 | environment: 53 | - KOPF_SERVER_NAME=dockerhost 54 | - KOPF_ES_SERVERS=172.17.42.1:9200 55 | EOF 56 | $ fig up -d 57 | Creating docker_kopf_1... 58 | $ 59 | ``` 60 | This docker container will connect to an ES instance running on the DOCKER_HOST, which exposes 9200. 61 | -------------------------------------------------------------------------------- /src/kopf/elastic/cluster_changes.js: -------------------------------------------------------------------------------- 1 | function ClusterChanges() { 2 | 3 | this.nodeJoins = null; 4 | this.nodeLeaves = null; 5 | this.indicesCreated = null; 6 | this.indicesDeleted = null; 7 | 8 | this.docDelta = 0; 9 | this.dataDelta = 0; 10 | 11 | this.setDocDelta = function(delta) { 12 | this.docDelta = delta; 13 | }; 14 | 15 | this.getDocDelta = function() { 16 | return this.docDelta; 17 | }; 18 | 19 | this.absDocDelta = function() { 20 | return Math.abs(this.docDelta); 21 | }; 22 | 23 | this.absDataDelta = function() { 24 | return readablizeBytes(Math.abs(this.dataDelta)); 25 | }; 26 | 27 | this.getDataDelta = function() { 28 | return this.dataDelta; 29 | }; 30 | 31 | this.setDataDelta = function(delta) { 32 | this.dataDelta = delta; 33 | }; 34 | 35 | this.hasChanges = function() { 36 | return ( 37 | isDefined(this.nodeJoins) || 38 | isDefined(this.nodeLeaves) || 39 | isDefined(this.indicesCreated) || 40 | isDefined(this.indicesDeleted) 41 | ); 42 | }; 43 | 44 | this.addJoiningNode = function(node) { 45 | this.changes = true; 46 | if (!isDefined(this.nodeJoins)) { 47 | this.nodeJoins = []; 48 | } 49 | this.nodeJoins.push(node); 50 | }; 51 | 52 | this.addLeavingNode = function(node) { 53 | this.changes = true; 54 | if (!isDefined(this.nodeLeaves)) { 55 | this.nodeLeaves = []; 56 | } 57 | this.nodeLeaves.push(node); 58 | }; 59 | 60 | this.hasJoins = function() { 61 | return isDefined(this.nodeJoins); 62 | }; 63 | 64 | this.hasLeaves = function() { 65 | return isDefined(this.nodeLeaves); 66 | }; 67 | 68 | this.hasCreatedIndices = function() { 69 | return isDefined(this.indicesCreated); 70 | }; 71 | 72 | this.hasDeletedIndices = function() { 73 | return isDefined(this.indicesDeleted); 74 | }; 75 | 76 | this.addCreatedIndex = function(index) { 77 | if (!isDefined(this.indicesCreated)) { 78 | this.indicesCreated = []; 79 | } 80 | this.indicesCreated.push(index); 81 | }; 82 | 83 | this.addDeletedIndex = function(index) { 84 | if (!isDefined(this.indicesDeleted)) { 85 | this.indicesDeleted = []; 86 | } 87 | this.indicesDeleted.push(index); 88 | }; 89 | 90 | } 91 | -------------------------------------------------------------------------------- /tests/all.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Example 6 | 7 | 8 | 9 |
    10 |
    11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /tests/karma.config.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Sat Feb 01 2014 16:02:02 GMT-0500 (EST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path, that will be used to resolve files and exclude 8 | basePath: '', 9 | 10 | // frameworks to use 11 | frameworks: ['jasmine'], 12 | 13 | // list of files / patterns to load in the browser 14 | files: [ 15 | '../src/lib/angularjs/angular.min.js', 16 | '../src/lib/angularjs/angular-route.min.js', 17 | '../src/lib/angularjs/angular-animate.min.js', 18 | '../src/lib/jquery/jquery-1.10.2.min.js', 19 | '../src/lib/typeahead/typeahead.js', 20 | '../src/lib/ace/*.js', 21 | '../src/lib/jsontree/*.js', 22 | '../src/lib/angular-tree-dnd/*.js', 23 | '../src/kopf/*.js', 24 | '../src/kopf/**/*.js', 25 | 'angular-mocks.js', 26 | 'jasmine/**/*.tests.js' 27 | ], 28 | 29 | // list of files to exclude 30 | exclude: [ 31 | 32 | ], 33 | 34 | // test results reporter to use 35 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 36 | reporters: ['progress'], 37 | 38 | 39 | // web server port 40 | port: 9876, 41 | 42 | // enable / disable colors in the output (reporters and logs) 43 | colors: true, 44 | 45 | // level of logging 46 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 47 | logLevel: config.LOG_DEBUG, 48 | 49 | // enable / disable watching file and executing tests whenever any file changes 50 | autoWatch: true, 51 | 52 | // Start these browsers, currently available: 53 | // - Chrome 54 | // - ChromeCanary 55 | // - Firefox 56 | // - Opera (has to be installed with `npm install karma-opera-launcher`) 57 | // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`) 58 | // - PhantomJS 59 | // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`) 60 | browsers: ['PhantomJS'], 61 | 62 | // If browser does not capture in given timeout [ms], kill it 63 | captureTimeout: 60000, 64 | 65 | // Continuous Integration mode 66 | // if true, it capture browsers, run tests and exit 67 | singleRun: false 68 | }); 69 | }; 70 | -------------------------------------------------------------------------------- /_site/partials/index_settings/routing.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 | 6 | 7 | 8 | 12 |
    13 |
    14 |
    15 |
    16 | 17 | 18 | 19 | 20 | 24 |
    25 |
    26 |
    27 |
    28 | 29 | 30 | 31 | 32 | 36 |
    37 |
    38 |
    39 |
    40 | 41 | 42 | 43 | 44 | 45 |
    46 |
    47 |
    -------------------------------------------------------------------------------- /src/kopf/services/page.js: -------------------------------------------------------------------------------- 1 | kopf.factory('PageService', ['ElasticService', 'DebugService', '$rootScope', 2 | '$document', function(ElasticService, DebugService, $rootScope, $document) { 3 | 4 | var instance = this; 5 | 6 | this.clusterStatus = undefined; 7 | this.clusterName = undefined; 8 | 9 | this.link = $document[0].querySelector('link[rel~=\'icon\']'); 10 | 11 | if (this.link) { 12 | var faviconUrl = this.link.href; 13 | var img = $document[0].createElement('img'); 14 | img.src = faviconUrl; 15 | } 16 | 17 | $rootScope.$watch( 18 | function() { 19 | return ElasticService.cluster; 20 | }, 21 | function(cluster, oldValue) { 22 | instance.setFavIconColor(cluster ? cluster.status : undefined); 23 | instance.setPageTitle(cluster ? cluster.name : undefined); 24 | } 25 | ); 26 | 27 | /** 28 | * Updates page title if name is different than clusterName 29 | * 30 | * @param {string} name - cluster name 31 | */ 32 | this.setPageTitle = function(name) { 33 | if (name !== this.clusterName) { 34 | if (name) { 35 | $rootScope.title = 'kopf[' + name + ']'; 36 | } else { 37 | $rootScope.title = 'kopf - no connection'; 38 | } 39 | this.clusterName = name; 40 | } 41 | }; 42 | 43 | this.setFavIconColor = function(status) { 44 | if (this.link && this.clusterStatus !== status) { 45 | this.clusterStatus = status; 46 | try { 47 | var colors = {green: '#468847', yellow: '#c09853', red: '#B94A48'}; 48 | var color = status ? colors[status] : '#333'; 49 | var canvas = $document[0].createElement('canvas'); 50 | canvas.width = 32; 51 | canvas.height = 32; 52 | var context = canvas.getContext('2d'); 53 | context.drawImage(img, 0, 0); 54 | context.globalCompositeOperation = 'source-in'; 55 | context.fillStyle = color; 56 | context.fillRect(0, 0, 32, 32); 57 | this.link.type = 'image/x-icon'; 58 | this.link.href = canvas.toDataURL(); 59 | } catch (exception) { 60 | DebugService.debug('Error while changing favicon', exception); 61 | } 62 | } 63 | }; 64 | 65 | return this; 66 | 67 | }]); 68 | -------------------------------------------------------------------------------- /_site/partials/index_settings/blocks.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 | 6 | 7 | 8 | 12 |
    13 |
    14 |
    15 |
    16 | 17 | 18 | 19 | 20 | 24 |
    25 |
    26 |
    27 |
    28 | 29 | 30 | 31 | 32 | 36 |
    37 |
    38 |
    39 |
    40 | 41 | 42 | 43 | 44 | 48 |
    49 |
    50 |
    -------------------------------------------------------------------------------- /_site/partials/snapshot/hdfs_repository.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 |
    5 |
    6 | 7 | 8 |
    9 |
    10 | 11 | 12 |
    13 |
    14 | 15 | 16 |
    17 |
    18 | 19 | 20 |
    21 |
    22 | 23 | 24 |
    25 |
    26 | 29 | 32 |
    33 |
    34 |
    35 |
    36 | 37 | 38 |
    39 |
    40 | 43 | 46 |
    47 |
    48 |
    49 | -------------------------------------------------------------------------------- /_site/partials/cat.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 |
    6 |
    7 |

    8 | 9 | cat apis 10 | 11 |

    12 |
    13 |
    14 |
    15 |
    16 |
    17 |
    18 |
    19 | 22 |
    23 | 26 |
    27 |
    28 |
    29 |
    30 |
    31 | 32 | 33 | 36 | 37 | 38 | 39 | 42 | 43 | 44 |
    34 | {{column}} 35 |
    40 | {{value}} 41 |
    45 |
    46 | no data available 47 |
    48 |
    49 |
    50 |
    51 |
    52 |
    53 |
    54 |
    55 |
    56 |
    57 | -------------------------------------------------------------------------------- /src/kopf/models/paginator.js: -------------------------------------------------------------------------------- 1 | function Paginator(page, pageSize, collection, filter) { 2 | 3 | this.filter = filter; 4 | 5 | this.page = page; 6 | 7 | this.pageSize = pageSize; 8 | 9 | this.$collection = isDefined(collection) ? collection : []; 10 | 11 | this.nextPage = function() { 12 | this.page += 1; 13 | }; 14 | 15 | this.previousPage = function() { 16 | this.page -= 1; 17 | }; 18 | 19 | this.setPageSize = function(newSize) { 20 | this.pageSize = newSize; 21 | }; 22 | 23 | this.getPageSize = function() { 24 | return this.pageSize; 25 | }; 26 | 27 | this.getCurrentPage = function() { 28 | return this.page; 29 | }; 30 | 31 | this.getPage = function() { 32 | var results = this.getResults(); 33 | var total = results.length; 34 | 35 | var first = total > 0 ? ((this.page - 1) * this.pageSize) + 1 : 0; 36 | while (total < first) { 37 | this.previousPage(); 38 | first = (this.page - 1) * this.pageSize + 1; 39 | } 40 | var lastPage = this.page * this.pageSize > total; 41 | var last = lastPage ? total : this.page * this.pageSize; 42 | 43 | var elements = total > 0 ? results.slice(first - 1, last) : []; 44 | 45 | var next = this.pageSize * this.page < total; 46 | var previous = this.page > 1; 47 | while (elements.length < this.pageSize) { 48 | elements.push(null); 49 | } 50 | return new Page(elements, total, first, last, next, previous); 51 | }; 52 | 53 | this.setCollection = function(collection) { 54 | if (this.filter.getSorting()) { 55 | this.$collection = collection.sort(this.filter.getSorting()); 56 | } else { 57 | this.$collection = collection; 58 | } 59 | }; 60 | 61 | this.getResults = function() { 62 | var filter = this.filter; 63 | var collection = this.$collection; 64 | if (filter.isBlank()) { 65 | return collection; 66 | } else { 67 | var filtered = []; 68 | collection.forEach(function(item) { 69 | if (filter.matches(item)) { 70 | filtered.push(item); 71 | } 72 | }); 73 | return filtered; 74 | } 75 | }; 76 | 77 | this.getCollection = function() { 78 | return this.$collection; 79 | }; 80 | 81 | } 82 | 83 | function Page(elements, total, first, last, next, previous) { 84 | this.elements = elements; 85 | this.total = total; 86 | this.first = first; 87 | this.last = last; 88 | this.next = next; 89 | this.previous = previous; 90 | } 91 | -------------------------------------------------------------------------------- /_site/partials/index_settings/translog.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 | 6 | 7 | 8 | 9 |
    10 |
    11 |
    12 |
    13 | 14 | 15 | 16 | 17 | 18 |
    19 |
    20 |
    21 |
    22 | 23 | 24 | 25 | 26 | 27 |
    28 |
    29 |
    30 |
    31 | 32 | 33 | 34 | 35 | 39 |
    40 |
    41 |
    42 |
    43 | 44 | 45 | 46 | 47 | 48 |
    49 |
    50 |
    -------------------------------------------------------------------------------- /src/kopf/services/alerts.js: -------------------------------------------------------------------------------- 1 | var Alert = function(message, response, level, _class, icon) { 2 | var currentDate = new Date(); 3 | this.message = message; 4 | this.response = response; 5 | this.level = level; 6 | this.class = _class; 7 | this.icon = icon; 8 | this.timestamp = getTimeString(currentDate); 9 | this.id = 'alert_box_' + currentDate.getTime(); 10 | 11 | this.hasResponse = function() { 12 | return isDefined(this.response); 13 | }; 14 | 15 | this.getResponse = function() { 16 | if (isDefined(this.response)) { 17 | return JSON.stringify(this.response, undefined, 2); 18 | } 19 | }; 20 | }; 21 | 22 | kopf.factory('AlertService', function() { 23 | this.maxAlerts = 3; 24 | 25 | this.alerts = []; 26 | 27 | // removes ALL alerts 28 | this.clear = function() { 29 | this.alerts.length = 0; 30 | }; 31 | 32 | // remove a particular alert message 33 | this.remove = function(id) { 34 | $('#' + id).fadeTo(1000, 0).slideUp(200, function() { 35 | $(this).remove(); 36 | }); 37 | this.alerts = this.alerts.filter(function(a) { 38 | return id != a.id; 39 | }); 40 | }; 41 | 42 | // creates an error alert 43 | this.error = function(msg, resp, timeout) { 44 | timeout = isDefined(timeout) ? timeout : 7500; 45 | var alert = new Alert(msg, resp, 'error', 'alert-danger', 'fa fa-warning'); 46 | return this.addAlert(alert, timeout); 47 | }; 48 | 49 | // creates an info alert 50 | this.info = function(msg, resp, timeout) { 51 | timeout = isDefined(timeout) ? timeout : 2500; 52 | var alert = new Alert(msg, resp, 'info', 'alert-info', 'fa fa-info'); 53 | return this.addAlert(alert, timeout); 54 | }; 55 | 56 | // creates success alert 57 | this.success = function(msg, resp, timeout) { 58 | timeout = isDefined(timeout) ? timeout : 2500; 59 | var alert = new Alert(msg, resp, 'success', 'alert-success', 'fa fa-check'); 60 | return this.addAlert(alert, timeout); 61 | }; 62 | 63 | // creates a warn alert 64 | this.warn = function(msg, resp, timeout) { 65 | timeout = isDefined(timeout) ? timeout : 5000; 66 | var alert = new Alert(msg, resp, 'warn', 'alert-warning', 'fa fa-info'); 67 | return this.addAlert(alert, timeout); 68 | }; 69 | 70 | this.addAlert = function(alert, timeout) { 71 | this.alerts.unshift(alert); 72 | var service = this; 73 | setTimeout(function() { 74 | service.remove(alert.id); 75 | }, timeout); 76 | if (this.alerts.length >= this.maxAlerts) { 77 | this.alerts.length = 3; 78 | } 79 | return alert.id; 80 | }; 81 | 82 | return this; 83 | }); 84 | -------------------------------------------------------------------------------- /src/lib/csv/csv.js: -------------------------------------------------------------------------------- 1 | // adapted from konlone's in-browser json to csv converter 2 | // https://github.com/konklone/json 3 | var csv = (function($) { 4 | 5 | function getParam(name) { 6 | name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); 7 | var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), 8 | results = regex.exec(location.search); 9 | return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); 10 | } 11 | 12 | // adapted from csvkit's recursive JSON flattening mechanism: 13 | // https://github.com/onyxfish/csvkit/blob/61b9c208b7665c20e9a8e95ba6eee811d04705f0/csvkit/convert/js.py#L15-L34 14 | 15 | // depends on jquery and jquery-csv (for now) 16 | 17 | function parse_object(obj, path) { 18 | if (path == undefined) 19 | path = ""; 20 | 21 | var type = $.type(obj); 22 | var scalar = (type == "number" || type == "string" || type == "boolean" || type == "null"); 23 | 24 | if (type == "array" || type == "object") { 25 | var d = {}; 26 | for (var i in obj) { 27 | 28 | var newD = parse_object(obj[i], path + i + "/"); 29 | $.extend(d, newD); 30 | } 31 | 32 | return d; 33 | } 34 | 35 | else if (scalar) { 36 | var d = {}; 37 | var endPath = path.substr(0, path.length-1); 38 | d[endPath] = obj; 39 | return d; 40 | } 41 | 42 | // ? 43 | else return {}; 44 | } 45 | 46 | 47 | // otherwise, just find the first one 48 | function arrayFrom(json) { 49 | var queue = [], next = json; 50 | while (next !== undefined) { 51 | if ($.type(next) == "array") 52 | return next; 53 | if ($.type(next) == "object") { 54 | for (var key in next) 55 | queue.push(next[key]); 56 | } 57 | next = queue.shift(); 58 | } 59 | // none found, consider the whole object a row 60 | return [json]; 61 | } 62 | 63 | // todo: add graceful error handling 64 | function jsonFrom(input) { 65 | var string = $.trim(input); 66 | if (!string) return; 67 | return JSON.parse(string); 68 | } 69 | 70 | this.doCSV = function(json) { 71 | var inArray = arrayFrom(json); 72 | var outArray = []; 73 | for (var row in inArray) 74 | outArray[outArray.length] = parse_object(inArray[row]); 75 | var csv = $.csv.fromObjects(outArray); 76 | return csv; 77 | } 78 | 79 | return this 80 | })(window.jQuery); 81 | -------------------------------------------------------------------------------- /src/kopf/controllers/create_index.js: -------------------------------------------------------------------------------- 1 | kopf.controller('CreateIndexController', ['$scope', 'AlertService', 2 | 'ElasticService', 'AceEditorService', 3 | function($scope, AlertService, ElasticService, AceEditorService) { 4 | 5 | $scope.source_index = null; 6 | $scope.shards = ''; 7 | $scope.replicas = ''; 8 | $scope.name = ''; 9 | $scope.indices = []; 10 | 11 | $scope.initializeController = function() { 12 | $('#create_index_option a').tab('show'); 13 | $scope.prepareCreateIndex(); 14 | }; 15 | 16 | $scope.updateEditor = function() { 17 | ElasticService.getIndexMetadata($scope.source_index, 18 | function(meta) { 19 | var body = {settings: meta.settings, mappings: meta.mappings}; 20 | $scope.editor.setValue(JSON.stringify(body, null, 2)); 21 | }, 22 | function(error) { 23 | AlertService.error('Error while loading index settings', error); 24 | } 25 | ); 26 | }; 27 | 28 | $scope.createIndex = function() { 29 | if ($scope.name.trim().length === 0) { 30 | AlertService.error('You must specify a valid index name'); 31 | } else { 32 | var bodyString = $scope.editor.format(); 33 | if (isDefined($scope.editor.error)) { 34 | AlertService.error('Invalid JSON: ' + $scope.editor.error); 35 | } else { 36 | var body = JSON.parse(bodyString); 37 | if (Object.keys(body).length === 0) { 38 | body = {settings: {index: {}}}; 39 | if ($scope.shards.trim().length > 0) { 40 | body.settings.index.number_of_shards = $scope.shards; 41 | } 42 | if ($scope.replicas.trim().length > 0) { 43 | body.settings.index.number_of_replicas = $scope.replicas; 44 | } 45 | bodyString = JSON.stringify(body); 46 | } 47 | ElasticService.createIndex($scope.name, bodyString, 48 | function(response) { 49 | ElasticService.refresh(); 50 | }, 51 | function(error) { 52 | AlertService.error('Error while creating index', error); 53 | } 54 | ); 55 | } 56 | } 57 | }; 58 | 59 | $scope.prepareCreateIndex = function() { 60 | if (!isDefined($scope.editor)) { 61 | $scope.editor = AceEditorService.init('index-settings-editor'); 62 | } 63 | $scope.indices = ElasticService.getIndices(); 64 | $scope.source_index = null; 65 | $scope.editor.setValue('{}'); 66 | $scope.shards = ''; 67 | $scope.name = ''; 68 | $scope.replicas = ''; 69 | }; 70 | } 71 | ]); 72 | -------------------------------------------------------------------------------- /src/kopf/controllers/navbar.js: -------------------------------------------------------------------------------- 1 | kopf.controller('NavbarController', ['$scope', '$location', 2 | 'ExternalSettingsService', 'ElasticService', 'AlertService', 3 | 'HostHistoryService', 4 | function($scope, $location, ExternalSettingsService, ElasticService, 5 | AlertService, HostHistoryService) { 6 | 7 | $scope.new_refresh = '' + ExternalSettingsService.getRefreshRate(); 8 | $scope.theme = ExternalSettingsService.getTheme(); 9 | $scope.new_host = ''; 10 | $scope.current_host = ElasticService.getHost(); 11 | $scope.host_history = HostHistoryService.getHostHistory(); 12 | 13 | $scope.clusterStatus = undefined; 14 | $scope.clusterName = undefined; 15 | $scope.fetchedAt = undefined; 16 | 17 | $scope.$watch( 18 | function() { 19 | return ElasticService.getHost(); 20 | }, 21 | function(newValue, oldValue) { 22 | $scope.current_host = ElasticService.getHost(); 23 | } 24 | ); 25 | 26 | $scope.$watch( 27 | function() { 28 | return ElasticService.cluster; 29 | }, 30 | function(newValue, oldValue) { 31 | if (isDefined(ElasticService.cluster)) { 32 | $scope.clusterStatus = ElasticService.cluster.status; 33 | $scope.clusterName = ElasticService.cluster.name; 34 | $scope.fetchedAt = ElasticService.cluster.fetched_at; 35 | $scope.clientName = ElasticService.cluster.clientName; 36 | } else { 37 | $scope.clusterStatus = undefined; 38 | $scope.clusterName = undefined; 39 | $scope.fetchedAt = undefined; 40 | $scope.clientName = undefined; 41 | } 42 | } 43 | ); 44 | 45 | $scope.handleConnectToHost = function(event) { 46 | if (event.keyCode == 13 && notEmpty($scope.new_host)) { 47 | $scope.connectToHost($scope.new_host); 48 | } 49 | }; 50 | 51 | $scope.connectToHost = function(host) { 52 | try { 53 | ElasticService.connect(host); 54 | HostHistoryService.addToHistory(ElasticService.connection); 55 | $scope.host_history = HostHistoryService.getHostHistory(); 56 | } catch (error) { 57 | AlertService.error('Error while connecting to new target host', error); 58 | } finally { 59 | $scope.current_host = ElasticService.connection.host; 60 | ElasticService.refresh(); 61 | } 62 | }; 63 | 64 | $scope.changeRefresh = function() { 65 | ExternalSettingsService.setRefreshRate($scope.new_refresh); 66 | }; 67 | 68 | $scope.changeTheme = function() { 69 | ExternalSettingsService.setTheme($scope.theme); 70 | }; 71 | 72 | } 73 | ]); 74 | -------------------------------------------------------------------------------- /src/kopf/controllers/global.js: -------------------------------------------------------------------------------- 1 | kopf.controller('GlobalController', ['$scope', '$location', '$sce', '$window', 2 | 'AlertService', 'ElasticService', 'ExternalSettingsService', 'PageService', 3 | function($scope, $location, $sce, $window, AlertService, ElasticService, 4 | ExternalSettingsService, PageService) { 5 | 6 | $scope.version = '2.0.1'; 7 | 8 | $scope.modal = new ModalControls(); 9 | 10 | $scope.$watch( 11 | function() { 12 | return ElasticService.cluster; 13 | }, 14 | function(newValue, oldValue) { 15 | var version = ElasticService.getVersion(); 16 | if (version && version.isValid()) { 17 | var major = version.getMajor(); 18 | if (major != parseInt($scope.version.charAt(0))) { 19 | AlertService.warn( 20 | 'This version of kopf is not compatible with your ES version', 21 | 'Upgrading to newest supported version is recommended' 22 | ); 23 | } 24 | } 25 | } 26 | ); 27 | 28 | $scope.getTheme = function() { 29 | return ExternalSettingsService.getTheme(); 30 | }; 31 | 32 | $scope.readParameter = function(name) { 33 | var regExp = new RegExp('[\\?&]' + name + '=([^&#]*)'); 34 | var results = regExp.exec($window.location.href); 35 | return isDefined(results) ? results[1] : null; 36 | }; 37 | 38 | $scope.connect = function() { 39 | try { 40 | var host = 'http://localhost:9200'; // default 41 | if ($location.host() !== '') { // not opening from fs 42 | var location = $scope.readParameter('location'); 43 | var url = $location.absUrl(); 44 | if (isDefined(location) || 45 | isDefined(location = ExternalSettingsService.getElasticsearchHost())) { 46 | host = location; 47 | } else if (url.indexOf('/_plugin/kopf') > -1) { 48 | host = url.substring(0, url.indexOf('/_plugin/kopf')); 49 | } else { 50 | host = $location.protocol() + '://' + $location.host() + 51 | ':' + $location.port(); 52 | } 53 | } 54 | ElasticService.connect(host); 55 | } catch (error) { 56 | AlertService.error(error.message, error.body); 57 | } 58 | }; 59 | 60 | $scope.connect(); 61 | 62 | ElasticService.refresh(); 63 | 64 | $scope.hasConnection = function() { 65 | return isDefined(ElasticService.cluster); 66 | }; 67 | 68 | $scope.displayInfo = function(title, info) { 69 | $scope.modal.title = title; 70 | $scope.modal.info = $sce.trustAsHtml(JSONTree.create(info)); 71 | $('#modal_info').modal({show: true, backdrop: true}); 72 | }; 73 | 74 | } 75 | ]); 76 | -------------------------------------------------------------------------------- /src/kopf/elastic/index_metadata.js: -------------------------------------------------------------------------------- 1 | function IndexMetadata(index, metadata) { 2 | this.index = index; 3 | this.mappings = metadata.mappings; 4 | this.settings = metadata.settings; 5 | 6 | this.getTypes = function() { 7 | return Object.keys(this.mappings).sort(function(a, b) { 8 | return a.localeCompare(b); 9 | }); 10 | }; 11 | 12 | this.getAnalyzers = function() { 13 | var analyzers = Object.keys(getProperty(this.settings, 14 | 'index.analysis.analyzer', {})); 15 | if (analyzers.length === 0) { 16 | Object.keys(this.settings).forEach(function(setting) { 17 | if (setting.indexOf('index.analysis.analyzer') === 0) { 18 | var analyzer = setting.substring('index.analysis.analyzer.'.length); 19 | analyzer = analyzer.substring(0, analyzer.indexOf('.')); 20 | if ($.inArray(analyzer, analyzers) == -1) { 21 | analyzers.push(analyzer); 22 | } 23 | } 24 | }); 25 | } 26 | return analyzers.sort(function(a, b) { 27 | return a.localeCompare(b); 28 | }); 29 | }; 30 | 31 | function isAnalyzable(type) { 32 | var analyzableTypes = ['float', 'double', 'byte', 'short', 'integer', 33 | 'long', 'nested', 'object' 34 | ]; 35 | return analyzableTypes.indexOf(type) == -1; 36 | } 37 | 38 | this.getFields = function(type) { 39 | var fields = []; 40 | if (isDefined(this.mappings[type])) { 41 | fields = this.getProperties('', this.mappings[type].properties); 42 | } 43 | return fields.sort(function(a, b) { 44 | return a.localeCompare(b); 45 | }); 46 | }; 47 | 48 | this.getProperties = function(parent, fields) { 49 | var prefix = parent !== '' ? parent + '.' : ''; 50 | var validFields = []; 51 | for (var field in fields) { 52 | // multi field 53 | if (isDefined(fields[field].fields)) { 54 | var addPrefix = fields[field].path != 'just_name'; 55 | var multiPrefix = addPrefix ? prefix + field : prefix; 56 | var multiProps = this.getProperties(multiPrefix, fields[field].fields); 57 | validFields = validFields.concat(multiProps); 58 | } 59 | // nested and object types 60 | var nestedType = fields[field].type == 'nested'; 61 | var objectType = fields[field].type == 'object'; 62 | if (nestedType || objectType || !isDefined(fields[field].type)) { 63 | var nestedProperties = this.getProperties(prefix + field, 64 | fields[field].properties); 65 | validFields = validFields.concat(nestedProperties); 66 | } 67 | // normal fields 68 | if (isDefined(fields[field].type) && isAnalyzable(fields[field].type)) { 69 | validFields.push(prefix + field); 70 | } 71 | } 72 | return validFields; 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /src/kopf/elastic/repository.js: -------------------------------------------------------------------------------- 1 | function Repository(name, info) { 2 | this.name = name; 3 | this.type = info.type; 4 | this.settings = info.settings; 5 | 6 | this.asJson = function() { 7 | var json = {type: this.type}; 8 | if (this.type === 'fs') { 9 | var fsSettings = ['location', 'chunk_size', 'max_restore_bytes_per_sec', 10 | 'max_snapshot_bytes_per_sec', 'compress']; 11 | json.settings = this.getSettings(fsSettings); 12 | } 13 | if (this.type === 'url') { 14 | var urlSettings = ['url']; 15 | json.settings = this.getSettings(urlSettings); 16 | } 17 | if (this.type === 's3') { 18 | var s3Settings = ['region', 'bucket', 'base_path', 'access_key', 19 | 'secret_key', 'chunk_size', 'max_retries', 'compress', 20 | 'server_side_encryption' 21 | ]; 22 | json.settings = this.getSettings(s3Settings); 23 | } 24 | if (this.type === 'hdfs') { 25 | var hdfsSettings = ['uri', 'path', 'load_defaults', 'conf_location', 26 | 'concurrent_streams', 'compress', 'chunk_size']; 27 | json.settings = this.getSettings(hdfsSettings); 28 | } 29 | if (this.type === 'azure') { 30 | var azureSettings = ['container', 'base_path', 'concurrent_streams', 31 | 'chunk_size', 'compress']; 32 | json.settings = this.getSettings(azureSettings); 33 | } 34 | return JSON.stringify(json); 35 | }; 36 | 37 | this.validate = function() { 38 | if (!notEmpty(this.name)) { 39 | throw 'Repository name is required'; 40 | } 41 | if (!notEmpty(this.type)) { 42 | throw 'Repository type is required'; 43 | } 44 | if (this.type === 'fs') { 45 | var fsRequired = ['location']; 46 | this.validateSettings(fsRequired); 47 | } 48 | if (this.type === 'url') { 49 | var urlRequired = ['url']; 50 | this.validateSettings(urlRequired); 51 | } 52 | if (this.type === 's3') { 53 | var s3Required = ['bucket']; 54 | this.validateSettings(s3Required); 55 | } 56 | if (this.type === 'hdfs') { 57 | var hdfsRequired = ['path']; 58 | this.validateSettings(hdfsRequired); 59 | } 60 | }; 61 | 62 | this.validateSettings = function(required) { 63 | var repository = this; 64 | required.forEach(function(setting) { 65 | if (!notEmpty(repository.settings[setting])) { 66 | var type = repository.type; 67 | throw(setting + ' is required for snapshot of type ' + type); 68 | } 69 | }); 70 | }; 71 | 72 | this.getSettings = function(availableSettings) { 73 | var settings = {}; 74 | var currentSettings = this.settings; 75 | availableSettings.forEach(function(setting) { 76 | if (notEmpty(currentSettings[setting])) { 77 | settings[setting] = currentSettings[setting]; 78 | } 79 | }); 80 | return settings; 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /_site/partials/cluster_settings/cluster.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 | 6 | 7 | 8 | 9 |
    10 |
    11 |
    12 |
    13 | 14 | 15 | 16 | 17 | 18 |
    19 |
    20 |
    21 |
    22 | 23 | 24 | 25 | 26 | 27 |
    28 |
    29 |
    30 |
    31 | 32 | 33 | 34 | 35 |
    36 | 41 |
    42 |
    43 |
    44 | -------------------------------------------------------------------------------- /src/kopf/models/index_filter.js: -------------------------------------------------------------------------------- 1 | function IndexFilter(name, closed, special, healthy, asc, timestamp) { 2 | this.name = name; 3 | this.closed = closed; 4 | this.special = special; 5 | this.healthy = healthy; 6 | this.sort = 'name'; 7 | this.asc = asc; 8 | this.timestamp = timestamp; 9 | 10 | this.getSorting = function() { 11 | var asc = this.asc; 12 | switch (this.sort) { 13 | case 'name': 14 | return function(a, b) { 15 | if (asc) { 16 | return a.name.localeCompare(b.name); 17 | } else { 18 | return b.name.localeCompare(a.name); 19 | } 20 | }; 21 | default: 22 | return undefined; 23 | } 24 | }; 25 | 26 | this.clone = function() { 27 | return new IndexFilter( 28 | this.name, 29 | this.closed, 30 | this.special, 31 | this.healthy, 32 | this.asc, 33 | this.timestamp 34 | ); 35 | }; 36 | 37 | this.equals = function(other) { 38 | return ( 39 | other !== null && 40 | this.name === other.name && 41 | this.closed === other.closed && 42 | this.special === other.special && 43 | this.healthy === other.healthy && 44 | this.asc === other.asc && 45 | this.timestamp === other.timestamp 46 | ); 47 | }; 48 | 49 | this.isBlank = function() { 50 | return ( 51 | !notEmpty(this.name) && 52 | this.closed && 53 | this.special && 54 | this.healthy && 55 | this.asc 56 | ); 57 | }; 58 | 59 | this.matches = function(index) { 60 | var matches = true; 61 | if (!this.special && index.special) { 62 | matches = false; 63 | } 64 | if (!this.closed && index.closed) { 65 | matches = false; 66 | } 67 | // Hide healthy == show unhealthy only 68 | if (!this.healthy && !index.unhealthy) { 69 | matches = false; 70 | } 71 | if (matches && notEmpty(this.name)) { 72 | try { 73 | var regExp = new RegExp(this.name.trim(), 'i'); 74 | matches = regExp.test(index.name); 75 | if (!matches) { 76 | for (var idx = 0; idx < index.aliases.length; idx++) { 77 | if ((matches = regExp.test(index.aliases[idx]))) { 78 | break; 79 | } 80 | } 81 | } 82 | } 83 | catch (err) { // if not valid regexp, still try normal matching 84 | matches = index.name.indexOf(this.name.toLowerCase()) != -1; 85 | if (!matches) { 86 | for (var idx = 0; idx < index.aliases.length; idx++) { 87 | var alias = index.aliases[idx].toLowerCase(); 88 | matches = true; 89 | if ((matches = (alias.indexOf(this.name.toLowerCase()) != -1))) { 90 | break; 91 | } 92 | } 93 | } 94 | } 95 | } 96 | return matches; 97 | }; 98 | 99 | } 100 | -------------------------------------------------------------------------------- /tests/jasmine/cat.tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('CatController', function() { 4 | var scope, createController; 5 | 6 | beforeEach(angular.mock.module('kopf')); 7 | 8 | beforeEach(angular.mock.inject(function($rootScope, $controller, $injector) { 9 | this.scope = $rootScope.$new(); 10 | this.AlertService = $injector.get('AlertService'); 11 | this.ElasticService = $injector.get('ElasticService'); 12 | this.createController = function() { 13 | return $controller('CatController', {$scope: this.scope}, 14 | this.ElasticService, this.AlertService); 15 | }; 16 | this._controller = this.createController(); 17 | })); 18 | 19 | it('initial values are set', function() { 20 | expect(this.scope.api).toEqual(''); 21 | expect(this.scope.result).toEqual(undefined); 22 | expect(this.scope.apis).toEqual([ 23 | 'aliases', 24 | //'allocation', 25 | 'count', 26 | //'fielddata', 27 | //'health', 28 | //'indices', 29 | 'master', 30 | //'nodes', 31 | //'pending_tasks', 32 | 'plugins', 33 | 'recovery', 34 | //'thread_pool', 35 | //'shards', 36 | //'segments' 37 | ]); 38 | }); 39 | 40 | it('validates api is selected', function() { 41 | this.scope.api = ''; 42 | spyOn(this.AlertService, 'error').andReturn(true); 43 | this.scope.execute(); 44 | expect(this.AlertService.error).toHaveBeenCalledWith('You must select an API'); 45 | }); 46 | 47 | it('execute a successful cat request', function() { 48 | this.scope.api = 'shards'; 49 | this.ElasticService.executeCatRequest = function(api, success, error) { 50 | success(new CatResult('header\nvalue\n')); 51 | }; 52 | spyOn(this.ElasticService, 'executeCatRequest').andCallThrough(); 53 | this.scope.execute(); 54 | expect(this.ElasticService.executeCatRequest).toHaveBeenCalledWith( 55 | 'shards', 56 | jasmine.any(Function), 57 | jasmine.any(Function) 58 | ); 59 | expect(this.scope.result.columns).toEqual(['header']); 60 | expect(this.scope.result.lines).toEqual([["value"]]); 61 | }); 62 | 63 | it('alerts error while executing request', function() { 64 | this.scope.api = 'shards'; 65 | this.ElasticService.executeCatRequest = function(api, success, error) { 66 | error('some weird unknown reason'); 67 | }; 68 | spyOn(this.ElasticService, 'executeCatRequest').andCallThrough(); 69 | spyOn(this.AlertService, 'error').andReturn(true); 70 | this.scope.execute(); 71 | expect(this.ElasticService.executeCatRequest).toHaveBeenCalledWith( 72 | 'shards', 73 | jasmine.any(Function), 74 | jasmine.any(Function) 75 | ); 76 | expect(this.scope.result).toEqual(undefined); 77 | expect(this.AlertService.error).toHaveBeenCalledWith( 78 | 'Error while fetching data', 79 | 'some weird unknown reason' 80 | ); 81 | }); 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /src/kopf/elastic/alias.js: -------------------------------------------------------------------------------- 1 | function IndexAliases(index, aliases) { 2 | this.index = index; 3 | this.aliases = aliases; 4 | 5 | this.clone = function() { 6 | var cloned = new IndexAliases(this.index, []); 7 | cloned.aliases = this.aliases.map(function(alias) { 8 | return alias.clone(); 9 | }); 10 | return cloned; 11 | }; 12 | } 13 | 14 | IndexAliases.diff = function(original, modified) { 15 | var differences = []; 16 | modified.forEach(function(ia) { 17 | var isNew = true; 18 | original.forEach(function(origIA) { 19 | if (ia.index == origIA.index) { 20 | isNew = false; 21 | ia.aliases.forEach(function(alias) { 22 | var originalAliases = origIA.aliases.filter(function(originalAlias) { 23 | return alias.equals(originalAlias); 24 | }); 25 | if (originalAliases.length === 0) { 26 | differences.push(alias); 27 | } 28 | }); 29 | } 30 | }); 31 | if (isNew) { 32 | ia.aliases.forEach(function(alias) { 33 | differences.push(alias); 34 | }); 35 | } 36 | }); 37 | return differences; 38 | }; 39 | 40 | function Alias(alias, index, filter, indexRouting, searchRouting) { 41 | this.alias = isDefined(alias) ? alias.toLowerCase() : ''; 42 | this.index = isDefined(index) ? index.toLowerCase() : ''; 43 | this.filter = isDefined(filter) ? filter : ''; 44 | this.index_routing = isDefined(indexRouting) ? indexRouting : ''; 45 | this.search_routing = isDefined(searchRouting) ? searchRouting : ''; 46 | 47 | this.validate = function() { 48 | if (!notEmpty(this.alias)) { 49 | throw 'Alias must have a non empty name'; 50 | } 51 | if (!notEmpty(this.index)) { 52 | throw 'Alias must have a valid index name'; 53 | } 54 | }; 55 | 56 | this.equals = function(otherAlias) { 57 | var equal = 58 | (this.alias === otherAlias.alias) && 59 | (this.index === otherAlias.index) && 60 | (this.filter === otherAlias.filter) && 61 | (this.index_routing === otherAlias.index_routing) && 62 | (this.search_routing === otherAlias.search_routing); 63 | return equal; 64 | }; 65 | 66 | this.info = function() { 67 | var info = {}; 68 | info.index = this.index; 69 | info.alias = this.alias; 70 | 71 | if (isDefined(this.filter)) { 72 | if (typeof this.filter == 'string' && notEmpty(this.filter)) { 73 | info.filter = JSON.parse(this.filter); 74 | } else { 75 | info.filter = this.filter; 76 | } 77 | } 78 | if (notEmpty(this.index_routing)) { 79 | info.index_routing = this.index_routing; 80 | } 81 | if (notEmpty(this.search_routing)) { 82 | info.search_routing = this.search_routing; 83 | } 84 | return info; 85 | }; 86 | 87 | this.clone = function() { 88 | return new Alias(this.alias, this.index, this.filter, this.index_routing, 89 | this.search_routing); 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /_site/partials/cluster_stats.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 | {{cluster.number_of_nodes | number:0}} 6 | nodes 7 | 8 |
    9 |
    10 |
    11 |
    12 | 13 | {{cluster.total_indices | number:0}} 14 | indices 15 | 16 |
    17 |
    18 |
    19 |
    20 | 21 | {{cluster.shards | number:0}} 22 | shards 23 | 24 |
    25 |
    26 |
    27 |
    28 |
    29 | 30 | {{cluster.num_docs | number:0}} 31 | docs 32 | 33 | 34 | 35 | 36 | {{cluster.changes.absDocDelta() | number:0}} 37 | 38 |
    39 |
    40 |
    41 |
    42 |
    43 |
    44 | 45 | {{cluster.total_size_in_bytes | bytes}} 46 | 47 | 48 | 49 | 50 | {{cluster.changes.absDataDelta()}} 51 | 52 |
    53 |
    54 |
    55 |
    56 | 61 | -------------------------------------------------------------------------------- /src/lib/angular-tree-dnd/ng-tree-dnd.css: -------------------------------------------------------------------------------- 1 | .tree-dnd-animate-enter, 2 | .tree-dnd-node.ng-enter { 3 | -webkit-transition: 0 linear all; 4 | -moz-transition: 0 linear all; 5 | -ms-transition: 0 linear all; 6 | -o-transition: 0 linear all; 7 | transition: 0 linear all; 8 | position: relative; 9 | display: block; 10 | opacity: 0; 11 | max-height: 0; 12 | } 13 | 14 | .tree-dnd-animate-enter.tree-dnd-animate-enter-active, 15 | .tree-dnd-node.ng-enter-active { 16 | opacity: 1; 17 | max-height: 30px; 18 | } 19 | 20 | .tree-dnd-animate-leave, 21 | .tree-dnd-node.ng-leave { 22 | -webkit-transition: 0 linear all; 23 | -moz-transition: 0 linear all; 24 | -ms-transition: 0 linear all; 25 | -o-transition: 0 linear all; 26 | transition: 0 linear all; 27 | position: relative; 28 | display: block; 29 | height: 30px; 30 | max-height: 30px; 31 | opacity: 1; 32 | } 33 | 34 | 35 | .tree-dnd-animate-leave.tree-dnd-animate-leave-active, 36 | .tree-dnd-node.ng-leave-active { 37 | height: 0; 38 | max-height: 0; 39 | opacity: 0; 40 | } 41 | 42 | .tree-dnd-node > td { 43 | position: relative; 44 | } 45 | 46 | .tree-dnd-animate.ng-enter { 47 | } 48 | 49 | .tree-dnd .tree-icon, 50 | .tree-label { 51 | cursor: pointer; 52 | } 53 | 54 | .tree-dnd > thead > tr > td, .tree-dnd > tbody > tr > td, .tree-dnd > tfoot > tr > td { 55 | vertical-align: middle !important; 56 | } 57 | 58 | .tree-dnd-empty { 59 | border: 2px dashed #72da67 !important; 60 | min-height: 100px; 61 | background-color: #e5e5e5; 62 | } 63 | 64 | .indented { 65 | position: relative; 66 | } 67 | 68 | .tree-dnd-hidden { 69 | display: none; 70 | } 71 | 72 | .tree-dnd-placeholder { 73 | position: relative; 74 | margin: 0px; 75 | padding: 0px; 76 | min-height: 20px; 77 | line-height: 20px; 78 | } 79 | 80 | .tree-dnd-handle { 81 | cursor: pointer; 82 | text-decoration: none; 83 | -webkit-box-sizing: border-box; 84 | -moz-box-sizing: border-box; 85 | box-sizing: border-box; 86 | min-height: 20px; 87 | line-height: 20px; 88 | } 89 | 90 | .tree-dnd-drag { 91 | position: absolute; 92 | pointer-events: none; 93 | z-index: 999; 94 | opacity: .7; 95 | background-color: #cdffdc; 96 | border: 2px dashed #385736; 97 | } 98 | 99 | .tree-dnd-status { 100 | position: absolute; 101 | background-color: #ffffff; 102 | border: 1px solid #92e5d3; 103 | padding: 1px 4px; 104 | opacity: .85; 105 | } 106 | 107 | .tree-dnd-nodes { 108 | margin: 0px; 109 | padding: 0px; 110 | list-style: none; 111 | } 112 | 113 | .tree-dnd-nodes .tree-dnd-node { 114 | border-radius: 0px !important; 115 | } 116 | 117 | .tree-dnd-nodes .tree-dnd-nodes { 118 | padding-left: 20px; 119 | } 120 | 121 | .tree-dnd-node, .tree-dnd-placeholder { 122 | /*margin: 0px;*/ 123 | /*padding: 0px;*/ 124 | min-height: 20px; 125 | line-height: 20px; 126 | } 127 | -------------------------------------------------------------------------------- /_site/partials/cluster_settings/recovery.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 | 6 | 7 | 8 | 9 |
    10 |
    11 |
    12 |
    13 | 14 | 15 | 16 | 17 | 18 |
    19 |
    20 |
    21 |
    22 | 23 | 24 | 25 | 26 | 27 |
    28 |
    29 |
    30 |
    31 | 32 | 33 | 34 | 35 | 36 |
    37 |
    38 |
    39 |
    40 | 41 | 42 | 43 | 44 | 45 |
    46 |
    47 |
    48 |
    49 | 50 | 51 | 52 | 53 |
    54 | 59 |
    60 |
    61 |
    62 | 65 | -------------------------------------------------------------------------------- /tests/jasmine/services/explain.tests.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe('ExplainService', function() { 4 | var service; 5 | beforeEach(module('kopf')); 6 | 7 | beforeEach(inject(function($injector) { 8 | service = $injector.get('ExplainService'); 9 | expect(service).not.toBeUndefined(); 10 | })); 11 | 12 | it('should predict whether path will return explanation', function() { 13 | expect(service.isExplainPath('/_search')).toEqual(false); 14 | expect(service.isExplainPath('/_search?explain=true')).toEqual(true); 15 | expect(service.isExplainPath('/_search?pretty=true&explain=true')).toEqual(true); 16 | expect(service.isExplainPath('/x/y/z/_explain')).toEqual(true); 17 | }); 18 | 19 | it('should normalize search response', function() { 20 | var response = { 21 | hits: { 22 | hits: [ 23 | { 24 | _index:'x', _type: 'y', _id:'1', 25 | _score: 1.0, 26 | _source: {}, 27 | _explanation: { 28 | description: 'desc 1', value: 1.0, 29 | details: [ 30 | { 31 | description: 'desc 1.1', value: 0.5 32 | }, 33 | { 34 | description: 'desc 1.2', value: 0.5 35 | } 36 | ] 37 | } 38 | }, 39 | { 40 | _index:'x', _type: 'y', _id:'2', 41 | _score: 0.5, 42 | _source: {}, 43 | _explanation: { 44 | description: 'desc 2', value: 0.5, 45 | details: [ 46 | { 47 | description: 'desc 2.1', value: 0.5 48 | } 49 | ] 50 | } 51 | } 52 | ] 53 | } 54 | } 55 | var normalizedResponse = service.normalizeExplainResponse(response); 56 | expect(normalizedResponse[0].documentId).toEqual('x/y/1'); 57 | expect(normalizedResponse[0]._score).toEqual(1.0); 58 | expect(normalizedResponse[0].explanationTreeData).not.toBeUndefined(); 59 | expect(normalizedResponse[1].documentId).toEqual('x/y/2'); 60 | expect(normalizedResponse[1]._score).toEqual(0.5); 61 | expect(normalizedResponse[1].explanationTreeData).not.toBeUndefined(); 62 | }); 63 | 64 | it('should normalize explain document response', function() { 65 | var response = { 66 | _index:'x', _type: 'y', _id:'1', 67 | _source: {}, 68 | explanation: { 69 | description: 'desc 1', value: 1.0, 70 | details: [ 71 | { 72 | description: 'desc 1.1', value: 0.5 73 | }, 74 | { 75 | description: 'desc 1.2', value: 0.5 76 | } 77 | ] 78 | } 79 | }; 80 | var normalizedResponse = service.normalizeExplainResponse(response); 81 | expect(normalizedResponse[0].documentId).toEqual('x/y/1'); 82 | expect(normalizedResponse[0]._score).toEqual(1.0); 83 | expect(normalizedResponse[0]._explanation).not.toBeUndefined(); 84 | expect(normalizedResponse[0].explanationTreeData).not.toBeUndefined(); 85 | }); 86 | 87 | }); 88 | -------------------------------------------------------------------------------- /_site/partials/snapshot/s3_repository.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 | 6 |
    7 |
    8 | 9 | 10 |
    11 |
    12 |
    13 |
    14 | 15 | 16 |
    17 |
    18 | 19 | 20 |
    21 |
    22 | 23 | 24 |
    25 |
    26 | 27 | 28 |
    29 |
    30 | 31 | 32 |
    33 |
    34 | 35 | 36 |
    37 |
    38 | 41 | 44 |
    45 |
    46 |
    47 |
    48 | 49 | 50 |
    51 |
    52 | 55 | 58 |
    59 |
    60 |
    -------------------------------------------------------------------------------- /src/kopf/kopf.js: -------------------------------------------------------------------------------- 1 | var kopf = angular.module('kopf', ['ngRoute', 'ntt.TreeDnD', 'ngAnimate', 2 | 'ui.bootstrap']); 3 | 4 | // manages behavior of confirmation dialog 5 | kopf.factory('ConfirmDialogService', function() { 6 | this.header = 'Default Header'; 7 | this.body = 'Default Body'; 8 | this.cancel_text = 'cancel'; 9 | this.confirm_text = 'confirm'; 10 | 11 | this.confirm = function() { 12 | // when created, does nothing 13 | }; 14 | 15 | this.close = function() { 16 | // when created, does nothing 17 | }; 18 | 19 | this.open = function(header, body, action, confirmCallback, closeCallback) { 20 | this.header = header; 21 | this.body = body; 22 | this.action = action; 23 | this.confirm = confirmCallback; 24 | this.close = closeCallback; 25 | }; 26 | 27 | return this; 28 | }); 29 | 30 | kopf.config(function($routeProvider, $locationProvider) { 31 | $locationProvider.hashPrefix('!'); 32 | $routeProvider. 33 | when('/cluster', { 34 | templateUrl: 'partials/cluster_overview.html', 35 | controller: 'ClusterOverviewController' 36 | }). 37 | when('/nodes', { 38 | templateUrl: 'partials/nodes/nodes.html', 39 | controller: 'NodesController' 40 | }). 41 | when('/rest', { 42 | templateUrl: 'partials/rest_client.html', 43 | controller: 'RestController' 44 | }). 45 | when('/aliases', { 46 | templateUrl: 'partials/aliases.html', 47 | controller: 'AliasesController' 48 | }). 49 | when('/analysis', { 50 | templateUrl: 'partials/analysis.html', 51 | controller: 'AnalysisController' 52 | }). 53 | when('/percolator', { 54 | templateUrl: 'partials/percolator.html', 55 | controller: 'PercolatorController' 56 | }). 57 | when('/warmers', { 58 | templateUrl: 'partials/warmers.html', 59 | controller: 'WarmersController' 60 | }). 61 | when('/snapshot', { 62 | templateUrl: 'partials/snapshot.html', 63 | controller: 'SnapshotController' 64 | }). 65 | when('/createIndex', { 66 | templateUrl: 'partials/create_index.html', 67 | controller: 'CreateIndexController' 68 | }). 69 | when('/clusterHealth', { 70 | templateUrl: 'partials/cluster_health.html', 71 | controller: 'ClusterHealthController' 72 | }). 73 | when('/clusterSettings', { 74 | templateUrl: 'partials/cluster_settings.html', 75 | controller: 'ClusterSettingsController' 76 | }). 77 | when('/indexSettings', { 78 | templateUrl: 'partials/index_settings.html', 79 | controller: 'IndexSettingsController' 80 | }). 81 | when('/indexTemplates', { 82 | templateUrl: 'partials/index_templates.html', 83 | controller: 'IndexTemplatesController' 84 | }). 85 | when('/cat', { 86 | templateUrl: 'partials/cat.html', 87 | controller: 'CatController' 88 | }). 89 | when('/hotthreads', { 90 | templateUrl: 'partials/hotthreads.html', 91 | controller: 'HotThreadsController' 92 | }). 93 | otherwise({redirectTo: '/cluster'}); 94 | }); 95 | -------------------------------------------------------------------------------- /tests/models/node_filter.js: -------------------------------------------------------------------------------- 1 | test("Blank node filter", function() { 2 | var filter = new NodeFilter("", true, true, true, 0); 3 | var node = { name: 'a', master: true, client: false, data: true }; 4 | ok(filter.matches(node), "Matches any node if filter is blank"); 5 | }) 6 | 7 | test("Only master node filter x non master node", function() { 8 | var filter = new NodeFilter("", false, true, false, 0); 9 | var node = { name: 'a', master: false, client: false, data: true }; 10 | ok(!filter.matches(node),"Doesnt match a non master node"); 11 | }) 12 | 13 | test("Only master node filter x master node", function() { 14 | var filter = new NodeFilter("", false, true, false, 0); 15 | var node = { name: 'a', master: true, client: false, data: true }; 16 | ok(filter.matches(node),"Matches a master node"); 17 | }) 18 | 19 | test("Only data node filter x non data node", function() { 20 | var filter = new NodeFilter("", true, false, false, 0); 21 | var node = { name: 'a', master: false, client: true, data: false }; 22 | ok(!filter.matches(node),"Doesnt match a non data node"); 23 | }) 24 | 25 | test("Only data node filter x data node", function() { 26 | var filter = new NodeFilter("", true, false, false, 0); 27 | var node = { name: 'a', master: false, client: false, data: true }; 28 | ok(filter.matches(node),"Match a data node"); 29 | }) 30 | 31 | test("Only client node filter x non client node", function() { 32 | var filter = new NodeFilter("", false, false, true, 0); 33 | var node = { name: 'a', master: false, client: false, data: true }; 34 | ok(!filter.matches(node), "Doesnt match a non client node"); 35 | }) 36 | 37 | test("Only client node filter x client node", function() { 38 | var filter = new NodeFilter("", false, false, true, 0); 39 | var node = { name: 'a', master: false, client: true, data: false }; 40 | ok(filter.matches(node),"Match a client node"); 41 | }) 42 | 43 | test("Master or client node filter x data node", function() { 44 | var filter = new NodeFilter("", false, true, true, 0); 45 | var node = { name: 'a', master: false, client: false, data: true }; 46 | ok(!filter.matches(node), "Doesnt match a non master/client node"); 47 | }) 48 | 49 | test("Master or client node filter x client node x master node", function() { 50 | var filter = new NodeFilter("", false, true, true, 0); 51 | var node = { name: 'a', master: false, client: true, data: false }; 52 | var node2 = { name: 'a', master: true, client: false, data: false }; 53 | ok(filter.matches(node),"Match a client node"); 54 | ok(filter.matches(node2),"Match a master node"); 55 | }) 56 | 57 | test("node filter with name x non matching name", function() { 58 | var filter = new NodeFilter("moli", true, true, true, 0); 59 | var node = { name: 'milo_id', master: true, client: false, data: true }; 60 | ok(!filter.matches(node),"Doesnt match if name is not a substring"); 61 | }) 62 | 63 | test("node filter with name x matching name", function() { 64 | var filter = new NodeFilter("moli", true, true, true, 0); 65 | var node = { name: 'moliware', master: true, client: false, data: true }; 66 | ok(filter.matches(node),"Matches if name is not a substring"); 67 | }) -------------------------------------------------------------------------------- /_site/partials/snapshot/repositories_table.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    5 | 6 | repositories 7 | 8 |

    9 |
    10 |
    11 |
    12 |
    13 |
    14 | 15 | 16 |
    17 |
    18 | 19 | 20 |
    21 |
    22 |
    23 |
    24 |
    25 |
    26 |
    27 |
    28 |
    29 | {{validation_error}} 30 | 33 |
    34 |
    35 |
    36 |
    37 | 38 | 39 | 40 | 41 | 42 | 43 | 52 | 53 | 54 |
    nametype
    44 | {{r.name}} 45 | 46 | 47 | 48 | 49 | 50 | 51 | {{r.type}}
    55 |
    56 |
    57 |
    58 |
    59 |
    60 |
    61 | -------------------------------------------------------------------------------- /tests/jasmine/services/page.tests.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("PageService", function() { 4 | 5 | var PageService, ElasticService, $scope; 6 | 7 | beforeEach(module("kopf")); 8 | 9 | beforeEach(function() { 10 | module('kopf'); 11 | 12 | module(function($provide) { 13 | $provide.value('ElasticService', { 14 | cluster: {} 15 | }); 16 | }); 17 | }); 18 | 19 | beforeEach(angular.mock.inject(function($rootScope, $injector) { 20 | ElasticService = $injector.get('ElasticService'); 21 | PageService = $injector.get('PageService'); 22 | PageService.link = ''; 23 | $scope = $rootScope; 24 | })); 25 | 26 | it("should watch for cluster changes and update title and favicon", 27 | function() { 28 | spyOn(PageService, 'setFavIconColor').andReturn(true); 29 | spyOn(PageService, 'setPageTitle').andReturn(true); 30 | ElasticService.cluster = {name: 'super cluster', status: 'green'}; 31 | $scope.$digest(); 32 | expect(PageService.setFavIconColor).toHaveBeenCalledWith('green'); 33 | expect(PageService.setPageTitle).toHaveBeenCalledWith('super cluster'); 34 | }); 35 | 36 | it("should watch for disconnect with cluster and update title and favicon", 37 | function() { 38 | spyOn(PageService, 'setFavIconColor').andReturn(true); 39 | spyOn(PageService, 'setPageTitle').andReturn(true); 40 | ElasticService.cluster = undefined; 41 | $scope.$digest(); 42 | expect(PageService.setFavIconColor).toHaveBeenCalledWith(undefined); 43 | expect(PageService.setPageTitle).toHaveBeenCalledWith(undefined); 44 | }); 45 | 46 | it("should change title and favicon if changed", 47 | function() { 48 | spyOn(PageService, 'setFavIconColor').andCallThrough(true); 49 | spyOn(PageService, 'setPageTitle').andCallThrough(true); 50 | PageService.link = {}; 51 | ElasticService.cluster = {name: 'name1', status: 'status1'}; 52 | $scope.$digest(); 53 | expect(PageService.clusterName).toEqual('name1'); 54 | expect(PageService.clusterStatus).toEqual('status1'); 55 | ElasticService.cluster = {name: 'name2', status: 'status2'}; 56 | $scope.$digest(); 57 | expect(PageService.clusterName).toEqual('name2'); 58 | expect(PageService.clusterStatus).toEqual('status2'); 59 | ElasticService.cluster = {name: 'name2', status: 'status2', other: {}}; 60 | $scope.$digest(); 61 | expect(PageService.clusterName).toEqual('name2'); 62 | expect(PageService.clusterStatus).toEqual('status2'); 63 | }); 64 | 65 | it("should preserver title and favicon if not changed", 66 | function() { 67 | spyOn(PageService, 'setFavIconColor').andCallThrough(true); 68 | spyOn(PageService, 'setPageTitle').andCallThrough(true); 69 | PageService.link = {}; 70 | ElasticService.cluster = {name: 'name1', status: 'status1'}; 71 | $scope.$digest(); 72 | expect(PageService.clusterName).toEqual('name1'); 73 | expect(PageService.clusterStatus).toEqual('status1'); 74 | ElasticService.cluster = {name: 'name1', status: 'status1'}; 75 | $scope.$digest(); 76 | expect(PageService.clusterName).toEqual('name1'); 77 | expect(PageService.clusterStatus).toEqual('status1'); 78 | }); 79 | 80 | }); 81 | -------------------------------------------------------------------------------- /tests/jasmine/host_history.tests.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("Host history service", function() { 4 | var service; 5 | 6 | beforeEach(angular.mock.module('kopf')); 7 | 8 | beforeEach(angular.mock.inject(function($rootScope, $injector) { 9 | service = $injector.get('HostHistoryService'); 10 | service.clearHistory(); 11 | })); 12 | 13 | it("Successfully adds a new element to history", function() { 14 | var history = service.getHostHistory(); 15 | expect(history).toEqual([]); 16 | service.addToHistory(new ESConnection("http://localhost")); 17 | history = service.getHostHistory(); 18 | expect(history).toEqual([{host: 'http://localhost'}]); 19 | }); 20 | 21 | it("Successfully adds a new authenticated element to history", function() { 22 | var history = service.getHostHistory(); 23 | expect(history).toEqual([]); 24 | service.addToHistory(new ESConnection("http://usr:pwd@localhost")); 25 | history = service.getHostHistory(); 26 | expect(history).toEqual([{host: 'http://usr:pwd@localhost'}]); 27 | }); 28 | 29 | it("Only adds an element once", function() { 30 | var history = service.getHostHistory(); 31 | expect(history).toEqual([]); 32 | service.addToHistory(new ESConnection("http://localhost")); 33 | history = service.getHostHistory(); 34 | expect(history).toEqual([{host: 'http://localhost'}]); 35 | service.addToHistory(new ESConnection("http://localhost")); 36 | history = service.getHostHistory(); 37 | expect(history).toEqual([{host: 'http://localhost'}]); 38 | }); 39 | 40 | it("should move an alredy present entry to the top when added again", 41 | function() { 42 | var history = service.getHostHistory(); 43 | expect(history).toEqual([]); 44 | service.addToHistory(new ESConnection("http://localhost1")); 45 | history = service.getHostHistory(); 46 | expect(history).toEqual([{host: 'http://localhost1'}]); 47 | service.addToHistory(new ESConnection("http://localhost2")); 48 | history = service.getHostHistory(); 49 | expect(history).toEqual([{host: 'http://localhost2'}, 50 | {host: 'http://localhost1'}]); 51 | service.addToHistory(new ESConnection("http://localhost1")); 52 | history = service.getHostHistory(); 53 | expect(history).toEqual([{host: 'http://localhost1'}, 54 | {host: 'http://localhost2'}]); 55 | }); 56 | 57 | it("should limit history to 10 elements", function() { 58 | var history = service.getHostHistory(); 59 | expect(history).toEqual([]); 60 | service.addToHistory(new ESConnection("http://localhost1")); 61 | service.addToHistory(new ESConnection("http://localhost2")); 62 | service.addToHistory(new ESConnection("http://localhost3")); 63 | service.addToHistory(new ESConnection("http://localhost4")); 64 | service.addToHistory(new ESConnection("http://localhost5")); 65 | service.addToHistory(new ESConnection("http://localhost6")); 66 | service.addToHistory(new ESConnection("http://localhost7")); 67 | service.addToHistory(new ESConnection("http://localhost8")); 68 | service.addToHistory(new ESConnection("http://localhost9")); 69 | service.addToHistory(new ESConnection("http://localhost10")); 70 | service.addToHistory(new ESConnection("http://localhost11")); 71 | history = service.getHostHistory(); 72 | expect(history.length).toEqual(10); 73 | }); 74 | 75 | }); 76 | -------------------------------------------------------------------------------- /tests/jasmine/services/external_settings.tests.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe("ExternalSettingsService", function() { 4 | 5 | var service; 6 | 7 | beforeEach(module("kopf")); 8 | 9 | beforeEach(function() { 10 | var store = {}; 11 | spyOn(localStorage, 'getItem').andCallFake(function(key) { 12 | return store[key]; 13 | }); 14 | spyOn(localStorage, 'setItem').andCallFake(function(key, value) { 15 | return store[key] = value + ''; 16 | }); 17 | spyOn(localStorage, 'clear').andCallFake(function() { 18 | store = {}; 19 | }); 20 | }); 21 | 22 | beforeEach(inject(function($injector) { 23 | service = $injector.get('ExternalSettingsService'); 24 | this.DebugService = $injector.get('DebugService'); 25 | })); 26 | 27 | it("should correctly save/retrieve settings to/from localstorage", 28 | function() { 29 | var settings = {refresh_rate: 'doesntmatter', theme: 'whatever'}; 30 | service.settings = settings; 31 | service.saveSettings(); 32 | expect(localStorage.setItem).toHaveBeenCalledWith('kopfSettings', 33 | JSON.stringify(settings)); 34 | expect(service.loadLocalSettings()).toEqual(settings); 35 | }); 36 | 37 | it("should correctly save/retrieve ONLY updatable settings", 38 | function() { 39 | var settings = {refresh_rate: 'doesntmatter', theme: 'whatever', other: 'a'}; 40 | var expectedSettings = {refresh_rate: 'doesntmatter', theme: 'whatever'}; 41 | service.settings = settings; 42 | service.saveSettings(); 43 | expect(localStorage.setItem).toHaveBeenCalledWith('kopfSettings', 44 | JSON.stringify(expectedSettings)); 45 | expect(service.loadLocalSettings()).toEqual(expectedSettings); 46 | }); 47 | 48 | it("should handle invalid sotred content", 49 | function() { 50 | localStorage.setItem('kopfSettings', "invalid json"); 51 | spyOn(this.DebugService, 'debug').andReturn(true); 52 | var settings = service.loadLocalSettings(); 53 | expect(localStorage.getItem).toHaveBeenCalledWith('kopfSettings'); 54 | expect(this.DebugService.debug).toHaveBeenCalled(); 55 | expect(settings).toEqual({}); 56 | }); 57 | 58 | it("should handle invalid sotred content", 59 | function() { 60 | localStorage.setItem('kopfSettings', "invalid json"); 61 | spyOn(this.DebugService, 'debug').andReturn(true); 62 | var settings = service.loadLocalSettings(); 63 | expect(localStorage.getItem).toHaveBeenCalledWith('kopfSettings'); 64 | expect(this.DebugService.debug).toHaveBeenCalled(); 65 | expect(settings).toEqual({}); 66 | }); 67 | 68 | it("should correctly update the updatable settings", 69 | function() { 70 | var settings = {refresh_rate: '1', theme: 'whatever'}; 71 | service.settings = {}; 72 | service.updateSettings(settings); 73 | expect(service.settings).toEqual(settings); 74 | }); 75 | 76 | it("should update ONLY the updatable settings", 77 | function() { 78 | var settings = { 79 | refresh_rate: '1', 80 | theme: 'whatever', 81 | elasticsearch_root_path: 'blah blah', 82 | with_credentials: 'nono' 83 | }; 84 | service.settings = {}; 85 | service.updateSettings(settings); 86 | expect(service.settings).toEqual({refresh_rate: '1', theme: 'whatever'}); 87 | }); 88 | 89 | }); 90 | -------------------------------------------------------------------------------- /src/kopf/controllers/warmers.js: -------------------------------------------------------------------------------- 1 | kopf.controller('WarmersController', [ 2 | '$scope', 'ConfirmDialogService', 'AlertService', 'AceEditorService', 3 | 'ElasticService', 4 | function($scope, ConfirmDialogService, AlertService, AceEditorService, 5 | ElasticService) { 6 | $scope.editor = undefined; 7 | $scope.indices = []; 8 | $scope.index = null; 9 | $scope.paginator = new Paginator(1, 10, [], new WarmerFilter('')); 10 | $scope.page = $scope.paginator.getPage(); 11 | 12 | $scope.warmer = new Warmer('', '', {types: [], source: {}}); 13 | 14 | $scope.warmers = []; 15 | 16 | $scope.$watch( 17 | function() { 18 | return ElasticService.cluster; 19 | }, 20 | function(filter, previous) { 21 | $scope.indices = ElasticService.getIndices(); 22 | }, 23 | true 24 | ); 25 | 26 | $scope.$watch('paginator', function(filter, previous) { 27 | $scope.page = $scope.paginator.getPage(); 28 | }, true); 29 | 30 | $scope.initEditor = function() { 31 | if (!angular.isDefined($scope.editor)) { 32 | $scope.editor = AceEditorService.init('warmer-editor'); 33 | } 34 | }; 35 | 36 | $scope.createWarmer = function() { 37 | if ($scope.editor.hasContent()) { 38 | $scope.editor.format(); 39 | if (!isDefined($scope.editor.error)) { 40 | $scope.warmer.source = $scope.editor.getValue(); 41 | ElasticService.registerWarmer($scope.warmer, 42 | function(response) { 43 | $scope.loadIndexWarmers(); 44 | AlertService.success('Warmer successfully created', response); 45 | }, 46 | function(error) { 47 | AlertService.error('Request returned invalid JSON', error); 48 | } 49 | ); 50 | } 51 | } else { 52 | AlertService.error('Warmer query can\'t be empty'); 53 | } 54 | }; 55 | 56 | $scope.deleteWarmer = function(warmer) { 57 | ConfirmDialogService.open( 58 | 'are you sure you want to delete warmer ' + warmer.id + '?', 59 | warmer.source, 60 | 'Delete', 61 | function() { 62 | ElasticService.deleteWarmer(warmer, // FIXME: better send name + id 63 | function(response) { 64 | AlertService.success('Warmer successfully deleted', response); 65 | $scope.loadIndexWarmers(); 66 | }, 67 | function(error) { 68 | AlertService.error('Error while deleting warmer', error); 69 | } 70 | ); 71 | } 72 | ); 73 | }; 74 | 75 | $scope.loadIndexWarmers = function() { 76 | if (isDefined($scope.index)) { 77 | ElasticService.getIndexWarmers($scope.index, '', 78 | function(warmers) { 79 | $scope.paginator.setCollection(warmers); 80 | $scope.page = $scope.paginator.getPage(); 81 | }, 82 | function(error) { 83 | $scope.paginator.setCollection([]); 84 | $scope.page = $scope.paginator.getPage(); 85 | AlertService.error('Error while fetching warmers', error); 86 | } 87 | ); 88 | } else { 89 | $scope.paginator.setCollection([]); 90 | $scope.page = $scope.paginator.getPage(); 91 | } 92 | }; 93 | 94 | $scope.initializeController = function() { 95 | $scope.indices = ElasticService.getIndices(); 96 | $scope.initEditor(); 97 | }; 98 | 99 | } 100 | ]); 101 | --------------------------------------------------------------------------------