├── .dockerignore ├── .gitignore ├── .jshintrc ├── Dockerfile ├── Dockerfile-alpine ├── Gruntfile.js ├── LICENCE ├── README.textile ├── _site ├── app.css ├── app.js ├── background.js ├── base │ ├── favicon.png │ ├── loading.gif │ └── reset.css ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff ├── i18n.js ├── index.html ├── lang │ ├── en_strings.js │ ├── fr_strings.js │ ├── ja_strings.js │ ├── pt_strings.js │ ├── tr_strings.js │ ├── zh-TW_strings.js │ └── zh_strings.js ├── manifest.json ├── vendor.css └── vendor.js ├── crx └── es-head.crx ├── elasticsearch-head.sublime-project ├── grunt_fileSets.js ├── index.html ├── package.json ├── plugin-descriptor.properties ├── proxy ├── clusters │ ├── localhost9200.json │ └── xpack.json ├── index.js └── recipes │ └── http_proxy.js ├── src ├── app │ ├── app.css │ ├── app.js │ ├── base │ │ ├── boot.js │ │ ├── favicon.png │ │ ├── loading.gif │ │ └── reset.css │ ├── data │ │ ├── boolQuery.js │ │ ├── dataSourceInterface.js │ │ ├── metaData.js │ │ ├── metaDataFactory.js │ │ ├── model │ │ │ ├── model.js │ │ │ └── modelSpec.js │ │ ├── query.js │ │ ├── queryDataSourceInterface.js │ │ └── resultDataSourceInterface.js │ ├── lang │ │ ├── en_strings.js │ │ ├── fr_strings.js │ │ ├── ja_strings.js │ │ ├── pt_strings.js │ │ ├── tr_strings.js │ │ ├── vi_strings.js │ │ ├── zh-TW_strings.js │ │ └── zh_strings.js │ ├── services │ │ ├── cluster │ │ │ ├── cluster.js │ │ │ └── clusterSpec.js │ │ ├── clusterState │ │ │ ├── clusterState.js │ │ │ └── clusterStateSpec.js │ │ └── preferences │ │ │ ├── preferenceSpec.js │ │ │ └── preferences.js │ ├── ui │ │ ├── abstractField │ │ │ ├── abstractField.css │ │ │ └── abstractField.js │ │ ├── abstractPanel │ │ │ ├── abstractPanel.css │ │ │ └── abstractPanel.js │ │ ├── abstractWidget │ │ │ └── abstractWidget.js │ │ ├── anyRequest │ │ │ ├── anyRequest.css │ │ │ └── anyRequest.js │ │ ├── browser │ │ │ ├── browser.css │ │ │ └── browser.js │ │ ├── button │ │ │ ├── button.css │ │ │ ├── button.js │ │ │ └── buttonDemo.js │ │ ├── checkField │ │ │ ├── checkField.js │ │ │ ├── checkFieldDemo.js │ │ │ └── checkFieldSpec.js │ │ ├── clusterConnect │ │ │ ├── clusterConnect.css │ │ │ ├── clusterConnect.js │ │ │ └── clusterConnectSpec.js │ │ ├── clusterOverview │ │ │ ├── clusterOverview.css │ │ │ └── clusterOverview.js │ │ ├── csvTable │ │ │ └── csvTable.js │ │ ├── dateHistogram │ │ │ └── dateHistogram.js │ │ ├── dialogPanel │ │ │ └── dialogPanel.js │ │ ├── draggablePanel │ │ │ └── draggablePanel.js │ │ ├── filterBrowser │ │ │ ├── filterBrowser.css │ │ │ └── filterBrowser.js │ │ ├── header │ │ │ ├── header.css │ │ │ └── header.js │ │ ├── helpPanel │ │ │ └── helpPanel.js │ │ ├── indexOverview │ │ │ └── indexOverview.js │ │ ├── indexSelector │ │ │ └── indexSelector.js │ │ ├── infoPanel │ │ │ ├── infoPanel.css │ │ │ └── infoPanel.js │ │ ├── jsonPanel │ │ │ ├── jsonPanel.css │ │ │ └── jsonPanel.js │ │ ├── jsonPretty │ │ │ ├── jsonPretty.css │ │ │ └── jsonPretty.js │ │ ├── menuButton │ │ │ ├── menuButton.css │ │ │ └── menuButton.js │ │ ├── menuPanel │ │ │ ├── menuPanel.css │ │ │ └── menuPanel.js │ │ ├── nodesView │ │ │ ├── nodesView.css │ │ │ ├── nodesView.js │ │ │ └── nodesViewDemo.js │ │ ├── page │ │ │ └── page.js │ │ ├── panelForm │ │ │ ├── panelForm.css │ │ │ └── panelForm.js │ │ ├── queryFilter │ │ │ ├── queryFilter.css │ │ │ └── queryFilter.js │ │ ├── refreshButton │ │ │ ├── refreshButton.js │ │ │ ├── refreshButtonDemo.js │ │ │ └── refreshButtonSpec.js │ │ ├── resultTable │ │ │ └── resultTable.js │ │ ├── selectMenuPanel │ │ │ ├── selectMenuPanel.css │ │ │ └── selectMenuPanel.js │ │ ├── sidebarSection │ │ │ ├── sidebarSection.css │ │ │ └── sidebarSection.js │ │ ├── splitButton │ │ │ ├── splitButton.css │ │ │ ├── splitButton.js │ │ │ └── splitButtonDemo.js │ │ ├── structuredQuery │ │ │ ├── structuredQuery.css │ │ │ └── structuredQuery.js │ │ ├── table │ │ │ ├── table.css │ │ │ └── table.js │ │ ├── textField │ │ │ ├── textField.js │ │ │ └── textFieldDemo.js │ │ └── toolbar │ │ │ ├── toolbar.css │ │ │ └── toolbar.js │ └── ux │ │ ├── class.js │ │ ├── dragdrop.js │ │ ├── fieldCollection.js │ │ ├── observable.js │ │ ├── singleton.js │ │ ├── singletonSpec.js │ │ ├── table.css │ │ └── templates │ │ ├── templateSpec.js │ │ └── templates.js ├── chrome_ext │ ├── background.js │ └── manifest.json └── vendor │ ├── dateRangeParser │ └── date-range-parser.js │ ├── font-awesome │ ├── css │ │ ├── font-awesome.css │ │ └── font-awesome.min.css │ └── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ └── fontawesome-webfont.woff │ ├── graphael │ └── g.raphael.standalone.js │ ├── i18n │ └── i18n.js │ ├── joey │ └── joey.js │ ├── jquery │ └── jquery.js │ └── nohtml │ └── jquery-nohtml.js └── test ├── demo.html ├── generators ├── conflictingField.sh ├── delete_all_indices.sh ├── multi_type.sh ├── twitter_feed.sh └── twitter_river.sh ├── perf.html └── spec └── specHelper.js /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | *.tmproj 4 | *.sublime-workspace 5 | .project 6 | node_modules -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "maxerr": 1000, 3 | 4 | "predef": [ 5 | "exports", 6 | "describe", 7 | "expect", 8 | "it", 9 | "beforeEach", 10 | "afterEach", 11 | "jasmine", 12 | "test", 13 | "spyOn" 14 | ], 15 | 16 | "jquery" : true, 17 | "browser" : true, 18 | 19 | "curly": true, 20 | "debug": false, 21 | "devel": true, 22 | "eqeqeq": true, 23 | "eqnull": true, 24 | "expr": true, 25 | "forin": false, 26 | "immed": false, 27 | "latedef": true, 28 | "newcap": true, 29 | "noarg": true, 30 | "noempty": false, 31 | "nonew": false, 32 | "nomen": false, 33 | "plusplus": false, 34 | "regexp": false, 35 | "undef": true, 36 | "sub": true, 37 | "white": false, 38 | "scripturl": true, 39 | "esnext": true 40 | } 41 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node 2 | MAINTAINER Niko Bellic 3 | 4 | RUN mkdir -p /usr/src/app 5 | WORKDIR /usr/src/app 6 | 7 | RUN npm install -g grunt 8 | 9 | COPY package.json /usr/src/app/package.json 10 | RUN npm install 11 | 12 | COPY . /usr/src/app 13 | 14 | EXPOSE 9100 15 | 16 | CMD grunt server 17 | -------------------------------------------------------------------------------- /Dockerfile-alpine: -------------------------------------------------------------------------------- 1 | # docker build -t mobz/elasticsearch-head:5-alpine -f Dockerfile-alpine . 2 | 3 | FROM node:alpine 4 | WORKDIR /usr/src/app 5 | RUN npm install http-server 6 | COPY . . 7 | EXPOSE 9100 8 | CMD node_modules/http-server/bin/http-server _site -p 9100 9 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | var fileSets = require("./grunt_fileSets.js"); 4 | 5 | // Project configuration. 6 | grunt.initConfig({ 7 | clean: { 8 | _site: { 9 | src: ['_site'] 10 | } 11 | }, 12 | concat: { 13 | vendorjs: { 14 | src: fileSets.vendorJs, 15 | dest: '_site/vendor.js' 16 | }, 17 | vendorcss: { 18 | src: fileSets.vendorCss, 19 | dest: '_site/vendor.css' 20 | }, 21 | appjs: { 22 | src: fileSets.srcJs, 23 | dest: '_site/app.js' 24 | }, 25 | appcss: { 26 | src: fileSets.srcCss, 27 | dest: '_site/app.css' 28 | } 29 | }, 30 | 31 | copy: { 32 | site_index: { 33 | src: 'index.html', 34 | dest: '_site/index.html', 35 | options: { 36 | process: function( src ) { 37 | return src.replace(/_site\//g, ""); 38 | } 39 | } 40 | }, 41 | base: { 42 | expand: true, 43 | cwd: 'src/app/base/', 44 | src: [ '*.gif', '*.png', '*.css' ], 45 | dest: '_site/base/' 46 | }, 47 | iconFonts: { 48 | expand: true, 49 | cwd: 'src/vendor/font-awesome/fonts/', 50 | src: '**', 51 | dest: '_site/fonts' 52 | }, 53 | i18n: { 54 | src: 'src/vendor/i18n/i18n.js', 55 | dest: '_site/i18n.js' 56 | }, 57 | lang: { 58 | expand: true, 59 | cwd: 'src/app/lang/', 60 | src: '**', 61 | dest: '_site/lang/' 62 | }, 63 | chrome: { 64 | src: 'src/chrome_ext/*.*', 65 | dest: '_site/' 66 | } 67 | }, 68 | 69 | jasmine: { 70 | task: { 71 | src: [ fileSets.vendorJs, 'src/vendor/i18n/i18n.js', 'src/app/lang/en_strings.js', fileSets.srcJs ], 72 | options: { 73 | specs: 'src/app/**/*Spec.js', 74 | helpers: 'test/spec/*Helper.js', 75 | display: "short", 76 | summary: true 77 | } 78 | } 79 | }, 80 | 81 | watch: { 82 | "scripts": { 83 | files: ['src/**/*', 'test/spec/*' ], 84 | tasks: ['default'], 85 | options: { 86 | spawn: false 87 | } 88 | }, 89 | "grunt": { 90 | files: [ 'Gruntfile.js' ] 91 | } 92 | }, 93 | 94 | connect: { 95 | server: { 96 | options: { 97 | port: 9100, 98 | base: '.', 99 | keepalive: true 100 | } 101 | } 102 | } 103 | 104 | }); 105 | 106 | grunt.loadNpmTasks('grunt-contrib-clean'); 107 | grunt.loadNpmTasks('grunt-contrib-concat'); 108 | grunt.loadNpmTasks('grunt-contrib-watch'); 109 | grunt.loadNpmTasks('grunt-contrib-connect'); 110 | grunt.loadNpmTasks('grunt-contrib-copy'); 111 | grunt.loadNpmTasks('grunt-contrib-jasmine'); 112 | 113 | // Default task(s). 114 | grunt.registerTask('default', ['clean', 'concat', 'copy', 'jasmine']); 115 | grunt.registerTask('server', ['connect:server']); 116 | grunt.registerTask('dev', [ 'default', 'watch' ]); 117 | 118 | 119 | }; 120 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright 2010-2013 Ben Birch 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this software except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /_site/background.js: -------------------------------------------------------------------------------- 1 | chrome.browserAction.onClicked.addListener(function (tab) { 2 | chrome.tabs.create({'url': chrome.extension.getURL('index.html')}, function (tab) { 3 | }); 4 | }); 5 | -------------------------------------------------------------------------------- /_site/base/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobz/elasticsearch-head/2d51fecac2980d350fcd3319fd9fe2999f63c9db/_site/base/favicon.png -------------------------------------------------------------------------------- /_site/base/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobz/elasticsearch-head/2d51fecac2980d350fcd3319fd9fe2999f63c9db/_site/base/loading.gif -------------------------------------------------------------------------------- /_site/base/reset.css: -------------------------------------------------------------------------------- 1 | BODY { 2 | font-family: Verdana, sans-serif; 3 | font-size: 73%; 4 | padding: 0; 5 | margin: 0; 6 | } 7 | 8 | INPUT, SELECT, TEXTAREA { 9 | border: 1px solid #cecece; 10 | padding: 1px 3px; 11 | background: white; 12 | } 13 | 14 | SELECT { 15 | padding: 0; 16 | } 17 | 18 | .saf SELECT { 19 | margin-top: 0; 20 | margin-bottom: 0; 21 | } 22 | 23 | TEXTAREA, CODE { 24 | font-family: monospace; 25 | font-size: 13px; 26 | } 27 | 28 | BUTTON::-moz-focus-inner { 29 | border: none; 30 | } 31 | 32 | .pull-left { 33 | float: left; 34 | } 35 | 36 | .pull-right { 37 | float: right; 38 | } 39 | 40 | .loading { 41 | background-image: url(loading.gif); 42 | background-repeat: no-repeat; 43 | text-indent: 20px; 44 | } 45 | -------------------------------------------------------------------------------- /_site/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobz/elasticsearch-head/2d51fecac2980d350fcd3319fd9fe2999f63c9db/_site/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /_site/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobz/elasticsearch-head/2d51fecac2980d350fcd3319fd9fe2999f63c9db/_site/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /_site/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobz/elasticsearch-head/2d51fecac2980d350fcd3319fd9fe2999f63c9db/_site/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /_site/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobz/elasticsearch-head/2d51fecac2980d350fcd3319fd9fe2999f63c9db/_site/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /_site/i18n.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | /** 3 | * provides text formatting and i18n key storage features
4 | * implements most of the Sun Java MessageFormat functionality. 5 | * @see Sun's Documentation 6 | */ 7 | 8 | var keys = {}; 9 | var locale = undefined; 10 | 11 | var format = function(message, args) { 12 | var substitute = function() { 13 | var format = arguments[1].split(','); 14 | var substr = escape(args[format.shift()]); 15 | if(format.length === 0) { 16 | return substr; // simple substitution eg {0} 17 | } 18 | switch(format.shift()) { 19 | case "number" : return (new Number(substr)).toLocaleString(locale); 20 | case "date" : return (new Date(+substr)).toLocaleDateString(locale); // date and time require milliseconds since epoch 21 | case "time" : return (new Date(+substr)).toLocaleTimeString(locale); // eg i18n.text("Key", +(new Date())); for current time 22 | } 23 | var styles = format.join("").split("|").map(function(style) { 24 | return style.match(/(-?[\.\d]+)(#|<)([^{}]*)/); 25 | }); 26 | var match = styles[0][3]; 27 | for(var i=0; i elm.dataset && elm.dataset.langs).dataset; 84 | if( ! data["langs"] ) { 85 | return; 86 | } 87 | var langs = data["langs"].split(/\s*,\s*/); 88 | var script0 = scripts[0]; 89 | function install( lang ) { 90 | var s = document.createElement("script"); 91 | s.src = data["basedir"] + "/" + lang + '_strings.js'; 92 | s.async = false; 93 | script0.parentNode.appendChild(s); 94 | script0 = s; 95 | 96 | i18n.setLocale(lang); 97 | } 98 | 99 | install( langs.shift() ); // always install primary language 100 | userLang && langs 101 | .filter( function( lang ) { return userLang.indexOf( lang ) === 0; } ) 102 | .forEach( install ); 103 | }()); 104 | -------------------------------------------------------------------------------- /_site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | elasticsearch-head 7 | 8 | 9 | 10 | 11 | 12 | 13 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /_site/lang/zh-TW_strings.js: -------------------------------------------------------------------------------- 1 | i18n.setKeys({ 2 | "General.Elasticsearch": "Elasticsearch", 3 | "General.LoadingAggs": "讀取聚合查詢...", 4 | "General.Searching": "搜尋中...", 5 | "General.Search": "搜尋", 6 | "General.Help": "幫助", 7 | "General.HelpGlyph": "?", 8 | "General.CloseGlyph": "X", 9 | "General.RefreshResults": "更新", 10 | "General.ManualRefresh": "手動更新", 11 | "General.RefreshQuickly": "快速更新", 12 | "General.Refresh5seconds": "每5秒更新", 13 | "General.Refresh1minute": "每1分鐘更新", 14 | "AliasForm.AliasName": "别名", 15 | "AliasForm.NewAliasForIndex": "為 {0} 建立新别名", 16 | "AliasForm.DeleteAliasMessage": "輸入 ''{0}'' 删除 {1}. 此操作無法恢復", 17 | "AnyRequest.DisplayOptions" : "顯示選項", 18 | "AnyRequest.AsGraph" : "圖形視圖", 19 | "AnyRequest.AsJson" : "原始 JSON", 20 | "AnyRequest.AsTable" : "表格視圖", 21 | "AnyRequest.History" : "歷史記錄", 22 | "AnyRequest.RepeatRequest" : "重複請求", 23 | "AnyRequest.RepeatRequestSelect" : "重複周期 ", 24 | "AnyRequest.Transformer" : "結果轉換器", 25 | "AnyRequest.Pretty": "易讀", 26 | "AnyRequest.Query" : "查詢", 27 | "AnyRequest.Request": "送出", 28 | "AnyRequest.Requesting": "請求中...", 29 | "AnyRequest.ValidateJSON": "驗證 JSON", 30 | "Browser.Title": "資料瀏覽", 31 | "Browser.ResultSourcePanelTitle": "原始資料", 32 | "Command.DELETE": "删除", 33 | "Command.SHUTDOWN": "關閉", 34 | "Command.DeleteAliasMessage": "删除别名?", 35 | "ClusterOverView.IndexName": "索引名稱", 36 | "ClusterOverview.NumShards": "分片數", 37 | "ClusterOverview.NumReplicas": "副本數", 38 | "ClusterOverview.NewIndex": "新建索引", 39 | "IndexActionsMenu.Title": "動作", 40 | "IndexActionsMenu.NewAlias": "新建别名...", 41 | "IndexActionsMenu.Refresh": "更新", 42 | "IndexActionsMenu.Flush": "Flush更新", 43 | "IndexActionsMenu.Optimize": "最佳化...", 44 | "IndexActionsMenu.ForceMerge": "強制合併...", 45 | "IndexActionsMenu.Snapshot": "网关快照", 46 | "IndexActionsMenu.Analyser": "測試分析器", 47 | "IndexActionsMenu.Open": "開啟", 48 | "IndexActionsMenu.Close": "關閉", 49 | "IndexActionsMenu.Delete": "删除...", 50 | "IndexInfoMenu.Title": "訊息", 51 | "IndexInfoMenu.Status": "索引狀態", 52 | "IndexInfoMenu.Metadata": "索引訊息", 53 | "IndexCommand.TextToAnalyze": "文本分析", 54 | "IndexCommand.ShutdownMessage": "輸入 ''{0}'' 以關閉 {1} 節點. 關閉的節點無法從此界面重新啟動", 55 | "IndexOverview.PageTitle": "索引總覽", 56 | "IndexSelector.NameWithDocs": "{0} ({1} 個文件)", 57 | "IndexSelector.SearchIndexForDocs": "搜尋 {0} 的文件,查詢條件:", 58 | "FilterBrowser.OutputType": "返回格式: {0}", 59 | "FilterBrowser.OutputSize": "顯示數量: {0}", 60 | "Header.ClusterHealth": "叢集健康值: {0} ({1} of {2})", 61 | "Header.ClusterNotConnected": "叢集健康值: 未連接", 62 | "Header.Connect": "連接", 63 | "Nav.AnyRequest": "複合查詢", 64 | "Nav.Browser": "資料瀏覽", 65 | "Nav.ClusterHealth": "叢集健康值", 66 | "Nav.ClusterState": "群集狀態", 67 | "Nav.ClusterNodes": "叢集節點", 68 | "Nav.Info": "訊息", 69 | "Nav.NodeStats": "節點狀態", 70 | "Nav.Overview": "總覽", 71 | "Nav.Indices": "索引", 72 | "Nav.Plugins": "套件", 73 | "Nav.Status": "狀態", 74 | "Nav.Templates": "樣版", 75 | "Nav.StructuredQuery": "基本查詢", 76 | "NodeActionsMenu.Title": "動作", 77 | "NodeActionsMenu.Shutdown": "關閉節點...", 78 | "NodeInfoMenu.Title": "訊息", 79 | "NodeInfoMenu.ClusterNodeInfo": "叢集節點訊息", 80 | "NodeInfoMenu.NodeStats": "節點狀態", 81 | "NodeType.Client": "節點客户端", 82 | "NodeType.Coord": "協調器", 83 | "NodeType.Master": "主節點", 84 | "NodeType.Tribe": "分支節點", 85 | "NodeType.Worker": "工作節點", 86 | "NodeType.Unassigned": "未分配", 87 | "OptimizeForm.OptimizeIndex": "最佳化 {0}", 88 | "OptimizeForm.MaxSegments": "最大索引段數", 89 | "OptimizeForm.ExpungeDeletes": "只删除被標記為删除的", 90 | "OptimizeForm.FlushAfter": "最佳化後更新", 91 | "OptimizeForm.WaitForMerge": "等待合併", 92 | "ForceMergeForm.ForceMergeIndex": "強制合併 {0}", 93 | "ForceMergeForm.MaxSegments": "最大索引段數", 94 | "ForceMergeForm.ExpungeDeletes": "只删除被標記為删除的", 95 | "ForceMergeForm.FlushAfter": "強制合併後更新", 96 | "Overview.PageTitle" : "叢集總覽", 97 | "Output.JSON": "JSON", 98 | "Output.Table": "Table", 99 | "Output.CSV": "CSV", 100 | "Output.ShowSource": "顯示查詢語句", 101 | "Preference.SortCluster": "叢集排序", 102 | "Sort.ByName": "按名稱", 103 | "Sort.ByAddress": "按地址", 104 | "Sort.ByType": "按類型", 105 | "TableResults.Summary": "查詢 {1} 個分片中用的 {0} 個. {2} 命中. 耗時 {3} 秒", 106 | "QueryFilter.AllIndices": "所有索引", 107 | "QueryFilter.AnyValue": "任意", 108 | "QueryFilter-Header-Indices": "索引", 109 | "QueryFilter-Header-Types": "類型", 110 | "QueryFilter-Header-Fields": "欄位", 111 | "QueryFilter.DateRangeHint.from": "從 : {0}", 112 | "QueryFilter.DateRangeHint.to": " 到 : {0}", 113 | "Query.FailAndUndo": "查詢失敗. 撤銷最近的更改", 114 | "StructuredQuery.ShowRawJson": "顯示原始 JSON" 115 | }); 116 | 117 | i18n.setKeys({ 118 | "AnyRequest.TransformerHelp" : "\ 119 |

結果轉換器用於返回結果原始 JSON 的後續處理, 將結果轉換為更有用的格式.

\ 120 |

轉換器應當包含 JavaScript 函數內容. 函數的返回值將傳遞给 JSON 分析器

\ 121 |

Example:
\ 122 | return root.hits.hits[0];
\ 123 | 遍歷結果並只顯示第一個元素
\ 124 | return Object.keys(root.nodes).reduce(function(tot, node) { return tot + root.nodes[node].os.mem.used_in_bytes; }, 0);
\ 125 | 將返回整個叢集使用的總記憶體

\ 126 |

以下函數可以方便的處理陣列與物件
\ 127 |

\ 133 |

當啟用重複請求時, prev 參數將會傳遞给轉換器函數. 這將用於比較並累加圖形

\ 134 |

Example:
\ 135 | var la = [ root.nodes[Object.keys(root.nodes)[0]].os.load_average[0] ]; return prev ? la.concat(prev) : la;
\ 136 | 將返回第一個叢集節點最近一分鐘内的平均負載\ 137 | 將會把結果送入圖表以產生一個負載曲線圖\ 138 | " 139 | }); 140 | 141 | i18n.setKeys({ 142 | "AnyRequest.DisplayOptionsHelp" : "\ 143 |

原始 JSON: 將完整的查詢結果轉換為原始 JSON 格式

\ 144 |

圖形視圖: 將查詢結果圖形化, 將查詢結果轉換為陣列值的形式

\ 145 |

表格視圖: 如果查詢是一個搜尋, 可以將搜尋結果以表格形式顯示.

\ 146 | " 147 | }); 148 | 149 | i18n.setKeys({ 150 | "QueryFilter.DateRangeHelp" : "\ 151 |

Date 欄位接受日期範圍的形式查詢.

\ 152 |

以下格式被支援:

\ 153 | \ 176 | " 177 | }); 178 | -------------------------------------------------------------------------------- /_site/lang/zh_strings.js: -------------------------------------------------------------------------------- 1 | i18n.setKeys({ 2 | "General.Elasticsearch": "Elasticsearch", 3 | "General.LoadingAggs": "加载聚合查询...", 4 | "General.Searching": "搜索中...", 5 | "General.Search": "搜索", 6 | "General.Help": "帮助", 7 | "General.HelpGlyph": "?", 8 | "General.CloseGlyph": "X", 9 | "General.RefreshResults": "刷新", 10 | "General.ManualRefresh": "手动刷新", 11 | "General.RefreshQuickly": "快速刷新", 12 | "General.Refresh5seconds": "每5秒刷新", 13 | "General.Refresh1minute": "每1分钟刷新", 14 | "AliasForm.AliasName": "别名", 15 | "AliasForm.NewAliasForIndex": "为 {0} 创建新别名", 16 | "AliasForm.DeleteAliasMessage": "输入 ''{0}'' 删除 {1}. 此操作无法恢复", 17 | "AnyRequest.DisplayOptions" : "显示选项", 18 | "AnyRequest.AsGraph" : "图形视图", 19 | "AnyRequest.AsJson" : "原始 JSON", 20 | "AnyRequest.AsTable" : "表格视图", 21 | "AnyRequest.History" : "历史记录", 22 | "AnyRequest.RepeatRequest" : "重复请求", 23 | "AnyRequest.RepeatRequestSelect" : "重复周期 ", 24 | "AnyRequest.Transformer" : "结果转换器", 25 | "AnyRequest.Pretty": "易读", 26 | "AnyRequest.Query" : "查询", 27 | "AnyRequest.Request": "提交请求", 28 | "AnyRequest.Requesting": "请求中...", 29 | "AnyRequest.ValidateJSON": "验证 JSON", 30 | "Browser.Title": "数据浏览", 31 | "Browser.ResultSourcePanelTitle": "原始数据", 32 | "Command.DELETE": "删除", 33 | "Command.SHUTDOWN": "关闭", 34 | "Command.DeleteAliasMessage": "删除别名?", 35 | "ClusterOverView.IndexName": "索引名称", 36 | "ClusterOverview.NumShards": "分片数", 37 | "ClusterOverview.NumReplicas": "副本数", 38 | "ClusterOverview.NewIndex": "新建索引", 39 | "IndexActionsMenu.Title": "动作", 40 | "IndexActionsMenu.NewAlias": "新建别名...", 41 | "IndexActionsMenu.Refresh": "刷新", 42 | "IndexActionsMenu.Flush": "Flush刷新", 43 | "IndexActionsMenu.Optimize": "优化...", 44 | "IndexActionsMenu.ForceMerge": "ForceMerge...", 45 | "IndexActionsMenu.Snapshot": "网关快照", 46 | "IndexActionsMenu.Analyser": "测试分析器", 47 | "IndexActionsMenu.Open": "开启", 48 | "IndexActionsMenu.Close": "关闭", 49 | "IndexActionsMenu.Delete": "删除...", 50 | "IndexInfoMenu.Title": "信息", 51 | "IndexInfoMenu.Status": "索引状态", 52 | "IndexInfoMenu.Metadata": "索引信息", 53 | "IndexCommand.TextToAnalyze": "文本分析", 54 | "IndexCommand.ShutdownMessage": "输入 ''{0}'' 以关闭 {1} 节点. 关闭的节点无法从此界面重新启动", 55 | "IndexOverview.PageTitle": "索引概览", 56 | "IndexSelector.NameWithDocs": "{0} ({1} 个文档)", 57 | "IndexSelector.SearchIndexForDocs": "搜索 {0} 的文档, 查询条件:", 58 | "FilterBrowser.OutputType": "返回格式: {0}", 59 | "FilterBrowser.OutputSize": "显示数量: {0}", 60 | "Header.ClusterHealth": "集群健康值: {0} ({1} of {2})", 61 | "Header.ClusterNotConnected": "集群健康值: 未连接", 62 | "Header.Connect": "连接", 63 | "Nav.AnyRequest": "复合查询", 64 | "Nav.Browser": "数据浏览", 65 | "Nav.ClusterHealth": "集群健康值", 66 | "Nav.ClusterState": "群集状态", 67 | "Nav.ClusterNodes": "集群节点", 68 | "Nav.Info": "信息", 69 | "Nav.NodeStats": "节点状态", 70 | "Nav.Overview": "概览", 71 | "Nav.Indices": "索引", 72 | "Nav.Plugins": "插件", 73 | "Nav.Status": "状态", 74 | "Nav.Templates": "模板", 75 | "Nav.StructuredQuery": "基本查询", 76 | "NodeActionsMenu.Title": "动作", 77 | "NodeActionsMenu.Shutdown": "关停...", 78 | "NodeInfoMenu.Title": "信息", 79 | "NodeInfoMenu.ClusterNodeInfo": "集群节点信息", 80 | "NodeInfoMenu.NodeStats": "节点状态", 81 | "NodeType.Client": "节点客户端", 82 | "NodeType.Coord": "协调器", 83 | "NodeType.Master": "主节点", 84 | "NodeType.Tribe": "分支结点", 85 | "NodeType.Worker": "工作节点", 86 | "NodeType.Unassigned": "未分配", 87 | "OptimizeForm.OptimizeIndex": "优化 {0}", 88 | "OptimizeForm.MaxSegments": "最大索引段数", 89 | "OptimizeForm.ExpungeDeletes": "只删除被标记为删除的", 90 | "OptimizeForm.FlushAfter": "优化后刷新", 91 | "OptimizeForm.WaitForMerge": "等待合并", 92 | "ForceMergeForm.ForceMergeIndex": "ForceMerge {0}", 93 | "ForceMergeForm.MaxSegments": "最大索引段数", 94 | "ForceMergeForm.ExpungeDeletes": "只删除被标记为删除的", 95 | "ForceMergeForm.FlushAfter": "ForceMerge后刷新", 96 | "Overview.PageTitle" : "集群概览", 97 | "Output.JSON": "JSON", 98 | "Output.Table": "Table", 99 | "Output.CSV": "CSV", 100 | "Output.ShowSource": "显示查询语句", 101 | "Preference.SortCluster": "集群排序", 102 | "Sort.ByName": "按名称", 103 | "Sort.ByAddress": "按地址", 104 | "Sort.ByType": "按类型", 105 | "TableResults.Summary": "查询 {1} 个分片中用的 {0} 个. {2} 命中. 耗时 {3} 秒", 106 | "QueryFilter.AllIndices": "所有索引", 107 | "QueryFilter.AnyValue": "任意", 108 | "QueryFilter-Header-Indices": "索引", 109 | "QueryFilter-Header-Types": "类型", 110 | "QueryFilter-Header-Fields": "字段", 111 | "QueryFilter.DateRangeHint.from": "从 : {0}", 112 | "QueryFilter.DateRangeHint.to": " 到 : {0}", 113 | "Query.FailAndUndo": "查询失败. 撤消最近的更改", 114 | "StructuredQuery.ShowRawJson": "显示原始 JSON" 115 | }); 116 | 117 | i18n.setKeys({ 118 | "AnyRequest.TransformerHelp" : "\ 119 |

结果转换器用于返回结果原始JSON的后续处理, 将结果转换为更有用的格式.

\ 120 |

转换器应当包含javascript函数体. 函数的返回值将传递给json分析器

\ 121 |

Example:
\ 122 | return root.hits.hits[0];
\ 123 | 遍历结果并只显示第一个元素
\ 124 | return Object.keys(root.nodes).reduce(function(tot, node) { return tot + root.nodes[node].os.mem.used_in_bytes; }, 0);
\ 125 | 将返回整个集群使用的总内存

\ 126 |

以下函数可以方便的处理数组与对象
\ 127 |

\ 133 |

当启用重复请求时, prev 参数将会传递给转换器函数. 这将用于比较并累加图形

\ 134 |

Example:
\ 135 | var la = [ root.nodes[Object.keys(root.nodes)[0]].os.load_average[0] ]; return prev ? la.concat(prev) : la;
\ 136 | 将返回第一个集群节点最近一分钟内的平均负载\ 137 | 将会把结果送人图表以产生一个负载曲线图\ 138 | " 139 | }); 140 | 141 | i18n.setKeys({ 142 | "AnyRequest.DisplayOptionsHelp" : "\ 143 |

原始 Json: 将完整的查询结果转换为原始JSON格式

\ 144 |

图形视图: 将查询结果图形化, 将查询结果转换为数组值的形式

\ 145 |

表格视图: 如果查询是一个搜索, 可以将搜索结果以表格形式显示.

\ 146 | " 147 | }); 148 | 149 | i18n.setKeys({ 150 | "QueryFilter.DateRangeHelp" : "\ 151 |

Date 字段接受日期范围的形式查询.

\ 152 |

一下格式被支持:

\ 153 | \ 176 | " 177 | }); 178 | -------------------------------------------------------------------------------- /_site/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "elasticsearch-head", 4 | "version": "1.0.8", 5 | "background": { 6 | "scripts": ["background.js"], 7 | "persistent": false 8 | }, 9 | "icons": { 10 | "16": "base/favicon.png" 11 | }, 12 | "content_security_policy": "script-src 'self' 'sha256-Rpn+rjJuXCjZBPOPhhVloRXuzAUBRnAas+6gKVDohs0=' 'unsafe-eval'; object-src 'self';", 13 | "description": "Chrome Extension containing the excellent ElasticSearch Head application.", 14 | "browser_action": { 15 | "default_icon": "base/favicon.png" , 16 | "default_title": "es-head" 17 | }, 18 | "content_scripts":[{ 19 | "all_frames": false, 20 | "matches":["*://*/"], 21 | "js":["background.js"], 22 | "run_at": "document_end" 23 | }] 24 | } 25 | -------------------------------------------------------------------------------- /crx/es-head.crx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobz/elasticsearch-head/2d51fecac2980d350fcd3319fd9fe2999f63c9db/crx/es-head.crx -------------------------------------------------------------------------------- /elasticsearch-head.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": ".", 6 | "folder_exclude_patterns": [ "node_modules", "_site" ] 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /grunt_fileSets.js: -------------------------------------------------------------------------------- 1 | exports.vendorJs = [ 2 | 'src/vendor/jquery/jquery.js', 3 | 'src/vendor/joey/joey.js', 4 | 'src/vendor/nohtml/jquery-nohtml.js', 5 | 'src/vendor/graphael/g.raphael.standalone.js', 6 | 'src/vendor/dateRangeParser/date-range-parser.js' 7 | ]; 8 | 9 | exports.vendorCss = [ 10 | 'src/vendor/font-awesome/css/font-awesome.css' 11 | ]; 12 | 13 | exports.srcJs = [ 14 | 'src/app/base/boot.js', 15 | 16 | 'src/app/ux/class.js', 17 | 'src/app/ux/templates/templates.js', 18 | 'src/app/ux/observable.js', 19 | 'src/app/ux/singleton.js', 20 | 'src/app/ux/dragdrop.js', 21 | 'src/app/ux/fieldCollection.js', 22 | 23 | 'src/app/data/model/model.js', 24 | 'src/app/data/dataSourceInterface.js', 25 | 'src/app/data/resultDataSourceInterface.js', 26 | 'src/app/data/metaData.js', 27 | 'src/app/data/metaDataFactory.js', 28 | 'src/app/data/query.js', 29 | 'src/app/data/queryDataSourceInterface.js', 30 | 'src/app/data/boolQuery.js', 31 | 32 | 'src/app/services/preferences/preferences.js', 33 | 'src/app/services/cluster/cluster.js', 34 | 'src/app/services/clusterState/clusterState.js', 35 | 36 | 'src/app/ui/abstractWidget/abstractWidget.js', 37 | 'src/app/ui/abstractField/abstractField.js', 38 | 'src/app/ui/textField/textField.js', 39 | 'src/app/ui/checkField/checkField.js', 40 | 'src/app/ui/button/button.js', 41 | 'src/app/ui/menuButton/menuButton.js', 42 | 'src/app/ui/splitButton/splitButton.js', 43 | 'src/app/ui/refreshButton/refreshButton.js', 44 | 'src/app/ui/toolbar/toolbar.js', 45 | 'src/app/ui/abstractPanel/abstractPanel.js', 46 | 'src/app/ui/draggablePanel/draggablePanel.js', 47 | 'src/app/ui/infoPanel/infoPanel.js', 48 | 'src/app/ui/dialogPanel/dialogPanel.js', 49 | 'src/app/ui/menuPanel/menuPanel.js', 50 | 'src/app/ui/selectMenuPanel/selectMenuPanel.js', 51 | 'src/app/ui/table/table.js', 52 | 'src/app/ui/csvTable/csvTable.js', 53 | 'src/app/ui/jsonPretty/jsonPretty.js', 54 | 'src/app/ui/panelForm/panelForm.js', 55 | 'src/app/ui/helpPanel/helpPanel.js', 56 | 'src/app/ui/jsonPanel/jsonPanel.js', 57 | 'src/app/ui/sidebarSection/sidebarSection.js', 58 | 'src/app/ui/resultTable/resultTable.js', 59 | 'src/app/ui/queryFilter/queryFilter.js', 60 | 'src/app/ui/page/page.js', 61 | 'src/app/ui/browser/browser.js', 62 | 'src/app/ui/anyRequest/anyRequest.js', 63 | 'src/app/ui/nodesView/nodesView.js', 64 | 'src/app/ui/clusterOverview/clusterOverview.js', 65 | 'src/app/ui/dateHistogram/dateHistogram.js', 66 | 'src/app/ui/clusterConnect/clusterConnect.js', 67 | 'src/app/ui/structuredQuery/structuredQuery.js', 68 | 'src/app/ui/filterBrowser/filterBrowser.js', 69 | 'src/app/ui/indexSelector/indexSelector.js', 70 | 'src/app/ui/header/header.js', 71 | 'src/app/ui/indexOverview/indexOverview.js', 72 | 73 | 'src/app/app.js' 74 | ]; 75 | 76 | exports.srcCss = [ 77 | 'src/app/ux/table.css', 78 | 'src/app/ui/abstractField/abstractField.css', 79 | 'src/app/ui/button/button.css', 80 | 'src/app/ui/menuButton/menuButton.css', 81 | 'src/app/ui/splitButton/splitButton.css', 82 | 'src/app/ui/toolbar/toolbar.css', 83 | 'src/app/ui/abstractPanel/abstractPanel.css', 84 | 'src/app/ui/infoPanel/infoPanel.css', 85 | 'src/app/ui/menuPanel/menuPanel.css', 86 | 'src/app/ui/selectMenuPanel/selectMenuPanel.css', 87 | 'src/app/ui/table/table.css', 88 | 'src/app/ui/jsonPretty/jsonPretty.css', 89 | 'src/app/ui/jsonPanel/jsonPanel.css', 90 | 'src/app/ui/panelForm/panelForm.css', 91 | 'src/app/ui/sidebarSection/sidebarSection.css', 92 | 'src/app/ui/queryFilter/queryFilter.css', 93 | 'src/app/ui/browser/browser.css', 94 | 'src/app/ui/anyRequest/anyRequest.css', 95 | 'src/app/ui/nodesView/nodesView.css', 96 | 'src/app/ui/clusterOverview/clusterOverview.css', 97 | 'src/app/ui/clusterConnect/clusterConnect.css', 98 | 'src/app/ui/structuredQuery/structuredQuery.css', 99 | 'src/app/ui/filterBrowser/filterBrowser.css', 100 | 'src/app/ui/header/header.css', 101 | 'src/app/app.css' 102 | ]; 103 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | elasticsearch-head 7 | 8 | 9 | 10 | 11 | 12 | 13 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elasticsearch-head", 3 | "version": "0.0.0", 4 | "description": "Front end for an elasticsearch cluster", 5 | "main": "_site/index.html", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "start": "grunt server", 11 | "test": "grunt jasmine", 12 | "proxy": "node proxy/index.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/mobz/elasticsearch-head.git" 17 | }, 18 | "author": "", 19 | "license": "Apache2", 20 | "gitHead": "0c2ac0b5723b493e4454baa7398f386ecb829412", 21 | "readmeFilename": "README.textile", 22 | "devDependencies": { 23 | "grunt": "1.0.1", 24 | "grunt-contrib-concat": "1.0.1", 25 | "grunt-contrib-watch": "1.0.0", 26 | "grunt-contrib-connect": "1.0.2", 27 | "grunt-contrib-copy": "1.0.0", 28 | "grunt-contrib-clean": "1.0.0", 29 | "grunt-contrib-jasmine": "1.0.3", 30 | "karma": "1.3.0", 31 | "grunt-karma": "2.0.0", 32 | "http-proxy": "1.16.x" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /plugin-descriptor.properties: -------------------------------------------------------------------------------- 1 | description=head - A web front end for an elastic search cluster 2 | version=master 3 | site=true 4 | name=head 5 | -------------------------------------------------------------------------------- /proxy/clusters/localhost9200.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "localhost:9200", 3 | "enabled": true, 4 | "recipe": "http-proxy", 5 | "bind": 9101, 6 | "settings": { 7 | "target": "http://localhost:9200" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /proxy/clusters/xpack.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xpack-default", 3 | "enabled": false, 4 | "recipe": "http-proxy", 5 | "bind": 9102, 6 | "settings": { 7 | "target": "http://localhost:9200", 8 | "username": "elastic", 9 | "password": "changeme" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /proxy/index.js: -------------------------------------------------------------------------------- 1 | const http = require("http"); 2 | 3 | const CORS_SETTINGS = { 4 | origin: "http://localhost:9100", 5 | methods: "GET, PUT, POST, DELETE, OPTIONS, HEAD", 6 | headers: "Authorization, Content-Type" 7 | } 8 | 9 | 10 | const recipes = {}; 11 | recipes['http-proxy'] = require("./recipes/http_proxy.js"); 12 | 13 | 14 | const clusters = []; 15 | clusters.push( require("./clusters/localhost9200.json") ); 16 | 17 | 18 | clusters.forEach( cluster => { 19 | if( cluster.enabled ) { 20 | console.log( `creating proxy ${cluster.name}` ); 21 | 22 | recipes[ cluster.recipe ]( cluster.settings ) 23 | .then( function( proxy ) { 24 | const server = http.createServer(); 25 | server.on('request', (req, res ) => { 26 | console.log( `${req.method} ${cluster.name} ${req.url}` ); 27 | 28 | res.setHeader("Access-Control-Allow-Origin", CORS_SETTINGS.origin ); 29 | res.setHeader("Access-Control-Allow-Methods", CORS_SETTINGS.methods ); 30 | res.setHeader("Access-Control-Allow-Headers", CORS_SETTINGS.headers ); 31 | 32 | if (req.method === 'OPTIONS') { 33 | res.writeHead(200); 34 | res.end(); 35 | } else { 36 | proxy.request( req, res ); 37 | } 38 | } ); 39 | 40 | server.listen( cluster.bind ); 41 | console.log( `\tlocal: http://localhost:${cluster.bind}` ); 42 | }); 43 | } 44 | }); 45 | 46 | -------------------------------------------------------------------------------- /proxy/recipes/http_proxy.js: -------------------------------------------------------------------------------- 1 | const httpProxy = require("http-proxy"); 2 | 3 | module.exports = function( settings ) { 4 | const proxy = httpProxy.createProxy( { secure: false } ); 5 | 6 | proxy.on('proxyReq', function(proxyReq, req, res, options) { 7 | if( settings.username ) { 8 | proxyReq.setHeader( "Authorization", "Basic " + new Buffer(settings.username + ":" + settings.password).toString("base64") ); 9 | } 10 | }); 11 | 12 | function request( req, res ) { 13 | proxy.web( req, res, { target: settings.target } ); 14 | }; 15 | 16 | function close() { 17 | proxy.close(); 18 | } 19 | 20 | console.log( `\tremote: ${settings.target}` ); 21 | 22 | return new Promise( function( resolve, reject ) { 23 | resolve( { 24 | request: request, 25 | close: close 26 | } ); 27 | } ); 28 | 29 | 30 | }; 31 | -------------------------------------------------------------------------------- /src/app/app.css: -------------------------------------------------------------------------------- 1 | .uiApp-header { 2 | background: #eee; 3 | position: fixed; 4 | width: 100%; 5 | z-index: 9; 6 | } 7 | 8 | .uiApp-header H1 { 9 | margin: -2px 0 -4px 0; 10 | float: left; 11 | padding-right: 25px; 12 | } 13 | 14 | .uiApp-headerMenu { 15 | border-bottom: 1px solid #bbb; 16 | padding: 0px 3px; 17 | height: 22px; 18 | } 19 | 20 | .uiApp-headerMenu .active { 21 | background: white; 22 | border-bottom-color: white; 23 | } 24 | 25 | .uiApp-headerMenuItem { 26 | border: 1px solid #bbb; 27 | padding: 4px 8px 1px ; 28 | margin: 2px 1px 0; 29 | height: 14px; 30 | cursor: pointer; 31 | } 32 | 33 | .uiApp-body { 34 | padding: 51px 0px 0px 0px; 35 | } 36 | 37 | .uiApp-headerNewMenuItem { 38 | color: blue; 39 | } 40 | -------------------------------------------------------------------------------- /src/app/app.js: -------------------------------------------------------------------------------- 1 | (function( app, i18n ) { 2 | 3 | var ui = app.ns("ui"); 4 | var services = app.ns("services"); 5 | 6 | app.App = ui.AbstractWidget.extend({ 7 | defaults: { 8 | base_uri: null 9 | }, 10 | init: function(parent) { 11 | this._super(); 12 | this.prefs = services.Preferences.instance(); 13 | this.base_uri = this.config.base_uri || this.prefs.get("app-base_uri") || "http://localhost:9200"; 14 | if( this.base_uri.charAt( this.base_uri.length - 1 ) !== "/" ) { 15 | // XHR request fails if the URL is not ending with a "/" 16 | this.base_uri += "/"; 17 | } 18 | if( this.config.auth_user ) { 19 | var credentials = window.btoa( this.config.auth_user + ":" + this.config.auth_password ); 20 | $.ajaxSetup({ 21 | headers: { 22 | "Authorization": "Basic " + credentials 23 | } 24 | }); 25 | } 26 | this.cluster = new services.Cluster({ base_uri: this.base_uri }); 27 | this._clusterState = new services.ClusterState({ 28 | cluster: this.cluster 29 | }); 30 | 31 | this._header = new ui.Header({ cluster: this.cluster, clusterState: this._clusterState }); 32 | this.$body = $.joey( this._body_template() ); 33 | this.el = $.joey(this._main_template()); 34 | this.attach( parent ); 35 | this.instances = {}; 36 | this.el.find(".uiApp-headerMenuItem:first").click(); 37 | if( this.config.dashboard ) { 38 | if( this.config.dashboard === "cluster" ) { 39 | var page = this.instances["ClusterOverview"]; 40 | page._refreshButton.set( 5000 ); 41 | } 42 | } 43 | }, 44 | 45 | navigateTo: function( type, config, ev ) { 46 | if( ev.target.classList.contains( "uiApp-headerNewMenuItem" ) ) { 47 | this.showNew( type, config, ev ); 48 | } else { 49 | var ref = type + "0"; 50 | if(! this.instances[ ref ]) { 51 | this.createPage( type, 0, config ); 52 | } 53 | this.show( ref, ev ); 54 | } 55 | }, 56 | 57 | createPage: function( type, id, config ) { 58 | var page = this.instances[ type + id ] = new ui[ type ]( config ); 59 | this.$body.append( page ); 60 | return page; 61 | }, 62 | 63 | show: function( ref, ev ) { 64 | $( ev.target ).closest("DIV.uiApp-headerMenuItem").addClass("active").siblings().removeClass("active"); 65 | for(var p in this.instances) { 66 | this.instances[p][ p === ref ? "show" : "hide" ](); 67 | } 68 | }, 69 | 70 | showNew: function( type, config, jEv ) { 71 | var ref, page, $tab, 72 | type_index = 0; 73 | 74 | while ( ! page ) { 75 | ref = type + ( ++type_index ); 76 | if (! ( ref in this.instances ) ) { 77 | page = this.createPage( type, type_index, config ); 78 | } 79 | } 80 | 81 | // Add the tab and its click handlers 82 | $tab = $.joey({ 83 | tag: "DIV", 84 | cls: "uiApp-headerMenuItem pull-left", 85 | text: i18n.text("Nav." + type ) + " " + type_index, 86 | onclick: function( ev ) { this.show( ref, ev ); }.bind(this), 87 | children: [ 88 | { tag: "A", text: " [-]", onclick: function (ev) { 89 | $tab.remove(); 90 | page.remove(); 91 | delete this.instances[ ref ]; 92 | }.bind(this) } 93 | ] 94 | }); 95 | 96 | $('.uiApp-headerMenu').append( $tab ); 97 | $tab.trigger("click"); 98 | }, 99 | 100 | _openAnyRequest_handler: function(ev) { this.navigateTo("AnyRequest", { cluster: this.cluster }, ev ); }, 101 | _openStructuredQuery_handler: function(ev) { this.navigateTo("StructuredQuery", { cluster: this.cluster }, ev ); }, 102 | _openBrowser_handler: function(ev) { this.navigateTo("Browser", { cluster: this.cluster }, ev ); }, 103 | _openClusterOverview_handler: function(ev) { this.navigateTo("ClusterOverview", { cluster: this.cluster, clusterState: this._clusterState }, ev ); }, 104 | _openIndexOverview_handler: function(ev) { this.navigateTo("IndexOverview", { cluster: this.cluster, clusterState: this._clusterState }, ev ); }, 105 | 106 | _body_template: function() { return ( 107 | { tag: "DIV", id: this.id("body"), cls: "uiApp-body" } 108 | ); }, 109 | 110 | _main_template: function() { 111 | return { tag: "DIV", cls: "uiApp", children: [ 112 | { tag: "DIV", id: this.id("header"), cls: "uiApp-header", children: [ 113 | this._header, 114 | { tag: "DIV", cls: "uiApp-headerMenu", children: [ 115 | { tag: "DIV", cls: "uiApp-headerMenuItem pull-left", text: i18n.text("Nav.Overview"), onclick: this._openClusterOverview_handler }, 116 | { tag: "DIV", cls: "uiApp-headerMenuItem pull-left", text: i18n.text("Nav.Indices"), onclick: this._openIndexOverview_handler }, 117 | { tag: "DIV", cls: "uiApp-headerMenuItem pull-left", text: i18n.text("Nav.Browser"), onclick: this._openBrowser_handler }, 118 | { tag: "DIV", cls: "uiApp-headerMenuItem pull-left", text: i18n.text("Nav.StructuredQuery"), onclick: this._openStructuredQuery_handler, children: [ 119 | { tag: "A", cls: "uiApp-headerNewMenuItem ", text: ' [+]' } 120 | ] }, 121 | { tag: "DIV", cls: "uiApp-headerMenuItem pull-left", text: i18n.text("Nav.AnyRequest"), onclick: this._openAnyRequest_handler, children: [ 122 | { tag: "A", cls: "uiApp-headerNewMenuItem ", text: ' [+]' } 123 | ] }, 124 | ]} 125 | ]}, 126 | this.$body 127 | ]}; 128 | } 129 | 130 | }); 131 | 132 | })( this.app, this.i18n ); 133 | -------------------------------------------------------------------------------- /src/app/base/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobz/elasticsearch-head/2d51fecac2980d350fcd3319fd9fe2999f63c9db/src/app/base/favicon.png -------------------------------------------------------------------------------- /src/app/base/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobz/elasticsearch-head/2d51fecac2980d350fcd3319fd9fe2999f63c9db/src/app/base/loading.gif -------------------------------------------------------------------------------- /src/app/base/reset.css: -------------------------------------------------------------------------------- 1 | BODY { 2 | font-family: Verdana, sans-serif; 3 | font-size: 73%; 4 | padding: 0; 5 | margin: 0; 6 | } 7 | 8 | INPUT, SELECT, TEXTAREA { 9 | border: 1px solid #cecece; 10 | padding: 1px 3px; 11 | background: white; 12 | } 13 | 14 | SELECT { 15 | padding: 0; 16 | } 17 | 18 | .saf SELECT { 19 | margin-top: 0; 20 | margin-bottom: 0; 21 | } 22 | 23 | TEXTAREA, CODE { 24 | font-family: monospace; 25 | font-size: 13px; 26 | } 27 | 28 | BUTTON::-moz-focus-inner { 29 | border: none; 30 | } 31 | 32 | .pull-left { 33 | float: left; 34 | } 35 | 36 | .pull-right { 37 | float: right; 38 | } 39 | 40 | .loading { 41 | background-image: url(loading.gif); 42 | background-repeat: no-repeat; 43 | text-indent: 20px; 44 | } 45 | -------------------------------------------------------------------------------- /src/app/data/boolQuery.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var data = app.ns("data"); 4 | var ux = app.ns("ux"); 5 | 6 | data.BoolQuery = ux.Observable.extend({ 7 | defaults: { 8 | size: 50 // size of pages to return 9 | }, 10 | init: function() { 11 | this._super(); 12 | this.refuid = 0; 13 | this.refmap = {}; 14 | this.search = { 15 | query: { bool: { must: [], must_not: [], should: [] } }, 16 | from: 0, 17 | size: this.config.size, 18 | sort: [], 19 | aggs: {} 20 | }; 21 | this.defaultClause = this.addClause(); 22 | }, 23 | setSize: function(size) { 24 | this.search.size = parseInt( size, 10 ); 25 | }, 26 | setPage: function(page) { 27 | this.search.from = this.config.size * (page - 1) + 1; 28 | }, 29 | addClause: function(value, field, op, bool) { 30 | bool = bool || "should"; 31 | op = op || "match_all"; 32 | field = field || "_all"; 33 | var clause = this._setClause(value, field, op, bool); 34 | var uqid = "q-" + this.refuid++; 35 | this.refmap[uqid] = { clause: clause, value: value, field: field, op: op, bool: bool }; 36 | if(this.search.query.bool.must.length + this.search.query.bool.should.length > 1) { 37 | this.removeClause(this.defaultClause); 38 | } 39 | this.fire("queryChanged", this, { uqid: uqid, search: this.search} ); 40 | return uqid; // returns reference to inner query object to allow fast updating 41 | }, 42 | removeClause: function(uqid) { 43 | var ref = this.refmap[uqid], 44 | bool = this.search.query.bool[ref.bool]; 45 | var clauseIdx = bool.indexOf(ref.clause); 46 | // Check that this clause hasn't already been removed 47 | if (clauseIdx >=0) { 48 | bool.splice(clauseIdx, 1); 49 | } 50 | }, 51 | _setClause: function(value, field, op, bool) { 52 | var clause = {}, query = {}; 53 | if(op === "match_all") { 54 | } else if(op === "query_string") { 55 | query["default_field"] = field.substring(field.indexOf(".")+1); 56 | query["query"] = value; 57 | } else if(op === "missing") { 58 | op = "exists"; 59 | if (bool === "must_not") { 60 | bool = "must" 61 | } else if (bool === "must") { 62 | bool = "must_not" 63 | } 64 | query["field"] = field.substring(field.indexOf(".")+1); 65 | } else { 66 | query[field.substring(field.indexOf(".")+1)] = value; 67 | } 68 | clause[op] = query; 69 | this.search.query.bool[bool].push(clause); 70 | return clause; 71 | }, 72 | getData: function() { 73 | return JSON.stringify(this.search); 74 | } 75 | }); 76 | 77 | })( this.app ); -------------------------------------------------------------------------------- /src/app/data/dataSourceInterface.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var data = app.ns("data"); 4 | var ux = app.ns("ux"); 5 | 6 | data.DataSourceInterface = ux.Observable.extend({ 7 | /* 8 | properties 9 | meta = { total: 0 }, 10 | headers = [ { name: "" } ], 11 | data = [ { column: value, column: value } ], 12 | sort = { column: "name", dir: "desc" } 13 | events 14 | data: function( DataSourceInterface ) 15 | */ 16 | _getSummary: function(res) { 17 | this.summary = i18n.text("TableResults.Summary", res._shards.successful, res._shards.total, (typeof res.hits.total === 'object') ? res.hits.total.value : res.hits.total, (res.took / 1000).toFixed(3)); 18 | }, 19 | _getMeta: function(res) { 20 | this.meta = { total: res.hits.total, shards: res._shards, tool: res.took }; 21 | } 22 | }); 23 | 24 | })( this.app ); -------------------------------------------------------------------------------- /src/app/data/metaData.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | /* 4 | notes on elasticsearch terminology used in this project 5 | 6 | indices[index] contains one or more 7 | types[type] contains one or more 8 | documents contain one or more 9 | paths[path] 10 | each path contains one element of data 11 | each path maps to one field 12 | 13 | eg PUT, "/twitter/tweet/1" 14 | { 15 | user: "mobz", 16 | date: "2011-01-01", 17 | message: "You know, for browsing elasticsearch", 18 | name: { 19 | first: "Ben", 20 | last: "Birch" 21 | } 22 | } 23 | 24 | creates 25 | 1 index: twitter 26 | this is the collection of index data 27 | 1 type: tweet 28 | this is the type of document (kind of like a table in sql) 29 | 1 document: /twitter/tweet/1 30 | this is an actual document in the index ( kind of like a row in sql) 31 | 5 paths: [ ["user"], ["date"], ["message"], ["name","first"], ["name","last"] ] 32 | since documents can be heirarchical this maps a path from a document root to a piece of data 33 | 5 fields: [ "user", "date", "message", "first", "last" ] 34 | this is an indexed 'column' of data. fields are not heirarchical 35 | 36 | the relationship between a path and a field is called a mapping. mappings also contain a wealth of information about how es indexes the field 37 | 38 | notes 39 | 1) a path is stored as an array, the dpath is . . path.join("."), 40 | which can be considered the canonical reference for a mapping 41 | 2) confusingly, es uses the term index for both the collection of indexed data, and the individually indexed fields 42 | so the term index_name is the same as field_name in this sense. 43 | 44 | */ 45 | 46 | var data = app.ns("data"); 47 | var ux = app.ns("ux"); 48 | 49 | var coretype_map = { 50 | "string" : "string", 51 | "keyword" : "string", 52 | "text" : "string", 53 | "byte" : "number", 54 | "short" : "number", 55 | "long" : "number", 56 | "integer" : "number", 57 | "float" : "number", 58 | "double" : "number", 59 | "ip" : "number", 60 | "date" : "date", 61 | "boolean" : "boolean", 62 | "binary" : "binary", 63 | "multi_field" : "multi_field" 64 | }; 65 | 66 | var default_property_map = { 67 | "string" : { "store" : "no", "index" : "analysed" }, 68 | "number" : { "store" : "no", "precision_steps" : 4 }, 69 | "date" : { "store" : "no", "format" : "dateOptionalTime", "index": "yes", "precision_steps": 4 }, 70 | "boolean" : { "store" : "no", "index": "yes" }, 71 | "binary" : { }, 72 | "multi_field" : { } 73 | }; 74 | 75 | // parses metatdata from a cluster, into a bunch of useful data structures 76 | data.MetaData = ux.Observable.extend({ 77 | defaults: { 78 | state: null // (required) response from a /_cluster/state request 79 | }, 80 | init: function() { 81 | this._super(); 82 | this.refresh(this.config.state); 83 | }, 84 | getIndices: function(alias) { 85 | return alias ? this.aliases[alias] : this.indicesList; 86 | }, 87 | // returns an array of strings containing all types that are in all of the indices passed in, or all types 88 | getTypes: function(indices) { 89 | var indices = indices || [], types = []; 90 | this.typesList.forEach(function(type) { 91 | for(var i = 0; i < indices.length; i++) { 92 | if(! this.indices[indices[i]].types.contains(type)) 93 | return; 94 | } 95 | types.push(type); 96 | }, this); 97 | return types; 98 | }, 99 | refresh: function(state) { 100 | // currently metadata expects all like named fields to have the same type, even when from different types and indices 101 | var aliases = this.aliases = {}; 102 | var indices = this.indices = {}; 103 | var types = this.types = {}; 104 | var fields = this.fields = {}; 105 | var paths = this.paths = {}; 106 | 107 | function createField( mapping, index, type, path, name ) { 108 | var dpath = [ index, type ].concat( path ).join( "." ); 109 | var field_name = mapping.index_name || path.join( "." ); 110 | var field = paths[ dpath ] = fields[ field_name ] || $.extend({ 111 | field_name : field_name, 112 | core_type : coretype_map[ mapping.type ], 113 | dpaths : [] 114 | }, default_property_map[ coretype_map[ mapping.type ] ], mapping ); 115 | 116 | if (field.type === "multi_field" && typeof field.fields !== "undefined") { 117 | for (var subField in field.fields) { 118 | field.fields[ subField ] = createField( field.fields[ subField ], index, type, path.concat( subField ), name + "." + subField ); 119 | } 120 | } 121 | if (fields.dpaths) { 122 | field.dpaths.push(dpath); 123 | } 124 | return field; 125 | } 126 | function getFields(properties, type, index, listeners) { 127 | (function procPath(prop, path) { 128 | for (var n in prop) { 129 | if ("properties" in prop[n]) { 130 | procPath( prop[ n ].properties, path.concat( n ) ); 131 | } else { 132 | var field = createField(prop[n], index, type, path.concat(n), n); 133 | listeners.forEach( function( listener ) { 134 | listener[ field.field_name ] = field; 135 | } ); 136 | } 137 | } 138 | })(properties, []); 139 | } 140 | for (var index in state.metadata.indices) { 141 | indices[index] = { 142 | types : [], fields : {}, paths : {}, parents : {} 143 | }; 144 | indices[index].aliases = state.metadata.indices[index].aliases; 145 | indices[index].aliases.forEach(function(alias) { 146 | (aliases[alias] || (aliases[alias] = [])).push(index); 147 | }); 148 | var mapping = state.metadata.indices[index].mappings; 149 | for (var type in mapping) { 150 | indices[index].types.push(type); 151 | if ( type in types) { 152 | types[type].indices.push(index); 153 | } else { 154 | types[type] = { 155 | indices : [index], fields : {} 156 | }; 157 | } 158 | getFields(mapping[type].properties, type, index, [fields, types[type].fields, indices[index].fields]); 159 | if ( typeof mapping[type]._parent !== "undefined") { 160 | indices[index].parents[type] = mapping[type]._parent.type; 161 | } 162 | } 163 | } 164 | 165 | this.aliasesList = Object.keys(aliases); 166 | this.indicesList = Object.keys(indices); 167 | this.typesList = Object.keys(types); 168 | this.fieldsList = Object.keys(fields); 169 | } 170 | }); 171 | 172 | })( this.app ); 173 | -------------------------------------------------------------------------------- /src/app/data/metaDataFactory.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var data = app.ns("data"); 4 | var ux = app.ns("ux"); 5 | 6 | data.MetaDataFactory = ux.Observable.extend({ 7 | defaults: { 8 | cluster: null // (required) an app.services.Cluster 9 | }, 10 | init: function() { 11 | this._super(); 12 | var _cluster = this.config.cluster; 13 | this.config.cluster.get("_cluster/state", function(data) { 14 | this.metaData = new app.data.MetaData({state: data}); 15 | this.fire("ready", this.metaData, { originalData: data, "k": 1 }); // TODO originalData needed for legacy ui.FilterBrowser 16 | }.bind(this), function() { 17 | 18 | var _this = this; 19 | 20 | _cluster.get("_all", function( data ) { 21 | clusterState = {routing_table:{indices:{}}, metadata:{indices:{}}}; 22 | 23 | for(var k in data) { 24 | clusterState["routing_table"]["indices"][k] = {"shards":{"1":[{ 25 | "state":"UNASSIGNED", 26 | "primary":false, 27 | "node":"unknown", 28 | "relocating_node":null, 29 | "shard":'?', 30 | "index":k 31 | }]}}; 32 | 33 | 34 | clusterState["metadata"]["indices"][k] = {}; 35 | clusterState["metadata"]["indices"][k]["mappings"] = data[k]["mappings"]; 36 | clusterState["metadata"]["indices"][k]["aliases"] = $.makeArray(Object.keys(data[k]["aliases"])); 37 | clusterState["metadata"]["indices"][k]["settings"] = data[k]["settings"]; 38 | clusterState["metadata"]["indices"][k]["fields"] = {}; 39 | } 40 | 41 | _this.metaData = new app.data.MetaData({state: clusterState}); 42 | _this.fire("ready", _this.metaData, {originalData: clusterState}); 43 | }); 44 | 45 | }.bind(this)); 46 | } 47 | }); 48 | 49 | })( this.app ); 50 | -------------------------------------------------------------------------------- /src/app/data/model/model.js: -------------------------------------------------------------------------------- 1 | (function( $, app ) { 2 | 3 | var data = app.ns("data"); 4 | var ux = app.ns("ux"); 5 | 6 | data.Model = ux.Observable.extend({ 7 | defaults: { 8 | data: null 9 | }, 10 | init: function() { 11 | this.set( this.config.data ); 12 | }, 13 | set: function( key, value ) { 14 | if( arguments.length === 1 ) { 15 | this._data = $.extend( {}, key ); 16 | } else { 17 | key.split(".").reduce(function( ptr, prop, i, props) { 18 | if(i === (props.length - 1) ) { 19 | ptr[prop] = value; 20 | } else { 21 | if( !(prop in ptr) ) { 22 | ptr[ prop ] = {}; 23 | } 24 | return ptr[prop]; 25 | } 26 | }, this._data ); 27 | } 28 | }, 29 | get: function( key ) { 30 | return key.split(".").reduce( function( ptr, prop ) { 31 | return ( ptr && ( prop in ptr ) ) ? ptr[ prop ] : undefined; 32 | }, this._data ); 33 | }, 34 | }); 35 | })( this.jQuery, this.app ); 36 | -------------------------------------------------------------------------------- /src/app/data/model/modelSpec.js: -------------------------------------------------------------------------------- 1 | describe("app.data.Model", function() { 2 | 3 | var Model = window.app.data.Model; 4 | 5 | it("test setting model does a shallow copy", function() { 6 | var test = {}; 7 | var array = [ 1, 2, 3 ]; 8 | var m = new Model({ 9 | data: { 10 | "foo": "bar", 11 | "test": test, 12 | "array": array 13 | } 14 | }); 15 | expect( m.get("foo") ).toBe("bar"); 16 | expect( m.get("array").length ).toBe( 3 ); 17 | expect( m.get("array")[1] ).toBe( 2 ); 18 | expect( m.get("array") ).toBe( array ); 19 | expect( m.get("test") ).toBe( test ); 20 | }); 21 | 22 | it("should replace model with shallow copy when put with no path", function() { 23 | var m = new Model({ foo: "bar" }); 24 | m.set({ bar: "blat" }); 25 | expect( m.get("foo")).toBe( undefined ); 26 | expect( m.get("bar")).toBe("blat"); 27 | }); 28 | 29 | it("test getting values from deep in a model", function() { 30 | var m = new Model({ 31 | data: { 32 | "foo": { 33 | "bar": { 34 | "baz": { 35 | "quix": "abcdefg" 36 | } 37 | } 38 | } 39 | } 40 | }); 41 | 42 | expect( m.get("foo.bar.baz.quix") ).toBe( "abcdefg" ); 43 | }); 44 | 45 | it("test setting non-existant values creates new values", function() { 46 | var m = new Model({ 47 | data: { 48 | "foo": { 49 | "bar": "abc" 50 | } 51 | } 52 | }); 53 | m.set("foo.bar", "123" ); 54 | m.set("foo.baz", "456" ); 55 | expect( m.get("foo.bar") ).toBe( "123" ); 56 | expect( m.get("foo.baz") ).toBe( "456" ); 57 | }); 58 | 59 | it("test setting values deep in a model", function() { 60 | var m = new Model({ 61 | data: { 62 | "foo": { 63 | "bar": "abc" 64 | } 65 | } 66 | }); 67 | m.set("foo.bar", "123" ); 68 | m.set("foo.baz", "456" ); 69 | m.set("foo.something.else.is.here", "xyz" ); 70 | expect( m.get("foo.something.else.is").here ).toBe( "xyz" ); 71 | expect( m.get("foo.something.else.is.here") ).toBe( "xyz" ); 72 | }); 73 | 74 | }); 75 | -------------------------------------------------------------------------------- /src/app/data/queryDataSourceInterface.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var data = app.ns("data"); 4 | 5 | data.QueryDataSourceInterface = data.DataSourceInterface.extend({ 6 | defaults: { 7 | metadata: null, // (required) instanceof app.data.MetaData, the cluster metadata 8 | query: null // (required) instanceof app.data.Query the data source 9 | }, 10 | init: function() { 11 | this._super(); 12 | this.config.query.on("results", this._results_handler.bind(this) ); 13 | this.config.query.on("resultsWithParents", this._load_parents.bind(this) ); 14 | }, 15 | _results_handler: function(query, res) { 16 | this._getSummary(res); 17 | this._getMeta(res); 18 | var sort = query.search.sort[0] || { "_score": { order: "asc" }}; 19 | var sortField = Object.keys(sort)[0]; 20 | this.sort = { column: sortField, dir: sort[sortField].order }; 21 | this._getData(res, this.config.metadata); 22 | this.fire("data", this); 23 | }, 24 | _load_parents: function(query, res) { 25 | query.loadParents(res, this.config.metadata); 26 | }, 27 | _getData: function(res, metadata) { 28 | var metaColumns = ["_index", "_type", "_id", "_score"]; 29 | var columns = this.columns = [].concat(metaColumns); 30 | 31 | this.data = res.hits.hits.map(function(hit) { 32 | var row = (function(path, spec, row) { 33 | for(var prop in spec) { 34 | if(acx.isObject(spec[prop])) { 35 | arguments.callee(path.concat(prop), spec[prop], row); 36 | } else if(acx.isArray(spec[prop])) { 37 | if(spec[prop].length) { 38 | arguments.callee(path.concat(prop), spec[prop][0], row) 39 | } 40 | } else { 41 | var dpath = path.concat(prop).join("."); 42 | if(metadata.paths[dpath]) { 43 | var field_name = metadata.paths[dpath].field_name; 44 | if(! columns.contains(field_name)) { 45 | columns.push(field_name); 46 | } 47 | row[field_name] = (spec[prop] === null ? "null" : spec[prop] ).toString(); 48 | } else { 49 | // TODO: field not in metadata index 50 | } 51 | } 52 | } 53 | return row; 54 | })([ hit._index, hit._type ], hit._source, {}); 55 | metaColumns.forEach(function(n) { row[n] = hit[n]; }); 56 | row._source = hit; 57 | if (typeof hit._parent!= "undefined") { 58 | (function(prefix, path, spec, row) { 59 | for(var prop in spec) { 60 | if(acx.isObject(spec[prop])) { 61 | arguments.callee(prefix, path.concat(prop), spec[prop], row); 62 | } else if(acx.isArray(spec[prop])) { 63 | if(spec[prop].length) { 64 | arguments.callee(prefix, path.concat(prop), spec[prop][0], row) 65 | } 66 | } else { 67 | var dpath = path.concat(prop).join("."); 68 | if(metadata.paths[dpath]) { 69 | var field_name = metadata.paths[dpath].field_name; 70 | var column_name = prefix+"."+field_name; 71 | if(! columns.contains(column_name)) { 72 | columns.push(column_name); 73 | } 74 | row[column_name] = (spec[prop] === null ? "null" : spec[prop] ).toString(); 75 | } else { 76 | // TODO: field not in metadata index 77 | } 78 | } 79 | } 80 | })(hit._parent._type,[hit._parent._index, hit._parent._type], hit._parent._source, row); 81 | } 82 | return row; 83 | }, this); 84 | } 85 | }); 86 | 87 | })( this.app ); 88 | -------------------------------------------------------------------------------- /src/app/data/resultDataSourceInterface.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var data = app.ns("data"); 4 | 5 | data.ResultDataSourceInterface = data.DataSourceInterface.extend({ 6 | results: function(res) { 7 | this._getSummary(res); 8 | this._getMeta(res); 9 | this._getData(res); 10 | this.sort = {}; 11 | this.fire("data", this); 12 | }, 13 | _getData: function(res) { 14 | var columns = this.columns = []; 15 | this.data = res.hits.hits.map(function(hit) { 16 | var row = (function(path, spec, row) { 17 | for(var prop in spec) { 18 | if(acx.isObject(spec[prop])) { 19 | arguments.callee(path.concat(prop), spec[prop], row); 20 | } else if(acx.isArray(spec[prop])) { 21 | if(spec[prop].length) { 22 | arguments.callee(path.concat(prop), spec[prop][0], row) 23 | } 24 | } else { 25 | var dpath = path.concat(prop).join("."); 26 | if(! columns.contains(dpath)) { 27 | columns.push(dpath); 28 | } 29 | row[dpath] = (spec[prop] || "null").toString(); 30 | } 31 | } 32 | return row; 33 | })([ hit._type ], hit, {}); 34 | row._source = hit; 35 | return row; 36 | }, this); 37 | } 38 | }); 39 | 40 | })( this.app ); 41 | -------------------------------------------------------------------------------- /src/app/lang/zh-TW_strings.js: -------------------------------------------------------------------------------- 1 | i18n.setKeys({ 2 | "General.Elasticsearch": "Elasticsearch", 3 | "General.LoadingAggs": "讀取聚合查詢...", 4 | "General.Searching": "搜尋中...", 5 | "General.Search": "搜尋", 6 | "General.Help": "幫助", 7 | "General.HelpGlyph": "?", 8 | "General.CloseGlyph": "X", 9 | "General.RefreshResults": "更新", 10 | "General.ManualRefresh": "手動更新", 11 | "General.RefreshQuickly": "快速更新", 12 | "General.Refresh5seconds": "每5秒更新", 13 | "General.Refresh1minute": "每1分鐘更新", 14 | "AliasForm.AliasName": "别名", 15 | "AliasForm.NewAliasForIndex": "為 {0} 建立新别名", 16 | "AliasForm.DeleteAliasMessage": "輸入 ''{0}'' 删除 {1}. 此操作無法恢復", 17 | "AnyRequest.DisplayOptions" : "顯示選項", 18 | "AnyRequest.AsGraph" : "圖形視圖", 19 | "AnyRequest.AsJson" : "原始 JSON", 20 | "AnyRequest.AsTable" : "表格視圖", 21 | "AnyRequest.History" : "歷史記錄", 22 | "AnyRequest.RepeatRequest" : "重複請求", 23 | "AnyRequest.RepeatRequestSelect" : "重複周期 ", 24 | "AnyRequest.Transformer" : "結果轉換器", 25 | "AnyRequest.Pretty": "易讀", 26 | "AnyRequest.Query" : "查詢", 27 | "AnyRequest.Request": "送出", 28 | "AnyRequest.Requesting": "請求中...", 29 | "AnyRequest.ValidateJSON": "驗證 JSON", 30 | "Browser.Title": "資料瀏覽", 31 | "Browser.ResultSourcePanelTitle": "原始資料", 32 | "Command.DELETE": "删除", 33 | "Command.SHUTDOWN": "關閉", 34 | "Command.DeleteAliasMessage": "删除别名?", 35 | "ClusterOverView.IndexName": "索引名稱", 36 | "ClusterOverview.NumShards": "分片數", 37 | "ClusterOverview.NumReplicas": "副本數", 38 | "ClusterOverview.NewIndex": "新建索引", 39 | "IndexActionsMenu.Title": "動作", 40 | "IndexActionsMenu.NewAlias": "新建别名...", 41 | "IndexActionsMenu.Refresh": "更新", 42 | "IndexActionsMenu.Flush": "Flush更新", 43 | "IndexActionsMenu.Optimize": "最佳化...", 44 | "IndexActionsMenu.ForceMerge": "強制合併...", 45 | "IndexActionsMenu.Snapshot": "网关快照", 46 | "IndexActionsMenu.Analyser": "測試分析器", 47 | "IndexActionsMenu.Open": "開啟", 48 | "IndexActionsMenu.Close": "關閉", 49 | "IndexActionsMenu.Delete": "删除...", 50 | "IndexInfoMenu.Title": "訊息", 51 | "IndexInfoMenu.Status": "索引狀態", 52 | "IndexInfoMenu.Metadata": "索引訊息", 53 | "IndexCommand.TextToAnalyze": "文本分析", 54 | "IndexCommand.ShutdownMessage": "輸入 ''{0}'' 以關閉 {1} 節點. 關閉的節點無法從此界面重新啟動", 55 | "IndexOverview.PageTitle": "索引總覽", 56 | "IndexSelector.NameWithDocs": "{0} ({1} 個文件)", 57 | "IndexSelector.SearchIndexForDocs": "搜尋 {0} 的文件,查詢條件:", 58 | "FilterBrowser.OutputType": "返回格式: {0}", 59 | "FilterBrowser.OutputSize": "顯示數量: {0}", 60 | "Header.ClusterHealth": "叢集健康值: {0} ({1} of {2})", 61 | "Header.ClusterNotConnected": "叢集健康值: 未連接", 62 | "Header.Connect": "連接", 63 | "Nav.AnyRequest": "複合查詢", 64 | "Nav.Browser": "資料瀏覽", 65 | "Nav.ClusterHealth": "叢集健康值", 66 | "Nav.ClusterState": "群集狀態", 67 | "Nav.ClusterNodes": "叢集節點", 68 | "Nav.Info": "訊息", 69 | "Nav.NodeStats": "節點狀態", 70 | "Nav.Overview": "總覽", 71 | "Nav.Indices": "索引", 72 | "Nav.Plugins": "套件", 73 | "Nav.Status": "狀態", 74 | "Nav.Templates": "樣版", 75 | "Nav.StructuredQuery": "基本查詢", 76 | "NodeActionsMenu.Title": "動作", 77 | "NodeActionsMenu.Shutdown": "關閉節點...", 78 | "NodeInfoMenu.Title": "訊息", 79 | "NodeInfoMenu.ClusterNodeInfo": "叢集節點訊息", 80 | "NodeInfoMenu.NodeStats": "節點狀態", 81 | "NodeType.Client": "節點客户端", 82 | "NodeType.Coord": "協調器", 83 | "NodeType.Master": "主節點", 84 | "NodeType.Tribe": "分支節點", 85 | "NodeType.Worker": "工作節點", 86 | "NodeType.Unassigned": "未分配", 87 | "OptimizeForm.OptimizeIndex": "最佳化 {0}", 88 | "OptimizeForm.MaxSegments": "最大索引段數", 89 | "OptimizeForm.ExpungeDeletes": "只删除被標記為删除的", 90 | "OptimizeForm.FlushAfter": "最佳化後更新", 91 | "OptimizeForm.WaitForMerge": "等待合併", 92 | "ForceMergeForm.ForceMergeIndex": "強制合併 {0}", 93 | "ForceMergeForm.MaxSegments": "最大索引段數", 94 | "ForceMergeForm.ExpungeDeletes": "只删除被標記為删除的", 95 | "ForceMergeForm.FlushAfter": "強制合併後更新", 96 | "Overview.PageTitle" : "叢集總覽", 97 | "Output.JSON": "JSON", 98 | "Output.Table": "Table", 99 | "Output.CSV": "CSV", 100 | "Output.ShowSource": "顯示查詢語句", 101 | "Preference.SortCluster": "叢集排序", 102 | "Sort.ByName": "按名稱", 103 | "Sort.ByAddress": "按地址", 104 | "Sort.ByType": "按類型", 105 | "TableResults.Summary": "查詢 {1} 個分片中用的 {0} 個. {2} 命中. 耗時 {3} 秒", 106 | "QueryFilter.AllIndices": "所有索引", 107 | "QueryFilter.AnyValue": "任意", 108 | "QueryFilter-Header-Indices": "索引", 109 | "QueryFilter-Header-Types": "類型", 110 | "QueryFilter-Header-Fields": "欄位", 111 | "QueryFilter.DateRangeHint.from": "從 : {0}", 112 | "QueryFilter.DateRangeHint.to": " 到 : {0}", 113 | "Query.FailAndUndo": "查詢失敗. 撤銷最近的更改", 114 | "StructuredQuery.ShowRawJson": "顯示原始 JSON" 115 | }); 116 | 117 | i18n.setKeys({ 118 | "AnyRequest.TransformerHelp" : "\ 119 |

結果轉換器用於返回結果原始 JSON 的後續處理, 將結果轉換為更有用的格式.

\ 120 |

轉換器應當包含 JavaScript 函數內容. 函數的返回值將傳遞给 JSON 分析器

\ 121 |

Example:
\ 122 | return root.hits.hits[0];
\ 123 | 遍歷結果並只顯示第一個元素
\ 124 | return Object.keys(root.nodes).reduce(function(tot, node) { return tot + root.nodes[node].os.mem.used_in_bytes; }, 0);
\ 125 | 將返回整個叢集使用的總記憶體

\ 126 |

以下函數可以方便的處理陣列與物件
\ 127 |

    \ 128 |
  • Object.keys(object) := array
  • \ 129 |
  • array.forEach(function(prop, index))
  • \ 130 |
  • array.map(function(prop, index)) := array
  • \ 131 |
  • array.reduce(function(accumulator, prop, index), initial_value) := final_value
  • \ 132 |
\ 133 |

當啟用重複請求時, prev 參數將會傳遞给轉換器函數. 這將用於比較並累加圖形

\ 134 |

Example:
\ 135 | var la = [ root.nodes[Object.keys(root.nodes)[0]].os.load_average[0] ]; return prev ? la.concat(prev) : la;
\ 136 | 將返回第一個叢集節點最近一分鐘内的平均負載\ 137 | 將會把結果送入圖表以產生一個負載曲線圖\ 138 | " 139 | }); 140 | 141 | i18n.setKeys({ 142 | "AnyRequest.DisplayOptionsHelp" : "\ 143 |

原始 JSON: 將完整的查詢結果轉換為原始 JSON 格式

\ 144 |

圖形視圖: 將查詢結果圖形化, 將查詢結果轉換為陣列值的形式

\ 145 |

表格視圖: 如果查詢是一個搜尋, 可以將搜尋結果以表格形式顯示.

\ 146 | " 147 | }); 148 | 149 | i18n.setKeys({ 150 | "QueryFilter.DateRangeHelp" : "\ 151 |

Date 欄位接受日期範圍的形式查詢.

\ 152 |

以下格式被支援:

\ 153 |
    \ 154 |
  • 關鍵詞 / 關鍵短語
    \ 155 | now
    today
    tomorrow
    yesterday
    last / this / next + week / month / year

    \ 156 | 搜尋關鍵字匹配的日期. last year 將搜尋過去全年.
  • \ 157 |
  • 範圍
    \ 158 | 1000 secs
    5mins
    1day
    2days
    80d
    9 months
    2yrs
    (空格可選, 同等於多個範圍修飾詞)
    \ 159 | 建立一個指定時間範圍的搜尋, 將圍繞现在 並延伸至過去與未來時間段.
  • \ 160 |
  • DateTime 與 DateTime局部
    \ 161 | 2011
    2011-01
    2011-01-18
    2011-01-18 12
    2011-01-18 12:32
    2011-01-18 12:32:45

    \ 162 | 指定一個特定的日期範圍. 2011會搜尋整個 2011年, 而 2011-01-18 12:32:45 將只搜尋1秒範圍内
  • \ 163 |
  • Time 與 Time局部
    \ 164 | 12
    12:32
    12:32:45

    \ 165 | 這些格式只搜尋當天的特定時間. 12:32 將搜尋當天的那一分鐘
  • \ 166 |
  • 日期範圍
    \ 167 | 2010 -> 2011
    last week -> next week
    2011-05 ->
    < now

    \ 168 | 日期範圍是將兩個日期格式串 (日期關鍵字 / DateTime / Time) 用 < 或 -> (效果相同) 分隔. 如果缺少任意一端,那麼在這個方向上時間將沒有限制.
  • \ 169 |
  • 偏移日期範圍
    \ 170 | 2010 -> 1yr
    3mins < now
    \ 171 | 搜尋包括指定方向上偏移的日期.
  • \ 172 |
  • 錨定範圍
    \ 173 | 2010-05-13 05:13 <> 10m
    now <> 1yr
    lastweek <> 1month

    \ 174 | 類似於上面的便宜日期,在兩個方向上將錨定的日期延長
  • \ 175 |
\ 176 | " 177 | }); 178 | -------------------------------------------------------------------------------- /src/app/lang/zh_strings.js: -------------------------------------------------------------------------------- 1 | i18n.setKeys({ 2 | "General.Elasticsearch": "Elasticsearch", 3 | "General.LoadingAggs": "加载聚合查询...", 4 | "General.Searching": "搜索中...", 5 | "General.Search": "搜索", 6 | "General.Help": "帮助", 7 | "General.HelpGlyph": "?", 8 | "General.CloseGlyph": "X", 9 | "General.RefreshResults": "刷新", 10 | "General.ManualRefresh": "手动刷新", 11 | "General.RefreshQuickly": "快速刷新", 12 | "General.Refresh5seconds": "每5秒刷新", 13 | "General.Refresh1minute": "每1分钟刷新", 14 | "AliasForm.AliasName": "别名", 15 | "AliasForm.NewAliasForIndex": "为 {0} 创建新别名", 16 | "AliasForm.DeleteAliasMessage": "输入 ''{0}'' 删除 {1}. 此操作无法恢复", 17 | "AnyRequest.DisplayOptions" : "显示选项", 18 | "AnyRequest.AsGraph" : "图形视图", 19 | "AnyRequest.AsJson" : "原始 JSON", 20 | "AnyRequest.AsTable" : "表格视图", 21 | "AnyRequest.History" : "历史记录", 22 | "AnyRequest.RepeatRequest" : "重复请求", 23 | "AnyRequest.RepeatRequestSelect" : "重复周期 ", 24 | "AnyRequest.Transformer" : "结果转换器", 25 | "AnyRequest.Pretty": "易读", 26 | "AnyRequest.Query" : "查询", 27 | "AnyRequest.Request": "提交请求", 28 | "AnyRequest.Requesting": "请求中...", 29 | "AnyRequest.ValidateJSON": "验证 JSON", 30 | "Browser.Title": "数据浏览", 31 | "Browser.ResultSourcePanelTitle": "原始数据", 32 | "Command.DELETE": "删除", 33 | "Command.SHUTDOWN": "关闭", 34 | "Command.DeleteAliasMessage": "删除别名?", 35 | "ClusterOverView.IndexName": "索引名称", 36 | "ClusterOverview.NumShards": "分片数", 37 | "ClusterOverview.NumReplicas": "副本数", 38 | "ClusterOverview.NewIndex": "新建索引", 39 | "IndexActionsMenu.Title": "动作", 40 | "IndexActionsMenu.NewAlias": "新建别名...", 41 | "IndexActionsMenu.Refresh": "刷新", 42 | "IndexActionsMenu.Flush": "Flush刷新", 43 | "IndexActionsMenu.Optimize": "优化...", 44 | "IndexActionsMenu.ForceMerge": "ForceMerge...", 45 | "IndexActionsMenu.Snapshot": "网关快照", 46 | "IndexActionsMenu.Analyser": "测试分析器", 47 | "IndexActionsMenu.Open": "开启", 48 | "IndexActionsMenu.Close": "关闭", 49 | "IndexActionsMenu.Delete": "删除...", 50 | "IndexInfoMenu.Title": "信息", 51 | "IndexInfoMenu.Status": "索引状态", 52 | "IndexInfoMenu.Metadata": "索引信息", 53 | "IndexCommand.TextToAnalyze": "文本分析", 54 | "IndexCommand.ShutdownMessage": "输入 ''{0}'' 以关闭 {1} 节点. 关闭的节点无法从此界面重新启动", 55 | "IndexOverview.PageTitle": "索引概览", 56 | "IndexSelector.NameWithDocs": "{0} ({1} 个文档)", 57 | "IndexSelector.SearchIndexForDocs": "搜索 {0} 的文档, 查询条件:", 58 | "FilterBrowser.OutputType": "返回格式: {0}", 59 | "FilterBrowser.OutputSize": "显示数量: {0}", 60 | "Header.ClusterHealth": "集群健康值: {0} ({1} of {2})", 61 | "Header.ClusterNotConnected": "集群健康值: 未连接", 62 | "Header.Connect": "连接", 63 | "Nav.AnyRequest": "复合查询", 64 | "Nav.Browser": "数据浏览", 65 | "Nav.ClusterHealth": "集群健康值", 66 | "Nav.ClusterState": "群集状态", 67 | "Nav.ClusterNodes": "集群节点", 68 | "Nav.Info": "信息", 69 | "Nav.NodeStats": "节点状态", 70 | "Nav.Overview": "概览", 71 | "Nav.Indices": "索引", 72 | "Nav.Plugins": "插件", 73 | "Nav.Status": "状态", 74 | "Nav.Templates": "模板", 75 | "Nav.StructuredQuery": "基本查询", 76 | "NodeActionsMenu.Title": "动作", 77 | "NodeActionsMenu.Shutdown": "关停...", 78 | "NodeInfoMenu.Title": "信息", 79 | "NodeInfoMenu.ClusterNodeInfo": "集群节点信息", 80 | "NodeInfoMenu.NodeStats": "节点状态", 81 | "NodeType.Client": "节点客户端", 82 | "NodeType.Coord": "协调器", 83 | "NodeType.Master": "主节点", 84 | "NodeType.Tribe": "分支结点", 85 | "NodeType.Worker": "工作节点", 86 | "NodeType.Unassigned": "未分配", 87 | "OptimizeForm.OptimizeIndex": "优化 {0}", 88 | "OptimizeForm.MaxSegments": "最大索引段数", 89 | "OptimizeForm.ExpungeDeletes": "只删除被标记为删除的", 90 | "OptimizeForm.FlushAfter": "优化后刷新", 91 | "OptimizeForm.WaitForMerge": "等待合并", 92 | "ForceMergeForm.ForceMergeIndex": "ForceMerge {0}", 93 | "ForceMergeForm.MaxSegments": "最大索引段数", 94 | "ForceMergeForm.ExpungeDeletes": "只删除被标记为删除的", 95 | "ForceMergeForm.FlushAfter": "ForceMerge后刷新", 96 | "Overview.PageTitle" : "集群概览", 97 | "Output.JSON": "JSON", 98 | "Output.Table": "Table", 99 | "Output.CSV": "CSV", 100 | "Output.ShowSource": "显示查询语句", 101 | "Preference.SortCluster": "集群排序", 102 | "Sort.ByName": "按名称", 103 | "Sort.ByAddress": "按地址", 104 | "Sort.ByType": "按类型", 105 | "TableResults.Summary": "查询 {1} 个分片中用的 {0} 个. {2} 命中. 耗时 {3} 秒", 106 | "QueryFilter.AllIndices": "所有索引", 107 | "QueryFilter.AnyValue": "任意", 108 | "QueryFilter-Header-Indices": "索引", 109 | "QueryFilter-Header-Types": "类型", 110 | "QueryFilter-Header-Fields": "字段", 111 | "QueryFilter.DateRangeHint.from": "从 : {0}", 112 | "QueryFilter.DateRangeHint.to": " 到 : {0}", 113 | "Query.FailAndUndo": "查询失败. 撤消最近的更改", 114 | "StructuredQuery.ShowRawJson": "显示原始 JSON" 115 | }); 116 | 117 | i18n.setKeys({ 118 | "AnyRequest.TransformerHelp" : "\ 119 |

结果转换器用于返回结果原始JSON的后续处理, 将结果转换为更有用的格式.

\ 120 |

转换器应当包含javascript函数体. 函数的返回值将传递给json分析器

\ 121 |

Example:
\ 122 | return root.hits.hits[0];
\ 123 | 遍历结果并只显示第一个元素
\ 124 | return Object.keys(root.nodes).reduce(function(tot, node) { return tot + root.nodes[node].os.mem.used_in_bytes; }, 0);
\ 125 | 将返回整个集群使用的总内存

\ 126 |

以下函数可以方便的处理数组与对象
\ 127 |

    \ 128 |
  • Object.keys(object) := array
  • \ 129 |
  • array.forEach(function(prop, index))
  • \ 130 |
  • array.map(function(prop, index)) := array
  • \ 131 |
  • array.reduce(function(accumulator, prop, index), initial_value) := final_value
  • \ 132 |
\ 133 |

当启用重复请求时, prev 参数将会传递给转换器函数. 这将用于比较并累加图形

\ 134 |

Example:
\ 135 | var la = [ root.nodes[Object.keys(root.nodes)[0]].os.load_average[0] ]; return prev ? la.concat(prev) : la;
\ 136 | 将返回第一个集群节点最近一分钟内的平均负载\ 137 | 将会把结果送人图表以产生一个负载曲线图\ 138 | " 139 | }); 140 | 141 | i18n.setKeys({ 142 | "AnyRequest.DisplayOptionsHelp" : "\ 143 |

原始 Json: 将完整的查询结果转换为原始JSON格式

\ 144 |

图形视图: 将查询结果图形化, 将查询结果转换为数组值的形式

\ 145 |

表格视图: 如果查询是一个搜索, 可以将搜索结果以表格形式显示.

\ 146 | " 147 | }); 148 | 149 | i18n.setKeys({ 150 | "QueryFilter.DateRangeHelp" : "\ 151 |

Date 字段接受日期范围的形式查询.

\ 152 |

一下格式被支持:

\ 153 |
    \ 154 |
  • 关键词 / 关键短语
    \ 155 | now
    today
    tomorrow
    yesterday
    last / this / next + week / month / year

    \ 156 | 搜索关键字匹配的日期. last year 将搜索过去全年.
  • \ 157 |
  • 范围
    \ 158 | 1000 secs
    5mins
    1day
    2days
    80d
    9 months
    2yrs
    (空格可选, 同等于多个范围修饰词)
    \ 159 | 创建一个指定时间范围的搜索, 将围绕现在 并延伸至过去与未来时间段.
  • \ 160 |
  • DateTime 与 DateTime局部
    \ 161 | 2011
    2011-01
    2011-01-18
    2011-01-18 12
    2011-01-18 12:32
    2011-01-18 12:32:45

    \ 162 | 指定一个特定的日期范围. 2011会搜索整个 2011年, 而 2011-01-18 12:32:45 将只搜索1秒范围内
  • \ 163 |
  • Time 与 Time局部
    \ 164 | 12
    12:32
    12:32:45

    \ 165 | 这些格式只搜索当天的特定时间. 12:32 将搜索当天的那一分钟
  • \ 166 |
  • 日期范围
    \ 167 | 2010 -> 2011
    last week -> next week
    2011-05 ->
    < now

    \ 168 | 日期范围是将两个日期格式串 (日期关键字 / DateTime / Time) 用 < 或 -> (效果相同) 分隔. 如果缺少任意一端,那么在这个方向上时间将没有限制.
  • \ 169 |
  • 偏移日期范围
    \ 170 | 2010 -> 1yr
    3mins < now
    \ 171 | 搜索包括指定方向上偏移的日期.
  • \ 172 |
  • 锚定范围
    \ 173 | 2010-05-13 05:13 <> 10m
    now <> 1yr
    lastweek <> 1month

    \ 174 | 类似于上面的便宜日期,在两个方向上将锚定的日期延长
  • \ 175 |
\ 176 | " 177 | }); 178 | -------------------------------------------------------------------------------- /src/app/services/cluster/cluster.js: -------------------------------------------------------------------------------- 1 | (function( $, app ) { 2 | 3 | var services = app.ns("services"); 4 | var ux = app.ns("ux"); 5 | 6 | function parse_version( v ) { 7 | return v.match(/^(\d+)\.(\d+)\.(\d+)/).slice(1,4).map( function(d) { return parseInt(d || 0, 10); } ); 8 | } 9 | 10 | services.Cluster = ux.Class.extend({ 11 | defaults: { 12 | base_uri: null 13 | }, 14 | init: function() { 15 | this.base_uri = this.config.base_uri; 16 | }, 17 | setVersion: function( v ) { 18 | this.version = v; 19 | this._version_parts = parse_version( v ); 20 | }, 21 | versionAtLeast: function( v ) { 22 | var testVersion = parse_version( v ); 23 | for( var i = 0; i < 3; i++ ) { 24 | if( testVersion[i] !== this._version_parts[i] ) { 25 | return testVersion[i] < this._version_parts[i]; 26 | } 27 | } 28 | return true; 29 | }, 30 | request: function( params ) { 31 | return $.ajax( $.extend({ 32 | url: this.base_uri + params.path, 33 | contentType: "application/json", 34 | dataType: "json", 35 | error: function(xhr, type, message) { 36 | if("console" in window) { 37 | console.log({ "XHR Error": type, "message": message }); 38 | } 39 | } 40 | }, params) ); 41 | }, 42 | "get": function(path, success, error) { return this.request( { type: "GET", path: path, success: success, error: error } ); }, 43 | "post": function(path, data, success, error) { return this.request( { type: "POST", path: path, data: data, success: success, error: error } ); }, 44 | "put": function(path, data, success, error) { return this.request( { type: "PUT", path: path, data: data, success: success, error: error } ); }, 45 | "delete": function(path, data, success, error) { return this.request( { type: "DELETE", path: path, data: data, success: success, error: error } ); } 46 | }); 47 | 48 | })( this.jQuery, this.app ); 49 | -------------------------------------------------------------------------------- /src/app/services/cluster/clusterSpec.js: -------------------------------------------------------------------------------- 1 | describe("app.services.Cluster", function() { 2 | 3 | var Cluster = window.app.services.Cluster; 4 | var test = window.test; 5 | 6 | var cluster; 7 | 8 | beforeEach( function() { 9 | cluster = new Cluster({ base_uri: "http://localhost:9200/" }); 10 | }); 11 | 12 | describe( "when it is initialised", function() { 13 | 14 | it("should have a localhost base_uri", function() { 15 | expect( cluster.base_uri ).toBe( "http://localhost:9200/" ); 16 | }); 17 | 18 | it("should have no version", function() { 19 | expect( cluster.version ).toBe( undefined ); 20 | }); 21 | 22 | }); 23 | 24 | describe( "setVersion()", function() { 25 | 26 | it("have a version", function() { 27 | cluster.setVersion( "1.12.3-5" ); 28 | expect( cluster.version ).toBe( "1.12.3-5" ); 29 | }); 30 | 31 | }); 32 | 33 | describe("versionAtLeast()", function() { 34 | var vs = [ "0.0.3", "0.13.5", "0.90.3", "1.0.0", "1.1.0", "1.2.3", "1.12.4.rc2", "13.0.0" ]; 35 | 36 | it("should return true for versions that are less than or equal to the current version", function() { 37 | cluster.setVersion("1.12.5"); 38 | expect( cluster.versionAtLeast("1.12.5" ) ).toBe( true ); 39 | expect( cluster.versionAtLeast("1.12.5rc2" ) ).toBe( true ); 40 | expect( cluster.versionAtLeast("1.12.5-6" ) ).toBe( true ); 41 | expect( cluster.versionAtLeast("1.12.5-6.beta7" ) ).toBe( true ); 42 | expect( cluster.versionAtLeast("1.12.4" ) ).toBe( true ); 43 | expect( cluster.versionAtLeast("0.12.4" ) ).toBe( true ); 44 | expect( cluster.versionAtLeast("1.1.8" ) ).toBe( true ); 45 | 46 | for( var i = 0; i < vs.length - 1; i++ ) { 47 | cluster.setVersion( vs[i+1] ); 48 | expect( cluster.versionAtLeast( vs[i] ) ).toBe( true ); 49 | } 50 | }); 51 | 52 | it("should return false for versions that are greater than the current version", function() { 53 | cluster.setVersion("1.12.5"); 54 | expect( cluster.versionAtLeast("1.12.6" ) ).toBe( false ); 55 | expect( cluster.versionAtLeast("1.13.4" ) ).toBe( false ); 56 | expect( cluster.versionAtLeast("2.0.0" ) ).toBe( false ); 57 | 58 | for( var i = 0; i < vs.length - 1; i++ ) { 59 | cluster.setVersion( vs[i] ); 60 | expect( cluster.versionAtLeast( vs[i+1] ) ).toBe( false ); 61 | } 62 | }); 63 | }); 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /src/app/services/clusterState/clusterState.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var services = app.ns("services"); 4 | var ux = app.ns("ux"); 5 | 6 | services.ClusterState = ux.Observable.extend({ 7 | defaults: { 8 | cluster: null 9 | }, 10 | init: function() { 11 | this._super(); 12 | this.cluster = this.config.cluster; 13 | this.clusterState = null; 14 | this.status = null; 15 | this.nodeStats = null; 16 | this.clusterNodes = null; 17 | }, 18 | refresh: function() { 19 | var self = this, clusterState, status, nodeStats, clusterNodes, clusterHealth; 20 | function updateModel() { 21 | if( clusterState && status && nodeStats && clusterNodes && clusterHealth ) { 22 | this.clusterState = clusterState; 23 | this.status = status; 24 | this.nodeStats = nodeStats; 25 | this.clusterNodes = clusterNodes; 26 | this.clusterHealth = clusterHealth; 27 | this.fire( "data", this ); 28 | } 29 | } 30 | var _cluster = this.cluster; 31 | _cluster.get("_cluster/state", function( data ) { 32 | clusterState = data; 33 | updateModel.call( self ); 34 | },function() { 35 | 36 | _cluster.get("_all", function( data ) { 37 | clusterState = {routing_table:{indices:{}}, metadata:{indices:{}}}; 38 | 39 | for(var k in data) { 40 | clusterState["routing_table"]["indices"][k] = {"shards":{"1":[{ 41 | "state":"UNASSIGNED", 42 | "primary":false, 43 | "node":"unknown", 44 | "relocating_node":null, 45 | "shard":'?', 46 | "index":k 47 | }]}}; 48 | 49 | 50 | clusterState["metadata"]["indices"][k] = {}; 51 | clusterState["metadata"]["indices"][k]["mappings"] = data[k]["mappings"]; 52 | clusterState["metadata"]["indices"][k]["aliases"] = $.makeArray(Object.keys(data[k]["aliases"])); 53 | clusterState["metadata"]["indices"][k]["settings"] = data[k]["settings"]; 54 | } 55 | 56 | updateModel.call( self ); 57 | }); 58 | 59 | }); 60 | this.cluster.get("_stats", function( data ) { 61 | status = data; 62 | updateModel.call( self ); 63 | }); 64 | this.cluster.get("_nodes/stats", function( data ) { 65 | nodeStats = data; 66 | updateModel.call( self ); 67 | }); 68 | this.cluster.get("_nodes", function( data ) { 69 | clusterNodes = data; 70 | updateModel.call( self ); 71 | }); 72 | this.cluster.get("_cluster/health", function( data ) { 73 | clusterHealth = data; 74 | updateModel.call( self ); 75 | }); 76 | }, 77 | _clusterState_handler: function(state) { 78 | this.clusterState = state; 79 | this.redraw("clusterState"); 80 | }, 81 | _status_handler: function(status) { 82 | this.status = status; 83 | this.redraw("status"); 84 | }, 85 | _clusterNodeStats_handler: function(stats) { 86 | this.nodeStats = stats; 87 | this.redraw("nodeStats"); 88 | }, 89 | _clusterNodes_handler: function(nodes) { 90 | this.clusterNodes = nodes; 91 | this.redraw("clusterNodes"); 92 | }, 93 | _clusterHealth_handler: function(health) { 94 | this.clusterHealth = health; 95 | this.redraw("status"); 96 | } 97 | }); 98 | 99 | })( this.app ); 100 | -------------------------------------------------------------------------------- /src/app/services/clusterState/clusterStateSpec.js: -------------------------------------------------------------------------------- 1 | describe("app.services.ClusterState", function() { 2 | 3 | var ClusterState = window.app.services.ClusterState; 4 | var test = window.test; 5 | 6 | var c; 7 | var dummyData = {}; 8 | var dataEventCallback; 9 | 10 | function expectAllDataToBeNull() { 11 | expect( c.clusterState ).toBe( null ); 12 | expect( c.status ).toBe( null ); 13 | expect( c.nodeStats ).toBe( null ); 14 | expect( c.clusterNodes ).toBe( null ); 15 | } 16 | 17 | beforeEach( function() { 18 | test.cb.use(); 19 | dataEventCallback = jasmine.createSpy("onData"); 20 | c = new ClusterState({ 21 | cluster: { 22 | get: test.cb.createSpy("get", 1, [ dummyData ] ) 23 | }, 24 | onData: dataEventCallback 25 | }); 26 | }); 27 | 28 | describe( "when it is initialised", function() { 29 | 30 | it("should have null data", function() { 31 | expectAllDataToBeNull(); 32 | }); 33 | 34 | }); 35 | 36 | describe( "when refresh is called", function() { 37 | 38 | beforeEach( function() { 39 | c.refresh(); 40 | }); 41 | 42 | it("should not not update models until all network requests have completed", function() { 43 | test.cb.execOne(); 44 | expectAllDataToBeNull(); 45 | test.cb.execOne(); 46 | expectAllDataToBeNull(); 47 | test.cb.execOne(); 48 | expectAllDataToBeNull(); 49 | test.cb.execOne(); 50 | expectAllDataToBeNull(); 51 | test.cb.execOne(); 52 | expect( c.clusterState ).toBe( dummyData ); 53 | expect( c.status ).toBe( dummyData ); 54 | expect( c.nodeStats ).toBe( dummyData ); 55 | expect( c.clusterNodes ).toBe( dummyData ); 56 | }); 57 | 58 | it("should fire a 'data' event when all data is ready", function() { 59 | test.cb.execAll(); 60 | expect( dataEventCallback ).toHaveBeenCalledWith( c ); 61 | }); 62 | }); 63 | 64 | }); 65 | -------------------------------------------------------------------------------- /src/app/services/preferences/preferenceSpec.js: -------------------------------------------------------------------------------- 1 | describe("app.services.Preferences", function(){ 2 | 3 | var Preferences = window.app.services.Preferences; 4 | 5 | var prefs; 6 | 7 | beforeEach( function() { 8 | spyOn(window.localStorage, "getItem").and.returnValue( '{"foo":true}' ); 9 | spyOn(window.localStorage, "setItem"); 10 | prefs = Preferences.instance(); 11 | }); 12 | 13 | it("should return a preference from localStorage", function() { 14 | expect( prefs.get("foo") ).toEqual( {foo:true} ); 15 | }); 16 | 17 | it("should set a preference in localStorage", function() { 18 | prefs.set("foo", { foo: false } ); 19 | expect( window.localStorage.setItem ).toHaveBeenCalledWith('foo', '{"foo":false}'); 20 | }); 21 | 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/services/preferences/preferences.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var ux = app.ns("ux"); 4 | var services = app.ns("services"); 5 | 6 | services.Preferences = ux.Singleton.extend({ 7 | init: function() { 8 | this._storage = window.localStorage; 9 | this._setItem("__version", 1 ); 10 | }, 11 | get: function( key ) { 12 | return this._getItem( key ); 13 | }, 14 | set: function( key, val ) { 15 | return this._setItem( key, val ); 16 | }, 17 | _getItem: function( key ) { 18 | try { 19 | return JSON.parse( this._storage.getItem( key ) ); 20 | } catch(e) { 21 | console.warn( e ); 22 | return undefined; 23 | } 24 | }, 25 | _setItem: function( key, val ) { 26 | try { 27 | return this._storage.setItem( key, JSON.stringify( val ) ); 28 | } catch(e) { 29 | console.warn( e ); 30 | return undefined; 31 | } 32 | } 33 | }); 34 | 35 | })( this.app ); 36 | -------------------------------------------------------------------------------- /src/app/ui/abstractField/abstractField.css: -------------------------------------------------------------------------------- 1 | .require { color: #a00; } 2 | -------------------------------------------------------------------------------- /src/app/ui/abstractField/abstractField.js: -------------------------------------------------------------------------------- 1 | (function( $, app, joey ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.AbstractField = ui.AbstractWidget.extend({ 6 | 7 | defaults: { 8 | name : "", // (required) - name of the field 9 | require: false, // validation requirements (false, true, regexp, function) 10 | value: "", // default value 11 | label: "" // human readable label of this field 12 | }, 13 | 14 | init: function(parent) { 15 | this._super(); 16 | this.el = $.joey(this._main_template()); 17 | this.field = this.el.find("[name="+this.config.name+"]"); 18 | this.label = this.config.label; 19 | this.require = this.config.require; 20 | this.name = this.config.name; 21 | this.val( this.config.value ); 22 | this.attach( parent ); 23 | }, 24 | 25 | val: function( val ) { 26 | if(val === undefined) { 27 | return this.field.val(); 28 | } else { 29 | this.field.val( val ); 30 | return this; 31 | } 32 | }, 33 | 34 | validate: function() { 35 | var val = this.val(), req = this.require; 36 | if( req === false ) { 37 | return true; 38 | } else if( req === true ) { 39 | return val.length > 0; 40 | } else if( req.test && $.isFunction(req.test) ) { 41 | return req.test( val ); 42 | } else if( $.isFunction(req) ) { 43 | return req( val, this ); 44 | } 45 | } 46 | 47 | }); 48 | 49 | })( this.jQuery, this.app, this.joey ); 50 | -------------------------------------------------------------------------------- /src/app/ui/abstractPanel/abstractPanel.css: -------------------------------------------------------------------------------- 1 | #uiModal { 2 | background: black; 3 | } 4 | 5 | .uiPanel { 6 | box-shadow: -1px 2.5px 4px -3px black, -1px -2.5px 4px -3px black, 3px 2.5px 4px -3px black, 3px -2.5px 4px -3px black; 7 | position: absolute; 8 | background: #eee; 9 | border: 1px solid #666; 10 | } 11 | 12 | .uiPanel-titleBar { 13 | text-align: center; 14 | font-weight: bold; 15 | padding: 2px 0; 16 | background: rgba(223, 223, 223, 0.75); 17 | background: -moz-linear-gradient(top, rgba(223, 223, 223, 0.75), rgba(193, 193, 193, 0.75), rgba(223, 223, 223, 0.75)); 18 | background: -webkit-linear-gradient(top, rgba(223, 223, 223, 0.75), rgba(193, 193, 193, 0.75), rgba(223, 223, 223, 0.75)); 19 | border-bottom: 1px solid #bbb; 20 | } 21 | 22 | .uiPanel-close { 23 | cursor: pointer; 24 | border: 1px solid #aaa; 25 | background: #fff; 26 | color: #fff; 27 | float: left; 28 | height: 10px; 29 | left: 3px; 30 | line-height: 9px; 31 | padding: 1px 0; 32 | position: relative; 33 | text-shadow: 0 0 1px #000; 34 | top: 0px; 35 | width: 12px; 36 | } 37 | .uiPanel-close:hover { 38 | background: #eee; 39 | } 40 | 41 | .uiPanel-body { 42 | overflow: auto; 43 | } 44 | -------------------------------------------------------------------------------- /src/app/ui/abstractPanel/abstractPanel.js: -------------------------------------------------------------------------------- 1 | (function( $, app ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.AbstractPanel = ui.AbstractWidget.extend({ 6 | defaults: { 7 | body: null, // initial content of the body 8 | modal: true, // create a modal panel - creates a div that blocks interaction with page 9 | height: 'auto', // panel height 10 | width: 400, // panel width (in pixels) 11 | open: false, // show the panel when it is created 12 | parent: 'BODY', // node that panel is attached to 13 | autoRemove: false // remove the panel from the dom and destroy it when the widget is closed 14 | }, 15 | shared: { // shared data for all instances of ui.Panel and decendants 16 | stack: [], // array of all open panels 17 | modal: $( { tag: "DIV", id: "uiModal", css: { opacity: 0.2, position: "absolute", top: "0px", left: "0px" } } ) 18 | }, 19 | init: function() { 20 | this._super(); 21 | }, 22 | open: function( ev ) { 23 | this.el 24 | .css( { visibility: "hidden" } ) 25 | .appendTo( this.config.parent ) 26 | .css( this._getPosition( ev ) ) 27 | .css( { zIndex: (this.shared.stack.length ? (+this.shared.stack[this.shared.stack.length - 1].el.css("zIndex") + 10) : 100) } ) 28 | .css( { visibility: "visible", display: "block" } ); 29 | this.shared.stack.remove(this); 30 | this.shared.stack.push(this); 31 | this._setModal(); 32 | $(document).bind("keyup", this._close_handler ); 33 | this.fire("open", { source: this, event: ev } ); 34 | return this; 35 | }, 36 | close: function() { 37 | var index = this.shared.stack.indexOf(this); 38 | if(index !== -1) { 39 | this.shared.stack.splice(index, 1); 40 | this.el.css( { left: "-2999px" } ); // move the dialog to the left rather than hiding to prevent ie6 rendering artifacts 41 | this._setModal(); 42 | this.fire("close", this ); 43 | if(this.config.autoRemove) { 44 | this.remove(); 45 | } 46 | } 47 | return this; 48 | }, 49 | // close the panel and remove it from the dom, destroying it (you can not reuse the panel after calling remove) 50 | remove: function() { 51 | this.close(); 52 | $(document).unbind("keyup", this._close_handler ); 53 | this._super(); 54 | }, 55 | // starting at the top of the stack, find the first panel that wants a modal and put it just underneath, otherwise remove the modal 56 | _setModal: function() { 57 | for(var stackPtr = this.shared.stack.length - 1; stackPtr >= 0; stackPtr--) { 58 | if(this.shared.stack[stackPtr].config.modal) { 59 | this.shared.modal 60 | .appendTo( document.body ) 61 | .css( { zIndex: this.shared.stack[stackPtr].el.css("zIndex") - 5 } ) 62 | .css( $(document).vSize().asSize() ); 63 | return; 64 | } 65 | } 66 | this.shared.modal.remove(); // no panels that want a modal were found 67 | }, 68 | _getPosition: function() { 69 | return $(window).vSize() // get the current viewport size 70 | .sub(this.el.vSize()) // subtract the size of the panel 71 | .mod(function(s) { return s / 2; }) // divide by 2 (to center it) 72 | .add($(document).vScroll()) // add the current scroll offset 73 | .mod(function(s) { return Math.max(5, s); }) // make sure the panel is not off the edge of the window 74 | .asOffset(); // and return it as a {top, left} object 75 | }, 76 | _close_handler: function( ev ) { 77 | if( ev.type === "keyup" && ev.keyCode !== 27) { return; } // press esc key to close 78 | $(document).unbind("keyup", this._close_handler); 79 | this.close( ev ); 80 | } 81 | }); 82 | 83 | })( this.jQuery, this.app ); 84 | -------------------------------------------------------------------------------- /src/app/ui/abstractWidget/abstractWidget.js: -------------------------------------------------------------------------------- 1 | (function( $, joey, app ) { 2 | 3 | var ui = app.ns("ui"); 4 | var ux = app.ns("ux"); 5 | 6 | ui.AbstractWidget = ux.Observable.extend({ 7 | defaults : { 8 | id: null // the id of the widget 9 | }, 10 | 11 | el: null, // this is the jquery wrapped dom element(s) that is the root of the widget 12 | 13 | init: function() { 14 | this._super(); 15 | for(var prop in this) { // automatically bind all the event handlers 16 | if(prop.contains("_handler")) { 17 | this[prop] = this[prop].bind(this); 18 | } 19 | } 20 | }, 21 | 22 | id: function(suffix) { 23 | return this.config.id ? (this.config.id + (suffix ? "-" + suffix : "")) : undefined; 24 | }, 25 | 26 | attach: function( parent, method ) { 27 | if( parent ) { 28 | this.el[ method || "appendTo"]( parent ); 29 | } 30 | this.fire("attached", this ); 31 | return this; 32 | }, 33 | 34 | remove: function() { 35 | if ( this.el !== null ) { this.el.remove(); } 36 | this.fire("removed", this ); 37 | this.removeAllObservers(); 38 | this.el = null; 39 | return this; 40 | } 41 | }); 42 | 43 | joey.plugins.push( function( obj ) { 44 | if( obj instanceof ui.AbstractWidget ) { 45 | return obj.el[0]; 46 | } 47 | }); 48 | 49 | })( this.jQuery, this.joey, this.app ); 50 | -------------------------------------------------------------------------------- /src/app/ui/anyRequest/anyRequest.css: -------------------------------------------------------------------------------- 1 | .uiAnyRequest-request { 2 | float: left; 3 | width: 350px; 4 | padding: 5px; 5 | background: #d8e7ff; 6 | background: -moz-linear-gradient(left, #d8e7ff, #e8f1ff); 7 | background: -webkit-linear-gradient(left, #d8e7ff, #e8f1ff); 8 | } 9 | 10 | .uiAnyRequest-request INPUT[type=text], 11 | .uiAnyRequest-request TEXTAREA { 12 | width: 340px; 13 | } 14 | 15 | .anyRequest INPUT[name=path] { 16 | width: 259px; 17 | } 18 | 19 | .uiAnyRequest-out { 20 | margin-left: 365px; 21 | } 22 | 23 | .uiAnyRequest-out P { 24 | margin-top: 0; 25 | } 26 | 27 | .uiAnyRequest-jsonErr { 28 | color: red; 29 | } 30 | 31 | .uiAnyRequest-history { 32 | margin: 0; 33 | padding: 0; 34 | list-style: none; 35 | max-height: 100px; 36 | overflow-x: hidden; 37 | overflow-y: auto; 38 | } 39 | -------------------------------------------------------------------------------- /src/app/ui/browser/browser.css: -------------------------------------------------------------------------------- 1 | .uiBrowser-filter { 2 | float: left; 3 | } 4 | 5 | .uiBrowser-table { 6 | margin-left: 365px; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/ui/browser/browser.js: -------------------------------------------------------------------------------- 1 | (function( $, app, i18n ){ 2 | 3 | var ui = app.ns("ui"); 4 | var data = app.ns("data"); 5 | 6 | ui.Browser = ui.Page.extend({ 7 | defaults: { 8 | cluster: null // (required) instanceof app.services.Cluster 9 | }, 10 | init: function() { 11 | this._super(); 12 | this.cluster = this.config.cluster; 13 | this.query = new app.data.Query( { cluster: this.cluster } ); 14 | this._refreshButton = new ui.Button({ 15 | label: i18n.text("General.RefreshResults"), 16 | onclick: function( btn ) { 17 | this.query.query(); 18 | }.bind(this) 19 | }); 20 | this.el = $(this._main_template()); 21 | new data.MetaDataFactory({ 22 | cluster: this.cluster, 23 | onReady: function(metadata) { 24 | this.metadata = metadata; 25 | this.store = new data.QueryDataSourceInterface( { metadata: metadata, query: this.query } ); 26 | this.queryFilter = new ui.QueryFilter({ metadata: metadata, query: this.query }); 27 | this.queryFilter.attach(this.el.find("> .uiBrowser-filter") ); 28 | this.resultTable = new ui.ResultTable( { 29 | onHeaderClick: this._changeSort_handler, 30 | store: this.store 31 | } ); 32 | this.resultTable.attach( this.el.find("> .uiBrowser-table") ); 33 | this.updateResults(); 34 | }.bind(this) 35 | }); 36 | }, 37 | updateResults: function() { 38 | this.query.query(); 39 | }, 40 | _changeSort_handler: function(table, wEv) { 41 | this.query.setSort(wEv.column, wEv.dir === "desc"); 42 | this.query.setPage(1); 43 | this.query.query(); 44 | }, 45 | _main_template: function() { 46 | return { tag: "DIV", cls: "uiBrowser", children: [ 47 | new ui.Toolbar({ 48 | label: i18n.text("Browser.Title"), 49 | left: [ ], 50 | right: [ this._refreshButton ] 51 | }), 52 | { tag: "DIV", cls: "uiBrowser-filter" }, 53 | { tag: "DIV", cls: "uiBrowser-table" } 54 | ] }; 55 | } 56 | }); 57 | 58 | })( this.jQuery, this.app, this.i18n ); 59 | -------------------------------------------------------------------------------- /src/app/ui/button/button.css: -------------------------------------------------------------------------------- 1 | .uiButton { 2 | padding: 0; 3 | border: 0; 4 | margin: 3px; 5 | width: auto; 6 | overflow: visible; 7 | cursor: pointer; 8 | background: transparent; 9 | } 10 | 11 | .uiButton-content { 12 | height: 20px; 13 | border: 1px solid #668dc6; 14 | border-radius: 2px; 15 | background: #96c6eb; 16 | background: -moz-linear-gradient(top, #96c6eb, #5296c7); 17 | background: -webkit-linear-gradient(top, #96c6eb, #5296c7); 18 | color: white; 19 | font-weight: bold; 20 | } 21 | 22 | .moz .uiButton-content { margin: 0 -2px; } 23 | 24 | .uiButton-label { 25 | padding: 2px 6px; 26 | white-space: nowrap; 27 | } 28 | .uiButton:hover .uiButton-content { 29 | background: #2777ba; 30 | background: -moz-linear-gradient(top, #6aaadf, #2777ba); 31 | background: -webkit-linear-gradient(top, #6aaadf, #2777ba); 32 | } 33 | .uiButton.active .uiButton-content, 34 | .uiButton:active .uiButton-content { 35 | background: #2575b7; 36 | background: -moz-linear-gradient(top, #2576b8, #2575b7); 37 | background: -webkit-linear-gradient(top, #2576b8, #2575b7); 38 | } 39 | .uiButton.disabled .uiButton-content, 40 | .uiButton.disabled:active .uiButton-content { 41 | border-color: #c6c6c6; 42 | color: #999999; 43 | background: #ddd; 44 | background: -moz-linear-gradient(top, #ddd, #ddd); 45 | background: -webkit-linear-gradient(top, #ddd, #ddd); 46 | } 47 | 48 | .uiButton.disabled { 49 | cursor: default; 50 | } 51 | -------------------------------------------------------------------------------- /src/app/ui/button/button.js: -------------------------------------------------------------------------------- 1 | (function( $, joey, app ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.Button = ui.AbstractWidget.extend({ 6 | defaults : { 7 | label: "", // the label text 8 | disabled: false, // create a disabled button 9 | autoDisable: false // automatically disable the button when clicked 10 | }, 11 | 12 | _baseCls: "uiButton", 13 | 14 | init: function(parent) { 15 | this._super(); 16 | this.el = $.joey(this.button_template()) 17 | .bind("click", this.click_handler); 18 | this.config.disabled && this.disable(); 19 | this.attach( parent ); 20 | }, 21 | 22 | click_handler: function(jEv) { 23 | if(! this.disabled) { 24 | this.fire("click", jEv, this); 25 | this.config.autoDisable && this.disable(); 26 | } 27 | }, 28 | 29 | enable: function() { 30 | this.el.removeClass("disabled"); 31 | this.disabled = false; 32 | return this; 33 | }, 34 | 35 | disable: function(disable) { 36 | if(disable === false) { 37 | return this.enable(); 38 | } 39 | this.el.addClass("disabled"); 40 | this.disabled = true; 41 | return this; 42 | }, 43 | 44 | button_template: function() { return ( 45 | { tag: 'BUTTON', type: 'button', id: this.id(), cls: this._baseCls, children: [ 46 | { tag: 'DIV', cls: 'uiButton-content', children: [ 47 | { tag: 'DIV', cls: 'uiButton-label', text: this.config.label } 48 | ] } 49 | ] } 50 | ); } 51 | }); 52 | 53 | })( this.jQuery, this.joey, this.app ); 54 | -------------------------------------------------------------------------------- /src/app/ui/button/buttonDemo.js: -------------------------------------------------------------------------------- 1 | $( function() { 2 | 3 | var ui = window.app.ns("ui"); 4 | 5 | window.builder = function() { 6 | return new ui.Button({ label: "Default" }); 7 | } ; 8 | 9 | }); -------------------------------------------------------------------------------- /src/app/ui/checkField/checkField.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.CheckField = ui.AbstractField.extend({ 6 | _main_template: function() { return ( 7 | { tag: "DIV", id: this.id(), cls: "uiCheckField", children: [ 8 | { tag: "INPUT", type: "checkbox", name: this.config.name, checked: !!this.config.value } 9 | ] } 10 | ); }, 11 | validate: function() { 12 | return this.val() || ( ! this.require ); 13 | }, 14 | val: function( val ) { 15 | if( val === undefined ) { 16 | return !!this.field.attr( "checked" ); 17 | } else { 18 | this.field.attr( "checked", !!val ); 19 | } 20 | } 21 | }); 22 | 23 | })( this.app ); 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/app/ui/checkField/checkFieldDemo.js: -------------------------------------------------------------------------------- 1 | $( function() { 2 | 3 | var ui = window.app.ns("ui"); 4 | var ux = window.app.ns("ux"); 5 | var ut = window.app.ns("ut"); 6 | 7 | window.builder = function() { 8 | var form = new ux.FieldCollection({ 9 | fields: [ 10 | new ui.CheckField({ 11 | label: "default", 12 | name: "check_default" 13 | }), 14 | new ui.CheckField({ 15 | label: "checked", 16 | name: "check_true", 17 | value: true 18 | }), 19 | new ui.CheckField({ 20 | label: "unchecked", 21 | name: "check_false", 22 | value: false 23 | }), 24 | new ui.CheckField({ 25 | label: "required", 26 | name: "check_required", 27 | require: true 28 | }) 29 | ] 30 | }); 31 | 32 | return ( 33 | { tag: "DIV", children: form.fields.map( function( field ) { 34 | return { tag: "LABEL", cls: "uiPanelForm-field", children: [ 35 | { tag: "DIV", cls: "uiPanelForm-label", children: [ field.label, ut.require_template(field) ] }, 36 | field 37 | ]}; 38 | }).concat( new ui.Button({ 39 | label: "Evaluate Form", 40 | onclick: function() { console.log( "valid=" + form.validate(), form.getData() ); } 41 | })) } 42 | ); 43 | }; 44 | 45 | }); -------------------------------------------------------------------------------- /src/app/ui/checkField/checkFieldSpec.js: -------------------------------------------------------------------------------- 1 | describe("app.ui.CheckField", function() { 2 | 3 | var CheckField = window.app.ui.CheckField; 4 | 5 | it("should have a label", function() { 6 | expect( ( new CheckField({ label: "foo" }) ).label ).toBe( "foo" ); 7 | }); 8 | 9 | it("should have a name", function() { 10 | expect( ( new CheckField({ name: "foo" }) ).name ).toBe( "foo" ); 11 | }); 12 | 13 | it("should have a val that is false when then field is not checked", function() { 14 | expect( ( new CheckField({ name: "foo", value: false }) ).val() ).toBe( false ); 15 | }); 16 | 17 | it("should have a val that is true when the field is checked", function() { 18 | expect( ( new CheckField({ name: "foo", value: true }) ).val() ).toBe( true ); 19 | }); 20 | 21 | it("should be valid if the field value is true", function() { 22 | expect( ( new CheckField({ name: "foo", value: true }) ).validate() ).toBe( true ); 23 | }); 24 | 25 | it("should be valid if require is false", function() { 26 | expect( ( new CheckField({ name: "foo", require: false, value: true }) ).validate() ).toBe( true ); 27 | expect( ( new CheckField({ name: "foo", require: false, value: false }) ).validate() ).toBe( true ); 28 | }); 29 | 30 | it("should be invalid if require is true and value is false", function() { 31 | expect( ( new CheckField({ name: "foo", require: true, value: false }) ).validate() ).toBe( false ); 32 | }); 33 | 34 | }); -------------------------------------------------------------------------------- /src/app/ui/clusterConnect/clusterConnect.css: -------------------------------------------------------------------------------- 1 | .uiClusterConnect-uri { 2 | width: 280px; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/ui/clusterConnect/clusterConnect.js: -------------------------------------------------------------------------------- 1 | (function( $, app, i18n ) { 2 | 3 | var ui = app.ns("ui"); 4 | var services = app.ns("services"); 5 | 6 | ui.ClusterConnect = ui.AbstractWidget.extend({ 7 | defaults: { 8 | cluster: null 9 | }, 10 | init: function() { 11 | this._super(); 12 | this.prefs = services.Preferences.instance(); 13 | this.cluster = this.config.cluster; 14 | this.el = $.joey(this._main_template()); 15 | this.cluster.get( "", this._node_handler ); 16 | }, 17 | 18 | _node_handler: function(data) { 19 | if(data) { 20 | this.prefs.set("app-base_uri", this.cluster.base_uri); 21 | if(data.version && data.version.number) 22 | this.cluster.setVersion(data.version.number); 23 | } 24 | }, 25 | 26 | _reconnect_handler: function() { 27 | var base_uri = this.el.find(".uiClusterConnect-uri").val(); 28 | var url; 29 | if(base_uri.indexOf("?") !== -1) { 30 | url = base_uri.substring(0, base_uri.indexOf("?")-1); 31 | } else { 32 | url = base_uri; 33 | } 34 | var argstr = base_uri.substring(base_uri.indexOf("?")+1, base_uri.length); 35 | var args = argstr.split("&").reduce(function(r, p) { 36 | r[decodeURIComponent(p.split("=")[0])] = decodeURIComponent(p.split("=")[1]); 37 | return r; 38 | }, {}); 39 | $("body").empty().append(new app.App("body", { id: "es", 40 | base_uri: url, 41 | auth_user : args["auth_user"] || "", 42 | auth_password : args["auth_password"] || "" 43 | })); 44 | }, 45 | 46 | _main_template: function() { 47 | return { tag: "SPAN", cls: "uiClusterConnect", children: [ 48 | { tag: "INPUT", type: "text", cls: "uiClusterConnect-uri", onkeyup: function( ev ) { 49 | if(ev.which === 13) { 50 | ev.preventDefault(); 51 | this._reconnect_handler(); 52 | } 53 | }.bind(this), id: this.id("baseUri"), value: this.cluster.base_uri }, 54 | { tag: "BUTTON", type: "button", text: i18n.text("Header.Connect"), onclick: this._reconnect_handler } 55 | ]}; 56 | } 57 | }); 58 | 59 | })( this.jQuery, this.app, this.i18n ); 60 | 61 | -------------------------------------------------------------------------------- /src/app/ui/clusterConnect/clusterConnectSpec.js: -------------------------------------------------------------------------------- 1 | describe("clusterConnect", function() { 2 | 3 | var ClusterConnect = window.app.ui.ClusterConnect; 4 | 5 | describe("when created", function() { 6 | 7 | var prefs, success_callback, cluster, clusterConnect; 8 | 9 | beforeEach( function() { 10 | prefs = { 11 | set: jasmine.createSpy("set") 12 | }; 13 | spyOn( window.app.services.Preferences, "instance" ).and.callFake( function() { 14 | return prefs; 15 | }); 16 | cluster = { 17 | get: jasmine.createSpy("get").and.callFake( function(uri, success) { 18 | success_callback = success; 19 | }) 20 | }; 21 | clusterConnect = new ClusterConnect({ 22 | base_uri: "http://localhost:9200", 23 | cluster: cluster 24 | }); 25 | }); 26 | 27 | it("should test the connection to the cluster", function() { 28 | expect( cluster.get ).toHaveBeenCalled(); 29 | }); 30 | 31 | it("should store successful connection in preferences", function() { 32 | success_callback("fakePayload"); 33 | expect( prefs.set ).toHaveBeenCalled(); 34 | }); 35 | 36 | }); 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /src/app/ui/clusterOverview/clusterOverview.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobz/elasticsearch-head/2d51fecac2980d350fcd3319fd9fe2999f63c9db/src/app/ui/clusterOverview/clusterOverview.css -------------------------------------------------------------------------------- /src/app/ui/csvTable/csvTable.js: -------------------------------------------------------------------------------- 1 | ( function( $, app, joey ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | var CELL_SEPARATOR = ","; 6 | var CELL_QUOTE = '"'; 7 | var LINE_SEPARATOR = "\r\n"; 8 | 9 | ui.CSVTable = ui.AbstractWidget.extend({ 10 | defaults: { 11 | results: null 12 | }, 13 | _baseCls: "uiCSVTable", 14 | init: function( parent ) { 15 | this._super(); 16 | var results = this.config.results.hits.hits; 17 | var columns = this._parseResults( results ); 18 | this._downloadButton = new ui.Button({ 19 | label: "Generate Download Link", 20 | onclick: this._downloadLinkGenerator_handler 21 | }); 22 | this._downloadLink = $.joey( { tag: "A", text: "download", }); 23 | this._downloadLink.hide(); 24 | this._csvText = this._csv_template( columns, results ); 25 | this.el = $.joey( this._main_template() ); 26 | this.attach( parent ); 27 | }, 28 | _downloadLinkGenerator_handler: function() { 29 | var csvData = new Blob( [ this._csvText ], { type: 'text/csv' }); 30 | var csvURL = URL.createObjectURL( csvData ); 31 | this._downloadLink.attr( "href", csvURL ); 32 | this._downloadLink.show(); 33 | }, 34 | _parseResults: function( results ) { 35 | var columnPaths = {}; 36 | (function parse( path, obj ) { 37 | if( obj instanceof Array ) { 38 | for( var i = 0; i < obj.length; i++ ) { 39 | parse( path, obj[i] ); 40 | } 41 | } else if( typeof obj === "object" ) { 42 | for( var prop in obj ) { 43 | parse( path + "." + prop, obj[ prop ] ); 44 | } 45 | } else { 46 | columnPaths[ path ] = true; 47 | } 48 | })( "root", results ); 49 | var columns = []; 50 | for( var column in columnPaths ) { 51 | columns.push( column.split(".").slice(1) ); 52 | } 53 | return columns; 54 | }, 55 | _main_template: function() { return ( 56 | { tag: "DIV", cls: this._baseCls, id: this.id(), children: [ 57 | this._downloadButton, 58 | this._downloadLink, 59 | { tag: "PRE", text: this._csvText } 60 | ] } 61 | ); }, 62 | _csv_template: function( columns, results ) { 63 | return this._header_template( columns ) + LINE_SEPARATOR + this._results_template( columns, results ); 64 | }, 65 | _header_template: function( columns ) { 66 | return columns.map( function( column ) { 67 | return column.join("."); 68 | }).join( CELL_SEPARATOR ); 69 | }, 70 | _results_template: function( columns, results ) { 71 | return results.map( function( result ) { 72 | return columns.map( function( column ) { 73 | var l = 0, 74 | ptr = result; 75 | while( l !== column.length && ptr != null ) { 76 | ptr = ptr[ column[ l++ ] ]; 77 | } 78 | return ( ptr == null ) ? "" : ( CELL_QUOTE + ptr.toString().replace(/"/g, '""') + CELL_QUOTE ); 79 | }).join( CELL_SEPARATOR ); 80 | }).join( LINE_SEPARATOR ); 81 | } 82 | }); 83 | 84 | })( this.jQuery, this.app, this.joey ); 85 | -------------------------------------------------------------------------------- /src/app/ui/dateHistogram/dateHistogram.js: -------------------------------------------------------------------------------- 1 | (function( app, i18n, raphael ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.DateHistogram = ui.AbstractWidget.extend({ 6 | defaults: { 7 | printEl: null, // (optional) if supplied, clicking on elements in the histogram changes the query 8 | cluster: null, // (required) 9 | query: null, // (required) the current query 10 | spec: null // (required) // date field spec 11 | }, 12 | init: function() { 13 | this._super(); 14 | this.el = $(this._main_template()); 15 | this.query = this.config.query.clone(); 16 | // check if the index/types have changed and rebuild the histogram 17 | this.config.query.on("results", function(query) { 18 | if(this.queryChanged) { 19 | this.buildHistogram(query); 20 | this.queryChanged = false; 21 | } 22 | }.bind(this)); 23 | this.config.query.on("setIndex", function(query, params) { 24 | this.query.setIndex(params.index, params.add); 25 | this.queryChanged = true; 26 | }.bind(this)); 27 | this.config.query.on("setType", function(query, params) { 28 | this.query.setType(params.type, params.add); 29 | this.queryChanged = true; 30 | }.bind(this)); 31 | this.query.search.size = 0; 32 | this.query.on("results", this._stat_handler); 33 | this.query.on("results", this._aggs_handler); 34 | this.buildHistogram(); 35 | }, 36 | buildHistogram: function(query) { 37 | this.statAggs = this.query.addAggs({ 38 | stats: { field: this.config.spec.field_name } 39 | }); 40 | this.query.query(); 41 | this.query.removeAggs(this.statAggs); 42 | }, 43 | _stat_handler: function(query, results) { 44 | if(! results.aggregations[this.statAggs]) { return; } 45 | this.stats = results.aggregations[this.statAggs]; 46 | // here we are calculating the approximate range that will give us less than 121 columns 47 | var rangeNames = [ "year", "year", "month", "day", "hour", "minute" ]; 48 | var rangeFactors = [100000, 12, 30, 24, 60, 60000 ]; 49 | this.intervalRange = 1; 50 | var range = this.stats.max - this.stats.min; 51 | do { 52 | this.intervalName = rangeNames.pop(); 53 | var factor = rangeFactors.pop(); 54 | this.intervalRange *= factor; 55 | range = range / factor; 56 | } while(range > 70); 57 | this.dateAggs = this.query.addAggs({ 58 | date_histogram : { 59 | field: this.config.spec.field_name, 60 | interval: this.intervalName 61 | } 62 | }); 63 | this.query.query(); 64 | this.query.removeAggs(this.dateAggs); 65 | }, 66 | _aggs_handler: function(query, results) { 67 | if(! results.aggregations[this.dateAggs]) { return; } 68 | var buckets = [], range = this.intervalRange; 69 | var min = Math.floor(this.stats.min / range) * range; 70 | var prec = [ "year", "month", "day", "hour", "minute", "second" ].indexOf(this.intervalName); 71 | results.aggregations[this.dateAggs].buckets.forEach(function(entry) { 72 | buckets[parseInt((entry.key - min) / range , 10)] = entry.doc_count; 73 | }, this); 74 | for(var i = 0; i < buckets.length; i++) { 75 | buckets[i] = buckets[i] || 0; 76 | } 77 | this.el.removeClass("loading"); 78 | var el = this.el.empty(); 79 | var w = el.width(), h = el.height(); 80 | var r = raphael(el[0], w, h ); 81 | var printEl = this.config.printEl; 82 | query = this.config.query; 83 | r.g.barchart(0, 0, w, h, [buckets], { gutter: "0", vgutter: 0 }).hover( 84 | function() { 85 | this.flag = r.g.popup(this.bar.x, h - 5, this.value || "0").insertBefore(this); 86 | }, function() { 87 | this.flag.animate({opacity: 0}, 200, ">", function () {this.remove();}); 88 | } 89 | ).click(function() { 90 | if(printEl) { 91 | printEl.val(window.dateRangeParser.print(min + this.bar.index * range, prec)); 92 | printEl.trigger("keyup"); 93 | query.query(); 94 | } 95 | }); 96 | }, 97 | _main_template: function() { return ( 98 | { tag: "DIV", cls: "uiDateHistogram loading", css: { height: "50px" }, children: [ 99 | i18n.text("General.LoadingAggs") 100 | ] } 101 | ); } 102 | }); 103 | 104 | })( this.app, this.i18n, this.Raphael ); -------------------------------------------------------------------------------- /src/app/ui/dialogPanel/dialogPanel.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.DialogPanel = ui.DraggablePanel.extend({ 6 | _commit_handler: function(jEv) { 7 | this.fire("commit", this, { jEv: jEv }); 8 | }, 9 | _main_template: function() { 10 | var t = this._super(); 11 | t.children.push(this._actionsBar_template()); 12 | return t; 13 | }, 14 | _actionsBar_template: function() { 15 | return { tag: "DIV", cls: "pull-right", children: [ 16 | new app.ui.Button({ label: "Cancel", onclick: this._close_handler }), 17 | new app.ui.Button({ label: "OK", onclick: this._commit_handler }) 18 | ]}; 19 | } 20 | }); 21 | 22 | })( this.app ); 23 | -------------------------------------------------------------------------------- /src/app/ui/draggablePanel/draggablePanel.js: -------------------------------------------------------------------------------- 1 | (function( $, app ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.DraggablePanel = ui.AbstractPanel.extend({ 6 | defaults: { 7 | // title: "" // (required) text for the panel title 8 | }, 9 | 10 | _baseCls: "uiPanel", 11 | 12 | init: function() { 13 | this._super(); 14 | this.body = $(this._body_template()); 15 | this.title = $(this._title_template()); 16 | this.el = $.joey( this._main_template() ); 17 | this.el.css( { width: this.config.width } ); 18 | this.dd = new app.ux.DragDrop({ 19 | pickupSelector: this.el.find(".uiPanel-titleBar"), 20 | dragObj: this.el 21 | }); 22 | // open the panel if set in configuration 23 | this.config.open && this.open(); 24 | }, 25 | 26 | setBody: function(body) { 27 | this.body.empty().append(body); 28 | }, 29 | _body_template: function() { return { tag: "DIV", cls: "uiPanel-body", css: { height: this.config.height + (this.config.height === 'auto' ? "" : "px" ) }, children: [ this.config.body ] }; }, 30 | _title_template: function() { return { tag: "SPAN", cls: "uiPanel-title", text: this.config.title }; }, 31 | _main_template: function() { return ( 32 | { tag: "DIV", id: this.id(), cls: this._baseCls, children: [ 33 | { tag: "DIV", cls: "uiPanel-titleBar", children: [ 34 | { tag: "DIV", cls: "uiPanel-close", onclick: this._close_handler, text: "x" }, 35 | this.title 36 | ]}, 37 | this.body 38 | ] } 39 | ); } 40 | }); 41 | 42 | })( this.jQuery, this.app ); 43 | -------------------------------------------------------------------------------- /src/app/ui/filterBrowser/filterBrowser.css: -------------------------------------------------------------------------------- 1 | .uiFilterBrowser-row * { 2 | margin-right: 0.4em; 3 | } 4 | 5 | .uiFilterBrowser-row BUTTON { 6 | height: 22px; 7 | position: relative; 8 | top: 1px; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/ui/header/header.css: -------------------------------------------------------------------------------- 1 | .uiHeader { 2 | padding: 3px 10px; 3 | } 4 | 5 | .uiHeader-name, .uiHeader-status { 6 | font-size: 1.2em; 7 | font-weight: bold; 8 | padding: 0 10px; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/app/ui/header/header.js: -------------------------------------------------------------------------------- 1 | (function( $, app, i18n ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.Header = ui.AbstractWidget.extend({ 6 | defaults: { 7 | cluster: null, 8 | clusterState: null 9 | }, 10 | _baseCls: "uiHeader", 11 | init: function() { 12 | this._clusterConnect = new ui.ClusterConnect({ 13 | cluster: this.config.cluster 14 | }); 15 | var quicks = [ 16 | { text: i18n.text("Nav.Info"), path: "" }, 17 | { text: i18n.text("Nav.Status"), path: "_stats" }, 18 | { text: i18n.text("Nav.NodeStats"), path: "_nodes/stats" }, 19 | { text: i18n.text("Nav.ClusterNodes"), path: "_nodes" }, 20 | { text: i18n.text("Nav.Plugins"), path: "_nodes/plugins" }, 21 | { text: i18n.text("Nav.ClusterState"), path: "_cluster/state" }, 22 | { text: i18n.text("Nav.ClusterHealth"), path: "_cluster/health" }, 23 | { text: i18n.text("Nav.Templates"), path: "_template" } 24 | ]; 25 | var cluster = this.config.cluster; 26 | var quickPanels = {}; 27 | var menuItems = quicks.map( function( item ) { 28 | return { text: item.text, onclick: function() { 29 | cluster.get( item.path, function( data ) { 30 | quickPanels[ item.path ] && quickPanels[ item.path ].el && quickPanels[ item.path ].remove(); 31 | quickPanels[ item.path ] = new ui.JsonPanel({ 32 | title: item.text, 33 | json: data 34 | }); 35 | } ); 36 | } }; 37 | }, this ); 38 | this._quickMenu = new ui.MenuButton({ 39 | label: i18n.text("NodeInfoMenu.Title"), 40 | menu: new ui.MenuPanel({ 41 | items: menuItems 42 | }) 43 | }); 44 | this.el = $.joey( this._main_template() ); 45 | this.nameEl = this.el.find(".uiHeader-name"); 46 | this.statEl = this.el.find(".uiHeader-status"); 47 | this._clusterState = this.config.clusterState; 48 | this._clusterState.on("data", function( state ) { 49 | var shards = state.status._shards; 50 | var colour = state.clusterHealth.status; 51 | var name = state.clusterState.cluster_name; 52 | this.nameEl.text( name ); 53 | this.statEl 54 | .text( i18n.text("Header.ClusterHealth", colour, shards.successful, shards.total ) ) 55 | .css( "background", colour ); 56 | }.bind(this)); 57 | this.statEl.text( i18n.text("Header.ClusterNotConnected") ).css("background", "grey"); 58 | this._clusterState.refresh(); 59 | }, 60 | _main_template: function() { return ( 61 | { tag: "DIV", cls: this._baseCls, children: [ 62 | this._clusterConnect, 63 | { tag: "SPAN", cls: "uiHeader-name" }, 64 | { tag: "SPAN", cls: "uiHeader-status" }, 65 | { tag: "H1", text: i18n.text("General.Elasticsearch") }, 66 | { tag: "SPAN", cls: "pull-right", children: [ 67 | this._quickMenu 68 | ] } 69 | ] } 70 | ); } 71 | } ); 72 | 73 | })( this.jQuery, this.app, this.i18n ); 74 | -------------------------------------------------------------------------------- /src/app/ui/helpPanel/helpPanel.js: -------------------------------------------------------------------------------- 1 | (function( app ){ 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.HelpPanel = ui.InfoPanel.extend({ 6 | defaults: { 7 | ref: "", 8 | open: true, 9 | autoRemove: true, 10 | modal: false, 11 | width: 500, 12 | height: 450, 13 | title: i18n.text("General.Help") 14 | }, 15 | init: function() { 16 | this._super(); 17 | this.body.append(i18n.text(this.config.ref)); 18 | } 19 | }); 20 | 21 | })( this.app ); 22 | -------------------------------------------------------------------------------- /src/app/ui/indexOverview/indexOverview.js: -------------------------------------------------------------------------------- 1 | (function( $, app, i18n ) { 2 | 3 | var ui = app.ns("ui"); 4 | var ut = app.ns("ut"); 5 | 6 | ui.IndexOverview = ui.Page.extend({ 7 | defaults: { 8 | cluster: null 9 | }, 10 | init: function() { 11 | this._super(); 12 | this.cluster = this.config.cluster; 13 | this._clusterState = this.config.clusterState; 14 | this._clusterState.on("data", this._refresh_handler ); 15 | this.el = $(this._main_template()); 16 | this._refresh_handler(); 17 | }, 18 | remove: function() { 19 | this._clusterState.removeObserver( "data", this._refresh_handler ); 20 | }, 21 | _refresh_handler: function() { 22 | var state = this._clusterState; 23 | var view = { 24 | indices: acx.eachMap( state.status.indices, function( name, index ) { 25 | return { 26 | name: name, 27 | state: index 28 | }; 29 | }).sort( function( a, b ) { 30 | return a.name < b.name ? -1 : 1; 31 | }) 32 | }; 33 | this._indexViewEl && this._indexViewEl.remove(); 34 | this._indexViewEl = $( this._indexTable_template( view ) ); 35 | this.el.find(".uiIndexOverview-table").append( this._indexViewEl ); 36 | }, 37 | _newIndex_handler: function() { 38 | var fields = new app.ux.FieldCollection({ 39 | fields: [ 40 | new ui.TextField({ label: i18n.text("ClusterOverView.IndexName"), name: "_name", require: true }), 41 | new ui.TextField({ 42 | label: i18n.text("ClusterOverview.NumShards"), 43 | name: "number_of_shards", 44 | value: "5", 45 | require: function( val ) { return parseInt( val, 10 ) >= 1; } 46 | }), 47 | new ui.TextField({ 48 | label: i18n.text("ClusterOverview.NumReplicas"), 49 | name: "number_of_replicas", 50 | value: "1", 51 | require: function( val ) { return parseInt( val, 10 ) >= 0; } 52 | }) 53 | ] 54 | }); 55 | var dialog = new ui.DialogPanel({ 56 | title: i18n.text("ClusterOverview.NewIndex"), 57 | body: new ui.PanelForm({ fields: fields }), 58 | onCommit: function(panel, args) { 59 | if(fields.validate()) { 60 | var data = fields.getData(); 61 | var name = data["_name"]; 62 | delete data["_name"]; 63 | this.config.cluster.put( encodeURIComponent( name ), JSON.stringify({ settings: { index: data } }), function(d) { 64 | dialog.close(); 65 | alert(JSON.stringify(d)); 66 | this._clusterState.refresh(); 67 | }.bind(this) ); 68 | } 69 | }.bind(this) 70 | }).open(); 71 | }, 72 | _indexTable_template: function( view ) { return ( 73 | { tag: "TABLE", cls: "table", children: [ 74 | { tag: "THEAD", children: [ 75 | { tag: "TR", children: [ 76 | { tag: "TH" }, 77 | { tag: "TH", children: [ 78 | { tag: "H3", text: "Size" } 79 | ] }, 80 | { tag: "TH", children: [ 81 | { tag: "H3", text: "Docs" } 82 | ] } 83 | ] } 84 | ] }, 85 | { tag: "TBODY", cls: "striped", children: view.indices.map( this._index_template, this ) } 86 | ] } 87 | ); }, 88 | 89 | _index_template: function( index ) { return ( 90 | { tag: "TR", children: [ 91 | { tag: "TD", children: [ 92 | { tag: "H3", text: index.name } 93 | ] }, 94 | { tag: "TD", text: ut.byteSize_template( index.state.primaries.store.size_in_bytes ) + "/" + ut.byteSize_template( index.state.total.store.size_in_bytes ) }, 95 | { tag: "TD", text: ut.count_template( index.state.primaries.docs.count ) } 96 | ] } 97 | ); }, 98 | _main_template: function() { 99 | return { tag: "DIV", id: this.id(), cls: "uiIndexOverview", children: [ 100 | new ui.Toolbar({ 101 | label: i18n.text("IndexOverview.PageTitle"), 102 | left: [ 103 | new ui.Button({ 104 | label: i18n.text("ClusterOverview.NewIndex"), 105 | onclick: this._newIndex_handler 106 | }), 107 | ] 108 | }), 109 | { tag: "DIV", cls: "uiIndexOverview-table", children: this._indexViewEl } 110 | ] }; 111 | } 112 | 113 | }); 114 | 115 | })( this.jQuery, this.app, this.i18n ); 116 | -------------------------------------------------------------------------------- /src/app/ui/indexSelector/indexSelector.js: -------------------------------------------------------------------------------- 1 | (function( $, app, i18n ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.IndexSelector = ui.AbstractWidget.extend({ 6 | init: function(parent) { 7 | this._super(); 8 | this.el = $(this._main_template()); 9 | this.attach( parent ); 10 | this.cluster = this.config.cluster; 11 | this.update(); 12 | }, 13 | update: function() { 14 | this.cluster.get( "_stats", this._update_handler ); 15 | }, 16 | 17 | _update_handler: function(data) { 18 | var options = []; 19 | var index_names = Object.keys(data.indices).sort(); 20 | for(var i=0; i < index_names.length; i++) { 21 | name = index_names[i]; 22 | options.push(this._option_template(name, data.indices[name])); 23 | } 24 | this.el.find(".uiIndexSelector-select").empty().append(this._select_template(options)); 25 | this._indexChanged_handler(); 26 | }, 27 | 28 | _main_template: function() { 29 | return { tag: "DIV", cls: "uiIndexSelector", children: i18n.complex( "IndexSelector.SearchIndexForDocs", { tag: "SPAN", cls: "uiIndexSelector-select" } ) }; 30 | }, 31 | 32 | _indexChanged_handler: function() { 33 | this.fire("indexChanged", this.el.find("SELECT").val()); 34 | }, 35 | 36 | _select_template: function(options) { 37 | return { tag: "SELECT", children: options, onChange: this._indexChanged_handler }; 38 | }, 39 | 40 | _option_template: function(name, index) { 41 | return { tag: "OPTION", value: name, text: i18n.text("IndexSelector.NameWithDocs", name, index.primaries.docs.count ) }; 42 | } 43 | }); 44 | 45 | })( this.jQuery, this.app, this.i18n ); 46 | -------------------------------------------------------------------------------- /src/app/ui/infoPanel/infoPanel.css: -------------------------------------------------------------------------------- 1 | 2 | .uiInfoPanel { 3 | background: rgba(0, 0, 0, 0.75); 4 | color: white; 5 | border-radius: 8px; 6 | padding: 1px; 7 | } 8 | .uiInfoPanel .uiPanel-titleBar { 9 | background: rgba(74, 74, 74, 0.75); 10 | background: -moz-linear-gradient(top, rgba(84, 84, 84, 0.75), rgba(54, 54, 54, 0.75), rgba(64, 64, 64, 0.75)); 11 | background: -webkit-linear-gradient(top, rgba(84, 84, 84, 0.75), rgba(54, 54, 54, 0.75), rgba(64, 64, 64, 0.75)); 12 | border-radius: 8px 8px 0 0; 13 | padding: 1px 0 2px 0; 14 | border-bottom: 0; 15 | } 16 | .uiInfoPanel .uiPanel-close { 17 | border-radius: 6px; 18 | height: 13px; 19 | width: 13px; 20 | background: #ccc; 21 | left: 3px; 22 | top: 1px; 23 | color: #333; 24 | text-shadow: #222 0 0 1px; 25 | line-height: 11px; 26 | border: 0; 27 | padding: 0; 28 | } 29 | .uiInfoPanel .uiPanel-close:hover { 30 | background: #eee; 31 | } 32 | 33 | .uiInfoPanel .uiPanel-body { 34 | background: transparent; 35 | padding: 20px; 36 | border-radius: 0 0 8px 8px; 37 | border: 1px solid #222; 38 | } 39 | -------------------------------------------------------------------------------- /src/app/ui/infoPanel/infoPanel.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.InfoPanel = ui.DraggablePanel.extend({ 6 | _baseCls: "uiPanel uiInfoPanel" 7 | }); 8 | 9 | })( this.app ); 10 | -------------------------------------------------------------------------------- /src/app/ui/jsonPanel/jsonPanel.css: -------------------------------------------------------------------------------- 1 | .uiJsonPanel SPAN.uiJsonPretty-string { color: #6F6; } 2 | .uiJsonPanel SPAN.uiJsonPretty-number { color: #66F; } 3 | .uiJsonPanel SPAN.uiJsonPretty-null { color: #F66; } 4 | .uiJsonPanel SPAN.uiJsonPretty-boolean { color: #F6F; } 5 | -------------------------------------------------------------------------------- /src/app/ui/jsonPanel/jsonPanel.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.JsonPanel = ui.InfoPanel.extend({ 6 | defaults: { 7 | json: null, // (required) 8 | modal: false, 9 | open: true, 10 | autoRemove: true, 11 | height: 500, 12 | width: 600 13 | }, 14 | 15 | _baseCls: "uiPanel uiInfoPanel uiJsonPanel", 16 | 17 | _body_template: function() { 18 | var body = this._super(); 19 | body.children = [ new ui.JsonPretty({ obj: this.config.json }) ]; 20 | return body; 21 | } 22 | }); 23 | 24 | })( this.app ); 25 | -------------------------------------------------------------------------------- /src/app/ui/jsonPretty/jsonPretty.css: -------------------------------------------------------------------------------- 1 | DIV.uiJsonPretty-object { font-size: 1.26em; font-family: monospace; } 2 | UL.uiJsonPretty-object, 3 | UL.uiJsonPretty-array { margin: 0; padding: 0 0 0 2em; list-style: none; } 4 | UL.uiJsonPretty-object LI, 5 | UL.uiJsonPretty-array LI { padding: 0; margin: 0; } 6 | .expando > SPAN.uiJsonPretty-name:before { content: "\25bc\a0"; color: #555; position: relative; top: 2px; } 7 | .expando.uiJsonPretty-minimised > SPAN.uiJsonPretty-name:before { content: "\25ba\a0"; top: 0; } 8 | .uiJsonPretty-minimised > UL SPAN.uiJsonPretty-name:before, 9 | .expando .uiJsonPretty-minimised > UL SPAN.uiJsonPretty-name:before { content: ""; } 10 | SPAN.uiJsonPretty-string, 11 | SPAN.uiJsonPretty-string A { color: green; } 12 | SPAN.uiJsonPretty-string A { text-decoration: underline;} 13 | SPAN.uiJsonPretty-number { color: blue; } 14 | SPAN.uiJsonPretty-null { color: red; } 15 | SPAN.uiJsonPretty-boolean { color: purple; } 16 | .expando > .uiJsonPretty-name { cursor: pointer; } 17 | .expando > .uiJsonPretty-name:hover { text-decoration: underline; } 18 | .uiJsonPretty-minimised { white-space: nowrap; overflow: hidden; } 19 | .uiJsonPretty-minimised > UL { opacity: 0.6; } 20 | .uiJsonPretty-minimised .uiJsonPretty-minimised > UL { opacity: 1; } 21 | .uiJsonPretty-minimised UL, .uiJsonPretty-minimised LI { display: inline; padding: 0; } 22 | 23 | -------------------------------------------------------------------------------- /src/app/ui/jsonPretty/jsonPretty.js: -------------------------------------------------------------------------------- 1 | (function( $, app ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.JsonPretty = ui.AbstractWidget.extend({ 6 | defaults: { 7 | obj: null 8 | }, 9 | init: function(parent) { 10 | this._super(); 11 | this.el = $(this._main_template()); 12 | this.attach(parent); 13 | this.el.click(this._click_handler); 14 | }, 15 | 16 | _click_handler: function(jEv) { 17 | var t = $(jEv.target).closest(".uiJsonPretty-name").closest("LI"); 18 | if(t.length === 0 || t.parents(".uiJsonPretty-minimised").length > 0) { return; } 19 | t.toggleClass("uiJsonPretty-minimised"); 20 | jEv.stopPropagation(); 21 | }, 22 | 23 | _main_template: function() { 24 | try { 25 | return { tag: "DIV", cls: "uiJsonPretty", children: this.pretty.parse(this.config.obj) }; 26 | } catch (error) { 27 | throw "JsonPretty error: " + error.message; 28 | } 29 | }, 30 | 31 | pretty: { // from https://github.com/RyanAmos/Pretty-JSON/blob/master/pretty_json.js 32 | "expando" : function(value) { 33 | return (value && (/array|object/i).test(value.constructor.name)) ? "expando" : ""; 34 | }, 35 | "parse": function (member) { 36 | return this[(member == null) ? 'null' : member.constructor.name.toLowerCase()](member); 37 | }, 38 | "null": function (value) { 39 | return this['value']('null', 'null'); 40 | }, 41 | "array": function (value) { 42 | var results = []; 43 | var lastItem = value.length - 1; 44 | value.forEach(function( v, i ) { 45 | results.push({ tag: "LI", cls: this.expando(v), children: [ this['parse'](v) ] }); 46 | if( i !== lastItem ) { 47 | results.push(","); 48 | } 49 | }, this); 50 | return [ "[ ", ((results.length > 0) ? { tag: "UL", cls: "uiJsonPretty-array", children: results } : null), "]" ]; 51 | }, 52 | "object": function (value) { 53 | var results = []; 54 | var keys = Object.keys( value ); 55 | var lastItem = keys.length - 1; 56 | keys.forEach( function( key, i ) { 57 | var children = [ this['value']( 'name', '"' + key + '"' ), ": ", this['parse']( value[ key ]) ]; 58 | if( i !== lastItem ) { 59 | children.push(","); 60 | } 61 | results.push( { tag: "LI", cls: this.expando( value[ key ] ), children: children } ); 62 | }, this); 63 | return [ "{ ", ((results.length > 0) ? { tag: "UL", cls: "uiJsonPretty-object", children: results } : null ), "}" ]; 64 | }, 65 | "number": function (value) { 66 | return this['value']('number', value.toString()); 67 | }, 68 | "string": function (value) { 69 | if (/^(http|https|file):\/\/[^\s]+$/.test(value)) { 70 | return this['link']( value ); 71 | } else { 72 | return this['value']('string', '"' + value.toString() + '"'); 73 | } 74 | }, 75 | "boolean": function (value) { 76 | return this['value']('boolean', value.toString()); 77 | }, 78 | "link": function( value ) { 79 | return this['value']("string", { tag: "A", href: value, target: "_blank", text: '"' + value + '"' } ); 80 | }, 81 | "value": function (type, value) { 82 | if (/^(http|https|file):\/\/[^\s]+$/.test(value)) { 83 | } 84 | return { tag: "SPAN", cls: "uiJsonPretty-" + type, text: value }; 85 | } 86 | } 87 | }); 88 | 89 | })( this.jQuery, this.app ); 90 | -------------------------------------------------------------------------------- /src/app/ui/menuButton/menuButton.css: -------------------------------------------------------------------------------- 1 | .uiMenuButton { 2 | display: inline-block; 3 | } 4 | 5 | .uiMenuButton .uiButton-label { 6 | background-image: url('data:image/gif;base64,R0lGODlhDwAPAIABAP///////yH5BAEAAAEALAAAAAAPAA8AAAITjI+py+0P4wG0gmavq1HLD4ZiAQA7'); 7 | background-position: right 50%; 8 | background-repeat: no-repeat; 9 | padding-right: 17px; 10 | text-align: left; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/ui/menuButton/menuButton.js: -------------------------------------------------------------------------------- 1 | (function( $, app ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.MenuButton = app.ui.Button.extend({ 6 | defaults: { 7 | menu: null 8 | }, 9 | _baseCls: "uiButton uiMenuButton", 10 | init: function(parent) { 11 | this._super(parent); 12 | this.menu = this.config.menu; 13 | this.on("click", this.openMenu_handler); 14 | this.menu.on("open", function() { this.el.addClass("active"); }.bind(this)); 15 | this.menu.on("close", function() { this.el.removeClass("active"); }.bind(this)); 16 | }, 17 | openMenu_handler: function(jEv) { 18 | this.menu && this.menu.open(jEv); 19 | } 20 | }); 21 | 22 | })( this.jQuery, this.app ); 23 | -------------------------------------------------------------------------------- /src/app/ui/menuPanel/menuPanel.css: -------------------------------------------------------------------------------- 1 | .uiMenuPanel { 2 | border: 1px solid #668dc6; 3 | position: absolute; 4 | background: #96c6eb; 5 | color: white; 6 | } 7 | 8 | .uiMenuPanel LI { 9 | list-style: none; 10 | border-bottom: 1px solid #668dc6; 11 | } 12 | 13 | .uiMenuPanel LI:hover { 14 | background: #2575b7; 15 | } 16 | 17 | .uiMenuPanel LI:last-child { 18 | border-bottom: 0; 19 | } 20 | 21 | .uiMenuPanel-label { 22 | white-space: nowrap; 23 | padding: 2px 10px 2px 10px; 24 | cursor: pointer; 25 | } 26 | 27 | .disabled .uiMenuPanel-label { 28 | cursor: auto; 29 | color: #888; 30 | } 31 | -------------------------------------------------------------------------------- /src/app/ui/menuPanel/menuPanel.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.MenuPanel = ui.AbstractPanel.extend({ 6 | defaults: { 7 | items: [], // (required) an array of menu items 8 | modal: false 9 | }, 10 | _baseCls: "uiMenuPanel", 11 | init: function() { 12 | this._super(); 13 | this.el = $(this._main_template()); 14 | }, 15 | open: function(jEv) { 16 | this._super(jEv); 17 | var cx = this; setTimeout(function() { $(document).bind("click", cx._close_handler); }, 50); 18 | }, 19 | _getItems: function() { 20 | return this.config.items; 21 | }, 22 | _close_handler: function(jEv) { 23 | this._super(jEv); 24 | $(document).unbind("click", this._close_handler); 25 | }, 26 | _main_template: function() { 27 | return { tag: "DIV", cls: this._baseCls, children: this._getItems().map(this._menuItem_template, this) }; 28 | }, 29 | _menuItem_template: function(item) { 30 | var dx = item.disabled ? { onclick: function() {} } : {}; 31 | return { tag: "LI", cls: "uiMenuPanel-item" + (item.disabled ? " disabled" : "") + (item.selected ? " selected" : ""), children: [ $.extend({ tag: "DIV", cls: "uiMenuPanel-label" }, item, dx ) ] }; 32 | }, 33 | _getPosition: function(jEv) { 34 | var right = !! $(jEv.target).parents(".pull-right").length; 35 | var parent = $(jEv.target).closest("BUTTON"); 36 | return parent.vOffset() 37 | .addY(parent.vSize().y) 38 | .addX( right ? parent.vSize().x - this.el.vOuterSize().x : 0 ) 39 | .asOffset(); 40 | } 41 | }); 42 | 43 | })( this.app ); 44 | -------------------------------------------------------------------------------- /src/app/ui/nodesView/nodesView.css: -------------------------------------------------------------------------------- 1 | .uiNodesView TH, 2 | .uiNodesView TD { 3 | vertical-align: top; 4 | padding: 2px 20px; 5 | } 6 | 7 | .uiNodesView TH.close, 8 | .uiNodesView TD.close { 9 | color: #888; 10 | background: #f2f2f2; 11 | } 12 | 13 | .uiNodesView .uiMenuButton .uiButton-content { 14 | padding-right: 3px; 15 | border-radius: 8px; 16 | height: 14px; 17 | } 18 | 19 | .uiNodesView .uiMenuButton.active .uiButton-content, 20 | .uiNodesView .uiMenuButton:active .uiButton-content { 21 | border-bottom-right-radius: 0px; 22 | border-bottom-left-radius: 0px; 23 | } 24 | 25 | .uiNodesView .uiMenuButton .uiButton-label { 26 | padding: 0px 17px 0px 7px; 27 | } 28 | 29 | .uiNodesView-hasAlias { 30 | text-align: center; 31 | } 32 | .uiNodesView-hasAlias.max { 33 | border-top-right-radius: 8px; 34 | border-bottom-right-radius: 8px; 35 | } 36 | .uiNodesView-hasAlias.min { 37 | border-top-left-radius: 8px; 38 | border-bottom-left-radius: 8px; 39 | } 40 | .uiNodesView-hasAlias-remove { 41 | float: right; 42 | font-weight: bold; 43 | cursor: pointer; 44 | } 45 | 46 | .uiNodesView TD.uiNodesView-icon { 47 | padding: 20px 0px 15px 20px; 48 | } 49 | 50 | .uiNodesView-node:nth-child(odd) { 51 | background: #eee; 52 | } 53 | 54 | .uiNodesView-routing { 55 | position: relative; 56 | min-width: 90px; 57 | } 58 | 59 | .uiNodesView-nullReplica, 60 | .uiNodesView-replica { 61 | box-sizing: border-box; 62 | cursor: pointer; 63 | float: left; 64 | height: 40px; 65 | width: 35px; 66 | margin: 4px; 67 | border: 2px solid #444; 68 | padding: 2px; 69 | font-size: 32px; 70 | line-height: 32px; 71 | text-align: center; 72 | letter-spacing: -5px; 73 | text-indent: -7px; 74 | } 75 | 76 | .uiNodesView-replica.primary { 77 | border-width: 4px; 78 | line-height: 29px; 79 | } 80 | 81 | .uiNodesView-nullReplica { 82 | border-color: transparent; 83 | } 84 | 85 | .uiNodesView-replica.state-UNASSIGNED { background: #eeeeee; color: #999; border-color: #666; float: none; display: inline-block; } 86 | .uiNodesView-replica.state-INITIALIZING { background: #dddc88; } 87 | .uiNodesView-replica.state-STARTED { background: #99dd88; } 88 | .uiNodesView-replica.state-RELOCATING { background: #dc88dd; } 89 | -------------------------------------------------------------------------------- /src/app/ui/page/page.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.Page = ui.AbstractWidget.extend({ 6 | show: function() { 7 | this.el.show(); 8 | }, 9 | hide: function() { 10 | this.el.hide(); 11 | } 12 | }); 13 | 14 | })( this.app ); -------------------------------------------------------------------------------- /src/app/ui/panelForm/panelForm.css: -------------------------------------------------------------------------------- 1 | .uiPanelForm-field { 2 | display: block; 3 | padding: 2px 0; 4 | clear: both; 5 | } 6 | 7 | .uiPanelForm-label { 8 | float: left; 9 | width: 200px; 10 | padding: 3px 7px; 11 | text-align: right; 12 | } 13 | -------------------------------------------------------------------------------- /src/app/ui/panelForm/panelForm.js: -------------------------------------------------------------------------------- 1 | (function( $, app ) { 2 | 3 | var ui = app.ns("ui"); 4 | var ut = app.ns("ut"); 5 | 6 | ui.PanelForm = ui.AbstractWidget.extend({ 7 | defaults: { 8 | fields: null // (required) instanceof app.ux.FieldCollection 9 | }, 10 | init: function(parent) { 11 | this._super(); 12 | this.el = $.joey(this._main_template()); 13 | this.attach( parent ); 14 | }, 15 | _main_template: function() { 16 | return { tag: "DIV", id: this.id(), cls: "uiPanelForm", children: this.config.fields.fields.map(this._field_template, this) }; 17 | }, 18 | _field_template: function(field) { 19 | return { tag: "LABEL", cls: "uiPanelForm-field", children: [ 20 | { tag: "DIV", cls: "uiPanelForm-label", children: [ field.label, ut.require_template(field) ] }, 21 | field 22 | ]}; 23 | } 24 | }); 25 | 26 | })( this.jQuery, this.app ); 27 | -------------------------------------------------------------------------------- /src/app/ui/queryFilter/queryFilter.css: -------------------------------------------------------------------------------- 1 | .uiQueryFilter { 2 | width: 350px; 3 | padding: 5px; 4 | background: #d8e7ff; 5 | background: -moz-linear-gradient(left, #d8e7ff, #e8f1ff); 6 | background: -webkit-linear-gradient(left, #d8e7ff, #e8f1ff); 7 | } 8 | 9 | .uiQueryFilter DIV.uiQueryFilter-section { 10 | margin-bottom: 5px; 11 | } 12 | 13 | .uiQueryFilter HEADER { 14 | display: block; 15 | font-variant: small-caps; 16 | font-weight: bold; 17 | margin: 5px 0; 18 | } 19 | 20 | .uiQueryFilter-aliases SELECT { 21 | width: 100%; 22 | } 23 | 24 | .uiQueryFilter-booble { 25 | cursor: pointer; 26 | background: #e8f1ff; 27 | border: 1px solid #e8f1ff; 28 | border-radius: 5px; 29 | padding: 1px 4px; 30 | margin-bottom: 1px; 31 | overflow: hidden; 32 | white-space: nowrap; 33 | } 34 | 35 | .uiQueryFilter-booble.selected { 36 | background: #dae3f0; 37 | border-top: 1px solid #c8d4e6; 38 | border-left: 1px solid #c8d4e6; 39 | border-bottom: 1px solid #ffffff; 40 | border-right: 1px solid #ffffff; 41 | } 42 | 43 | .uiQueryFilter-filterName { 44 | background-color: #cbdfff; 45 | margin-bottom: 4px; 46 | padding: 3px; 47 | cursor: pointer; 48 | } 49 | 50 | .uiQueryFilter-filters INPUT { 51 | width: 300px; 52 | } 53 | 54 | .uiQueryFilter-subMultiFields { 55 | padding-left: 10px; 56 | } 57 | 58 | .uiQueryFilter-rangeHintFrom, 59 | .uiQueryFilter-rangeHintTo { 60 | margin: 0; 61 | opacity: 0.75; 62 | } -------------------------------------------------------------------------------- /src/app/ui/refreshButton/refreshButton.js: -------------------------------------------------------------------------------- 1 | (function( $, app, i18n ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.RefreshButton = ui.SplitButton.extend({ 6 | defaults: { 7 | timer: -1 8 | }, 9 | init: function( parent ) { 10 | this.config.label = i18n.text("General.RefreshResults"); 11 | this._super( parent ); 12 | this.set( this.config.timer ); 13 | }, 14 | set: function( value ) { 15 | this.value = value; 16 | window.clearInterval( this._timer ); 17 | if( this.value > 0 ) { 18 | this._timer = window.setInterval( this._refresh_handler, this.value ); 19 | } 20 | }, 21 | _click_handler: function() { 22 | this._refresh_handler(); 23 | }, 24 | _select_handler: function( el, event ) { 25 | this.set( event.value ); 26 | this.fire("change", this ); 27 | }, 28 | _refresh_handler: function() { 29 | this.fire("refresh", this ); 30 | }, 31 | _getItems: function() { 32 | return [ 33 | { text: i18n.text("General.ManualRefresh"), value: -1 }, 34 | { text: i18n.text("General.RefreshQuickly"), value: 100 }, 35 | { text: i18n.text("General.Refresh5seconds"), value: 5000 }, 36 | { text: i18n.text("General.Refresh1minute"), value: 60000 } 37 | ]; 38 | } 39 | }); 40 | 41 | })( this.jQuery, this.app, this.i18n ); 42 | -------------------------------------------------------------------------------- /src/app/ui/refreshButton/refreshButtonDemo.js: -------------------------------------------------------------------------------- 1 | $( function() { 2 | 3 | var ui = window.app.ns("ui"); 4 | 5 | window.builder = function() { 6 | return new ui.RefreshButton({ 7 | onRefresh: function() { console.log("-> refresh", arguments ); }, 8 | onChange: function() { console.log("-> change", arguments ); } 9 | }); 10 | }; 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /src/app/ui/refreshButton/refreshButtonSpec.js: -------------------------------------------------------------------------------- 1 | describe("app.ui.RefreshButton", function() { 2 | 3 | var RefreshButton = window.app.ui.RefreshButton; 4 | 5 | var r, refresh_handler, change_handler; 6 | 7 | function openMenuPanel( button, label ) { 8 | button.el.find("BUTTON").eq(1).click(); 9 | $(".uiMenuPanel-label:contains(" + label + ")").click(); 10 | test.clock.tick(); // menuPanel -> bind _close_handler 11 | } 12 | 13 | 14 | beforeEach( function() { 15 | test.clock.steal(); 16 | refresh_handler = jasmine.createSpy("refresh_handler"); 17 | change_handler = jasmine.createSpy("change_handler"); 18 | r = new RefreshButton({ 19 | onRefresh: refresh_handler, 20 | onChange: change_handler 21 | }); 22 | r.attach( document.body ); 23 | }); 24 | 25 | afterEach( function() { 26 | r.remove(); 27 | test.clock.restore(); 28 | }); 29 | 30 | it("should have an initial default value", function() { 31 | expect( r.value ).toBe( -1 ); 32 | }); 33 | 34 | it("should fire a refresh event after clicking the refresh button ", function() { 35 | r.el.find("BUTTON").eq(0).click(); 36 | 37 | expect( refresh_handler ).toHaveBeenCalled(); 38 | }); 39 | 40 | it("should change the refresh rate when set it called", function() { 41 | r.set( 100 ); 42 | expect( r.value ).toBe( 100 ); 43 | }); 44 | 45 | it("should set an interval when rate is set to a positive value", function() { 46 | r.set( 100 ); 47 | test.clock.tick(); 48 | expect( refresh_handler.calls.count() ).toBe( 1 ); 49 | }); 50 | 51 | it("should not set an interval when rate is set to a non positive value", function() { 52 | r.set( -1 ); 53 | test.clock.tick(); 54 | expect( refresh_handler.calls.count() ).toBe( 0 ); 55 | }); 56 | 57 | it("should fire a refresh event on intervals if refresh menu item is set to quickly", function() { 58 | openMenuPanel( r, "quickly" ); 59 | 60 | expect( refresh_handler.calls.count() ).toBe( 0 ); 61 | test.clock.tick(); 62 | expect( refresh_handler.calls.count() ).toBe( 1 ); 63 | test.clock.tick(); 64 | expect( refresh_handler.calls.count() ).toBe( 2 ); 65 | }); 66 | 67 | it("should not fire refresh events when user selects Manual", function() { 68 | 69 | openMenuPanel( r, "quickly" ); 70 | 71 | expect( refresh_handler.calls.count() ).toBe( 0 ); 72 | test.clock.tick(); 73 | expect( refresh_handler.calls.count() ).toBe( 1 ); 74 | 75 | openMenuPanel( r, "Manual" ); 76 | 77 | test.clock.tick(); 78 | expect( refresh_handler.calls.count() ).toBe( 1 ); 79 | test.clock.tick(); 80 | expect( refresh_handler.calls.count() ).toBe( 1 ); 81 | }); 82 | 83 | it("should fire a change event when a new refresh rate is selected", function() { 84 | openMenuPanel( r, "quickly" ); 85 | expect( change_handler.calls.count() ).toBe( 1 ); 86 | expect( r.value ).toBe( 100 ); 87 | openMenuPanel( r, "Manual" ); 88 | expect( change_handler.calls.count() ).toBe( 2 ); 89 | expect( r.value ).toBe( -1 ); 90 | }); 91 | 92 | }); 93 | -------------------------------------------------------------------------------- /src/app/ui/resultTable/resultTable.js: -------------------------------------------------------------------------------- 1 | (function( $, app ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.ResultTable = ui.Table.extend({ 6 | defaults: { 7 | width: 500, 8 | height: 400 9 | }, 10 | 11 | init: function() { 12 | this._super(); 13 | this.on("rowClick", this._showPreview_handler); 14 | this.selectedRow = null; 15 | $(document).bind("keydown", this._nav_handler); 16 | }, 17 | remove: function() { 18 | $(document).unbind("keydown", this._nav_handler); 19 | this._super(); 20 | }, 21 | attach: function(parent) { 22 | if(parent) { 23 | var height = parent.height() || ( $(document).height() - parent.offset().top - 41 ); // 41 = height in px of .uiTable-tools + uiTable-header 24 | var width = parent.width(); 25 | this.el.width( width ); 26 | this.body.width( width ).height( height ); 27 | } 28 | this._super(parent); 29 | }, 30 | showPreview: function(row) { 31 | row.addClass("selected"); 32 | this.preview = new app.ui.JsonPanel({ 33 | title: i18n.text("Browser.ResultSourcePanelTitle"), 34 | json: row.data("row")._source, 35 | onClose: function() { row.removeClass("selected"); } 36 | }); 37 | }, 38 | _nav_handler: function(jEv) { 39 | if(jEv.keyCode !== 40 && jEv.keyCode !== 38) { 40 | return; 41 | } 42 | this.selectedRow && this.preview && this.preview.remove(); 43 | if(jEv.keyCode === 40) { // up arrow 44 | this.selectedRow = this.selectedRow ? this.selectedRow.next("TR") : this.body.find("TR:first"); 45 | } else if(jEv.keyCode === 38) { // down arrow 46 | this.selectedRow = this.selectedRow ? this.selectedRow.prev("TR") : this.body.find("TR:last"); 47 | } 48 | this.selectedRow && this.showPreview(this.selectedRow); 49 | }, 50 | _showPreview_handler: function(obj, data) { 51 | this.showPreview(this.selectedRow = data.row); 52 | } 53 | }); 54 | 55 | })( this.jQuery, this.app ); 56 | -------------------------------------------------------------------------------- /src/app/ui/selectMenuPanel/selectMenuPanel.css: -------------------------------------------------------------------------------- 1 | .uiSelectMenuPanel .uiMenuPanel-label { 2 | margin-left: 1em; 3 | padding-left: 4px; 4 | } 5 | 6 | .uiSelectMenuPanel .uiMenuPanel-item.selected .uiMenuPanel-label:before { 7 | content: "\2713"; 8 | width: 12px; 9 | margin-left: -12px; 10 | display: inline-block; 11 | } 12 | -------------------------------------------------------------------------------- /src/app/ui/selectMenuPanel/selectMenuPanel.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.SelectMenuPanel = ui.MenuPanel.extend({ 6 | defaults: { 7 | items: [], // (required) an array of menu items 8 | value: null 9 | }, 10 | _baseCls: "uiSelectMenuPanel uiMenuPanel", 11 | init: function() { 12 | this.value = this.config.value; 13 | this._super(); 14 | }, 15 | _getItems: function() { 16 | return this.config.items.map( function( item ) { 17 | return { 18 | text: item.text, 19 | selected: this.value === item.value, 20 | onclick: function( jEv ) { 21 | var el = $( jEv.target ).closest("LI"); 22 | el.parent().children().removeClass("selected"); 23 | el.addClass("selected"); 24 | this.fire( "select", this, { value: item.value } ); 25 | this.value = item.value; 26 | }.bind(this) 27 | }; 28 | }, this ); 29 | 30 | } 31 | }); 32 | 33 | })( this.app ); 34 | -------------------------------------------------------------------------------- /src/app/ui/sidebarSection/sidebarSection.css: -------------------------------------------------------------------------------- 1 | .uiSidebarSection-head { 2 | background-color: #b9cfff; 3 | background-image: url('data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAcCAMAAABifa5OAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAMAUExURQUCFf///wICAgMDAwQEBAUFBQYGBgcHBwgICAkJCQoKCgsLCwwMDA0NDQ4ODg8PDxAQEBERERISEhMTExQUFBUVFRYWFhcXFxgYGBkZGRoaGhsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiMjIyQkJCUlJSYmJicnJygoKCkpKSoqKisrKywsLC0tLS4uLi8vLzAwMDExMTIyMjMzMzQ0NDU1NTY2Njc3Nzg4ODk5OTo6Ojs7Ozw8PD09PT4+Pj8/P0BAQEFBQUJCQkNDQ0REREVFRUZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1BQUFFRUVJSUlNTU1RUVFVVVVZWVldXV1hYWFlZWVpaWltbW1xcXF1dXV5eXl9fX2BgYGFhYWJiYmNjY2RkZGVlZWZmZmdnZ2hoaGlpaWpqamtra2xsbG1tbW5ubm9vb3BwcHFxcXJycnNzc3R0dHV1dXZ2dnd3d3h4eHl5eXp6ent7e3x8fH19fX5+fn9/f4CAgIGBgYKCgoODg4SEhIWFhYaGhoeHh4iIiImJiYqKiouLi4yMjI2NjY6Ojo+Pj5CQkJGRkZKSkpOTk5SUlJWVlZaWlpeXl5iYmJmZmZqampubm5ycnJ2dnZ6enp+fn6CgoKGhoaKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq6ysrK2tra6urq+vr7CwsLGxsbKysrOzs7S0tLW1tba2tre3t7i4uLm5ubq6uru7u7y8vL29vb6+vr+/v8DAwMHBwcLCwsPDw8TExMXFxcbGxsfHx8jIyMnJycrKysvLy8zMzM3Nzc7Ozs/Pz9DQ0NHR0dLS0tPT09TU1NXV1dbW1tfX19jY2NnZ2dra2tvb29zc3N3d3d7e3t/f3+Dg4OHh4eLi4uPj4+Tk5OXl5ebm5ufn5+jo6Onp6erq6uvr6+zs7O3t7e7u7u/v7/Dw8PHx8fLy8vPz8/T09PX19fb29vf39/j4+Pn5+fr6+vv7+/z8/P39/f7+/v///2Oyy/cAAAACdFJOU/8A5bcwSgAAADxJREFUeNq8zzkOACAMA8Hd/3+agiuRcIsrRopIjArOoLK1QAMNNBCRPkhLyzkn35Bjfd7JR1Nr09NoDACnvgDl1zlzoQAAAABJRU5ErkJggg=='); 4 | background-repeat: no-repeat; 5 | background-position: 2px 5px; 6 | margin-bottom: 1px; 7 | padding: 3px 3px 3px 17px; 8 | cursor: pointer; 9 | } 10 | 11 | .shown > .uiSidebarSection-head { 12 | background-position: 2px -13px; 13 | } 14 | 15 | .uiSidebarSection-body { 16 | margin-bottom: 3px; 17 | display: none; 18 | } 19 | 20 | .uiSidebarSection-help { 21 | text-shadow: #228 1px 1px 2px; 22 | color: blue; 23 | cursor: pointer; 24 | } 25 | 26 | .uiSidebarSection-help:hover { 27 | text-decoration: underline; 28 | } 29 | -------------------------------------------------------------------------------- /src/app/ui/sidebarSection/sidebarSection.js: -------------------------------------------------------------------------------- 1 | (function( $, app, i18n ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.SidebarSection = ui.AbstractWidget.extend({ 6 | defaults: { 7 | title: "", 8 | help: null, 9 | body: null, 10 | open: false 11 | }, 12 | init: function() { 13 | this._super(); 14 | this.el = $.joey( this._main_template() ); 15 | this.body = this.el.children(".uiSidebarSection-body"); 16 | this.config.open && ( this.el.addClass("shown") && this.body.css("display", "block") ); 17 | }, 18 | _showSection_handler: function( ev ) { 19 | var shown = $( ev.target ).closest(".uiSidebarSection") 20 | .toggleClass("shown") 21 | .children(".uiSidebarSection-body").slideToggle(200, function() { this.fire("animComplete", this); }.bind(this)) 22 | .end() 23 | .hasClass("shown"); 24 | this.fire(shown ? "show" : "hide", this); 25 | }, 26 | _showHelp_handler: function( ev ) { 27 | new ui.HelpPanel({ref: this.config.help}); 28 | ev.stopPropagation(); 29 | }, 30 | _main_template: function() { return ( 31 | { tag: "DIV", cls: "uiSidebarSection", children: [ 32 | (this.config.title && { tag: "DIV", cls: "uiSidebarSection-head", onclick: this._showSection_handler, children: [ 33 | this.config.title, 34 | ( this.config.help && { tag: "SPAN", cls: "uiSidebarSection-help pull-right", onclick: this._showHelp_handler, text: i18n.text("General.HelpGlyph") } ) 35 | ] }), 36 | { tag: "DIV", cls: "uiSidebarSection-body", children: [ this.config.body ] } 37 | ] } 38 | ); } 39 | }); 40 | 41 | })( this.jQuery, this.app, this.i18n ); 42 | -------------------------------------------------------------------------------- /src/app/ui/splitButton/splitButton.css: -------------------------------------------------------------------------------- 1 | .uiSplitButton { 2 | white-space: nowrap; 3 | } 4 | 5 | .uiSplitButton .uiButton:first-child { 6 | margin-right: 0; 7 | display: inline-block; 8 | } 9 | 10 | .uiSplitButton .uiButton:first-child .uiButton-content { 11 | border-right-width: 1; 12 | border-right-color: #5296c7; 13 | border-top-right-radius: 0; 14 | border-bottom-right-radius: 0; 15 | } 16 | 17 | .uiSplitButton .uiMenuButton { 18 | margin-left: 0; 19 | } 20 | 21 | .uiSplitButton .uiButton:last-child .uiButton-content { 22 | border-radius: 2px; 23 | border-left-width: 1; 24 | border-left-color: #96c6eb; 25 | border-top-left-radius: 0; 26 | border-bottom-left-radius: 0; 27 | height: 20px; 28 | } 29 | 30 | .uiSplitButton .uiButton:last-child .uiButton-label { 31 | padding: 2px 17px 2px 6px; 32 | margin-left: -8px; 33 | } 34 | -------------------------------------------------------------------------------- /src/app/ui/splitButton/splitButton.js: -------------------------------------------------------------------------------- 1 | (function( $, app ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.SplitButton = ui.AbstractWidget.extend({ 6 | defaults: { 7 | items: [], 8 | label: "" 9 | }, 10 | _baseCls: "uiSplitButton", 11 | init: function( parent ) { 12 | this._super( parent ); 13 | this.value = null; 14 | this.button = new ui.Button({ 15 | label: this.config.label, 16 | onclick: this._click_handler 17 | }); 18 | this.menu = new ui.SelectMenuPanel({ 19 | value: this.config.value, 20 | items: this._getItems(), 21 | onSelect: this._select_handler 22 | }); 23 | this.menuButton = new ui.MenuButton({ 24 | label: "\u00a0", 25 | menu: this.menu 26 | }); 27 | this.el = $.joey(this._main_template()); 28 | }, 29 | remove: function() { 30 | this.menu.remove(); 31 | }, 32 | disable: function() { 33 | this.button.disable(); 34 | }, 35 | enable: function() { 36 | this.button.enable(); 37 | }, 38 | _click_handler: function() { 39 | this.fire("click", this, { value: this.value } ); 40 | }, 41 | _select_handler: function( panel, event ) { 42 | this.fire( "select", this, event ); 43 | }, 44 | _getItems: function() { 45 | return this.config.items; 46 | }, 47 | _main_template: function() { 48 | return { tag: "DIV", cls: this._baseCls, children: [ 49 | this.button, this.menuButton 50 | ] }; 51 | } 52 | }); 53 | 54 | })( this.jQuery, this.app ); 55 | -------------------------------------------------------------------------------- /src/app/ui/splitButton/splitButtonDemo.js: -------------------------------------------------------------------------------- 1 | $( function() { 2 | 3 | var ui = window.app.ns("ui"); 4 | 5 | window.builder = function() { 6 | return new ui.SplitButton({ 7 | label: "Default", 8 | items: [ 9 | { label: "Action" }, 10 | { label: "Another Action" }, 11 | { label: "Selected", selected: true } 12 | ] 13 | }); 14 | }; 15 | 16 | }); -------------------------------------------------------------------------------- /src/app/ui/structuredQuery/structuredQuery.css: -------------------------------------------------------------------------------- 1 | .uiStructuredQuery { 2 | padding: 10px; 3 | } 4 | 5 | .uiStructuredQuery-out { 6 | min-height: 30px; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/ui/structuredQuery/structuredQuery.js: -------------------------------------------------------------------------------- 1 | (function( $, app, i18n ) { 2 | 3 | var ui = app.ns("ui"); 4 | var data = app.ns("data"); 5 | 6 | var StructuredQuery = ui.AbstractWidget.extend({ 7 | defaults: { 8 | cluster: null // (required) instanceof app.services.Cluster 9 | }, 10 | _baseCls: "uiStructuredQuery", 11 | init: function(parent) { 12 | this._super(); 13 | this.selector = new ui.IndexSelector({ 14 | onIndexChanged: this._indexChanged_handler, 15 | cluster: this.config.cluster 16 | }); 17 | this.el = $(this._main_template()); 18 | this.out = this.el.find("DIV.uiStructuredQuery-out"); 19 | this.attach( parent ); 20 | }, 21 | 22 | _indexChanged_handler: function( index ) { 23 | this.filter && this.filter.remove(); 24 | this.filter = new ui.FilterBrowser({ 25 | cluster: this.config.cluster, 26 | index: index, 27 | onStartingSearch: function() { this.el.find("DIV.uiStructuredQuery-out").text( i18n.text("General.Searching") ); this.el.find("DIV.uiStructuredQuery-src").hide(); }.bind(this), 28 | onSearchSource: this._searchSource_handler, 29 | onResults: this._results_handler 30 | }); 31 | this.el.find(".uiStructuredQuery-body").append(this.filter); 32 | }, 33 | 34 | _results_handler: function( filter, event ) { 35 | var typeMap = { 36 | "json": this._jsonResults_handler, 37 | "table": this._tableResults_handler, 38 | "csv": this._csvResults_handler 39 | }; 40 | typeMap[ event.type ].call( this, event.data, event.metadata ); 41 | }, 42 | _jsonResults_handler: function( results ) { 43 | this.el.find("DIV.uiStructuredQuery-out").empty().append( new ui.JsonPretty({ obj: results })); 44 | }, 45 | _csvResults_handler: function( results ) { 46 | this.el.find("DIV.uiStructuredQuery-out").empty().append( new ui.CSVTable({ results: results })); 47 | }, 48 | _tableResults_handler: function( results, metadata ) { 49 | // hack up a QueryDataSourceInterface so that StructuredQuery keeps working without using a Query object 50 | var qdi = new data.QueryDataSourceInterface({ metadata: metadata, query: new data.Query() }); 51 | var tab = new ui.Table( { 52 | store: qdi, 53 | height: 400, 54 | width: this.out.innerWidth() 55 | } ).attach(this.out.empty()); 56 | qdi._results_handler(qdi.config.query, results); 57 | }, 58 | 59 | _showRawJSON : function() { 60 | if($("#rawJsonText").length === 0) { 61 | var hiddenButton = $("#showRawJSON"); 62 | var jsonText = $({tag: "P", type: "p", id: "rawJsonText"}); 63 | jsonText.text(hiddenButton[0].value); 64 | hiddenButton.parent().append(jsonText); 65 | } 66 | }, 67 | 68 | _searchSource_handler: function(src) { 69 | var searchSourceDiv = this.el.find("DIV.uiStructuredQuery-src"); 70 | searchSourceDiv.empty().append(new app.ui.JsonPretty({ obj: src })); 71 | if(typeof JSON !== "undefined") { 72 | var showRawJSON = $({ tag: "BUTTON", type: "button", text: i18n.text("StructuredQuery.ShowRawJson"), id: "showRawJSON", value: JSON.stringify(src), onclick: this._showRawJSON }); 73 | searchSourceDiv.append(showRawJSON); 74 | } 75 | searchSourceDiv.show(); 76 | }, 77 | 78 | _main_template: function() { 79 | return { tag: "DIV", cls: this._baseCls, children: [ 80 | this.selector, 81 | { tag: "DIV", cls: "uiStructuredQuery-body" }, 82 | { tag: "DIV", cls: "uiStructuredQuery-src", css: { display: "none" } }, 83 | { tag: "DIV", cls: "uiStructuredQuery-out" } 84 | ]}; 85 | } 86 | }); 87 | 88 | ui.StructuredQuery = ui.Page.extend({ 89 | init: function() { 90 | this.q = new StructuredQuery( this.config ); 91 | this.el = this.q.el; 92 | } 93 | }); 94 | 95 | })( this.jQuery, this.app, this.i18n ); 96 | -------------------------------------------------------------------------------- /src/app/ui/table/table.css: -------------------------------------------------------------------------------- 1 | .uiTable TABLE { 2 | border-collapse: collapse; 3 | } 4 | 5 | .uiTable-body { 6 | overflow-y: scroll; 7 | overflow-x: auto; 8 | } 9 | 10 | .uiTable-headers { 11 | overflow-x: hidden; 12 | } 13 | 14 | .uiTable-body TD { 15 | white-space: nowrap; 16 | } 17 | 18 | .uiTable-body .uiTable-header-row TH, 19 | .uiTable-body .uiTable-header-row TH DIV { 20 | padding-top: 0; 21 | padding-bottom: 0; 22 | } 23 | 24 | .uiTable-body .uiTable-header-cell > DIV { 25 | height: 0; 26 | overflow: hidden; 27 | } 28 | 29 | .uiTable-headercell-menu { 30 | float: right; 31 | } 32 | 33 | .uiTable-tools { 34 | padding: 3px 4px; 35 | height: 14px; 36 | } 37 | 38 | .uiTable-header-row { 39 | background: #ddd; 40 | background: -moz-linear-gradient(top, #eee, #ccc); 41 | background: -webkit-linear-gradient(top, #eee, #ccc); 42 | } 43 | 44 | .uiTable-headercell-text { 45 | margin-right: 20px; 46 | } 47 | 48 | .uiTable-headercell-menu { 49 | display: none; 50 | } 51 | 52 | .uiTable-header-row TH { 53 | border-right: 1px solid #bbb; 54 | padding: 0; 55 | text-align: left; 56 | } 57 | 58 | .uiTable-header-row TH > DIV { 59 | padding: 3px 4px; 60 | border-right: 1px solid #eee; 61 | } 62 | 63 | .uiTable-headerEndCap > DIV { 64 | width: 19px; 65 | } 66 | 67 | .uiTable-header-row .uiTable-sort { 68 | background: #ccc; 69 | background: -moz-linear-gradient(top, #bebebe, #ccc); 70 | background: -webkit-linear-gradient(top, #bebebe, #ccc); 71 | } 72 | .uiTable-header-row TH.uiTable-sort > DIV { 73 | border-right: 1px solid #ccc; 74 | } 75 | 76 | .uiTable-sort .uiTable-headercell-menu { 77 | display: block; 78 | } 79 | 80 | .uiTable TABLE TD { 81 | border-right: 1px solid transparent; 82 | padding: 3px 4px; 83 | } 84 | 85 | .uiTable-body TABLE TR:nth-child(even) { 86 | background: #f3f3f3; 87 | } 88 | 89 | .uiTable-body TABLE TR.selected { 90 | color: white; 91 | background: #6060f1; 92 | } 93 | -------------------------------------------------------------------------------- /src/app/ui/table/table.js: -------------------------------------------------------------------------------- 1 | ( function( $, app ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.Table = ui.AbstractWidget.extend({ 6 | defaults: { 7 | store: null, // (required) implements interface app.data.DataSourceInterface 8 | height: 0, 9 | width: 0 10 | }, 11 | _baseCls: "uiTable", 12 | init: function(parent) { 13 | this._super(); 14 | this.initElements(parent); 15 | this.config.store.on("data", this._data_handler); 16 | }, 17 | attach: function(parent) { 18 | if(parent) { 19 | this._super(parent); 20 | this._reflow(); 21 | } 22 | }, 23 | initElements: function(parent) { 24 | this.el = $.joey(this._main_template()); 25 | this.body = this.el.find(".uiTable-body"); 26 | this.headers = this.el.find(".uiTable-headers"); 27 | this.tools = this.el.find(".uiTable-tools"); 28 | this.attach( parent ); 29 | }, 30 | _data_handler: function(store) { 31 | this.tools.text(store.summary); 32 | this.headers.empty().append(this._header_template(store.columns)); 33 | this.body.empty().append(this._body_template(store.data, store.columns)); 34 | this._reflow(); 35 | }, 36 | _reflow: function() { 37 | var firstCol = this.body.find("TR:first TH.uiTable-header-cell > DIV"), 38 | headers = this.headers.find("TR:first TH.uiTable-header-cell > DIV"); 39 | for(var i = 0; i < headers.length; i++) { 40 | $(headers[i]).width( $(firstCol[i]).width() ); 41 | } 42 | this._scroll_handler(); 43 | }, 44 | _scroll_handler: function(ev) { 45 | this.el.find(".uiTable-headers").scrollLeft(this.body.scrollLeft()); 46 | }, 47 | _dataClick_handler: function(ev) { 48 | var row = $(ev.target).closest("TR"); 49 | if(row.length) { 50 | this.fire("rowClick", this, { row: row } ); 51 | } 52 | }, 53 | _headerClick_handler: function(ev) { 54 | var header = $(ev.target).closest("TH.uiTable-header-cell"); 55 | if(header.length) { 56 | this.fire("headerClick", this, { header: header, column: header.data("column"), dir: header.data("dir") }); 57 | } 58 | }, 59 | _main_template: function() { 60 | return { tag: "DIV", id: this.id(), css: { width: this.config.width + "px" }, cls: this._baseCls, children: [ 61 | { tag: "DIV", cls: "uiTable-tools" }, 62 | { tag: "DIV", cls: "uiTable-headers", onclick: this._headerClick_handler }, 63 | { tag: "DIV", cls: "uiTable-body", 64 | onclick: this._dataClick_handler, 65 | onscroll: this._scroll_handler, 66 | css: { height: this.config.height + "px", width: this.config.width + "px" } 67 | } 68 | ] }; 69 | }, 70 | _header_template: function(columns) { 71 | var ret = { tag: "TABLE", children: [ this._headerRow_template(columns) ] }; 72 | ret.children[0].children.push(this._headerEndCap_template()); 73 | return ret; 74 | }, 75 | _headerRow_template: function(columns) { 76 | return { tag: "TR", cls: "uiTable-header-row", children: columns.map(function(column) { 77 | var dir = ((this.config.store.sort.column === column) && this.config.store.sort.dir) || "none"; 78 | return { tag: "TH", data: { column: column, dir: dir }, cls: "uiTable-header-cell" + ((dir !== "none") ? " uiTable-sort" : ""), children: [ 79 | { tag: "DIV", children: [ 80 | { tag: "DIV", cls: "uiTable-headercell-menu", text: dir === "asc" ? "\u25b2" : "\u25bc" }, 81 | { tag: "DIV", cls: "uiTable-headercell-text", text: column } 82 | ]} 83 | ]}; 84 | }, this)}; 85 | }, 86 | _headerEndCap_template: function() { 87 | return { tag: "TH", cls: "uiTable-headerEndCap", children: [ { tag: "DIV" } ] }; 88 | }, 89 | _body_template: function(data, columns) { 90 | return { tag: "TABLE", children: [] 91 | .concat(this._headerRow_template(columns)) 92 | .concat(data.map(function(row) { 93 | return { tag: "TR", data: { row: row }, cls: "uiTable-row", children: columns.map(function(column){ 94 | return { tag: "TD", cls: "uiTable-cell", children: [ { tag: "DIV", text: (row[column] || "").toString() } ] }; 95 | })}; 96 | })) 97 | }; 98 | } 99 | 100 | }); 101 | 102 | })( this.jQuery, this.app ); 103 | -------------------------------------------------------------------------------- /src/app/ui/textField/textField.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.TextField = ui.AbstractField.extend({ 6 | init: function() { 7 | this._super(); 8 | }, 9 | _keyup_handler: function() { 10 | this.fire("change", this ); 11 | }, 12 | _main_template: function() { 13 | return { tag: "DIV", id: this.id(), cls: "uiField uiTextField", children: [ 14 | { tag: "INPUT", 15 | type: "text", 16 | name: this.config.name, 17 | placeholder: this.config.placeholder, 18 | onkeyup: this._keyup_handler 19 | } 20 | ]}; 21 | } 22 | }); 23 | 24 | })( this.app ); 25 | -------------------------------------------------------------------------------- /src/app/ui/textField/textFieldDemo.js: -------------------------------------------------------------------------------- 1 | $( function() { 2 | 3 | var ui = window.app.ns("ui"); 4 | 5 | window.builder = function() { return ( 6 | { tag: "DIV", children: [ 7 | new ui.TextField({}), 8 | new ui.TextField({ placeholder: "placeholder" }), 9 | new ui.TextField({ onchange: function( tf ) { console.log( tf.val() ); } }) 10 | ] } 11 | ); }; 12 | 13 | }); -------------------------------------------------------------------------------- /src/app/ui/toolbar/toolbar.css: -------------------------------------------------------------------------------- 1 | .uiToolbar { 2 | height: 28px; 3 | background: #fdfefe; 4 | background: -moz-linear-gradient(top, #fdfefe, #eaedef); 5 | background: -webkit-linear-gradient(top, #fdfefe, #eaedef); 6 | border-bottom: 1px solid #d2d5d7; 7 | padding: 3px 10px; 8 | } 9 | 10 | .uiToolbar H2 { 11 | display: inline-block; 12 | font-size: 120%; 13 | margin: 0; 14 | padding: 5px 20px 5px 0; 15 | } 16 | 17 | .uiToolbar .uiTextField { 18 | display: inline-block; 19 | } 20 | 21 | .uiToolbar .uiTextField INPUT { 22 | padding-top: 2px; 23 | padding-bottom: 5px; 24 | } -------------------------------------------------------------------------------- /src/app/ui/toolbar/toolbar.js: -------------------------------------------------------------------------------- 1 | (function( $, app ) { 2 | 3 | var ui = app.ns("ui"); 4 | 5 | ui.Toolbar = ui.AbstractWidget.extend({ 6 | defaults: { 7 | label: "", 8 | left: [], 9 | right: [] 10 | }, 11 | init: function(parent) { 12 | this._super(); 13 | this.el = $.joey(this._main_template()); 14 | }, 15 | _main_template: function() { 16 | return { tag: "DIV", cls: "uiToolbar", children: [ 17 | { tag: "DIV", cls: "pull-left", children: [ 18 | { tag: "H2", text: this.config.label } 19 | ].concat(this.config.left) }, 20 | { tag: "DIV", cls: "pull-right", children: this.config.right } 21 | ]}; 22 | } 23 | }); 24 | 25 | })( this.jQuery, this.app ); 26 | -------------------------------------------------------------------------------- /src/app/ux/class.js: -------------------------------------------------------------------------------- 1 | /** 2 | * base class for creating inheritable classes 3 | * based on resigs 'Simple Javascript Inheritance Class' (based on base2 and prototypejs) 4 | * modified with static super and auto config 5 | * @name Class 6 | * @constructor 7 | */ 8 | (function( $, app ){ 9 | 10 | var ux = app.ns("ux"); 11 | 12 | var initializing = false, fnTest = /\b_super\b/; 13 | 14 | ux.Class = function(){}; 15 | 16 | ux.Class.extend = function(prop) { 17 | function Class() { 18 | if(!initializing) { 19 | var args = Array.prototype.slice.call(arguments); 20 | this.config = $.extend( function(t) { // automatically construct a config object based on defaults and last item passed into the constructor 21 | return $.extend(t._proto && t._proto() && arguments.callee(t._proto()) || {}, t.defaults); 22 | } (this) , args.pop() ); 23 | this.init && this.init.apply(this, args); // automatically run the init function when class created 24 | } 25 | } 26 | 27 | initializing = true; 28 | var prototype = new this(); 29 | initializing = false; 30 | 31 | var _super = this.prototype; 32 | prototype._proto = function() { 33 | return _super; 34 | }; 35 | 36 | for(var name in prop) { 37 | prototype[name] = typeof prop[name] === "function" && typeof _super[name] === "function" && fnTest.test(prop[name]) ? 38 | (function(name, fn){ 39 | return function() { this._super = _super[name]; return fn.apply(this, arguments); }; 40 | })(name, prop[name]) : prop[name]; 41 | } 42 | 43 | Class.prototype = prototype; 44 | Class.constructor = Class; 45 | 46 | Class.extend = arguments.callee; // make class extendable 47 | 48 | return Class; 49 | }; 50 | })( this.jQuery, this.app ); 51 | -------------------------------------------------------------------------------- /src/app/ux/dragdrop.js: -------------------------------------------------------------------------------- 1 | (function( $, app ) { 2 | 3 | var ux = app.ns("ux"); 4 | 5 | /** 6 | * Provides drag and drop functionality
7 | * a DragDrop instance is created for each usage pattern and then used over and over again
8 | * first a dragObj is defined - this is the jquery node that will be dragged around
9 | * second, the event callbacks are defined - these allow you control the ui during dragging and run functions when successfully dropping
10 | * thirdly drop targets are defined - this is a list of DOM nodes, the constructor works in one of two modes: 11 | *
  • without targets - objects can be picked up and dragged around, dragStart and dragStop events fire
  • 12 | *
  • with targets - as objects are dragged over targets dragOver, dragOut and DragDrop events fire 13 | * to start dragging call the DragDrop.pickup_handler() function, dragging stops when the mouse is released. 14 | * @constructor 15 | * The following options are supported 16 | *
    targetSelector
    17 | *
    an argument passed directly to jquery to create a list of targets, as such it can be a CSS style selector, or an array of DOM nodes
    if target selector is null the DragDrop does Drag only and will not fire dragOver dragOut and dragDrop events
    18 | *
    pickupSelector
    19 | *
    a jquery selector. The pickup_handler is automatically bound to matched elements (eg clicking on these elements starts the drag). if pickupSelector is null, the pickup_handler must be manually bound $(el).bind("mousedown", dragdrop.pickup_handler)
    20 | *
    dragObj
    21 | *
    the jQuery element to drag around when pickup is called. If not defined, dragObj must be set in onDragStart
    22 | *
    draggingClass
    23 | *
    the class(es) added to items when they are being dragged
    24 | * The following observables are supported 25 | *
    dragStart
    26 | *
    a callback when start to drag
    function(jEv)
    27 | *
    dragOver
    28 | *
    a callback when we drag into a target
    function(jEl)
    29 | *
    dragOut
    30 | *
    a callback when we drag out of a target, or when we drop over a target
    function(jEl)
    31 | *
    dragDrop
    32 | *
    a callback when we drop on a target
    function(jEl)
    33 | *
    dragStop
    34 | *
    a callback when we stop dragging
    function(jEv)
    35 | */ 36 | ux.DragDrop = ux.Observable.extend({ 37 | defaults : { 38 | targetsSelector : null, 39 | pickupSelector: null, 40 | dragObj : null, 41 | draggingClass : "dragging" 42 | }, 43 | 44 | init: function(options) { 45 | this._super(); // call the class initialiser 46 | 47 | this.drag_handler = this.drag.bind(this); 48 | this.drop_handler = this.drop.bind(this); 49 | this.pickup_handler = this.pickup.bind(this); 50 | this.targets = []; 51 | this.dragObj = null; 52 | this.dragObjOffset = null; 53 | this.currentTarget = null; 54 | if(this.config.pickupSelector) { 55 | $(this.config.pickupSelector).bind("mousedown", this.pickup_handler); 56 | } 57 | }, 58 | 59 | drag : function(jEv) { 60 | jEv.preventDefault(); 61 | var mloc = acx.vector( this.lockX || jEv.pageX, this.lockY || jEv.pageY ); 62 | this.dragObj.css(mloc.add(this.dragObjOffset).asOffset()); 63 | if(this.targets.length === 0) { 64 | return; 65 | } 66 | if(this.currentTarget !== null && mloc.within(this.currentTarget[1], this.currentTarget[2])) { 67 | return; 68 | } 69 | if(this.currentTarget !== null) { 70 | this.fire('dragOut', this.currentTarget[0]); 71 | this.currentTarget = null; 72 | } 73 | for(var i = 0; i < this.targets.length; i++) { 74 | if(mloc.within(this.targets[i][1], this.targets[i][2])) { 75 | this.currentTarget = this.targets[i]; 76 | break; 77 | } 78 | } 79 | if(this.currentTarget !== null) { 80 | this.fire('dragOver', this.currentTarget[0]); 81 | } 82 | }, 83 | 84 | drop : function(jEv) { 85 | $(document).unbind("mousemove", this.drag_handler); 86 | $(document).unbind("mouseup", this.drop_handler); 87 | this.dragObj.removeClass(this.config.draggingClass); 88 | if(this.currentTarget !== null) { 89 | this.fire('dragOut', this.currentTarget[0]); 90 | this.fire('dragDrop', this.currentTarget[0]); 91 | } 92 | this.fire('dragStop', jEv); 93 | this.dragObj = null; 94 | }, 95 | 96 | pickup : function(jEv, opts) { 97 | $.extend(this.config, opts); 98 | this.fire('dragStart', jEv); 99 | this.dragObj = this.dragObj || this.config.dragObj; 100 | this.dragObjOffset = this.config.dragObjOffset || acx.vector(this.dragObj.offset()).sub(jEv.pageX, jEv.pageY); 101 | this.lockX = this.config.lockX ? jEv.pageX : 0; 102 | this.lockY = this.config.lockY ? jEv.pageY : 0; 103 | this.dragObj.addClass(this.config.draggingClass); 104 | if(!this.dragObj.get(0).parentNode || this.dragObj.get(0).parentNode.nodeType === 11) { // 11 = document fragment 105 | $(document.body).append(this.dragObj); 106 | } 107 | if(this.config.targetsSelector) { 108 | this.currentTarget = null; 109 | var targets = ( this.targets = [] ); 110 | // create an array of elements optimised for rapid collision detection calculation 111 | $(this.config.targetsSelector).each(function(i, el) { 112 | var jEl = $(el); 113 | var tl = acx.vector(jEl.offset()); 114 | var br = tl.add(jEl.width(), jEl.height()); 115 | targets.push([jEl, tl, br]); 116 | }); 117 | } 118 | $(document).bind("mousemove", this.drag_handler); 119 | $(document).bind("mouseup", this.drop_handler); 120 | this.drag_handler(jEv); 121 | } 122 | }); 123 | 124 | })( this.jQuery, this.app ); 125 | -------------------------------------------------------------------------------- /src/app/ux/fieldCollection.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var ux = app.ns("ux"); 4 | 5 | ux.FieldCollection = ux.Observable.extend({ 6 | defaults: { 7 | fields: [] // the collection of fields 8 | }, 9 | init: function() { 10 | this._super(); 11 | this.fields = this.config.fields; 12 | }, 13 | validate: function() { 14 | return this.fields.reduce(function(r, field) { 15 | return r && field.validate(); 16 | }, true); 17 | }, 18 | getData: function(type) { 19 | return this.fields.reduce(function(r, field) { 20 | r[field.name] = field.val(); return r; 21 | }, {}); 22 | } 23 | }); 24 | 25 | })( this.app ); 26 | -------------------------------------------------------------------------------- /src/app/ux/observable.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var ux = app.ns("ux"); 4 | 5 | ux.Observable = ux.Class.extend((function() { 6 | return { 7 | init: function() { 8 | this.observers = {}; 9 | for( var opt in this.config ) { // automatically install observers that are defined in the configuration 10 | if( opt.indexOf( 'on' ) === 0 ) { 11 | this.on( opt.substring(2) , this.config[ opt ] ); 12 | } 13 | } 14 | }, 15 | _getObs: function( type ) { 16 | return ( this.observers[ type.toLowerCase() ] || ( this.observers[ type.toLowerCase() ] = [] ) ); 17 | }, 18 | on: function( type, fn, params, thisp ) { 19 | this._getObs( type ).push( { "cb" : fn, "args" : params || [] , "cx" : thisp || this } ); 20 | return this; 21 | }, 22 | fire: function( type ) { 23 | var params = Array.prototype.slice.call( arguments, 1 ); 24 | this._getObs( type ).slice().forEach( function( ob ) { 25 | ob["cb"].apply( ob["cx"], ob["args"].concat( params ) ); 26 | } ); 27 | return this; 28 | }, 29 | removeAllObservers: function() { 30 | this.observers = {}; 31 | }, 32 | removeObserver: function( type, fn ) { 33 | var obs = this._getObs( type ), 34 | index = obs.reduce( function(p, t, i) { return (t.cb === fn) ? i : p; }, -1 ); 35 | if(index !== -1) { 36 | obs.splice( index, 1 ); 37 | } 38 | return this; // make observable functions chainable 39 | }, 40 | hasObserver: function( type ) { 41 | return !! this._getObs( type ).length; 42 | } 43 | }; 44 | })()); 45 | 46 | })( this.app ); -------------------------------------------------------------------------------- /src/app/ux/singleton.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var ux = app.ns("ux"); 4 | 5 | var extend = ux.Observable.extend; 6 | var instance = function() { 7 | if( ! ("me" in this) ) { 8 | this.me = new this(); 9 | } 10 | return this.me; 11 | }; 12 | 13 | ux.Singleton = ux.Observable.extend({}); 14 | 15 | ux.Singleton.extend = function() { 16 | var Self = extend.apply( this, arguments ); 17 | Self.instance = instance; 18 | return Self; 19 | }; 20 | 21 | })( this.app ); 22 | -------------------------------------------------------------------------------- /src/app/ux/singletonSpec.js: -------------------------------------------------------------------------------- 1 | describe("app.ux.singleton", function(){ 2 | 3 | var Singleton = window.app.ux.Singleton; 4 | 5 | describe("creating a singleton", function() { 6 | var X = Singleton.extend({ 7 | foo: function() { 8 | return "bar"; 9 | } 10 | }); 11 | 12 | var Y = Singleton.extend({ 13 | bar: function() { 14 | return "baz"; 15 | } 16 | }); 17 | 18 | it("should have properties like a normal class", function() { 19 | var a = X.instance(); 20 | 21 | expect( a instanceof X ).toBe( true ); 22 | expect( a.foo() ).toBe( "bar" ); 23 | }); 24 | 25 | it("should return single instance each time instance() is called", function() { 26 | var a = X.instance(); 27 | var b = X.instance(); 28 | 29 | expect( a ).toBe( b ); 30 | }); 31 | 32 | it("should not share instances with different singletons", function() { 33 | var a = X.instance(); 34 | var c = Y.instance(); 35 | 36 | expect( a ).not.toBe( c ); 37 | }); 38 | 39 | }); 40 | 41 | }); 42 | -------------------------------------------------------------------------------- /src/app/ux/table.css: -------------------------------------------------------------------------------- 1 | TABLE.table { 2 | border-collapse: collapse; 3 | } 4 | 5 | 6 | TABLE.table TH { 7 | font-weight: normal; 8 | text-align: left; 9 | vertical-align: middle; 10 | } 11 | 12 | TABLE.table TBODY.striped TR:nth-child(odd) { 13 | background: #eee; 14 | } 15 | 16 | TABLE.table H3 { 17 | margin: 0; 18 | font-weight: bold; 19 | font-size: 140%; 20 | } 21 | -------------------------------------------------------------------------------- /src/app/ux/templates/templateSpec.js: -------------------------------------------------------------------------------- 1 | describe("app.ut.byteSize_template", function() { 2 | 3 | describe("byteSize_template()", function() { 4 | var byteSize_template = window.app.ut.byteSize_template; 5 | 6 | it("should postfix with a B and have not decimal for number less than 1000", function() { 7 | expect( byteSize_template( 0 ) ).toBe( "0B" ); 8 | expect( byteSize_template( 1 ) ).toBe( "1B" ); 9 | expect( byteSize_template( 10 ) ).toBe( "10B" ); 10 | expect( byteSize_template( 100 ) ).toBe( "100B" ); 11 | expect( byteSize_template( 999 ) ).toBe( "999B" ); 12 | }); 13 | 14 | it("should have 0.xxX for values between 1000 and 1023", function() { 15 | expect( byteSize_template( 1000 ) ).toBe( "0.98ki" ); 16 | expect( byteSize_template( 1024 * 1000 ) ).toBe( "0.98Mi" ); 17 | }); 18 | 19 | it("should always have three significant digits", function() { 20 | expect( byteSize_template( 1023 ) ).toBe( "1.00ki" ); 21 | expect( byteSize_template( 1024 ) ).toBe( "1.00ki" ); 22 | expect( byteSize_template( 1025 ) ).toBe( "1.00ki" ); 23 | expect( byteSize_template( 1024 * 5 ) ).toBe( "5.00ki" ); 24 | expect( byteSize_template( 1024 * 55 ) ).toBe( "55.0ki" ); 25 | expect( byteSize_template( 1024 * 555 ) ).toBe( "555ki" ); 26 | }); 27 | 28 | it("should have the correct postfix", function() { 29 | expect( byteSize_template( 3 * Math.pow( 1024, 1) ) ).toBe( "3.00ki" ); 30 | expect( byteSize_template( 3 * Math.pow( 1024, 2) ) ).toBe( "3.00Mi" ); 31 | expect( byteSize_template( 3 * Math.pow( 1024, 3) ) ).toBe( "3.00Gi" ); 32 | expect( byteSize_template( 3 * Math.pow( 1024, 4) ) ).toBe( "3.00Ti" ); 33 | expect( byteSize_template( 3 * Math.pow( 1024, 5) ) ).toBe( "3.00Pi" ); 34 | expect( byteSize_template( 3 * Math.pow( 1024, 6) ) ).toBe( "3.00Ei" ); 35 | expect( byteSize_template( 3 * Math.pow( 1024, 7) ) ).toBe( "3.00Zi" ); 36 | expect( byteSize_template( 3 * Math.pow( 1024, 8) ) ).toBe( "3.00Yi" ); 37 | }); 38 | 39 | it("should show an overflow for stupidly big numbers", function() { 40 | expect( byteSize_template( 3 * Math.pow( 1024, 10) ) ).toBe( "3.00..E" ); 41 | }); 42 | }); 43 | 44 | describe("count_template()", function() { 45 | var count_template = window.app.ut.count_template; 46 | 47 | it("should not postfix and not decimal for number less than 1000", function() { 48 | expect( count_template( 0 ) ).toBe( "0" ); 49 | expect( count_template( 1 ) ).toBe( "1" ); 50 | expect( count_template( 10 ) ).toBe( "10" ); 51 | expect( count_template( 100 ) ).toBe( "100" ); 52 | expect( count_template( 999 ) ).toBe( "999" ); 53 | }); 54 | 55 | it("should always have three significant digits", function() { 56 | expect( count_template( 1000 ) ).toBe( "1.00k" ); 57 | expect( count_template( 1005 ) ).toBe( "1.00k" ); 58 | expect( count_template( 1055 ) ).toBe( "1.05k" ); 59 | expect( count_template( 1000 * 5 ) ).toBe( "5.00k" ); 60 | expect( count_template( 1000 * 55 ) ).toBe( "55.0k" ); 61 | expect( count_template( 1000 * 555 ) ).toBe( "555k" ); 62 | }); 63 | 64 | it("should have the correct postfix", function() { 65 | expect( count_template( 3 * Math.pow( 1000, 1) ) ).toBe( "3.00k" ); 66 | expect( count_template( 3 * Math.pow( 1000, 2) ) ).toBe( "3.00M" ); 67 | expect( count_template( 3 * Math.pow( 1000, 3) ) ).toBe( "3.00G" ); 68 | expect( count_template( 3 * Math.pow( 1000, 4) ) ).toBe( "3.00T" ); 69 | expect( count_template( 3 * Math.pow( 1000, 5) ) ).toBe( "3.00P" ); 70 | expect( count_template( 3 * Math.pow( 1000, 6) ) ).toBe( "3.00E" ); 71 | expect( count_template( 3 * Math.pow( 1000, 7) ) ).toBe( "3.00Z" ); 72 | expect( count_template( 3 * Math.pow( 1000, 8) ) ).toBe( "3.00Y" ); 73 | }); 74 | 75 | it("should show an overflow for stupidly big numbers", function() { 76 | expect( count_template( 3 * Math.pow( 1000, 10) ) ).toBe( "3.00..E" ); 77 | }); 78 | }); 79 | 80 | 81 | }); 82 | -------------------------------------------------------------------------------- /src/app/ux/templates/templates.js: -------------------------------------------------------------------------------- 1 | (function( app ) { 2 | 3 | var ut = app.ns("ut"); 4 | 5 | ut.option_template = function(v) { return { tag: "OPTION", value: v, text: v }; }; 6 | 7 | ut.require_template = function(f) { return f.require ? { tag: "SPAN", cls: "require", text: "*" } : null; }; 8 | 9 | 10 | var sib_prefix = ['B','ki','Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi']; 11 | 12 | ut.byteSize_template = function(n) { 13 | var i = 0; 14 | while( n >= 1000 ) { 15 | i++; 16 | n /= 1024; 17 | } 18 | return (i === 0 ? n.toString() : n.toFixed( 3 - parseInt(n,10).toString().length )) + ( sib_prefix[ i ] || "..E" ); 19 | }; 20 | 21 | var sid_prefix = ['','k','M', 'G', 'T', 'P', 'E', 'Z', 'Y']; 22 | 23 | ut.count_template = function(n) { 24 | var i = 0; 25 | while( n >= 1000 ) { 26 | i++; 27 | n /= 1000; 28 | } 29 | return i === 0 ? n.toString() : ( n.toFixed( 3 - parseInt(n,10).toString().length ) + ( sid_prefix[ i ] || "..E" ) ); 30 | }; 31 | 32 | })( this.app ); 33 | -------------------------------------------------------------------------------- /src/chrome_ext/background.js: -------------------------------------------------------------------------------- 1 | chrome.browserAction.onClicked.addListener(function (tab) { 2 | chrome.tabs.create({'url': chrome.extension.getURL('index.html')}, function (tab) { 3 | }); 4 | }); 5 | -------------------------------------------------------------------------------- /src/chrome_ext/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "elasticsearch-head", 4 | "version": "1.0.8", 5 | "background": { 6 | "scripts": ["background.js"], 7 | "persistent": false 8 | }, 9 | "icons": { 10 | "16": "base/favicon.png" 11 | }, 12 | "content_security_policy": "script-src 'self' 'sha256-Rpn+rjJuXCjZBPOPhhVloRXuzAUBRnAas+6gKVDohs0=' 'unsafe-eval'; object-src 'self';", 13 | "description": "Chrome Extension containing the excellent ElasticSearch Head application.", 14 | "browser_action": { 15 | "default_icon": "base/favicon.png" , 16 | "default_title": "es-head" 17 | }, 18 | "content_scripts":[{ 19 | "all_frames": false, 20 | "matches":["*://*/"], 21 | "js":["background.js"], 22 | "run_at": "document_end" 23 | }] 24 | } 25 | -------------------------------------------------------------------------------- /src/vendor/dateRangeParser/date-range-parser.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * date-range-parser.js 3 | * Contributed to the Apache Software Foundation by: 4 | * Ben Birch - Aconex 5 | * fork me at https://github.com/mobz/date-range-parser 6 | 7 | Licensed to the Apache Software Foundation (ASF) under one 8 | or more contributor license agreements. See the NOTICE file 9 | distributed with this work for additional information 10 | regarding copyright ownership. The ASF licenses this file 11 | to you under the Apache License, Version 2.0 (the 12 | "License"); you may not use this file except in compliance 13 | with the License. You may obtain a copy of the License at 14 | 15 | http://www.apache.org/licenses/LICENSE-2.0 16 | 17 | Unless required by applicable law or agreed to in writing, 18 | software distributed under the License is distributed on an 19 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 20 | KIND, either express or implied. See the License for the 21 | specific language governing permissions and limitations 22 | under the License. 23 | 24 | */ 25 | 26 | (function() { 27 | 28 | var drp = window.dateRangeParser = {}; 29 | 30 | drp.defaultRange = 1000 * 60 * 60 * 24; 31 | 32 | drp.now = null; // set a different value for now than the time at function invocation 33 | 34 | drp.parse = function(v) { 35 | try { 36 | var r = drp._parse(v); 37 | r.end && r.end--; // remove 1 millisecond from the final end range 38 | } catch(e) { 39 | r = null; 40 | } 41 | return r; 42 | }; 43 | 44 | drp.print = function(t, p) { 45 | var format = ["", "-", "-", " ", ":", ":", "."]; 46 | var da = makeArray(t); 47 | var str = ""; 48 | for(var i = 0; i <= p; i++) { 49 | str += format[i] + (da[i] < 10 ? "0" : "") + da[i]; 50 | } 51 | return str; 52 | }; 53 | 54 | (function() { 55 | drp._relTokens = {}; 56 | 57 | var values = { 58 | "yr" : 365*24*60*60*1000, 59 | "mon" : 31*24*60*60*1000, 60 | "day" : 24*60*60*1000, 61 | "hr" : 60*60*1000, 62 | "min" : 60*1000, 63 | "sec" : 1000 64 | }; 65 | 66 | var alias_lu = { 67 | "yr" : "y,yr,yrs,year,years", 68 | "mon" : "mo,mon,mos,mons,month,months", 69 | "day" : "d,dy,dys,day,days", 70 | "hr" : "h,hr,hrs,hour,hours", 71 | "min" : "m,min,mins,minute,minutes", 72 | "sec" : "s,sec,secs,second,seconds" 73 | }; 74 | 75 | for(var key in alias_lu) { 76 | if(alias_lu.hasOwnProperty(key)) { 77 | var aliases = alias_lu[key].split(","); 78 | for(var i = 0; i < aliases.length; i++) { 79 | drp._relTokens[aliases[i]] = values[key]; 80 | } 81 | } 82 | } 83 | })(); 84 | 85 | function makeArray(d) { 86 | var da = new Date(d); 87 | return [ da.getUTCFullYear(), da.getUTCMonth()+1, da.getUTCDate(), da.getUTCHours(), da.getUTCMinutes(), da.getUTCSeconds(), da.getUTCMilliseconds() ]; 88 | } 89 | 90 | function fromArray(a) { 91 | var d = [].concat(a); d[1]--; 92 | return Date.UTC.apply(null, d); 93 | } 94 | 95 | drp._parse = function parse(v) { 96 | var now = this.now || new Date().getTime(); 97 | 98 | function precArray(d, p, offset) { 99 | var tn = makeArray(d); 100 | tn[p] += offset || 0; 101 | for(var i = p+1; i < 7; i++) { 102 | tn[i] = i < 3 ? 1 : 0; 103 | } 104 | return tn; 105 | } 106 | function makePrecRange(dt, p, r) { 107 | var ret = { }; 108 | ret.start = fromArray(dt); 109 | dt[p] += r || 1; 110 | ret.end = fromArray(dt); 111 | return ret; 112 | } 113 | function procTerm(term) { 114 | var m = term.replace(/\s/g, "").toLowerCase().match(/^([a-z ]+)$|^([ 0-9:-]+)$|^(\d+[a-z]+)$/); 115 | if(m[1]) { // matches ([a-z ]+) 116 | function dra(p, o, r) { 117 | var dt = precArray(now, p, o); 118 | if(r) { 119 | dt[2] -= new Date(fromArray(dt)).getUTCDay(); 120 | } 121 | return makePrecRange(dt, p, r); 122 | } 123 | switch( m[1]) { 124 | case "now" : return { start: now, end: now, now: now }; 125 | case "today" : return dra( 2, 0 ); 126 | case "thisweek" : return dra( 2, 0, 7 ); 127 | case "thismonth" : return dra( 1, 0 ); 128 | case "thisyear" : return dra( 0, 0 ); 129 | case "yesterday" : return dra( 2, -1 ); 130 | case "lastweek" : return dra( 2, -7, 7 ); 131 | case "lastmonth" : return dra( 1, -1 ); 132 | case "lastyear" : return dra( 0, -1 ); 133 | case "tomorrow" : return dra( 2, 1 ); 134 | case "nextweek" : return dra( 2, 7, 7 ); 135 | case "nextmonth" : return dra( 1, 1 ); 136 | case "nextyear" : return dra(0, 1 ); 137 | } 138 | throw "unknown token " + m[1]; 139 | } else if(m[2]) { // matches ([ 0-9:-]+) 140 | dn = makeArray(now); 141 | var dt = m[2].match(/^(?:(\d{4})(?:\-(\d\d))?(?:\-(\d\d))?)? ?(?:(\d{1,2})(?:\:(\d\d)(?:\:(\d\d))?)?)?$/); 142 | dt.shift(); 143 | for(var p = 0, z = false, i = 0; i < 7; i++) { 144 | if(dt[i]) { 145 | dn[i] = parseInt(dt[i], 10); 146 | p = i; 147 | z = true; 148 | } else { 149 | if(z) 150 | dn[i] = i < 3 ? 1 : 0; 151 | } 152 | } 153 | return makePrecRange(dn, p); 154 | } else if(m[3]) { // matches (\d+[a-z]{1,4}) 155 | var dr = m[3].match(/(\d+)\s*([a-z]+)/i); 156 | var n = parseInt(dr[1], 10); 157 | return { rel: n * drp._relTokens[dr[2]] }; 158 | } 159 | throw "unknown term " + term; 160 | } 161 | 162 | if(!v) { 163 | return { start: null, end: null }; 164 | } 165 | var terms = v.split(/\s*([^<>]*[^<>-])?\s*(->|<>|<)?\s*([^<>]+)?\s*/); 166 | 167 | var term1 = terms[1] ? procTerm(terms[1]) : null; 168 | var op = terms[2] || ""; 169 | var term2 = terms[3] ? procTerm(terms[3]) : null; 170 | 171 | if(op === "<" || op === "->" ) { 172 | if(term1 && !term2) { 173 | return { start: term1.start, end: null }; 174 | } else if(!term1 && term2) { 175 | return { start: null, end: term2.end }; 176 | } else { 177 | if(term2.rel) { 178 | return { start: term1.start, end: term1.end + term2.rel }; 179 | } else if(term1.rel) { 180 | return { start: term2.start - term1.rel, end: term2.end }; 181 | } else { 182 | return { start: term1.start, end: term2.end }; 183 | } 184 | } 185 | } else if(op === "<>") { 186 | if(!term2) { 187 | return { start: term1.start - drp.defaultRange, end: term1.end + drp.defaultRange } 188 | } else { 189 | if(! ("rel" in term2)) throw "second term did not hav a range"; 190 | return { start: term1.start - term2.rel, end: term1.end + term2.rel }; 191 | } 192 | } else { 193 | if(term1.rel) { 194 | return { start: now - term1.rel, end: now + term1.rel }; 195 | } else if(term1.now) { 196 | return { start: term1.now - drp.defaultRange, end: term1.now + drp.defaultRange }; 197 | } else { 198 | return { start: term1.start, end: term1.end }; 199 | } 200 | } 201 | throw "could not process value " + v; 202 | }; 203 | })(); -------------------------------------------------------------------------------- /src/vendor/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobz/elasticsearch-head/2d51fecac2980d350fcd3319fd9fe2999f63c9db/src/vendor/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /src/vendor/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobz/elasticsearch-head/2d51fecac2980d350fcd3319fd9fe2999f63c9db/src/vendor/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/vendor/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobz/elasticsearch-head/2d51fecac2980d350fcd3319fd9fe2999f63c9db/src/vendor/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/vendor/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mobz/elasticsearch-head/2d51fecac2980d350fcd3319fd9fe2999f63c9db/src/vendor/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/vendor/i18n/i18n.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | /** 3 | * provides text formatting and i18n key storage features
    4 | * implements most of the Sun Java MessageFormat functionality. 5 | * @see Sun's Documentation 6 | */ 7 | 8 | var keys = {}; 9 | var locale = undefined; 10 | 11 | var format = function(message, args) { 12 | var substitute = function() { 13 | var format = arguments[1].split(','); 14 | var substr = escape(args[format.shift()]); 15 | if(format.length === 0) { 16 | return substr; // simple substitution eg {0} 17 | } 18 | switch(format.shift()) { 19 | case "number" : return (new Number(substr)).toLocaleString(locale); 20 | case "date" : return (new Date(+substr)).toLocaleDateString(locale); // date and time require milliseconds since epoch 21 | case "time" : return (new Date(+substr)).toLocaleTimeString(locale); // eg i18n.text("Key", +(new Date())); for current time 22 | } 23 | var styles = format.join("").split("|").map(function(style) { 24 | return style.match(/(-?[\.\d]+)(#|<)([^{}]*)/); 25 | }); 26 | var match = styles[0][3]; 27 | for(var i=0; i elm.dataset && elm.dataset.langs).dataset; 84 | if( ! data["langs"] ) { 85 | return; 86 | } 87 | var langs = data["langs"].split(/\s*,\s*/); 88 | var script0 = scripts[0]; 89 | function install( lang ) { 90 | var s = document.createElement("script"); 91 | s.src = data["basedir"] + "/" + lang + '_strings.js'; 92 | s.async = false; 93 | script0.parentNode.appendChild(s); 94 | script0 = s; 95 | 96 | i18n.setLocale(lang); 97 | } 98 | 99 | install( langs.shift() ); // always install primary language 100 | userLang && langs 101 | .filter( function( lang ) { return userLang.indexOf( lang ) === 0; } ) 102 | .forEach( install ); 103 | }()); 104 | -------------------------------------------------------------------------------- /src/vendor/joey/joey.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var joey = this.joey = function joey(elementDef, parentNode) { 4 | return createNode( elementDef, parentNode, parentNode ? parentNode.ownerDocument : this.document ); 5 | }; 6 | 7 | var shortcuts = joey.shortcuts = { 8 | "text" : "textContent", 9 | "cls" : "className" 10 | }; 11 | 12 | var plugins = joey.plugins = [ 13 | function( obj, context ) { 14 | if( typeof obj === 'string' ) { 15 | return context.createTextNode( obj ); 16 | } 17 | }, 18 | function( obj, context ) { 19 | if( "tag" in obj ) { 20 | var el = context.createElement( obj.tag ); 21 | for( var attr in obj ) { 22 | addAttr( el, attr, obj[ attr ], context ); 23 | } 24 | return el; 25 | } 26 | } 27 | ]; 28 | 29 | function addAttr( el, attr, value, context ) { 30 | attr = shortcuts[attr] || attr; 31 | if( attr === 'children' ) { 32 | for( var i = 0; i < value.length; i++) { 33 | createNode( value[i], el, context ); 34 | } 35 | } else if( attr === 'style' || attr === 'dataset' ) { 36 | for( var prop in value ) { 37 | el[ attr ][ prop ] = value[ prop ]; 38 | } 39 | } else if( attr.indexOf("on") === 0 ) { 40 | el.addEventListener( attr.substr(2), value, false ); 41 | } else if( value !== undefined ) { 42 | el[ attr ] = value; 43 | } 44 | } 45 | 46 | function createNode( obj, parent, context ) { 47 | var el; 48 | if( obj != null ) { 49 | plugins.some( function( plug ) { 50 | return ( el = plug( obj, context ) ); 51 | }); 52 | parent && parent.appendChild( el ); 53 | return el; 54 | } 55 | } 56 | 57 | }()); 58 | -------------------------------------------------------------------------------- /src/vendor/nohtml/jquery-nohtml.js: -------------------------------------------------------------------------------- 1 | (function($, document) { 2 | 3 | var create = $.create = (function() { 4 | 5 | function addAttrs( el, obj, context ) { 6 | for( var attr in obj ){ 7 | switch( attr ) { 8 | case 'tag' : 9 | break; 10 | case 'html' : 11 | el.innerHTML = obj[ attr ]; 12 | break; 13 | case 'css' : 14 | for( var style in obj.css ) { 15 | $.attr( el.style, style, obj.css[ style ] ); 16 | } 17 | break; 18 | case 'text' : case 'child' : case 'children' : 19 | createNode( obj[attr], el, context ); 20 | break; 21 | case 'cls' : 22 | el.className = obj[attr]; 23 | break; 24 | case 'data' : 25 | for( var data in obj.data ) { 26 | $.data( el, data, obj.data[data] ); 27 | } 28 | break; 29 | default : 30 | if( attr.indexOf("on") === 0 && $.isFunction(obj[attr]) ) { 31 | $.event.add( el, attr.substr(2).replace(/^[A-Z]/, function(a) { return a.toLowerCase(); }), obj[attr] ); 32 | } else { 33 | $.attr( el, attr, obj[attr] ); 34 | } 35 | } 36 | } 37 | } 38 | 39 | function createNode(obj, parent, context) { 40 | if(obj && ($.isArray(obj) || obj instanceof $)) { 41 | for(var ret = [], i = 0; i < obj.length; i++) { 42 | var newNode = createNode(obj[i], parent, context); 43 | if(newNode) { 44 | ret.push(newNode); 45 | } 46 | } 47 | return ret; 48 | } 49 | var el; 50 | if(typeof(obj) === 'string') { 51 | el = context.createTextNode( obj ); 52 | } else if(!obj) { 53 | return undefined; 54 | } else if(obj.nodeType === 1) { 55 | el = obj; 56 | } else if( obj instanceof app.ui.AbstractWidget ) { 57 | el = obj.el[0]; 58 | } else { 59 | el = context.createElement( obj.tag || 'DIV' ); 60 | addAttrs(el, obj, context); 61 | } 62 | if(parent){ parent.appendChild(el); } 63 | return el; 64 | } 65 | 66 | return function(elementDef, parentNode) { 67 | return createNode(elementDef, parentNode, (parentNode && parentNode.ownerDocument) || document); 68 | }; 69 | 70 | })(); 71 | 72 | 73 | // inject create into jquery internals so object definitions are treated as first class constructors (overrides non-public methods) 74 | var clean = $.clean, 75 | init = $.fn.init; 76 | 77 | $.clean = function( elems, context, fragment, scripts ) { 78 | for(var i = 0; i < elems.length; i++) { 79 | if( elems[i].tag || elems[i] instanceof app.ui.AbstractWidget ) { 80 | elems[i] = create( elems[i], null, context ); 81 | } 82 | } 83 | return clean( elems, context, fragment, scripts ); 84 | }; 85 | 86 | $.fn.init = function( selector, context, rootjQuery ) { 87 | if ( selector && ( selector.tag || selector instanceof app.ui.AbstractWidget )) { 88 | selector = create( selector, null, context ); 89 | } 90 | return init.call( this, selector, context, rootjQuery ); 91 | }; 92 | 93 | $.fn.init.prototype = $.fn; 94 | 95 | })(jQuery, window.document); 96 | 97 | -------------------------------------------------------------------------------- /test/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Elasticsearch UI Demo 7 | 8 | 9 | 10 | 11 | 12 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /test/generators/conflictingField.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | curl -XDELETE 'http://localhost:9200/conflicting_field_type' 4 | echo 5 | curl -XPUT 'http://localhost:9200/conflicting_field_type' 6 | echo 7 | curl -XPUT 'http://localhost:9200/conflicting_field_type/map1/_mapping' -d '{ 8 | "map1": { 9 | "date_formats": ["date_time", "yyyyMMddHHmmss", "yyyyMMddHHmmssSSS"], 10 | "_all": { 11 | "enabled": true, 12 | "store": "yes" 13 | }, 14 | "properties": { 15 | "field1": { 16 | "type": "date", 17 | "store": "yes", 18 | "format": "yyyyMMddHHmmssSSS", 19 | "include_in_all": false 20 | } 21 | } 22 | } 23 | }' 24 | echo 25 | curl -XPUT 'http://localhost:9200/conflicting_field_type/map2/_mapping' -d '{ 26 | "map2": { 27 | "date_formats": ["date_time", "yyyyMMddHHmmss", "yyyyMMddHHmmssSSS"], 28 | "_all": { 29 | "enabled": true, 30 | "store": "yes" 31 | }, 32 | "properties": { 33 | "field1": { 34 | "type": "string", 35 | "store": "yes", 36 | "term_vector": "yes", 37 | "include_in_all": false 38 | } 39 | } 40 | } 41 | }' 42 | echo 43 | curl -XPUT 'http://localhost:9200/conflicting_field_type/map1/1' -d '{ 44 | "field1" : "20110214172449000" 45 | }' 46 | echo 47 | curl -XPUT 'http://localhost:9200/conflicting_field_type/map2/2' -d '{ 48 | "field1" : "Test map2 with string type field" 49 | }' 50 | echo -------------------------------------------------------------------------------- /test/generators/delete_all_indices.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | curl -XDELETE 'http://localhost:9200/conflicting_field_type' 4 | echo 5 | curl -XDELETE 'http://localhost:9200/twitter' 6 | echo 7 | -------------------------------------------------------------------------------- /test/generators/multi_type.sh: -------------------------------------------------------------------------------- 1 | curl -XDELETE 'http://localhost:9200/multi_field_type' 2 | echo 3 | curl -XPUT 'http://localhost:9200/multi_field_type' 4 | echo 5 | curl -XPUT 'http://localhost:9200/multi_field_type/map1/_mapping' -d '{ 6 | "map1": { 7 | "properties": { 8 | "field1": { 9 | "type": "string", 10 | "store": "yes" 11 | }, 12 | "field2": { 13 | "type": "multi_field", 14 | "path": "full", 15 | "fields": { 16 | "field2": { "type": "string" }, 17 | "alt_name": { "type": "string" }, 18 | "alt_name2": { "type": "string" } 19 | } 20 | }, 21 | "field3": { 22 | "type": "multi_field", 23 | "path": "just_name", 24 | "fields": { 25 | "field3": { "type": "string" }, 26 | "foobar": { "type": "string" } 27 | } 28 | }, 29 | "field4": { 30 | "type": "multi_field", 31 | "path": "just_name", 32 | "fields": { 33 | "field4": { "type": "string" }, 34 | "foobar": { "type": "string" } 35 | } 36 | }, 37 | "field5": { 38 | "type": "string" 39 | } 40 | } 41 | } 42 | }' 43 | echo 44 | curl -XPUT 'http://localhost:9200/multi_field_type/map1/1' -d '{ 45 | "field1": "Whats the dogs name", 46 | "field2": "Max", 47 | "field3": "Hey Janelle, whats wrong with Wolfie? I can hear him barking", 48 | "field4": "Wolfies fine, honey, Wolfies just fine. Where are you", 49 | "field5": "Your foster parents are dead" 50 | }' 51 | echo 52 | curl -XPUT 'http://localhost:9200/multi_field_type/map1/2' -d '{ 53 | "field1": "Nice night for a walk, eh", 54 | "field2": "Nice night for a walk", 55 | "field3": "Wash day tomorrow? Nothing clean, right?", 56 | "field4": "Nothing clean. Right", 57 | "field5": "Hey, I think this guys a couple cans short of a six-pack" 58 | }' 59 | echo 60 | curl -XPUT 'http://localhost:9200/multi_field_type/map1/3' -d '{ 61 | "field1": "The 600 series had rubber skin. We spotted them easy, but these are new. They look human... sweat, bad breath, everything. Very hard to spot. I had to wait till he moved on you before I could zero him", 62 | "field2": "Look... I am not stupid, you know. They cannot make things like that yet.", 63 | "field3": "Not yet. Not for about 40 years", 64 | "field4": "Are you saying its from the future?", 65 | "field5": "One possible future. From your point of view... I dont know tech stuff" 66 | }' 67 | echo 68 | curl -XPUT 'http://localhost:9200/multi_field_type/map1/4' -d '{ 69 | "field1": "Did you see this war?", 70 | "field2": "No. I grew up after. In the ruins... starving... hiding from H-Ks", 71 | "field3": "H-Ks?", 72 | "field4": "Hunter-Killers. Patrol machines built in automated factories. Most of us were rounded up, put in camps for orderly disposal", 73 | "field5": "This is burned in by laser scan. Some of us were kept alive... to work... loading bodies. The disposal units ran night and day. We were that close to going out forever. But there was one man who taught us to fight, to storm the wire of the camps, to smash those metal motherfuckers into junk. He turned it around. He brought us back from the brink. His name is Connor. John Connor. Your son, Sarah, your unborn son" 74 | }' 75 | echo 76 | -------------------------------------------------------------------------------- /test/generators/twitter_feed.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | curl -XDELETE 'http://localhost:9200/twitter' 4 | echo 5 | curl -XPUT 'http://localhost:9200/twitter' 6 | echo 7 | curl -XPUT 'http://localhost:9200/twitter/_mapping' -d '{ 8 | "tweet": { 9 | "date_formats": ["date_time", "yyyyMMddHHmmss", "yyyyMMddHHmmssSSS"], 10 | "properties" : { 11 | "user" : { "type" : "string", "index" : "not_analyzed" }, 12 | "message" : { "type" : "string" }, 13 | "postDate" : { "type" : "date" }, 14 | "srcAddr" : { "type" : "ip" }, 15 | "priority" : { "type" : "integer", null_value: 1 }, 16 | "rank" : { "type" : "float", null_value: 1.0 }, 17 | "loc" : { "type": "geo_point" } 18 | } 19 | } 20 | }' 21 | echo 22 | curl -XPUT 'http://localhost:9200/twitter/tweet/1' -d '{ 23 | "user" : "mobz", 24 | "message" : "developing a tool to search with", 25 | "postDate" : "20110220100330", 26 | "srcAddr" : "203.19.74.11", 27 | "loc" : "-37.86,144.90" 28 | }' 29 | echo 30 | curl -XPUT 'http://localhost:9200/twitter/tweet/2' -d '{ 31 | "user" : "mobz", 32 | "message" : "you know, for elastic search", 33 | "postDate" : "20110220095900", 34 | "srcAddr" : "203.19.74.11", 35 | "loc" : "-37.86,144.90" 36 | }' 37 | echo 38 | curl -XPUT 'http://localhost:9200/twitter/tweet/3' -d '{ 39 | "user" : "mobz", 40 | "message" : "lets take some matilda bay", 41 | "postDate" : "20110221171330", 42 | "srcAddr" : "203.19.74.11", 43 | "loc" : "-37.86,144.90" 44 | }' 45 | echo -------------------------------------------------------------------------------- /test/generators/twitter_river.sh: -------------------------------------------------------------------------------- 1 | curl -XDELETE 'http://localhost:9200/twitter_river' 2 | echo 3 | curl -XDELETE 'http://localhost:9200/_river/twitter_river' 4 | echo 5 | curl -XPUT 'http://localhost:9200/twitter_river' 6 | echo 7 | read -p "consumer key: " consumer_key 8 | read -p "consumer secret: " consumer_secret 9 | read -p "access token: " access_token 10 | read -p "access token secret: " access_token_secret 11 | curl -XPUT 'localhost:9200/_river/twitter_river/_meta' -d ' 12 | { 13 | "type" : "twitter", 14 | "twitter" : { 15 | "oauth": { 16 | "consumer_key": "'${consumer_key}'", 17 | "consumer_secret": "'${consumer_secret}'", 18 | "access_token": "'${access_token}'", 19 | "access_token_secret": "'${access_token_secret}'" 20 | } 21 | }, 22 | "index": { 23 | "index": "twitter_river", 24 | "type": "status", 25 | "buk_size": 100 26 | } 27 | }' 28 | echo -------------------------------------------------------------------------------- /test/perf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Elasticsearch UI Performance Testing Harness 7 | 8 | 9 | 10 | 11 | 12 | 44 | 45 | 46 | 47 |
    48 | 49 | 50 | 51 |
    52 |
    53 | 54 | 55 | -------------------------------------------------------------------------------- /test/spec/specHelper.js: -------------------------------------------------------------------------------- 1 | // find *Spec.js files in the src directory next to the corresponding source file 2 | 3 | var test = window.test = {}; 4 | 5 | test.cb = (function( jasmine ) { 6 | var callbacks = []; 7 | 8 | return { 9 | use: function() { 10 | callbacks = []; 11 | }, 12 | createSpy: function( name, arg, data, context ) { 13 | return jasmine.createSpy( name ).and.callFake( function() { 14 | callbacks.push( { cb: arguments[ arg || 0 ], data: data, context: context } ); 15 | }); 16 | }, 17 | execOne: function() { 18 | var exec = callbacks.shift(); 19 | exec.cb.apply( exec.context, exec.data ); 20 | }, 21 | execAll: function() { 22 | while( callbacks.length ) { 23 | this.execOne(); 24 | } 25 | } 26 | }; 27 | })( this.jasmine ); 28 | 29 | 30 | test.clock = ( function() { 31 | var id = 0, timers, saved; 32 | var names = [ "setTimeout", "setInterval", "clearTimeout", "clearInterval" ]; 33 | var byNext = function( a, b ) { return a.next - b.next; }; 34 | var mocks = { 35 | setTimeout: function( fn, t ) { 36 | timers.push( { id: id, fn: fn, next: t, t: t, type: "t" } ); 37 | return id++; 38 | }, 39 | clearTimeout: function( id ) { 40 | timers = timers.filter( function( timer ) { return timer.id !== id; } ); 41 | }, 42 | setInterval: function( fn, t ) { 43 | timers.push( { id: id, fn: fn, next: t, t: t, type: "i" } ); 44 | return id++; 45 | }, 46 | clearInterval: function( id ) { 47 | timers = timers.filter( function( timer ) { return timer.id !== id; } ); 48 | } 49 | }; 50 | 51 | return { 52 | steal: function() { 53 | timers = []; 54 | saved = {}; 55 | names.forEach( function( n ) { 56 | saved[n] = window[n]; 57 | window[n] = mocks[n]; 58 | }); 59 | }, 60 | restore: function() { 61 | names.forEach( function( n ) { 62 | window[n] = saved[n]; 63 | }); 64 | timers = null; 65 | saved = null; 66 | }, 67 | tick: function() { 68 | if( timers.length ) { 69 | timers.sort( byNext ); 70 | var t0 = timers[0]; 71 | if( t0.type === "t" ) { 72 | timers.shift(); 73 | } else { 74 | t0.next += t0.t; 75 | } 76 | t0.fn(); 77 | } 78 | } 79 | }; 80 | 81 | })(); 82 | --------------------------------------------------------------------------------