Browse Source

Merge pull request #219 from rashidkpc/query_service

Refactor of queries, filters, dashboard service
Rashid Khan 12 năm trước cách đây
mục cha
commit
fdb3492825
79 tập tin đã thay đổi với 1734 bổ sung4988 xóa
  1. 10 2
      README.md
  2. 8 0
      common/css/bootstrap.light.min.css
  3. 0 858
      common/css/bootstrap.min.css
  4. 0 730
      common/css/bootstrap.min.css.orig
  5. 0 6
      common/css/elasticjs.css
  6. 0 39
      common/css/jquery-jvectormap.css
  7. 10 2
      common/css/main.css
  8. 2 2
      common/lib/shared.js
  9. 0 32
      common/lib/underscore.min.js.old
  10. 2 2
      config.js
  11. 105 68
      dashboards/default.json
  12. 5 6
      index.html
  13. 2 3
      js/app.js
  14. 6 15
      js/controllers.js
  15. 1 33
      js/directives.js
  16. 589 16
      js/services.js
  17. 0 7
      panels/bettermap/editor.html
  18. 32 42
      panels/bettermap/module.js
  19. 0 10
      panels/dashcontrol/editor.html
  20. 6 6
      panels/dashcontrol/load.html
  21. 1 1
      panels/dashcontrol/module.html
  22. 65 206
      panels/dashcontrol/module.js
  23. 3 3
      panels/dashcontrol/save.html
  24. 1 1
      panels/dashcontrol/share.html
  25. 11 7
      panels/derivequeries/editor.html
  26. 28 1
      panels/derivequeries/module.html
  27. 26 40
      panels/derivequeries/module.js
  28. 4 4
      panels/fields/micropanel.html
  29. 11 4
      panels/fields/module.js
  30. 7 0
      panels/filtering/editor.html
  31. 15 0
      panels/filtering/meta.html
  32. 45 0
      panels/filtering/module.html
  33. 46 0
      panels/filtering/module.js
  34. 9 35
      panels/histogram/editor.html
  35. 24 6
      panels/histogram/module.html
  36. 89 82
      panels/histogram/module.js
  37. 0 34
      panels/hits/editor.html
  38. 13 10
      panels/hits/module.html
  39. 42 60
      panels/hits/module.js
  40. 0 7
      panels/map/editor.html
  41. 62 2
      panels/map/module.html
  42. 23 41
      panels/map/module.js
  43. 0 92
      panels/map2/display/binning.js
  44. 0 28
      panels/map2/display/bullseye.js
  45. 0 44
      panels/map2/display/geopoints.js
  46. 0 287
      panels/map2/editor.html
  47. 0 1
      panels/map2/lib/d3.hexbin.v0.min.js
  48. 0 137
      panels/map2/lib/node-geohash.js
  49. 0 1
      panels/map2/lib/queue.v1.min.js
  50. 0 0
      panels/map2/lib/topojson.v1.min.js
  51. 0 0
      panels/map2/lib/world-110m.json
  52. 0 251
      panels/map2/lib/world-country-names.tsv
  53. 0 58
      panels/map2/module.html
  54. 0 452
      panels/map2/module.js
  55. 0 21
      panels/parallelcoordinates/editor.html
  56. 0 42
      panels/parallelcoordinates/module.html
  57. 0 514
      panels/parallelcoordinates/module.js
  58. 52 73
      panels/pie/editor.html
  59. 44 69
      panels/pie/module.js
  60. 7 0
      panels/query/editor.html
  61. 15 0
      panels/query/meta.html
  62. 51 0
      panels/query/module.html
  63. 60 0
      panels/query/module.js
  64. 0 5
      panels/sort/module.html
  65. 0 51
      panels/sort/module.js
  66. 0 17
      panels/stringquery/editor.html
  67. 0 49
      panels/stringquery/module.html
  68. 0 81
      panels/stringquery/module.js
  69. 0 17
      panels/table/editor.html
  70. 26 36
      panels/table/module.js
  71. 0 34
      panels/timepicker/editor.html
  72. 13 3
      panels/timepicker/module.html
  73. 61 76
      panels/timepicker/module.js
  74. 0 32
      panels/trends/editor.html
  75. 2 1
      panels/trends/module.html
  76. 37 64
      panels/trends/module.js
  77. 1 2
      partials/dashboard.html
  78. 62 24
      partials/dasheditor.html
  79. 0 3
      partials/panelgeneral.html

+ 10 - 2
README.md

@@ -5,8 +5,16 @@ Kibana 3 is completely new version of Kibana written entirely in HTML and Javasc
 the Kibana 2 repository at [https://github.com/rashidkpc/Kibana](https://github.com/rashidkpc/Kibana)
 
 ### Important!
-The index pattern format has changed in Kibana 3 milestone 2. Please update your index pattern in the
-timepicker panel for any dashboards you've built. The default has been updated.
+The dashboard storage format has changed in Kibana 3 milestone 3. Existing dashboards are unfortunately not backward compatible. However there's some great new features:
+* Every panel support multi-query
+* Customizable query colors and labels
+* Queries, label and colors are synced across panels at all times
+* New filtering functionality
+* Filters can be toggled and removed 
+* Drill down won't overwrite your queries, labels or colors
+* Confusing group functionality has been removed
+* Index configuration has been moved from the timepicker, to the main dashboard editor
+* The stringquery panel has been replaced with a more polished 'query' panel
 
 More information about Kibana 3 can be found at [http://three.kibana.org](http://three.kibana.org)  
 

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 8 - 0
common/css/bootstrap.light.min.css


+ 0 - 858
common/css/bootstrap.min.css

@@ -1,858 +0,0 @@
-.clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0;}
-.clearfix:after{clear:both;}
-.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;}
-.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}
-article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}
-audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}
-audio:not([controls]){display:none;}
-html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
-a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
-a:hover,a:active{outline:0;}
-sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;}
-sup{top:-0.5em;}
-sub{bottom:-0.25em;}
-img{max-width:100%;width:auto\9;height:auto;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic;}
-#map_canvas img,.google-maps img{max-width:none;}
-button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;}
-button,input{*overflow:visible;line-height:normal;}
-button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;}
-button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}
-label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer;}
-input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield;}
-input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;}
-textarea{overflow:auto;vertical-align:top;}
-@media print{*{text-shadow:none !important;color:#000 !important;background:transparent !important;box-shadow:none !important;} a,a:visited{text-decoration:underline;} a[href]:after{content:" (" attr(href) ")";} abbr[title]:after{content:" (" attr(title) ")";} .ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:"";} pre,blockquote{border:1px solid #999;page-break-inside:avoid;} thead{display:table-header-group;} tr,img{page-break-inside:avoid;} img{max-width:100% !important;} @page {margin:0.5cm;}p,h2,h3{orphans:3;widows:3;} h2,h3{page-break-after:avoid;}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333333;background-color:#ffffff;}
-a{color:#e66700;text-decoration:none;}
-a:hover,a:focus{color:#9a4500;text-decoration:underline;}
-.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
-.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);}
-.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px;}
-.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";line-height:0;}
-.row:after{clear:both;}
-[class*="span"]{float:left;min-height:1px;margin-left:20px;}
-.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;}
-.span12{width:940px;}
-.span11{width:860px;}
-.span10{width:780px;}
-.span9{width:700px;}
-.span8{width:620px;}
-.span7{width:540px;}
-.span6{width:460px;}
-.span5{width:380px;}
-.span4{width:300px;}
-.span3{width:220px;}
-.span2{width:140px;}
-.span1{width:60px;}
-.offset12{margin-left:980px;}
-.offset11{margin-left:900px;}
-.offset10{margin-left:820px;}
-.offset9{margin-left:740px;}
-.offset8{margin-left:660px;}
-.offset7{margin-left:580px;}
-.offset6{margin-left:500px;}
-.offset5{margin-left:420px;}
-.offset4{margin-left:340px;}
-.offset3{margin-left:260px;}
-.offset2{margin-left:180px;}
-.offset1{margin-left:100px;}
-.row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0;}
-.row-fluid:after{clear:both;}
-.row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;}
-.row-fluid [class*="span"]:first-child{margin-left:0;}
-.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%;}
-.row-fluid .span12{width:100%;*width:99.94680851063829%;}
-.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%;}
-.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%;}
-.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%;}
-.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%;}
-.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%;}
-.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%;}
-.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%;}
-.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%;}
-.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%;}
-.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%;}
-.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%;}
-.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%;}
-.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%;}
-.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%;}
-.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%;}
-.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%;}
-.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%;}
-.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%;}
-.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%;}
-.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%;}
-.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%;}
-.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%;}
-.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%;}
-.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%;}
-.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%;}
-.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%;}
-.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%;}
-.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%;}
-.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%;}
-.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%;}
-.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%;}
-.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%;}
-.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%;}
-.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%;}
-.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%;}
-[class*="span"].hide,.row-fluid [class*="span"].hide{display:none;}
-[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right;}
-.container{margin-right:auto;margin-left:auto;*zoom:1;}.container:before,.container:after{display:table;content:"";line-height:0;}
-.container:after{clear:both;}
-.container-fluid{padding-right:20px;padding-left:20px;*zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";line-height:0;}
-.container-fluid:after{clear:both;}
-p{margin:0 0 10px;}
-.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px;}
-small{font-size:85%;}
-strong{font-weight:bold;}
-em{font-style:italic;}
-cite{font-style:normal;}
-.muted{color:#4d4d4d;}
-a.muted:hover,a.muted:focus{color:#333333;}
-.text-warning{color:#c09853;}
-a.text-warning:hover,a.text-warning:focus{color:#a47e3c;}
-.text-error{color:#b94a48;}
-a.text-error:hover,a.text-error:focus{color:#953b39;}
-.text-info{color:#3a87ad;}
-a.text-info:hover,a.text-info:focus{color:#2d6987;}
-.text-success{color:#468847;}
-a.text-success:hover,a.text-success:focus{color:#356635;}
-.text-left{text-align:left;}
-.text-right{text-align:right;}
-.text-center{text-align:center;}
-h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#4d4d4d;}
-h1,h2,h3{line-height:40px;}
-h1{font-size:38.5px;}
-h2{font-size:31.5px;}
-h3{font-size:24.5px;}
-h4{font-size:17.5px;}
-h5{font-size:14px;}
-h6{font-size:11.9px;}
-h1 small{font-size:24.5px;}
-h2 small{font-size:17.5px;}
-h3 small{font-size:14px;}
-h4 small{font-size:14px;}
-.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #aaaaaa;}
-ul,ol{padding:0;margin:0 0 10px 25px;}
-ul ul,ul ol,ol ol,ol ul{margin-bottom:0;}
-li{line-height:20px;}
-ul.unstyled,ol.unstyled{margin-left:0;list-style:none;}
-ul.inline,ol.inline{margin-left:0;list-style:none;}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;*zoom:1;padding-left:5px;padding-right:5px;}
-dl{margin-bottom:20px;}
-dt,dd{line-height:20px;}
-dt{font-weight:bold;}
-dd{margin-left:10px;}
-.dl-horizontal{*zoom:1;}.dl-horizontal:before,.dl-horizontal:after{display:table;content:"";line-height:0;}
-.dl-horizontal:after{clear:both;}
-.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
-.dl-horizontal dd{margin-left:180px;}
-hr{margin:20px 0;border:0;border-top:1px solid #aaaaaa;border-bottom:1px solid #ffffff;}
-abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #4d4d4d;}
-abbr.initialism{font-size:90%;text-transform:uppercase;}
-blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #aaaaaa;}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25;}
-blockquote small{display:block;line-height:20px;color:#4d4d4d;}blockquote small:before{content:'\2014 \00A0';}
-blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #aaaaaa;border-left:0;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;}
-blockquote.pull-right small:before{content:'';}
-blockquote.pull-right small:after{content:'\00A0 \2014';}
-q:before,q:after,blockquote:before,blockquote:after{content:"";}
-address{display:block;margin-bottom:20px;font-style:normal;line-height:20px;}
-code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
-code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;white-space:nowrap;}
-pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}pre.prettyprint{margin-bottom:20px;}
-pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0;}
-.pre-scrollable{max-height:340px;overflow-y:scroll;}
-form{margin:0 0 20px;}
-fieldset{padding:0;margin:0;border:0;}
-legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333333;border:0;border-bottom:1px solid #e5e5e5;}legend small{font-size:15px;color:#4d4d4d;}
-label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px;}
-input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;}
-label{display:block;margin-bottom:5px;}
-select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#333333;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;vertical-align:middle;}
-input,textarea,.uneditable-input{width:206px;}
-textarea{height:auto;}
-textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#ffffff;border:1px solid #cccccc;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border linear .2s, box-shadow linear .2s;-moz-transition:border linear .2s, box-shadow linear .2s;-o-transition:border linear .2s, box-shadow linear .2s;transition:border linear .2s, box-shadow linear .2s;}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82, 168, 236, 0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);}
-input[type="radio"],input[type="checkbox"]{margin:4px 0 0;*margin-top:0;margin-top:1px \9;line-height:normal;}
-input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto;}
-select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px;}
-select{width:220px;border:1px solid #cccccc;background-color:#ffffff;}
-select[multiple],select[size]{height:auto;}
-select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
-.uneditable-input,.uneditable-textarea{color:#4d4d4d;background-color:#fcfcfc;border-color:#cccccc;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;}
-.uneditable-input{overflow:hidden;white-space:nowrap;}
-.uneditable-textarea{width:auto;height:auto;}
-input:-moz-placeholder,textarea:-moz-placeholder{color:#4d4d4d;}
-input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#4d4d4d;}
-input:-webkit-input-placeholder{color:#4d4d4d;}
-textarea:-webkit-input-placeholder{color:#4d4d4d;}
-.radio,.checkbox{min-height:20px;padding-left:20px;}
-.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px;}
-.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px;}
-.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle;}
-.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px;}
-.input-mini{width:60px;}
-.input-small{width:90px;}
-.input-medium{width:150px;}
-.input-large{width:210px;}
-.input-xlarge{width:270px;}
-.input-xxlarge{width:530px;}
-input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0;}
-.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block;}
-input,textarea,.uneditable-input{margin-left:0;}
-.controls-row [class*="span"]+[class*="span"]{margin-left:20px;}
-input.span12,textarea.span12,.uneditable-input.span12{width:926px;}
-input.span11,textarea.span11,.uneditable-input.span11{width:846px;}
-input.span10,textarea.span10,.uneditable-input.span10{width:766px;}
-input.span9,textarea.span9,.uneditable-input.span9{width:686px;}
-input.span8,textarea.span8,.uneditable-input.span8{width:606px;}
-input.span7,textarea.span7,.uneditable-input.span7{width:526px;}
-input.span6,textarea.span6,.uneditable-input.span6{width:446px;}
-input.span5,textarea.span5,.uneditable-input.span5{width:366px;}
-input.span4,textarea.span4,.uneditable-input.span4{width:286px;}
-input.span3,textarea.span3,.uneditable-input.span3{width:206px;}
-input.span2,textarea.span2,.uneditable-input.span2{width:126px;}
-input.span1,textarea.span1,.uneditable-input.span1{width:46px;}
-.controls-row{*zoom:1;}.controls-row:before,.controls-row:after{display:table;content:"";line-height:0;}
-.controls-row:after{clear:both;}
-.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left;}
-.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px;}
-input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eeeeee;}
-input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent;}
-.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853;}
-.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853;}
-.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #dbc59e;}
-.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853;}
-.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48;}
-.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48;}
-.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #d59392;}
-.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48;}
-.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847;}
-.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847;}
-.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7aba7b;}
-.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847;}
-.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad;}
-.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad;}
-.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7ab5d3;}
-.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad;}
-input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b;}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;}
-.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1;}.form-actions:before,.form-actions:after{display:table;content:"";line-height:0;}
-.form-actions:after{clear:both;}
-.help-block,.help-inline{color:#595959;}
-.help-block{display:block;margin-bottom:10px;}
-.help-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding-left:5px;}
-.input-append,.input-prepend{display:inline-block;margin-bottom:10px;vertical-align:middle;font-size:0;white-space:nowrap;}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px;}
-.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2;}
-.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #ffffff;background-color:#eeeeee;border:1px solid #ccc;}
-.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
-.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546;}
-.input-prepend .add-on,.input-prepend .btn{margin-right:-1px;}
-.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}
-.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
-.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px;}
-.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
-.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
-.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}
-.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
-.input-prepend.input-append .btn-group:first-child{margin-left:0;}
-input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}
-.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
-.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px;}
-.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0;}
-.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0;}
-.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px;}
-.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;*zoom:1;margin-bottom:0;vertical-align:middle;}
-.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none;}
-.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block;}
-.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0;}
-.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle;}
-.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0;}
-.control-group{margin-bottom:10px;}
-legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate;}
-.form-horizontal .control-group{margin-bottom:20px;*zoom:1;}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";line-height:0;}
-.form-horizontal .control-group:after{clear:both;}
-.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right;}
-.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0;}.form-horizontal .controls:first-child{*padding-left:180px;}
-.form-horizontal .help-block{margin-bottom:0;}
-.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px;}
-.form-horizontal .form-actions{padding-left:180px;}
-table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0;}
-.table{width:100%;margin-bottom:20px;}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #aaaaaa;}
-.table th{font-weight:bold;}
-.table thead th{vertical-align:bottom;}
-.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0;}
-.table tbody+tbody{border-top:2px solid #aaaaaa;}
-.table .table{background-color:#ffffff;}
-.table-condensed th,.table-condensed td{padding:4px 5px;}
-.table-bordered{border:1px solid #aaaaaa;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th,.table-bordered td{border-left:1px solid #aaaaaa;}
-.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0;}
-.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;}
-.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;}
-.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;}
-.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;}
-.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;-moz-border-radius-bottomleft:0;border-bottom-left-radius:0;}
-.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;-moz-border-radius-bottomright:0;border-bottom-right-radius:0;}
-.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;}
-.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;}
-.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9;}
-.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5;}
-table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0;}
-.table td.span1,.table th.span1{float:none;width:44px;margin-left:0;}
-.table td.span2,.table th.span2{float:none;width:124px;margin-left:0;}
-.table td.span3,.table th.span3{float:none;width:204px;margin-left:0;}
-.table td.span4,.table th.span4{float:none;width:284px;margin-left:0;}
-.table td.span5,.table th.span5{float:none;width:364px;margin-left:0;}
-.table td.span6,.table th.span6{float:none;width:444px;margin-left:0;}
-.table td.span7,.table th.span7{float:none;width:524px;margin-left:0;}
-.table td.span8,.table th.span8{float:none;width:604px;margin-left:0;}
-.table td.span9,.table th.span9{float:none;width:684px;margin-left:0;}
-.table td.span10,.table th.span10{float:none;width:764px;margin-left:0;}
-.table td.span11,.table th.span11{float:none;width:844px;margin-left:0;}
-.table td.span12,.table th.span12{float:none;width:924px;margin-left:0;}
-.table tbody tr.success>td{background-color:#dff0d8;}
-.table tbody tr.error>td{background-color:#f2dede;}
-.table tbody tr.warning>td{background-color:#fcf8e3;}
-.table tbody tr.info>td{background-color:#d9edf7;}
-.table-hover tbody tr.success:hover>td{background-color:#d0e9c6;}
-.table-hover tbody tr.error:hover>td{background-color:#ebcccc;}
-.table-hover tbody tr.warning:hover>td{background-color:#faf2cc;}
-.table-hover tbody tr.info:hover>td{background-color:#c4e3f3;}
-[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat;margin-top:1px;}
-.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png");}
-.icon-glass{background-position:0 0;}
-.icon-music{background-position:-24px 0;}
-.icon-search{background-position:-48px 0;}
-.icon-envelope{background-position:-72px 0;}
-.icon-heart{background-position:-96px 0;}
-.icon-star{background-position:-120px 0;}
-.icon-star-empty{background-position:-144px 0;}
-.icon-user{background-position:-168px 0;}
-.icon-film{background-position:-192px 0;}
-.icon-th-large{background-position:-216px 0;}
-.icon-th{background-position:-240px 0;}
-.icon-th-list{background-position:-264px 0;}
-.icon-ok{background-position:-288px 0;}
-.icon-remove{background-position:-312px 0;}
-.icon-zoom-in{background-position:-336px 0;}
-.icon-zoom-out{background-position:-360px 0;}
-.icon-off{background-position:-384px 0;}
-.icon-signal{background-position:-408px 0;}
-.icon-cog{background-position:-432px 0;}
-.icon-trash{background-position:-456px 0;}
-.icon-home{background-position:0 -24px;}
-.icon-file{background-position:-24px -24px;}
-.icon-time{background-position:-48px -24px;}
-.icon-road{background-position:-72px -24px;}
-.icon-download-alt{background-position:-96px -24px;}
-.icon-download{background-position:-120px -24px;}
-.icon-upload{background-position:-144px -24px;}
-.icon-inbox{background-position:-168px -24px;}
-.icon-play-circle{background-position:-192px -24px;}
-.icon-repeat{background-position:-216px -24px;}
-.icon-refresh{background-position:-240px -24px;}
-.icon-list-alt{background-position:-264px -24px;}
-.icon-lock{background-position:-287px -24px;}
-.icon-flag{background-position:-312px -24px;}
-.icon-headphones{background-position:-336px -24px;}
-.icon-volume-off{background-position:-360px -24px;}
-.icon-volume-down{background-position:-384px -24px;}
-.icon-volume-up{background-position:-408px -24px;}
-.icon-qrcode{background-position:-432px -24px;}
-.icon-barcode{background-position:-456px -24px;}
-.icon-tag{background-position:0 -48px;}
-.icon-tags{background-position:-25px -48px;}
-.icon-book{background-position:-48px -48px;}
-.icon-bookmark{background-position:-72px -48px;}
-.icon-print{background-position:-96px -48px;}
-.icon-camera{background-position:-120px -48px;}
-.icon-font{background-position:-144px -48px;}
-.icon-bold{background-position:-167px -48px;}
-.icon-italic{background-position:-192px -48px;}
-.icon-text-height{background-position:-216px -48px;}
-.icon-text-width{background-position:-240px -48px;}
-.icon-align-left{background-position:-264px -48px;}
-.icon-align-center{background-position:-288px -48px;}
-.icon-align-right{background-position:-312px -48px;}
-.icon-align-justify{background-position:-336px -48px;}
-.icon-list{background-position:-360px -48px;}
-.icon-indent-left{background-position:-384px -48px;}
-.icon-indent-right{background-position:-408px -48px;}
-.icon-facetime-video{background-position:-432px -48px;}
-.icon-picture{background-position:-456px -48px;}
-.icon-pencil{background-position:0 -72px;}
-.icon-map-marker{background-position:-24px -72px;}
-.icon-adjust{background-position:-48px -72px;}
-.icon-tint{background-position:-72px -72px;}
-.icon-edit{background-position:-96px -72px;}
-.icon-share{background-position:-120px -72px;}
-.icon-check{background-position:-144px -72px;}
-.icon-move{background-position:-168px -72px;}
-.icon-step-backward{background-position:-192px -72px;}
-.icon-fast-backward{background-position:-216px -72px;}
-.icon-backward{background-position:-240px -72px;}
-.icon-play{background-position:-264px -72px;}
-.icon-pause{background-position:-288px -72px;}
-.icon-stop{background-position:-312px -72px;}
-.icon-forward{background-position:-336px -72px;}
-.icon-fast-forward{background-position:-360px -72px;}
-.icon-step-forward{background-position:-384px -72px;}
-.icon-eject{background-position:-408px -72px;}
-.icon-chevron-left{background-position:-432px -72px;}
-.icon-chevron-right{background-position:-456px -72px;}
-.icon-plus-sign{background-position:0 -96px;}
-.icon-minus-sign{background-position:-24px -96px;}
-.icon-remove-sign{background-position:-48px -96px;}
-.icon-ok-sign{background-position:-72px -96px;}
-.icon-question-sign{background-position:-96px -96px;}
-.icon-info-sign{background-position:-120px -96px;}
-.icon-screenshot{background-position:-144px -96px;}
-.icon-remove-circle{background-position:-168px -96px;}
-.icon-ok-circle{background-position:-192px -96px;}
-.icon-ban-circle{background-position:-216px -96px;}
-.icon-arrow-left{background-position:-240px -96px;}
-.icon-arrow-right{background-position:-264px -96px;}
-.icon-arrow-up{background-position:-289px -96px;}
-.icon-arrow-down{background-position:-312px -96px;}
-.icon-share-alt{background-position:-336px -96px;}
-.icon-resize-full{background-position:-360px -96px;}
-.icon-resize-small{background-position:-384px -96px;}
-.icon-plus{background-position:-408px -96px;}
-.icon-minus{background-position:-433px -96px;}
-.icon-asterisk{background-position:-456px -96px;}
-.icon-exclamation-sign{background-position:0 -120px;}
-.icon-gift{background-position:-24px -120px;}
-.icon-leaf{background-position:-48px -120px;}
-.icon-fire{background-position:-72px -120px;}
-.icon-eye-open{background-position:-96px -120px;}
-.icon-eye-close{background-position:-120px -120px;}
-.icon-warning-sign{background-position:-144px -120px;}
-.icon-plane{background-position:-168px -120px;}
-.icon-calendar{background-position:-192px -120px;}
-.icon-random{background-position:-216px -120px;width:16px;}
-.icon-comment{background-position:-240px -120px;}
-.icon-magnet{background-position:-264px -120px;}
-.icon-chevron-up{background-position:-288px -120px;}
-.icon-chevron-down{background-position:-313px -119px;}
-.icon-retweet{background-position:-336px -120px;}
-.icon-shopping-cart{background-position:-360px -120px;}
-.icon-folder-close{background-position:-384px -120px;width:16px;}
-.icon-folder-open{background-position:-408px -120px;width:16px;}
-.icon-resize-vertical{background-position:-432px -119px;}
-.icon-resize-horizontal{background-position:-456px -118px;}
-.icon-hdd{background-position:0 -144px;}
-.icon-bullhorn{background-position:-24px -144px;}
-.icon-bell{background-position:-48px -144px;}
-.icon-certificate{background-position:-72px -144px;}
-.icon-thumbs-up{background-position:-96px -144px;}
-.icon-thumbs-down{background-position:-120px -144px;}
-.icon-hand-right{background-position:-144px -144px;}
-.icon-hand-left{background-position:-168px -144px;}
-.icon-hand-up{background-position:-192px -144px;}
-.icon-hand-down{background-position:-216px -144px;}
-.icon-circle-arrow-right{background-position:-240px -144px;}
-.icon-circle-arrow-left{background-position:-264px -144px;}
-.icon-circle-arrow-up{background-position:-288px -144px;}
-.icon-circle-arrow-down{background-position:-312px -144px;}
-.icon-globe{background-position:-336px -144px;}
-.icon-wrench{background-position:-360px -144px;}
-.icon-tasks{background-position:-384px -144px;}
-.icon-filter{background-position:-408px -144px;}
-.icon-briefcase{background-position:-432px -144px;}
-.icon-fullscreen{background-position:-456px -144px;}
-.dropup,.dropdown{position:relative;}
-.dropdown-toggle{*margin-bottom:-3px;}
-.dropdown-toggle:active,.open .dropdown-toggle{outline:0;}
-.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000000;border-right:4px solid transparent;border-left:4px solid transparent;content:"";}
-.dropdown .caret{margin-top:8px;margin-left:2px;}
-.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;}.dropdown-menu.pull-right{right:0;left:auto;}
-.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;}
-.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333333;white-space:nowrap;}
-.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{text-decoration:none;color:#ffffff;background-color:#dc6200;background-image:-moz-linear-gradient(top, #e66700, #cd5c00);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#e66700), to(#cd5c00));background-image:-webkit-linear-gradient(top, #e66700, #cd5c00);background-image:-o-linear-gradient(top, #e66700, #cd5c00);background-image:linear-gradient(to bottom, #e66700, #cd5c00);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe66700', endColorstr='#ffcd5c00', GradientType=0);}
-.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#ffffff;text-decoration:none;outline:0;background-color:#dc6200;background-image:-moz-linear-gradient(top, #e66700, #cd5c00);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#e66700), to(#cd5c00));background-image:-webkit-linear-gradient(top, #e66700, #cd5c00);background-image:-o-linear-gradient(top, #e66700, #cd5c00);background-image:linear-gradient(to bottom, #e66700, #cd5c00);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe66700', endColorstr='#ffcd5c00', GradientType=0);}
-.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#4d4d4d;}
-.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:default;}
-.open{*z-index:1000;}.open>.dropdown-menu{display:block;}
-.pull-right>.dropdown-menu{right:0;left:auto;}
-.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000000;content:"";}
-.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px;}
-.dropdown-submenu{position:relative;}
-.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;}
-.dropdown-submenu:hover>.dropdown-menu{display:block;}
-.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0;}
-.dropdown-submenu>a:after{display:block;content:" ";float:right;width:0;height:0;border-color:transparent;border-style:solid;border-width:5px 0 5px 5px;border-left-color:#cccccc;margin-top:5px;margin-right:-10px;}
-.dropdown-submenu:hover>a:after{border-left-color:#ffffff;}
-.dropdown-submenu.pull-left{float:none;}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px;}
-.dropdown .dropdown-menu .nav-header{padding-left:20px;padding-right:20px;}
-.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
-.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);}
-.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
-.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
-.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;}.fade.in{opacity:1;}
-.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;}.collapse.in{height:auto;}
-.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);}.close:hover,.close:focus{color:#000000;text-decoration:none;cursor:pointer;opacity:0.4;filter:alpha(opacity=40);}
-button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;}
-.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 12px;margin-bottom:0;font-size:14px;line-height:20px;text-align:center;vertical-align:middle;cursor:pointer;color:#333333;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(to bottom, #ffffff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #cccccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333333;background-color:#e6e6e6;*background-color:#d9d9d9;}
-.btn:active,.btn.active{background-color:#cccccc \9;}
-.btn:first-child{*margin-left:0;}
-.btn:hover,.btn:focus{color:#333333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear;}
-.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
-.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);}
-.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
-.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
-.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px;}
-.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
-.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0;}
-.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px;}
-.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
-.btn-block{display:block;width:100%;padding-left:0;padding-right:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}
-.btn-block+.btn-block{margin-top:5px;}
-input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%;}
-.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255, 255, 255, 0.75);}
-.btn-primary{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#e68600;background-image:-moz-linear-gradient(top, #e66700, #e6b400);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#e66700), to(#e6b400));background-image:-webkit-linear-gradient(top, #e66700, #e6b400);background-image:-o-linear-gradient(top, #e66700, #e6b400);background-image:linear-gradient(to bottom, #e66700, #e6b400);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe66700', endColorstr='#ffe6b400', GradientType=0);border-color:#e6b400 #e6b400 #9a7800;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#e6b400;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#ffffff;background-color:#e6b400;*background-color:#cda000;}
-.btn-primary:active,.btn-primary.active{background-color:#b38c00 \9;}
-.btn-warning{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(to bottom, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#f89406;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#ffffff;background-color:#f89406;*background-color:#df8505;}
-.btn-warning:active,.btn-warning.active{background-color:#c67605 \9;}
-.btn-danger{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#da4f49;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(to bottom, #ee5f5b, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#ffffff;background-color:#bd362f;*background-color:#a9302a;}
-.btn-danger:active,.btn-danger.active{background-color:#942a25 \9;}
-.btn-success{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#5bb75b;background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(to bottom, #62c462, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#ffffff;background-color:#51a351;*background-color:#499249;}
-.btn-success:active,.btn-success.active{background-color:#408140 \9;}
-.btn-info{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#49afcd;background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(to bottom, #5bc0de, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#ffffff;background-color:#2f96b4;*background-color:#2a85a0;}
-.btn-info:active,.btn-info.active{background-color:#24748c \9;}
-.btn-inverse{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#333333;background-image:-moz-linear-gradient(top, #444444, #1a1a1a);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#1a1a1a));background-image:-webkit-linear-gradient(top, #444444, #1a1a1a);background-image:-o-linear-gradient(top, #444444, #1a1a1a);background-image:linear-gradient(to bottom, #444444, #1a1a1a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff1a1a1a', GradientType=0);border-color:#1a1a1a #1a1a1a #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#1a1a1a;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#ffffff;background-color:#1a1a1a;*background-color:#0d0d0d;}
-.btn-inverse:active,.btn-inverse.active{background-color:#000000 \9;}
-button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px;}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;}
-button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px;}
-button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px;}
-button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px;}
-.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
-.btn-link{border-color:transparent;cursor:pointer;color:#e66700;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
-.btn-link:hover,.btn-link:focus{color:#9a4500;text-decoration:underline;background-color:transparent;}
-.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333333;text-decoration:none;}
-.btn-group{position:relative;display:inline-block;*display:inline;*zoom:1;font-size:0;vertical-align:middle;white-space:nowrap;*margin-left:.3em;}.btn-group:first-child{*margin-left:0;}
-.btn-group+.btn-group{margin-left:5px;}
-.btn-toolbar{font-size:0;margin-top:10px;margin-bottom:10px;}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px;}
-.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
-.btn-group>.btn+.btn{margin-left:-1px;}
-.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px;}
-.btn-group>.btn-mini{font-size:10.5px;}
-.btn-group>.btn-small{font-size:11.9px;}
-.btn-group>.btn-large{font-size:17.5px;}
-.btn-group>.btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;}
-.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;}
-.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;}
-.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;}
-.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2;}
-.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0;}
-.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);*padding-top:5px;*padding-bottom:5px;}
-.btn-group>.btn-mini+.dropdown-toggle{padding-left:5px;padding-right:5px;*padding-top:2px;*padding-bottom:2px;}
-.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px;}
-.btn-group>.btn-large+.dropdown-toggle{padding-left:12px;padding-right:12px;*padding-top:7px;*padding-bottom:7px;}
-.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);}
-.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6;}
-.btn-group.open .btn-primary.dropdown-toggle{background-color:#e6b400;}
-.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406;}
-.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f;}
-.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351;}
-.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4;}
-.btn-group.open .btn-inverse.dropdown-toggle{background-color:#1a1a1a;}
-.btn .caret{margin-top:8px;margin-left:0;}
-.btn-large .caret{margin-top:6px;}
-.btn-large .caret{border-left-width:5px;border-right-width:5px;border-top-width:5px;}
-.btn-mini .caret,.btn-small .caret{margin-top:8px;}
-.dropup .btn-large .caret{border-bottom-width:5px;}
-.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
-.btn-group-vertical{display:inline-block;*display:inline;*zoom:1;}
-.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
-.btn-group-vertical>.btn+.btn{margin-left:0;margin-top:-1px;}
-.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}
-.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}
-.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0;}
-.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;}
-.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
-.alert,.alert h4{color:#c09853;}
-.alert h4{margin:0;}
-.alert .close{position:relative;top:-2px;right:-21px;line-height:20px;}
-.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847;}
-.alert-success h4{color:#468847;}
-.alert-danger,.alert-error{background-color:#f2dede;border-color:#eed3d7;color:#b94a48;}
-.alert-danger h4,.alert-error h4{color:#b94a48;}
-.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad;}
-.alert-info h4{color:#3a87ad;}
-.alert-block{padding-top:14px;padding-bottom:14px;}
-.alert-block>p,.alert-block>ul{margin-bottom:0;}
-.alert-block p+p{margin-top:5px;}
-.nav{margin-left:0;margin-bottom:20px;list-style:none;}
-.nav>li>a{display:block;}
-.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eeeeee;}
-.nav>li>a>img{max-width:none;}
-.nav>.pull-right{float:right;}
-.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#4d4d4d;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;}
-.nav li+.nav-header{margin-top:9px;}
-.nav-list{padding-left:15px;padding-right:15px;margin-bottom:0;}
-.nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}
-.nav-list>li>a{padding:3px 15px;}
-.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#e66700;}
-.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px;}
-.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;}
-.nav-tabs,.nav-pills{*zoom:1;}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";line-height:0;}
-.nav-tabs:after,.nav-pills:after{clear:both;}
-.nav-tabs>li,.nav-pills>li{float:left;}
-.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;}
-.nav-tabs{border-bottom:1px solid #ddd;}
-.nav-tabs>li{margin-bottom:-1px;}
-.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#aaaaaa #aaaaaa #dddddd;}
-.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#333333;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;}
-.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
-.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#ffffff;background-color:#e66700;}
-.nav-stacked>li{float:none;}
-.nav-stacked>li>a{margin-right:0;}
-.nav-tabs.nav-stacked{border-bottom:0;}
-.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
-.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;}
-.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;}
-.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{border-color:#ddd;z-index:2;}
-.nav-pills.nav-stacked>li>a{margin-bottom:3px;}
-.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;}
-.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;}
-.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
-.nav .dropdown-toggle .caret{border-top-color:#e66700;border-bottom-color:#e66700;margin-top:6px;}
-.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#9a4500;border-bottom-color:#9a4500;}
-.nav-tabs .dropdown-toggle .caret{margin-top:8px;}
-.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff;}
-.nav-tabs .active .dropdown-toggle .caret{border-top-color:#333333;border-bottom-color:#333333;}
-.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer;}
-.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#ffffff;background-color:#4d4d4d;border-color:#4d4d4d;}
-.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:1;filter:alpha(opacity=100);}
-.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#4d4d4d;}
-.tabbable{*zoom:1;}.tabbable:before,.tabbable:after{display:table;content:"";line-height:0;}
-.tabbable:after{clear:both;}
-.tab-content{overflow:auto;}
-.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0;}
-.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;}
-.tab-content>.active,.pill-content>.active{display:block;}
-.tabs-below>.nav-tabs{border-top:1px solid #ddd;}
-.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0;}
-.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-bottom-color:transparent;border-top-color:#ddd;}
-.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd;}
-.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none;}
-.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;}
-.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;}
-.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}
-.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#aaaaaa #dddddd #aaaaaa #aaaaaa;}
-.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;}
-.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;}
-.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
-.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#aaaaaa #aaaaaa #aaaaaa #dddddd;}
-.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;}
-.nav>.disabled>a{color:#4d4d4d;}
-.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;background-color:transparent;cursor:default;}
-.navbar{overflow:visible;margin-bottom:20px;*position:relative;*z-index:2;}
-.navbar-inner{min-height:40px;padding-left:20px;padding-right:20px;background-color:#2e2e2e;background-image:-moz-linear-gradient(top, #404040, #262626);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#404040), to(#262626));background-image:-webkit-linear-gradient(top, #404040, #262626);background-image:-o-linear-gradient(top, #404040, #262626);background-image:linear-gradient(to bottom, #404040, #262626);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff404040', endColorstr='#ff262626', GradientType=0);border:1px solid #080808;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);-moz-box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);*zoom:1;}.navbar-inner:before,.navbar-inner:after{display:table;content:"";line-height:0;}
-.navbar-inner:after{clear:both;}
-.navbar .container{width:auto;}
-.nav-collapse.collapse{height:auto;overflow:visible;}
-.navbar .brand{float:left;display:block;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#ffffff;text-shadow:0 1px 0 #333333;}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none;}
-.navbar-text{margin-bottom:0;line-height:40px;color:#ffffff;}
-.navbar-link{color:#ffffff;}.navbar-link:hover,.navbar-link:focus{color:#aaaaaa;}
-.navbar .divider-vertical{height:40px;margin:0 9px;border-left:1px solid #262626;border-right:1px solid #333333;}
-.navbar .btn,.navbar .btn-group{margin-top:5px;}
-.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0;}
-.navbar-form{margin-bottom:0;*zoom:1;}.navbar-form:before,.navbar-form:after{display:table;content:"";line-height:0;}
-.navbar-form:after{clear:both;}
-.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px;}
-.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0;}
-.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;}
-.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap;}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0;}
-.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0;}.navbar-search .search-query{margin-bottom:0;padding:4px 14px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}
-.navbar-static-top{position:static;margin-bottom:0;}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
-.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0;}
-.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px;}
-.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0;}
-.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-left:0;padding-right:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
-.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;}
-.navbar-fixed-top{top:0;}
-.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,.1);box-shadow:0 1px 10px rgba(0,0,0,.1);}
-.navbar-fixed-bottom{bottom:0;}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,.1);box-shadow:0 -1px 10px rgba(0,0,0,.1);}
-.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;}
-.navbar .nav.pull-right{float:right;margin-right:0;}
-.navbar .nav>li{float:left;}
-.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#ffffff;text-decoration:none;text-shadow:0 1px 0 #333333;}
-.navbar .nav .dropdown-toggle .caret{margin-top:8px;}
-.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{background-color:transparent;color:#aaaaaa;text-decoration:none;}
-.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#aaaaaa;text-decoration:none;background-color:#1a1a1a;-webkit-box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);-moz-box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);}
-.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#212121;background-image:-moz-linear-gradient(top, #262626, #1a1a1a);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#262626), to(#1a1a1a));background-image:-webkit-linear-gradient(top, #262626, #1a1a1a);background-image:-o-linear-gradient(top, #262626, #1a1a1a);background-image:linear-gradient(to bottom, #262626, #1a1a1a);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff262626', endColorstr='#ff1a1a1a', GradientType=0);border-color:#1a1a1a #1a1a1a #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#1a1a1a;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#ffffff;background-color:#1a1a1a;*background-color:#0d0d0d;}
-.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#000000 \9;}
-.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);}
-.btn-navbar .icon-bar+.icon-bar{margin-top:3px;}
-.navbar .nav>li>.dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0, 0, 0, 0.2);position:absolute;top:-7px;left:9px;}
-.navbar .nav>li>.dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:10px;}
-.navbar-fixed-bottom .nav>li>.dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0, 0, 0, 0.2);border-bottom:0;bottom:-7px;top:auto;}
-.navbar-fixed-bottom .nav>li>.dropdown-menu:after{border-top:6px solid #ffffff;border-bottom:0;bottom:-6px;top:auto;}
-.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#aaaaaa;border-bottom-color:#aaaaaa;}
-.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:#1a1a1a;color:#aaaaaa;}
-.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
-.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#aaaaaa;border-bottom-color:#aaaaaa;}
-.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{left:auto;right:0;}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{left:auto;right:12px;}
-.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{left:auto;right:13px;}
-.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{left:auto;right:100%;margin-left:0;margin-right:-1px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px;}
-.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top, #222222, #111111);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111));background-image:-webkit-linear-gradient(top, #222222, #111111);background-image:-o-linear-gradient(top, #222222, #111111);background-image:linear-gradient(to bottom, #222222, #111111);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);border-color:#252525;}
-.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#4d4d4d;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#ffffff;}
-.navbar-inverse .brand{color:#4d4d4d;}
-.navbar-inverse .navbar-text{color:#4d4d4d;}
-.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{background-color:transparent;color:#ffffff;}
-.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#ffffff;background-color:#111111;}
-.navbar-inverse .navbar-link{color:#4d4d4d;}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#ffffff;}
-.navbar-inverse .divider-vertical{border-left-color:#111111;border-right-color:#222222;}
-.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{background-color:#111111;color:#ffffff;}
-.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
-.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#4d4d4d;border-bottom-color:#4d4d4d;}
-.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
-.navbar-inverse .navbar-search .search-query{color:#ffffff;background-color:#515151;border-color:#111111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none;}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#cccccc;}
-.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#cccccc;}
-.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#cccccc;}
-.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;}
-.navbar-inverse .btn-navbar{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e0e0e;background-image:-moz-linear-gradient(top, #151515, #040404);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));background-image:-webkit-linear-gradient(top, #151515, #040404);background-image:-o-linear-gradient(top, #151515, #040404);background-image:linear-gradient(to bottom, #151515, #040404);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0);border-color:#040404 #040404 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#040404;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#ffffff;background-color:#040404;*background-color:#000000;}
-.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000000 \9;}
-.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.breadcrumb>li{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 0 #ffffff;}.breadcrumb>li>.divider{padding:0 5px;color:#ccc;}
-.breadcrumb>.active{color:#4d4d4d;}
-.pagination{margin:20px 0;}
-.pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);}
-.pagination ul>li{display:inline;}
-.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#ffffff;border:1px solid #dddddd;border-left-width:0;}
-.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5;}
-.pagination ul>.active>a,.pagination ul>.active>span{color:#4d4d4d;cursor:default;}
-.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#4d4d4d;background-color:transparent;cursor:default;}
-.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;}
-.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;}
-.pagination-centered{text-align:center;}
-.pagination-right{text-align:right;}
-.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px;}
-.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;}
-.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;}
-.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-top-left-radius:3px;-moz-border-radius-topleft:3px;border-top-left-radius:3px;-webkit-border-bottom-left-radius:3px;-moz-border-radius-bottomleft:3px;border-bottom-left-radius:3px;}
-.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;-moz-border-radius-topright:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;-moz-border-radius-bottomright:3px;border-bottom-right-radius:3px;}
-.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px;}
-.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px;}
-.pager{margin:20px 0;list-style:none;text-align:center;*zoom:1;}.pager:before,.pager:after{display:table;content:"";line-height:0;}
-.pager:after{clear:both;}
-.pager li{display:inline;}
-.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}
-.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5;}
-.pager .next>a,.pager .next>span{float:right;}
-.pager .previous>a,.pager .previous>span{float:left;}
-.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#4d4d4d;background-color:#fff;cursor:default;}
-.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000;}.modal-backdrop.fade{opacity:0;}
-.modal-backdrop,.modal-backdrop.fade.in{opacity:0.8;filter:alpha(opacity=80);}
-.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;outline:none;}.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;}
-.modal.fade.in{top:10%;}
-.modal-header{padding:9px 15px;border-bottom:1px solid #eee;}.modal-header .close{margin-top:2px;}
-.modal-header h3{margin:0;line-height:30px;}
-.modal-body{position:relative;overflow-y:auto;max-height:400px;padding:15px;}
-.modal-form{margin-bottom:0;}
-.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;*zoom:1;}.modal-footer:before,.modal-footer:after{display:table;content:"";line-height:0;}
-.modal-footer:after{clear:both;}
-.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0;}
-.modal-footer .btn-group .btn+.btn{margin-left:-1px;}
-.modal-footer .btn-block+.btn-block{margin-left:0;}
-.tooltip{position:absolute;z-index:1030;display:block;visibility:visible;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);}.tooltip.in{opacity:0.8;filter:alpha(opacity=80);}
-.tooltip.top{margin-top:-3px;padding:5px 0;}
-.tooltip.right{margin-left:3px;padding:0 5px;}
-.tooltip.bottom{margin-top:3px;padding:5px 0;}
-.tooltip.left{margin-left:-3px;padding:0 5px;}
-.tooltip-inner{max-width:200px;padding:8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
-.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid;}
-.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000000;}
-.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000000;}
-.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000000;}
-.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000000;}
-.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#ffffff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);white-space:normal;}.popover.top{margin-top:-10px;}
-.popover.right{margin-left:10px;}
-.popover.bottom{margin-top:10px;}
-.popover.left{margin-left:-10px;}
-.popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0;}.popover-title:empty{display:none;}
-.popover-content{padding:9px 14px;}
-.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid;}
-.popover .arrow{border-width:11px;}
-.popover .arrow:after{border-width:10px;content:"";}
-.popover.top .arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0, 0, 0, 0.25);bottom:-11px;}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#ffffff;}
-.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0, 0, 0, 0.25);}.popover.right .arrow:after{left:1px;bottom:-10px;border-left-width:0;border-right-color:#ffffff;}
-.popover.bottom .arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0, 0, 0, 0.25);top:-11px;}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#ffffff;}
-.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0, 0, 0, 0.25);}.popover.left .arrow:after{right:1px;border-right-width:0;border-left-color:#ffffff;bottom:-10px;}
-.thumbnails{margin-left:-20px;list-style:none;*zoom:1;}.thumbnails:before,.thumbnails:after{display:table;content:"";line-height:0;}
-.thumbnails:after{clear:both;}
-.row-fluid .thumbnails{margin-left:0;}
-.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px;}
-.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);-webkit-transition:all 0.2s ease-in-out;-moz-transition:all 0.2s ease-in-out;-o-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out;}
-a.thumbnail:hover,a.thumbnail:focus{border-color:#e66700;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);}
-.thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto;}
-.thumbnail .caption{padding:9px;color:#333333;}
-.media,.media-body{overflow:hidden;*overflow:visible;zoom:1;}
-.media,.media .media{margin-top:15px;}
-.media:first-child{margin-top:0;}
-.media-object{display:block;}
-.media-heading{margin:0 0 5px;}
-.media>.pull-left{margin-right:10px;}
-.media>.pull-right{margin-left:10px;}
-.media-list{margin-left:0;list-style:none;}
-.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#ffffff;vertical-align:baseline;white-space:nowrap;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#4d4d4d;}
-.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
-.badge{padding-left:9px;padding-right:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px;}
-.label:empty,.badge:empty{display:none;}
-a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer;}
-.label-important,.badge-important{background-color:#b94a48;}
-.label-important[href],.badge-important[href]{background-color:#953b39;}
-.label-warning,.badge-warning{background-color:#f89406;}
-.label-warning[href],.badge-warning[href]{background-color:#c67605;}
-.label-success,.badge-success{background-color:#468847;}
-.label-success[href],.badge-success[href]{background-color:#356635;}
-.label-info,.badge-info{background-color:#3a87ad;}
-.label-info[href],.badge-info[href]{background-color:#2d6987;}
-.label-inverse,.badge-inverse{background-color:#333333;}
-.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a;}
-.btn .label,.btn .badge{position:relative;top:-1px;}
-.btn-mini .label,.btn-mini .badge{top:0;}
-@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-o-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(to bottom, #f5f5f5, #f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
-.progress .bar{width:0%;height:100%;color:#ffffff;float:left;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(to bottom, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width 0.6s ease;-moz-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;}
-.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);}
-.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;}
-.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite;}
-.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(to bottom, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0);}
-.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
-.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(to bottom, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0);}
-.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
-.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(to bottom, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0);}
-.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
-.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(to bottom, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);}
-.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
-.accordion{margin-bottom:20px;}
-.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
-.accordion-heading{border-bottom:0;}
-.accordion-heading .accordion-toggle{display:block;padding:8px 15px;}
-.accordion-toggle{cursor:pointer;}
-.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5;}
-.carousel{position:relative;margin-bottom:20px;line-height:1;}
-.carousel-inner{overflow:hidden;width:100%;position:relative;}
-.carousel-inner>.item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left;}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1;}
-.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block;}
-.carousel-inner>.active{left:0;}
-.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%;}
-.carousel-inner>.next{left:100%;}
-.carousel-inner>.prev{left:-100%;}
-.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0;}
-.carousel-inner>.active.left{left:-100%;}
-.carousel-inner>.active.right{left:100%;}
-.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#1a1a1a;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);}.carousel-control.right{left:auto;right:15px;}
-.carousel-control:hover,.carousel-control:focus{color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90);}
-.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none;}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255, 255, 255, 0.25);border-radius:5px;}
-.carousel-indicators .active{background-color:#fff;}
-.carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:15px;background:#333333;background:rgba(0, 0, 0, 0.75);}
-.carousel-caption h4,.carousel-caption p{color:#ffffff;line-height:20px;}
-.carousel-caption h4{margin:0 0 5px;}
-.carousel-caption p{margin-bottom:0;}
-.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eeeeee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px;}
-.hero-unit li{line-height:30px;}
-.pull-right{float:right;}
-.pull-left{float:left;}
-.hide{display:none;}
-.show{display:block;}
-.invisible{visibility:hidden;}
-.affix{position:fixed;}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 730
common/css/bootstrap.min.css.orig


+ 0 - 6
common/css/elasticjs.css

@@ -1,6 +0,0 @@
-.axis path,
-.axis line {
-    fill: none;
-    stroke: #000;
-    shape-rendering: crispEdges;
-}

+ 0 - 39
common/css/jquery-jvectormap.css

@@ -1,39 +0,0 @@
-.jvectormap-label {
-    position: absolute;
-    display: none;
-    border: solid 1px #CDCDCD;
-    -webkit-border-radius: 3px;
-    -moz-border-radius: 3px;
-    border-radius: 3px;
-    background: #292929;
-    color: white;
-    font-family: sans-serif, Verdana;
-    font-size: smaller;
-    padding: 3px;
-}
-
-.jvectormap-zoomin, .jvectormap-zoomout {
-    position: absolute;
-    left: 10px;
-    -webkit-border-radius: 3px;
-    -moz-border-radius: 3px;
-    border-radius: 3px;
-    background: #292929;
-    padding: 3px;
-    color: white;
-    width: 10px;
-    height: 10px;
-    cursor: pointer;
-    line-height: 10px;
-    text-align: center;
-}
-
-.jvectormap-zoomin {
-    display: none;
-    top: 10px;
-}
-
-.jvectormap-zoomout {
-    display: none;
-    top: 30px;
-}

+ 10 - 2
common/css/main.css

@@ -6,6 +6,12 @@
   color: #000;
 }
 
+.spy {
+  position:absolute;
+  right:0px;
+  top:0px;
+}
+
 .navbar .brand {
   color: #eee;
 }
@@ -101,6 +107,10 @@
   cursor: pointer;
 }
 
+.pointer:hover {
+  color: #fff;
+}
+
 .pointer {
   cursor: pointer;
 }
@@ -113,8 +123,6 @@
   max-width: 500px;
 }
 
-.popover-title { display: none; }
-
 .tiny {
   font-size: 50%;
 }

+ 2 - 2
common/lib/shared.js

@@ -91,7 +91,7 @@ function top_field_values(docs,field,count) {
 }
 
 function add_to_query(original,field,value,negate) {
-  var not = negate ? "NOT " : "";
+  var not = negate ? "-" : "";
   if(value !== '')
     var query = field + ":" + "\"" + addslashes(value.toString()) + "\"";
   else
@@ -284,7 +284,7 @@ function flatten_json(object,root,array) {
         } else if(obj.length === 1 && _.isNumber(obj[0])) {
           array[rootname] = parseFloat(obj[0]);
         } else {
-          array[rootname] = typeof obj === 'undefined' ? null : obj.join(',');
+          array[rootname] = typeof obj === 'undefined' ? null : obj;
         }
       } else {
         flatten_json(obj,rootname,array)

+ 0 - 32
common/lib/underscore.min.js.old

@@ -1,32 +0,0 @@
-// Underscore.js 1.3.3
-// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
-// Underscore is freely distributable under the MIT license.
-// Portions of Underscore are inspired or borrowed from Prototype,
-// Oliver Steele's Functional, and John Resig's Micro-Templating.
-// For all details and documentation:
-// http://documentcloud.github.com/underscore
-(function(){function r(a,c,d){if(a===c)return 0!==a||1/a==1/c;if(null==a||null==c)return a===c;a._chain&&(a=a._wrapped);c._chain&&(c=c._wrapped);if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return!1;switch(e){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:0==a?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
-c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if("object"!=typeof a||"object"!=typeof c)return!1;for(var f=d.length;f--;)if(d[f]==a)return!0;d.push(a);var f=0,g=!0;if("[object Array]"==e){if(f=a.length,g=f==c.length)for(;f--&&(g=f in a==f in c&&r(a[f],c[f],d)););}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return!1;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,h)&&!f--)break;
-g=!f}}d.pop();return g}var s=this,I=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,J=k.unshift,l=p.toString,K=p.hasOwnProperty,y=k.forEach,z=k.map,A=k.reduce,B=k.reduceRight,C=k.filter,D=k.every,E=k.some,q=k.indexOf,F=k.lastIndexOf,p=Array.isArray,L=Object.keys,t=Function.prototype.bind,b=function(a){return new m(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var j=b.each=b.forEach=function(a,
-c,d){if(a!=null)if(y&&a.forEach===y)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===o)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===o)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.map===z)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(A&&
-a.reduce===A){e&&(c=b.bind(c,e));return f?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,i){if(f)d=c.call(e,d,a,b,i);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,
-c,b){var e;G(a,function(a,g,h){if(c.call(b,a,g,h)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b,
-a,g,h)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
-function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&
-(e={value:a,computed:b})});return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){d=Math.floor(Math.random()*(f+1));b[f]=b[d];b[d]=a});return b};b.sortBy=function(a,c,d){var e=b.isFunction(c)?c:function(a){return a[c]};return b.pluck(b.map(a,function(a,b,c){return{value:a,criteria:e.call(d,a,b,c)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c===void 0?1:d===void 0?-1:c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};
-j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:b.isArray(a)||b.isArguments(a)?i.call(a):a.toArray&&b.isFunction(a.toArray)?a.toArray():b.values(a)};b.size=function(a){return b.isArray(a)?a.length:b.keys(a).length};b.first=b.head=b.take=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,
-0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,
-e=[];a.length<3&&(c=true);b.reduce(d,function(d,g,h){if(c?b.last(d)!==g||!d.length:!b.include(d,g)){d.push(g);e.push(a[h])}return d},[]);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1),true);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=
-i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d){d=b.sortedIndex(a,c);return a[d]===c?d:-1}if(q&&a.indexOf===q)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(F&&a.lastIndexOf===F)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){if(arguments.length<=
-1){b=a||0;a=0}for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;){g[f++]=a;a=a+d}return g};var H=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));H.prototype=a.prototype;var b=new H,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=
-i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(null,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i,j=b.debounce(function(){h=
-g=false},c);return function(){d=this;e=arguments;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);j()},c));g?h=true:i=a.apply(d,e);j();g=true;return i}};b.debounce=function(a,b,d){var e;return function(){var f=this,g=arguments;d&&!e&&a.apply(f,g);clearTimeout(e);e=setTimeout(function(){e=null;d||a.apply(f,g)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));
-return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=L||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&
-c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={};j(b.flatten(i.call(arguments,1)),function(b){b in a&&(c[b]=a[b])});return c};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=
-function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFunction=function(a){return l.call(a)=="[object Function]"};
-b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,
-b){return K.call(a,b)};b.noConflict=function(){s._=I;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){M(c,b[c]=a[c])})};var N=0;b.uniqueId=
-function(a){var b=N++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},v;for(v in n)n[n[v]]=v;var O=/\\|'|\r|\n|\t|\u2028|\u2029/g,P=/\\(\\|'|r|n|t|u2028|u2029)/g,w=function(a){return a.replace(P,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(O,function(a){return"\\"+n[a]}).replace(d.escape||
-u,function(a,b){return"'+\n_.escape("+w(b)+")+\n'"}).replace(d.interpolate||u,function(a,b){return"'+\n("+w(b)+")+\n'"}).replace(d.evaluate||u,function(a,b){return"';\n"+w(b)+"\n;__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};
-b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var x=function(a,c){return c?b(a).chain():a},M=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);J.call(a,this._wrapped);return x(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return x(d,
-this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return x(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);

+ 2 - 2
config.js

@@ -20,8 +20,8 @@ var config = new Settings(
   elasticsearch:    "http://"+window.location.hostname+":9200",   
   // elasticsearch: 'http://localhost:9200',
   kibana_index:     "kibana-int", 
-  modules:          ['histogram','map','pie','table','stringquery','sort',
+  modules:          ['histogram','map','pie','table','filtering',
                     'timepicker','text','fields','hits','dashcontrol',
-                    'column','derivequeries','trends','bettermap'],
+                    'column','derivequeries','trends','bettermap','query'],
   }
 );

+ 105 - 68
dashboards/default → dashboards/default.json

@@ -1,5 +1,47 @@
 {
   "title": "Logstash Search",
+  "services": {
+    "query": {
+      "idQueue": [
+        1,
+        2,
+        3,
+        4
+      ],
+      "list": {
+        "0": {
+          "query": "*",
+          "alias": "",
+          "color": "#7EB26D",
+          "id": 0
+        }
+      },
+      "ids": [
+        0
+      ]
+    },
+    "filter": {
+      "idQueue": [
+        1,
+        2
+      ],
+      "list": {
+        "0": {
+          "from": "2013-07-15T16:50:45.363Z",
+          "to": "2013-07-15T17:50:45.363Z",
+          "field": "@timestamp",
+          "type": "time",
+          "mandate": "must",
+          "active": true,
+          "alias": "",
+          "id": 0
+        }
+      },
+      "ids": [
+        0
+      ]
+    }
+  },
   "rows": [
     {
       "title": "Options",
@@ -10,13 +52,14 @@
       "panels": [
         {
           "loading": false,
-          "error": false,
+          "error": "",
           "span": 5,
           "editable": true,
           "group": [
             "default"
           ],
           "type": "timepicker",
+          "status": "Stable",
           "mode": "relative",
           "time_options": [
             "5m",
@@ -26,18 +69,18 @@
             "12h",
             "24h",
             "2d",
-            "5d"
+            "7d",
+            "30d"
           ],
-          "timespan": "6h",
+          "timespan": "1h",
           "timefield": "@timestamp",
-          "index": "[logstash-]YYYY.MM.DD",
-          "defaultindex": "NOINDEX",
-          "index_interval": "day",
+          "timeformat": "",
           "refresh": {
             "enable": false,
             "interval": 30,
             "min": 3
-          }
+          },
+          "filter_id": 0
         },
         {
           "loading": false,
@@ -48,6 +91,7 @@
             "default"
           ],
           "type": "dashcontrol",
+          "status": "Stable",
           "save": {
             "gist": false,
             "elasticsearch": true,
@@ -61,7 +105,6 @@
           },
           "hide_control": false,
           "elasticsearch_size": 20,
-          "elasticsearch_saveto": "kibana-int",
           "temp": true,
           "temp_ttl": "30d"
         }
@@ -82,16 +125,32 @@
           "group": [
             "default"
           ],
-          "type": "stringquery",
+          "type": "query",
+          "status": "Experimental",
           "label": "Search",
           "query": "*",
-          "size": 100,
-          "sort": [
-            "_score",
-            "desc"
+          "history": [],
+          "remember": 10
+        }
+      ]
+    },
+    {
+      "title": "Filters",
+      "height": "50px",
+      "editable": true,
+      "collapse": true,
+      "collapsable": true,
+      "panels": [
+        {
+          "loading": false,
+          "error": false,
+          "span": 12,
+          "editable": true,
+          "group": [
+            "default"
           ],
-          "multi": false,
-          "multi_arrange": "horizontal"
+          "type": "filtering",
+          "status": "Experimental"
         }
       ]
     },
@@ -110,55 +169,33 @@
             "default"
           ],
           "type": "histogram",
+          "status": "Stable",
           "query": [
             {
               "query": "*",
-              "label": "*"
+              "label": "Query"
             }
           ],
-          "interval": "5m",
-          "show": [
-            "points",
-            "lines",
-            "legend",
-            "x-axis",
-            "y-axis"
-          ],
+          "mode": "count",
+          "time_field": "@timestamp",
+          "value_field": null,
+          "auto_int": true,
+          "resolution": 100,
+          "interval": "30s",
+          "fill": 3,
+          "linewidth": 3,
           "timezone": "browser",
           "spyable": true,
           "zoomlinks": true,
-          "fill": 0,
-          "linewidth": 2,
           "bars": true,
-          "stack": true,
+          "stack": false,
           "points": false,
           "lines": false,
           "legend": true,
           "x-axis": true,
-          "y-axis": true
-        },
-        {
-          "loading": false,
-          "span": 0,
-          "editable": true,
-          "group": [
-            "default"
-          ],
-          "type": "hits",
-          "query": [
-            {
-              "query": "*",
-              "label": "*"
-            }
-          ],
-          "style": {
-            "font-size": "9pt"
-          },
-          "aggregate": false,
-          "arrangement": "horizontal",
-          "chart": true,
-          "counters": true,
-          "count_pos": "above"
+          "y-axis": true,
+          "percentage": false,
+          "interactive": true
         }
       ]
     },
@@ -178,6 +215,7 @@
             "default"
           ],
           "type": "fields",
+          "status": "Beta",
           "style": {},
           "arrange": "vertical",
           "micropanel_position": "right",
@@ -196,21 +234,10 @@
             "default"
           ],
           "type": "table",
+          "status": "Stable",
           "query": "*",
-          "interval": "1y",
-          "show": [
-            "bars",
-            "y-axis",
-            "x-axis",
-            "legend"
-          ],
-          "fill": 3,
-          "overflow": "min-height",
-          "timezone": "browser",
-          "spyable": true,
-          "zoomlinks": true,
-          "size": 50,
-          "pages": 10,
+          "size": 100,
+          "pages": 5,
           "offset": 0,
           "sort": [
             "@timestamp",
@@ -219,14 +246,24 @@
           "style": {
             "font-size": "9pt"
           },
+          "overflow": "min-height",
           "fields": [
             "@timestamp",
             "@message"
           ],
-          "sortable": true
+          "highlight": [],
+          "sortable": true,
+          "header": true,
+          "paging": true,
+          "spyable": true
         }
       ]
     }
   ],
-  "editable": true
-}
+  "editable": true,
+  "index": {
+    "interval": "day",
+    "pattern": "[logstash-]YYYY.MM.DD",
+    "default": "logstash-*"
+  }
+}

+ 5 - 6
index.html

@@ -6,18 +6,17 @@
   <head>
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
-    <meta name="description" content="Search based application built using ElasticSearch, elastic.js, and Angular.js">
     <meta name="viewport" content="width=device-width">
 
     <title>Kibana 3</title>
 
     <link rel="stylesheet" href="common/css/normalize.min.css">
-    <link rel="stylesheet" href="common/css/bootstrap.dark.min.css">
+    <link rel="stylesheet" href="common/css/bootstrap.dark.min.css" title="Dark">
+    <link rel="alternate stylesheet" href="common/css/bootstrap.light.min.css" title="Light">
     <link rel="stylesheet" href="common/css/animate.min.css">
     <link rel="stylesheet" href="common/css/bootstrap-responsive.min.css">
     <link rel="stylesheet" href="common/css/font-awesome.min.css">
     <link rel="stylesheet" href="common/css/main.css">
-    <link rel="stylesheet" href="common/css/elasticjs.css">
     <link rel="stylesheet" href="common/css/timepicker.css">
   
     <!-- project dependency libs -->
@@ -38,9 +37,9 @@
     <div class="navbar navbar-static-top">
       <div class="navbar-inner">
         <div class="container-fluid">
-          <p class="navbar-text pull-right"><small><strong>Kibana 3</strong> <small>milestone 2</small></small></p>
-          <span class="brand">{{dashboards.title}}</span>
-          <div class="brand"><i class='icon-cog pointer' ng-show='dashboards.editable' bs-modal="'partials/dasheditor.html'"></i></div>
+          <p class="navbar-text pull-right"><small><strong>Kibana 3</strong> <small>milestone pre-3</small></small></p>
+          <span class="brand">{{dashboard.current.title}}</span>
+          <div class="brand"><i class='icon-cog pointer' ng-show='dashboard.current.editable' bs-modal="'partials/dasheditor.html'"></i></div>
         </div>
       </div>
     </div>

+ 2 - 3
js/app.js

@@ -19,7 +19,6 @@ var scripts = []
 var labjs = $LAB
   .script("common/lib/jquery-1.8.0.min.js").wait()
   .script("common/lib/modernizr-2.6.1.min.js")
-  .script("common/lib/underscore.min.js")  
   .script("common/lib/angular.min.js").wait()
   .script("common/lib/angular-strap.min.js")
   .script("common/lib/angular-sanitize.min.js")
@@ -47,10 +46,10 @@ labjs.wait(function(){
   angular.module('kibana', modules).config(['$routeProvider', function($routeProvider) {
       $routeProvider
         .when('/dashboard', {
-          templateUrl: 'partials/dashboard.html' 
+          templateUrl: 'partials/dashboard.html',
         })
         .when('/dashboard/:type/:id', {
-          templateUrl: 'partials/dashboard.html'
+          templateUrl: 'partials/dashboard.html',
         })
         .when('/dashboard/:type/:id/:params', {
           templateUrl: 'partials/dashboard.html'

+ 6 - 15
js/controllers.js

@@ -3,7 +3,8 @@
 'use strict';
 
 angular.module('kibana.controllers', [])
-.controller('DashCtrl', function($scope, $rootScope, $http, $timeout, ejsResource, eventBus, fields) {
+.controller('DashCtrl', function($scope, $rootScope, $http, $timeout, $route, ejsResource, eventBus, 
+  fields, dashboard) {
 
   var _d = {
     title: "",
@@ -18,28 +19,18 @@ angular.module('kibana.controllers', [])
     // Make underscore.js available to views
     $scope._ = _;
 
+    $scope.dashboard = dashboard;
+
     // Provide a global list of all see fields
     $scope.fields = fields
     $scope.reset_row();
     $scope.clear_all_alerts();
 
-    // Load dashboard by event 
-    eventBus.register($scope,'dashboard', function(event,dashboard){
-      $scope.dashboards = dashboard.dashboard;
-      $scope.dashboards.last = dashboard.last;
-      _.defaults($scope.dashboards,_d)
-    })
-
-    // If the route changes, clear the existing dashboard
-    $rootScope.$on( "$routeChangeStart", function(event, next, current) {
-      delete $scope.dashboards
-    });
-
     var ejs = $scope.ejs = ejsResource(config.elasticsearch);  
   }
 
-  $scope.add_row = function(dashboards,row) {
-    $scope.dashboards.rows.push(row);
+  $scope.add_row = function(dash,row) {
+    dash.rows.push(row);
   }
 
   $scope.reset_row = function() {

+ 1 - 33
js/directives.js

@@ -49,39 +49,7 @@ angular.module('kibana.directives', [])
     }
   };
 })
-.directive('upload', function(timer){
-  return {
-    restrict: 'A',
-    link: function(scope, elem, attrs) {
-      function file_selected(evt) {
-        var files = evt.target.files; // FileList object
-
-        // files is a FileList of File objects. List some properties.
-        var output = [];
-        for (var i = 0, f; f = files[i]; i++) {
-          var reader = new FileReader();
-          reader.onload = (function(theFile) {
-            return function(e) {
-              // Render thumbnail.
-              scope.dashboards = JSON.parse(e.target.result)
-              timer.cancel_all();
-              scope.$apply();
-            };
-          })(f);
-          reader.readAsText(f);
-        }
-      }
-
-      // Check for the various File API support.
-      if (window.File && window.FileReader && window.FileList && window.Blob) {
-        // Something
-        document.getElementById('upload').addEventListener('change', file_selected, false);
-      } else {
-        alert('Sorry, the HTML5 File APIs are not fully supported in this browser.');
-      }
-    }
-  }
-}).directive('ngModelOnblur', function() {
+.directive('ngModelOnblur', function() {
   return {
     restrict: 'A',
     require: 'ngModel',

+ 589 - 16
js/services.js

@@ -23,7 +23,6 @@ angular.module('kibana.services', [])
     if(_.contains(_types,'$kibana_debug'))
       $rootScope.$broadcast('$kibana_debug',packet);
 
-    //console.log('Sent: '+type + ' to ' + to + ' from ' + from + ': ' + angular.toJson(data))
     $rootScope.$broadcast(type,{
       from: from,
       to: to,
@@ -46,13 +45,12 @@ angular.module('kibana.services', [])
       var _time   = packet.time
       var _group  = (!(_.isUndefined(scope.panel))) ? scope.panel.group : ["NONE"] 
 
-      //console.log('registered:' + type + " for " + scope.panel.title + " " + scope.$id)
       if(!(_.isArray(_to)))
         _to = [_to];
       if(!(_.isArray(_group)))
         _group = [_group];
       
-      // Transmit even only if the send is not the receiver AND one of the following:
+      // Transmit event only if the sender is not the receiver AND one of the following:
       // 1) Receiver has group in _to 2) Receiver's $id is in _to
       // 3) Event is addressed to ALL 4) Receiver is in ALL group 
       if((_.intersection(_to,_group).length > 0 || 
@@ -61,15 +59,14 @@ angular.module('kibana.services', [])
         _.indexOf(_to,'ALL') > -1) &&
         _from !== _id
       ) {
-        //console.log('Got: '+type + ' from ' + _from + ' to ' + _to + ': ' + angular.toJson(packet.data))
         fn(event,packet.data,{time:_time,to:_to,from:_from,type:_type});
       }
     });
   }
-
 })
-/* Service: fields
-   Provides a global list of all seen fields for use in editor panels
+/* 
+  Service: fields
+  Provides a global list of all seen fields for use in editor panels
 */
 .factory('fields', function($rootScope) {
   var fields = {
@@ -84,6 +81,7 @@ angular.module('kibana.services', [])
 
 })
 .service('kbnIndex',function($http) {
+
   // returns a promise containing an array of all indices matching the index
   // pattern that exist in a given range
   this.indices = function(from,to,pattern,interval) {
@@ -119,7 +117,7 @@ angular.module('kibana.services', [])
   }
 
   // this is stupid, but there is otherwise no good way to ensure that when
-  // I extract the date from an object that I'm get the UTC date. Stupid js.
+  // I extract the date from an object that I get the UTC date. Stupid js.
   // I die a little inside every time I call this function.
   // Update: I just read this again. I died a little more inside.
   // Update2: More death.
@@ -186,17 +184,592 @@ angular.module('kibana.services', [])
   }
 
 })
-.service('keylistener', function($rootScope) {
-    var keys = [];
-    $(document).keydown(function (e) {
-      keys[e.which] = true;
+.service('query', function(dashboard) {
+  // Create an object to hold our service state on the dashboard
+  dashboard.current.services.query = dashboard.current.services.query || {};
+  _.defaults(dashboard.current.services.query,{
+    idQueue : [],
+    list : {},
+    ids : [],
+  });
+
+  // For convenience 
+  var _q = dashboard.current.services.query;
+  this.colors = [ 
+    "#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1
+    "#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2
+    "#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0", //3
+    "#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93", //4
+    "#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7", //5
+    "#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B", //6
+    "#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7"  //7
+  ];
+
+
+  // Save a reference to this
+  var self = this;
+
+  this.init = function() {
+    _q = dashboard.current.services.query;
+    self.list = dashboard.current.services.query.list;
+    self.ids = dashboard.current.services.query.ids;
+    
+    if (self.ids.length == 0) {
+      self.set({});
+    }
+  }
+
+  // This is used both for adding queries and modifying them. If an id is passed, the query at that id is updated
+  this.set = function(query,id) {
+    if(!_.isUndefined(id)) {
+      if(!_.isUndefined(self.list[id])) {
+        _.extend(self.list[id],query);
+        return id;
+      } else {
+        return false;
+      }
+    } else {
+      var _id = nextId();
+      var _query = {
+        query: '*',
+        alias: '',
+        color: colorAt(_id),
+        id: _id
+      }
+      _.defaults(query,_query)
+      self.list[_id] = query;
+      self.ids.push(_id)
+      return _id;
+    }
+  }
+
+  this.remove = function(id) {
+    if(!_.isUndefined(self.list[id])) {
+      delete self.list[id];
+      // This must happen on the full path also since _.without returns a copy
+      self.ids = dashboard.current.services.query.ids = _.without(self.ids,id)
+      _q.idQueue.unshift(id)
+      _q.idQueue.sort(function(a,b){return a-b});
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  this.findQuery = function(queryString) {
+    return _.findWhere(self.list,{query:queryString})
+  }
+
+  var nextId = function() {
+    if(_q.idQueue.length > 0) {
+      return _q.idQueue.shift()
+    } else {
+      return self.ids.length;
+    }
+  }
+
+  var colorAt = function(id) {
+    return self.colors[id % self.colors.length]
+  }
+
+  self.init();
+
+})
+.service('filterSrv', function(dashboard, ejsResource) {
+  // Create an object to hold our service state on the dashboard
+  dashboard.current.services.filter = dashboard.current.services.filter || {};
+  _.defaults(dashboard.current.services.filter,{
+    idQueue : [],
+    list : {},
+    ids : [],
+  });
+
+  // For convenience
+  var ejs = ejsResource(config.elasticsearch);  
+  var _f = dashboard.current.services.filter;
+
+  // Save a reference to this
+  var self = this;
+
+  // Call this whenever we need to reload the important stuff
+  this.init = function() {
+    // Accessors
+    self.list = dashboard.current.services.filter.list;
+    self.ids = dashboard.current.services.filter.ids;
+    _f = dashboard.current.services.filter;
+
+    _.each(self.getByType('time',true),function(time) {
+      self.list[time.id].from = new Date(time.from)
+      self.list[time.id].to = new Date(time.to)
+    })
+
+  }
+
+  // This is used both for adding filters and modifying them. 
+  // If an id is passed, the filter at that id is updated
+  this.set = function(filter,id) {
+    _.defaults(filter,{mandate:'must'})
+    filter.active = true;
+    if(!_.isUndefined(id)) {
+      if(!_.isUndefined(self.list[id])) {
+        _.extend(self.list[id],filter);
+        return id;
+      } else {
+        return false;
+      }
+    } else {
+      if(_.isUndefined(filter.type)) {
+        return false;
+      } else {
+        var _id = nextId();
+        var _filter = {
+          alias: '',
+          id: _id
+        }
+        _.defaults(filter,_filter)
+        self.list[_id] = filter;
+        self.ids.push(_id)
+        return _id;
+      }
+    }
+  }
+
+  this.getBoolFilter = function(ids) {
+    // A default match all filter, just in case there are no other filters
+    var bool = ejs.BoolFilter().must(ejs.MatchAllFilter());
+    _.each(ids,function(id) {
+      if(self.list[id].active) {
+        switch(self.list[id].mandate) 
+        {
+        case 'mustNot':
+          bool = bool.mustNot(self.getEjsObj(id));
+          break;
+        case 'should':
+          bool = bool.should(self.getEjsObj(id));
+          break;
+        default:
+          bool = bool.must(self.getEjsObj(id));
+        }
+      }
+    })
+    return bool;
+  }
+
+  this.getEjsObj = function(id) {
+    return self.toEjsObj(self.list[id])
+  }
+
+  this.toEjsObj = function (filter) {
+    if(!filter.active) {
+      return false
+    }
+    switch(filter.type)
+    {
+    case 'time':
+      return ejs.RangeFilter(filter.field)
+        .from(filter.from)
+        .to(filter.to)
+      break;
+    case 'range':
+      return ejs.RangeFilter(filter.field)
+        .from(filter.from)
+        .to(filter.to)
+      break;
+    case 'querystring':
+      return ejs.QueryFilter(ejs.QueryStringQuery(filter.query))
+      break;
+    case 'terms':
+      return ejs.TermsFilter(filter.field,filter.value)
+      break;
+    case 'exists':
+      return ejs.ExistsFilter(filter.field)
+      break;
+    case 'missing':
+      return ejs.MissingFilter(filter.field)
+      break;
+    default:
+      return false;
+    }
+  }
+
+  this.getByType = function(type,inactive) {
+    return _.pick(self.list,self.idsByType(type,inactive))
+  }
+
+  this.removeByType = function(type) {
+    var ids = self.idsByType(type)
+    _.each(ids,function(id) {
+      self.remove(id)
+    })
+    return ids;
+  }
+
+  this.idsByType = function(type,inactive) {
+    var _require = inactive ? {type:type} : {type:type,active:true}
+    return _.pluck(_.where(self.list,_require),'id')
+  }
+
+  // This special function looks for all time filters, and returns a time range according to the mode
+  this.timeRange = function(mode) {
+    var _t = _.where(self.list,{type:'time',active:true})
+    if(_t.length == 0) {
+      return false;
+    }
+    switch(mode) {
+    case "min":
+      return {
+        from: new Date(_.max(_.pluck(_t,'from'))),
+        to: new Date(_.min(_.pluck(_t,'to')))
+      }
+      break;
+    case "max":
+      return {
+        from: new Date(_.min(_.pluck(_t,'from'))),
+        to: new Date(_.max(_.pluck(_t,'to')))
+      }
+      break;
+    default:
+      return false;
+    }
+
+  } 
+
+  this.remove = function(id) {
+    if(!_.isUndefined(self.list[id])) {
+      delete self.list[id];
+      // This must happen on the full path also since _.without returns a copy
+      self.ids = dashboard.current.services.filter.ids = _.without(self.ids,id)
+      _f.idQueue.unshift(id)
+      _f.idQueue.sort(function(a,b){return a-b});
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+
+  var nextId = function() {
+    if(_f.idQueue.length > 0) {
+      return _f.idQueue.shift()
+    } else {
+      return self.ids.length;
+    }
+  }
+
+  // Now init
+  self.init();
+
+})
+.service('dashboard', function($routeParams, $http, $rootScope, $injector, ejsResource, timer, kbnIndex) {
+  // A hash of defaults to use when loading a dashboard
+
+  var _dash = {
+    title: "",
+    editable: true,
+    rows: [],
+    services: {},
+    index: {
+      interval: 'none',
+      pattern: '_all',
+      default: '_all'
+    },
+  };
+
+  // An elasticJS client to use
+  var ejs = ejsResource(config.elasticsearch);  
+  var gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
+
+  // Store a reference to this
+  var self = this;
+  var filterSrv,query;
+
+  this.current = {};
+  this.last = {};
+
+  $rootScope.$on('$routeChangeSuccess',function(){
+    // Clear the current dashboard to prevent reloading
+    self.current = {};
+    self.indices = [];
+    route();
+  })
+
+  var route = function() {
+    // Is there a dashboard type and id in the URL?
+    if(!(_.isUndefined($routeParams.type)) && !(_.isUndefined($routeParams.id))) {
+      var _type = $routeParams.type;
+      var _id = $routeParams.id;
+
+      if(_type === 'elasticsearch')
+        self.elasticsearch_load('dashboard',_id)
+      if(_type === 'temp')
+        self.elasticsearch_load('temp',_id)
+      if(_type === 'file')
+        self.file_load(_id)
+
+    // No dashboard in the URL
+    } else {
+      // Check if browser supports localstorage, and if there's a dashboard 
+      if (Modernizr.localstorage && 
+        !(_.isUndefined(localStorage['dashboard'])) &&
+        localStorage['dashboard'] !== ''
+      ) {
+        var dashboard = JSON.parse(localStorage['dashboard']);
+        _.defaults(dashboard,_dash);
+        self.dash_load(dashboard)
+      // No? Ok, grab default.json, its all we have now
+      } else {
+        self.file_load('default.json')
+      } 
+    }
+  }
+
+  // Since the dashboard is responsible for index computation, we can compute and assign the indices 
+  // here before telling the panels to refresh
+  this.refresh = function() {
+    if(self.current.index.interval !== 'none') {
+      if(filterSrv.idsByType('time').length > 0) {
+        var _range = filterSrv.timeRange('min');
+        kbnIndex.indices(_range.from,_range.to,
+          self.current.index.pattern,self.current.index.interval
+        ).then(function (p) {
+          if(p.length > 0) {
+            self.indices = p;          
+          } else {
+            self.indices = [self.current.index.default]
+          }
+          $rootScope.$broadcast('refresh')
+        });
+      } else {
+        // This is not optimal, we should be getting the entire index list here, or at least every
+        // index that possibly matches the pattern
+        self.indices = [self.current.index.default]
+        $rootScope.$broadcast('refresh')
+      }
+    } else {
+      self.indices = [self.current.index.default]
+      $rootScope.$broadcast('refresh')
+    }
+  }
+
+  this.dash_load = function(dashboard) {
+    // Cancel all timers
+    timer.cancel_all();
+
+    // If not using time based indices, use the default index
+    if(dashboard.index.interval === 'none') {
+      self.indices = [dashboard.index.default]
+    }
+
+    self.current = _.clone(dashboard);
+
+    // Ok, now that we've setup the current dashboard, we can inject our services
+    query = $injector.get('query');
+    filterSrv = $injector.get('filterSrv')
+
+    // Make sure these re-init
+    query.init();
+    filterSrv.init();
+
+    if(dashboard.index.interval !== 'none' && filterSrv.idsByType('time').length == 0) {
+    //if(dashboard.index.interval !== 'none') {
+      self.refresh();
+    }
+
+    return true;
+  }
+
+  this.gist_id = function(string) {
+    if(self.is_gist(string))
+      return string.match(gist_pattern)[0].replace(/.*\//, '');
+  }
+
+  this.is_gist = function(string) {
+    if(!_.isUndefined(string) && string != '' && !_.isNull(string.match(gist_pattern)))
+      return string.match(gist_pattern).length > 0 ? true : false;
+    else
+      return false
+  }
+
+  this.to_file = function() {
+    var blob = new Blob([angular.toJson(self.current,true)], {type: "application/json;charset=utf-8"});
+    // from filesaver.js
+    saveAs(blob, self.current.title+"-"+new Date().getTime());
+    return true;
+  }
+
+  this.set_default = function(dashboard) {
+    if (Modernizr.localstorage) {
+      localStorage['dashboard'] = angular.toJson(dashboard || self.current);
+      return true;
+    } else {
+      return false;
+    }  
+  }
+
+  this.purge_default = function() {
+    if (Modernizr.localstorage) {
+      localStorage['dashboard'] = '';
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  // TOFIX: Pretty sure this breaks when you're on a saved dashboard already
+  this.share_link = function(title,type,id) {
+    return {
+      location  : location.href.replace(location.hash,""),
+      type      : type,
+      id        : id,
+      link      : location.href.replace(location.hash,"")+"#dashboard/"+type+"/"+id,
+      title     : title
+    };
+  }
+
+  this.file_load = function(file) {
+    return $http({
+      url: "dashboards/"+file,
+      method: "GET",
+    }).then(function(result) {
+      var _dashboard = result.data
+      _.defaults(_dashboard,_dash);
+      self.dash_load(_dashboard);
+      return true;
+    },function(result) {
+      return false;
     });
+  }
 
-    $(document).keyup(function (e) {
-      delete keys[e.which];
+  this.elasticsearch_load = function(type,id) {
+    var request = ejs.Request().indices(config.kibana_index).types(type);
+    var results = request.query(
+      ejs.IdsQuery(id)
+    ).doSearch();
+    return results.then(function(results) {
+      if(_.isUndefined(results)) {
+        return false;
+      } else {
+        self.dash_load(angular.fromJson(results.hits.hits[0]['_source']['dashboard']))
+        return true;
+      }
     });
+  }
 
-    this.keyActive = function(key) {
-      return keys[key] == true;
+  this.elasticsearch_save = function(type,title,ttl) {
+    // Clone object so we can modify it without influencing the existing obejct
+    var save = _.clone(self.current)
+
+    // Change title on object clone
+    if (type === 'dashboard') {
+      var id = save.title = _.isUndefined(title) ? self.current.title : title;
     }
+
+    // Create request with id as title. Rethink this.
+    var request = ejs.Document(config.kibana_index,type,id).source({
+      user: 'guest',
+      group: 'guest',
+      title: save.title,
+      dashboard: angular.toJson(save)
+    })
+    
+    if (type === 'temp')
+      request = request.ttl(ttl)
+
+    // TOFIX: Implement error handling here
+    return request.doIndex(
+      // Success
+      function(result) {
+        return result;
+      },
+      // Failure
+      function(result) {
+        return false;
+      }
+    );
+  }
+
+  this.elasticsearch_delete = function(id) {
+    return ejs.Document(config.kibana_index,'dashboard',id).doDelete(
+      // Success
+      function(result) {
+        return result;
+      },
+      // Failure
+      function(result) {
+        return false;
+      }
+    );
+  }
+
+  this.elasticsearch_list = function(query,count) {
+    var request = ejs.Request().indices(config.kibana_index).types('dashboard');
+    return request.query(
+      ejs.QueryStringQuery(query || '*')
+      ).size(count).doSearch(
+        // Success
+        function(result) {
+          return result;
+        },
+        // Failure
+        function(result) {
+          return false;
+        }
+      );
+  }
+
+  // TOFIX: Gist functionality
+  this.save_gist = function(title,dashboard) {
+    var save = _.clone(dashboard || self.current)
+    save.title = title || self.current.title;
+    return $http({
+      url: "https://api.github.com/gists",
+      method: "POST",
+      data: {
+        "description": save.title,
+        "public": false,
+        "files": {
+          "kibana-dashboard.json": {
+            "content": angular.toJson(save,true)
+          }
+        }
+      }
+    }).then(function(data, status, headers, config) {
+      return data.data.html_url;
+    }, function(data, status, headers, config) {
+      return false;
+    });
+  }
+
+  this.gist_list = function(id) {
+    return $http.jsonp("https://api.github.com/gists/"+id+"?callback=JSON_CALLBACK"
+    ).then(function(response) {
+      var files = []
+      _.each(response.data.data.files,function(v,k) {
+        try {
+          var file = JSON.parse(v.content)
+          files.push(file)
+        } catch(e) {
+          // Nothing?
+        }
+      });
+      return files;
+    }, function(data, status, headers, config) {
+      return false;
+    });
+  }
+
+})
+.service('keylistener', function($rootScope) {
+  var keys = [];
+  $(document).keydown(function (e) {
+    keys[e.which] = true;
+  });
+
+  $(document).keyup(function (e) {
+    delete keys[e.which];
+  });
+
+  this.keyActive = function(key) {
+    return keys[key] == true;
+  }
 });

+ 0 - 7
panels/bettermap/editor.html

@@ -5,13 +5,6 @@
     Also note that geoJSON is <strong>long,lat NOT lat,long</strong>.
     </div>
   </div>
-
-  <div class="row-fluid">
-    <div class="span11">
-      <h6>Query</h6>
-      <input type="text" style="width:100%" ng-model="panel.query">
-    </div>
-  </div>
   <div class="row-fluid">    
     <div class="span4">
       <form>

+ 32 - 42
panels/bettermap/module.js

@@ -12,17 +12,10 @@
   * field :: field containing a 2 element array in the format [lon,lat]
   * tooltip :: field to extract the tool tip value from
   * spyable :: Show the 'eye' icon that reveals the last ES query
-
-  ### Group Events
-  #### Sends
-  * get_time :: On panel initialization get time range to query
-  #### Receives
-  * time :: An object containing the time range to use and the index(es) to query
-  * query :: An Array of queries, this panel uses only the first one
 */
 
 angular.module('kibana.bettermap', [])
-.controller('bettermap', function($scope, eventBus) {
+.controller('bettermap', function($scope, query, dashboard, filterSrv) {
 
   // Set and populate defaults
   var _d = {
@@ -37,44 +30,53 @@ angular.module('kibana.bettermap', [])
   _.defaults($scope.panel,_d)
 
   $scope.init = function() {
-    eventBus.register($scope,'time', function(event,time){set_time(time)});
-    eventBus.register($scope,'query', function(event, query) {
-      $scope.panel.query = _.isArray(query) ? query[0] : query;
+    $scope.$on('refresh',function(){
       $scope.get_data();
-    });
-
-    // Now that we're all setup, request the time from our group
-    eventBus.broadcast($scope.$id,$scope.panel.group,'get_time')
+    })
+    $scope.get_data();
   }
 
-$scope.get_data = function(segment,query_id) {
+  $scope.get_data = function(segment,query_id) {
     $scope.panel.error =  false;
 
     // Make sure we have everything for the request to complete
-    if(_.isUndefined($scope.index) || _.isUndefined($scope.time))
-      return
+    if(dashboard.indices.length == 0) {
+      return;
+    }
 
     if(_.isUndefined($scope.panel.field)) {
       $scope.panel.error = "Please select a field that contains geo point in [lon,lat] format"
       return
     }
     
-    //$scope.panel.loading = true;
+    // Determine the field to sort on
+    var timeField = _.uniq(_.pluck(filterSrv.getByType('time'),'field'))
+    if(timeField.length > 1) {
+      $scope.panel.error = "Time field must be consistent amongst time filters"
+    } else if(timeField.length == 0) {
+      timeField = null;
+    } else {
+      timeField = timeField[0]
+    }
 
     var _segment = _.isUndefined(segment) ? 0 : segment
-    $scope.segment = _segment;
 
-    var request = $scope.ejs.Request().indices($scope.index[_segment])
+    var boolQuery = ejs.BoolQuery();
+    _.each(query.list,function(q) {
+      boolQuery = boolQuery.should(ejs.QueryStringQuery((q.query || '*')))
+    })
+
+    var request = $scope.ejs.Request().indices(dashboard.indices[_segment])
       .query(ejs.FilteredQuery(
-        ejs.QueryStringQuery(($scope.panel.query || '*') + " AND _exists_:"+$scope.panel.field),
-        ejs.RangeFilter($scope.time.field)
-          .from($scope.time.from)
-          .to($scope.time.to)
-        )
-      )
+        boolQuery,
+        filterSrv.getBoolFilter(filterSrv.ids).must(ejs.ExistsFilter($scope.panel.field))
+      ))
       .fields([$scope.panel.field,$scope.panel.tooltip])
       .size($scope.panel.size)
-      .sort($scope.time.field,'desc');
+
+    if(!_.isNull(timeField)) {
+      request = request.sort(timeField,'desc');
+    }
 
     $scope.populate_modal(request)
 
@@ -119,7 +121,7 @@ $scope.get_data = function(segment,query_id) {
       $scope.$emit('draw')
 
       // Get $size results then stop querying
-      if($scope.data.length < $scope.panel.size && _segment+1 < $scope.index.length)
+      if($scope.data.length < $scope.panel.size && _segment+1 < dashboard.indices.length)
         $scope.get_data(_segment+1,$scope.query_id)
 
     });
@@ -130,24 +132,12 @@ $scope.get_data = function(segment,query_id) {
     $scope.modal = {
       title: "Inspector",
       body : "<h5>Last Elasticsearch Query</h5><pre>"+
-          'curl -XGET '+config.elasticsearch+'/'+$scope.index+"/_search?pretty -d'\n"+
+          'curl -XGET '+config.elasticsearch+'/'+dashboard.indices+"/_search?pretty -d'\n"+
           angular.toJson(JSON.parse(request.toString()),true)+
         "'</pre>", 
     } 
   }
 
-  function set_time(time) {
-    $scope.time = time;
-    $scope.index = _.isUndefined(time.index) ? $scope.index : time.index
-    $scope.get_data();
-  }
-
-  $scope.build_search = function(field,value) {
-    $scope.panel.query = add_to_query($scope.panel.query,field,value,false)
-    $scope.get_data();
-    eventBus.broadcast($scope.$id,$scope.panel.group,'query',[$scope.panel.query]);
-  }
-
 })
 .directive('bettermap', function() {
   return {

+ 0 - 10
panels/dashcontrol/editor.html

@@ -28,9 +28,6 @@
     <div class="span3" ng-show="panel.load.elasticsearch">
       <label class="small">ES list size</label><input class="input-mini" type="number" ng-model="panel.elasticsearch_size">
     </div>
-    <div class="span3" ng-show="panel.load.elasticsearch">
-      <label class="small">ES store index</label><input class="input-small" type="text" ng-model="panel.elasticsearch_saveto">
-    </div>
   </div>
   <h5>Sharing</h5>
   <div class="row-fluid">    
@@ -41,11 +38,4 @@
       <label class="small">Shared Link TTL (examples: 1m,1d,1w,30d)</label><input class="input-small" type="text" ng-model="panel.temp_ttl">
     </div>
   </div>
-  <h5>Other Settings</h5>
-  <div class="row-fluid">    
-    <div class="span3" >
-      <label class="small"> Remove this Dashboard Control from saved copies </label><input type="checkbox" ng-model="panel.hide_control" ng-checked="panel.hide_control">    
-    </div>
-  </div>
-   
 </div>

+ 6 - 6
panels/dashcontrol/load.html

@@ -11,12 +11,12 @@
     <h5>Gist <small>Enter a gist number or url</small></h5>
     <form>
       <input type="text" ng-model="gist.url"/><br>
-      <button class="btn" ng-click="gist_dblist(gist_id(gist.url))" ng-show="is_gist(gist.url)"><i class="icon-github-alt"></i> Get gist:{{gist.url | gistid}}</button>
+      <button class="btn" ng-click="gist_dblist(dashboard.gist_id(gist.url))" ng-show="dashboard.is_gist(gist.url)"><i class="icon-github-alt"></i> Get gist:{{gist.url | gistid}}</button>
       <h6 ng-show="gist.files.length">Dashboards in gist:{{gist.url | gistid}} <small>click to load</small></h6>
       <h6 ng-hide="gist.files.length">No gist dashboards found</h6>
       <table class="table table-condensed table-striped">
         <tr ng-repeat="file in gist.files">
-          <td><a ng-click="dash_load(file)">{{file.title}}</a></td>
+          <td><a ng-click="dashboard.dash_load(file)">{{file.title}}</a></td>
         </tr>
       </table>
     </form>
@@ -30,10 +30,10 @@
     <h6 ng-show="elasticsearch.dashboards.length">Elasticsearch stored dashboards</h6>
     <h6 ng-hide="elasticsearch.dashboards.length">No dashboards matching your query found</h6>
     <table class="table table-condensed table-striped">
-      <tr ng-repeat="dashboard in elasticsearch.dashboards">
-        <td><a ng-click="elasticsearch_delete(dashboard)"><i class="icon-remove"></i></a></td>
-        <td><a href="#/dashboard/elasticsearch/{{dashboard._id}}">{{dashboard._id}}</a></td>
-        <td><a><i class="icon-share" ng-click="share_link(dashboard._id,'elasticsearch',dashboard._id)" bs-modal="'panels/dashcontrol/share.html'"></i></a></td>
+      <tr ng-repeat="row in elasticsearch.dashboards">
+        <td><a ng-click="elasticsearch_delete(row._id)"><i class="icon-remove"></i></a></td>
+        <td><a href="#/dashboard/elasticsearch/{{row._id}}">{{row._id}}</a></td>
+        <td><a><i class="icon-share" ng-click="share = dashboard.share_link(row._id,'elasticsearch',row._id)" bs-modal="'panels/dashcontrol/share.html'"></i></a></td>
       </tr>
     </table>
   </div>

+ 1 - 1
panels/dashcontrol/module.html

@@ -2,5 +2,5 @@
   <label class='small'>Dashboard Control</label>
   <button class='btn' ng-show="panel.load.gist || panel.load.elasticsearch || panel.load.local" data-placement="bottom" data-unique="1" ng-click="elasticsearch_dblist(elasticsearch.query)" bs-popover="'panels/dashcontrol/load.html'"><i class='icon-folder-open'></i>  <i class='icon-caret-down'></i></button>
   <button class='btn' ng-show="panel.save.gist || panel.save.elasticsearch || panel.save.local || panel.save.default" data-placement="bottom" data-unique="1" bs-popover="'panels/dashcontrol/save.html'"><i class='icon-save'></i>  <i class='icon-caret-down'></i></button>
-  <button ng-show="panel.temp" class='btn' ng-click="elasticsearch_save('temp')" bs-modal="'panels/dashcontrol/share.html'"><i class='icon-share'></i></button>
+  <button ng-show="panel.temp" class='btn' ng-click="elasticsearch_save('temp',panel.temp_ttl)" bs-modal="'panels/dashcontrol/share.html'"><i class='icon-share'></i></button>
 </kibana-panel>

+ 65 - 206
panels/dashcontrol/module.js

@@ -17,7 +17,6 @@
   ** local :: Allow loading of dashboards from Elasticsearch
   * hide_control :: Upon save, hide this panel
   * elasticsearch_size :: show this many dashboards under the ES section in the load drop down
-  * elasticsearch_saveto :: Special kibana index to save to
   * temp :: Allow saving of temp dashboards
   * temp_ttl :: How long should temp dashboards persist
 
@@ -28,7 +27,8 @@
 */
 
 angular.module('kibana.dashcontrol', [])
-.controller('dashcontrol', function($scope, $routeParams, $http, eventBus, timer) {
+.controller('dashcontrol', function($scope, $http, timer, dashboard) {
+
   $scope.panel = $scope.panel || {};
   // Set and populate defaults
   var _d = {
@@ -47,7 +47,6 @@ angular.module('kibana.dashcontrol', [])
     },
     hide_control: false,
     elasticsearch_size: 20,
-    elasticsearch_saveto: $scope.config.kibana_index,
     temp: true,
     temp_ttl: '30d'
   }
@@ -57,241 +56,101 @@ angular.module('kibana.dashcontrol', [])
   var _dash = {
     title: "",
     editable: true,
-    rows: []
+    rows: [],
+    services: {}
   }
 
   $scope.init = function() {
-    // Long ugly if statement for figuring out which dashboard to load on init
-    // If there is no dashboard defined, find one
-    if(_.isUndefined($scope.dashboards)) {
-      // First check the URL for a path to a dashboard
-      if(!(_.isUndefined($routeParams.type)) && !(_.isUndefined($routeParams.id))) {
-        var _type = $routeParams.type;
-        var _id = $routeParams.id;
-        
-        if(_type === 'elasticsearch')
-          $scope.elasticsearch_load('dashboard',_id)
-        if(_type === 'temp')
-          $scope.elasticsearch_load('temp',_id)
-        if(_type === 'file')
-          $scope.file_load(_id)
-
-      // No dashboard in the URL
-      } else {
-        // Check if browser supports localstorage, and if there's a dashboard 
-        if (Modernizr.localstorage && 
-          !(_.isUndefined(localStorage['dashboard'])) &&
-          localStorage['dashboard'] !== ''
-        ) {
-          var dashboard = JSON.parse(localStorage['dashboard']);
-          _.defaults(dashboard,_dash);
-          $scope.dash_load(JSON.stringify(dashboard))
-        // No? Ok, grab default.json, its all we have now
-        } else {
-          $scope.file_load('default')
-        }
-        
-      }
-    }
-
     $scope.gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
     $scope.gist = {};
     $scope.elasticsearch = {};
   }
 
-  $scope.to_file = function() {
-    var blob = new Blob([angular.toJson($scope.dashboards,true)], {type: "application/json;charset=utf-8"});
-    saveAs(blob, $scope.dashboards.title+"-"+new Date().getTime());
-  }
-
-  $scope.default = function() {
-    if (Modernizr.localstorage) {
-      localStorage['dashboard'] = angular.toJson($scope.dashboards);
-      $scope.alert('Success',
-        $scope.dashboards.title + " has been set as your default dashboard",
-        'success',5000)
+  $scope.set_default = function() {
+    if(dashboard.set_default()) {
+      $scope.alert('Local Default Set',dashboard.current.title+' has been set as your local default','success',5000)
     } else {
-      $scope.alert('Bummer!',
-        "Your browser is too old for this functionality",
-        'error',5000);
-    }  
-  }
-
-  $scope.share_link = function(title,type,id) {
-    $scope.share = {
-      location  : location.href.replace(location.hash,""),
-      type      : type,
-      id        : id,
-      link      : location.href.replace(location.hash,"")+"#dashboard/"+type+"/"+id,
-      title     : title
-    };
-  }
-
-  $scope.purge = function() {
-    if (Modernizr.localstorage) {
-      localStorage['dashboard'] = '';
-      $scope.alert('Success',
-        'Default dashboard cleared',
-        'success',5000)
-    } else {
-      $scope.alert('Doh!',
-        "Your browser is too old for this functionality",
-        'error',5000);
-    }  
-  }
-
-  $scope.file_load = function(file) {
-    $http({
-      url: "dashboards/"+file,
-      method: "GET",
-    }).success(function(data, status, headers, config) {
-      var dashboard = data
-       _.defaults(dashboard,_dash);
-       $scope.dash_load(JSON.stringify(dashboard))
-    }).error(function(data, status, headers, config) {
-      $scope.alert('Default dashboard missing!','Could not locate dashboards/'+file,'error')
-    });
+      $scope.alert('Incompatible Browser','Sorry, your browser is too old for this feature','error',5000)
+    }
   }
 
-  $scope.elasticsearch_save = function(type) {
-    // Clone object so we can modify it without influencing the existing obejct
-    if($scope.panel.hide_control) {
-      $scope.panel.hide = true;
-      var save = _.clone($scope.dashboards)
+  $scope.purge_default = function() {
+    if(dashboard.purge_default()) {
+      $scope.alert('Local Default Clear','Your local default dashboard has been cleared','success',5000)
     } else {
-      var save = _.clone($scope.dashboards)
+      $scope.alert('Incompatible Browser','Sorry, your browser is too old for this feature','error',5000)
     }
-
-    // Change title on object clone
-    if(type === 'dashboard')
-      var id = save.title = $scope.elasticsearch.title;
-
-    // Create request with id as title. Rethink this.
-    var request = $scope.ejs.Document($scope.panel.elasticsearch_saveto,type,id).source({
-      user: 'guest',
-      group: 'guest',
-      title: save.title,
-      dashboard: angular.toJson(save)
-    })
-    
-    if(type === 'temp')
-      request = request.ttl($scope.panel.temp_ttl)
-
-    var result = request.doIndex();
-    var id = result.then(function(result) {
-      $scope.alert('Dashboard Saved','This dashboard has been saved to Elasticsearch','success',5000)
-      $scope.elasticsearch_dblist($scope.elasticsearch.query);
-      $scope.elasticsearch.title = '';
-      if(type === 'temp')
-        $scope.share_link($scope.dashboards.title,'temp',result._id)
-    })
-
-    $scope.panel.hide = false;
   }
 
-  $scope.elasticsearch_delete = function(dashboard) {
-    var result = $scope.ejs.Document($scope.panel.elasticsearch_saveto,'dashboard',dashboard._id).doDelete();
-    result.then(function(result) {
-      $scope.alert('Dashboard Deleted','','success',5000)
-      $scope.elasticsearch.dashboards = _.without($scope.elasticsearch.dashboards,dashboard)
+  $scope.elasticsearch_save = function(type,ttl) {
+    dashboard.elasticsearch_save(type,($scope.elasticsearch.title || dashboard.current.title),ttl).then(
+      function(result) {
+      if(!_.isUndefined(result._id)) {
+        $scope.alert('Dashboard Saved','This dashboard has been saved to Elasticsearch as "' + 
+          result._id + '"','success',5000)
+        if(type === 'temp') {
+          $scope.share = dashboard.share_link(dashboard.current.title,'temp',result._id)
+        }
+      } else {
+        $scope.alert('Save failed','Dashboard could not be saved to Elasticsearch','error',5000)
+      }
     })
   }
 
-  $scope.elasticsearch_load = function(type,id) {
-    var request = $scope.ejs.Request().indices($scope.panel.elasticsearch_saveto).types(type);
-    var results = request.query(
-        $scope.ejs.IdsQuery(id)
-        ).size($scope.panel.elasticsearch_size).doSearch();
-    results.then(function(results) {
-      if(_.isUndefined(results)) {
-        return;
+  $scope.elasticsearch_delete = function(id) {
+    dashboard.elasticsearch_delete(id).then(
+      function(result) {
+        if(!_.isUndefined(result)) {
+          if(result.found) {
+            $scope.alert('Dashboard Deleted',id+' has been deleted','success',5000)
+            // Find the deleted dashboard in the cached list and remove it
+            var toDelete = _.where($scope.elasticsearch.dashboards,{_id:id})[0]
+            $scope.elasticsearch.dashboards = _.without($scope.elasticsearch.dashboards,toDelete)
+          } else {
+            $scope.alert('Dashboard Not Found','Could not find '+id+' in Elasticsearch','warning',5000)
+          }
+        } else {
+          $scope.alert('Dashboard Not Deleted','An error occurred deleting the dashboard',error,5000)
+        }
       }
-      $scope.panel.error =  false;
-      $scope.dash_load(results.hits.hits[0]['_source']['dashboard'])
-    });
+    )
   }
 
   $scope.elasticsearch_dblist = function(query) {
-    if($scope.panel.load.elasticsearch) {
-      var request = $scope.ejs.Request().indices($scope.panel.elasticsearch_saveto).types('dashboard');
-      var results = request.query(
-        $scope.ejs.QueryStringQuery(query || '*')
-        ).size($scope.panel.elasticsearch_size).doSearch();
-      
-      results.then(function(results) {
-        if(_.isUndefined(results.hits)) {
-          return;
-        }
+    dashboard.elasticsearch_list(query,$scope.panel.elasticsearch_size).then(
+      function(result) {
+      if(!_.isUndefined(result.hits)) {
         $scope.panel.error =  false;
-        $scope.hits = results.hits.total;
-        $scope.elasticsearch.dashboards = results.hits.hits
-      });
-    }
+        $scope.hits = result.hits.total;
+        $scope.elasticsearch.dashboards = result.hits.hits
+      }
+    })
   }
 
   $scope.save_gist = function() {
-    var save = _.clone($scope.dashboards)
-    save.title = $scope.gist.title;
-    $http({
-    url: "https://api.github.com/gists",
-    method: "POST",
-    data: {
-      "description": save.title,
-      "public": false,
-      "files": {
-        "kibana-dashboard.json": {
-          "content": angular.toJson(save,true)
-        }
+    dashboard.save_gist($scope.gist.title).then(
+      function(link) {
+      if(!_.isUndefined(link)) {
+        $scope.gist.last = link;
+        $scope.alert('Gist saved','You will be able to access your exported dashboard file at <a href="'+link+'">'+link+'</a> in a moment','success');
+      } else {
+        $scope.alert('Save failed','Gist could not be saved','error',5000)
       }
-    }
-    }).success(function(data, status, headers, config) {
-      $scope.gist.last = data.html_url;
-      $scope.alert('Gist saved','You will be able to access your exported dashboard file at <a href="'+data.html_url+'">'+data.html_url+'</a> in a moment','success')
-    }).error(function(data, status, headers, config) {
-      $scope.alert('Unable to save','Save to gist failed for some reason','error',5000)
-    });
+    })
   }
 
   $scope.gist_dblist = function(id) {
-    $http.jsonp("https://api.github.com/gists/"+id+"?callback=JSON_CALLBACK"
-    ).success(function(response) {
-      $scope.gist.files = []
-      _.each(response.data.files,function(v,k) {
-        try {
-          var file = JSON.parse(v.content)
-          $scope.gist.files.push(file)
-        } catch(e) {
-          $scope.alert('Gist failure','The dashboard file is invalid','warning',5000)
-        }
-      });
-    }).error(function(data, status, headers, config) {
-      $scope.alert('Gist Failed','Could not retrieve dashboard list from gist','error',5000)
-    });
-  }
-
-  $scope.dash_load = function(dashboard) {
-    if(!_.isObject(dashboard))
-      dashboard = JSON.parse(dashboard)
-    eventBus.broadcast($scope.$id,'ALL','dashboard',{
-      dashboard : dashboard,
-      last      : $scope.dashboards
+    dashboard.gist_list(id).then(
+      function(files) {
+      if(files && files.length > 0) {
+        $scope.gist.files = files;
+      } else {
+        $scope.alert('Gist Failed','Could not retrieve dashboard list from gist','error',5000)
+      }
     })
-    timer.cancel_all();
-  }
-
-  $scope.gist_id = function(string) {
-    if($scope.is_gist(string))
-      return string.match($scope.gist_pattern)[0].replace(/.*\//, '');
-  }
-
-  $scope.is_gist = function(string) {
-    if(!_.isUndefined(string) && string != '' && !_.isNull(string.match($scope.gist_pattern)))
-      return string.match($scope.gist_pattern).length > 0 ? true : false;
-    else
-      return false
   }
 })
-.directive('dashUpload', function(timer, eventBus){
+.directive('dashUpload', function(timer, dashboard){
   return {
     restrict: 'A',
     link: function(scope, elem, attrs) {
@@ -304,7 +163,7 @@ angular.module('kibana.dashcontrol', [])
           var reader = new FileReader();
           reader.onload = (function(theFile) {
             return function(e) {
-              scope.dash_load(JSON.parse(e.target.result))
+              dashboard.dash_load(JSON.parse(e.target.result))
               scope.$apply();
             };
           })(f);

+ 3 - 3
panels/dashcontrol/save.html

@@ -6,9 +6,9 @@
     <h5>Locally</h5>
     <form>
       <ul class="nav nav-list">
-        <li><a ng-show="panel.save.local" ng-click="to_file()"><i class="icon-download"></i> Export to File</a></li>
-        <li><a ng-show="panel.save.default" ng-click="default()"><i class="icon-bookmark"></i> Set as My Default</a></li>
-        <li><a ng-show="panel.save.default" ng-click="purge()"><i class="icon-ban-circle"></i> Clear My Default</a></li>    
+        <li><a ng-show="panel.save.local" ng-click="dashboard.to_file()"><i class="icon-download"></i> Export to File</a></li>
+        <li><a ng-show="panel.save.default" ng-click="set_default()"><i class="icon-bookmark"></i> Set as My Default</a></li>
+        <li><a ng-show="panel.save.default" ng-click="purge_default()"><i class="icon-ban-circle"></i> Clear My Default</a></li>    
       </ul>
     </form>
   </div>

+ 1 - 1
panels/dashcontrol/share.html

@@ -4,7 +4,7 @@
 </div>
 <div class="modal-body">
   <label>Share this dashboard with this URL</label>
-  <input ng-model='share.link' type="text" style="width:90%" onclick="this.select()" onfocus="this.select()" ng-change="share_link(share.title,share.type,share.id)">
+  <input ng-model='share.link' type="text" style="width:90%" onclick="this.select()" onfocus="this.select()" ng-change="share = dashboard.share_link(share.title,share.type,share.id)">
 </div>
 <div class="modal-footer">
   <button type="button" class="btn btn-success" ng-click="dismiss();$broadcast('render')">Close</button>

+ 11 - 7
panels/derivequeries/editor.html

@@ -1,22 +1,26 @@
 <div>
-  <div class="row-fluid">    
-    <div class="span12">
-      The derive queries panel takes a query and a field, then runs a terms facet against both and generates a list of terms to query on. For example, you might want to see a histogram of the top 5 requests that return a 404. <strong>You should be careful not to select a high cardinality field</strong> as Elasticsearch must load all of these values into memory.<p>
-      Query Mode allows to optionally append original query to each term in the list.
-    </div>
-  </div>
   <div class="row-fluid">
     <div class="span1">
       <label class="small">Length</label>
       <input type="number" style="width:80%" ng-model="panel.size" ng-change="set_refresh(true)">
     </div>
+    <div class="span3">
+      <label class="small">Field</label>
+      <input type="text" bs-typeahead="fields.list" style="width:80%" ng-change="set_refresh(true)" ng-model='panel.field'></select>
+    </div>
     <div class="span3">
       <label class="small">Query Mode</label>
       <select style="width:80%" ng-change="set_refresh(true)" ng-model='panel.mode' ng-options="f for f in ['terms only','AND', 'OR']"></select>
     </div>
-    <div class="span8">
+    <div class="span5">
       <label class="small">Exclude Terms(s) (comma seperated)</label>
       <input array-join type="text" style="width:90%" ng-change="set_refresh(true)" ng-model='panel.exclude'></input>
     </div>
   </div>
+  <div class="row-fluid">    
+    <div class="span12">
+      The derive queries panel takes a query and a field, runs a terms facet, then creates queries based on them. For example, you might want to see a histogram of the top 5 requests that return a 404. <strong>You should be careful not to select a high cardinality field</strong> as Elasticsearch must load all of these values into memory.<p>
+      Query Mode allows to optionally append original query to each term in the list.
+    </div>
+  </div>
 </div>

+ 28 - 1
panels/derivequeries/module.html

@@ -1,8 +1,23 @@
 <kibana-panel ng-controller='derivequeries' ng-init="init()">
+<style>
+  .end-derive {
+    position:absolute;
+    right:15px;
+    top:5px;
+  }
+  .panel-derive {
+    padding-right: 35px !important;
+    height: 31px !important;
+    -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
+    -moz-box-sizing: border-box;    /* Firefox, other Gecko */
+    box-sizing: border-box;         /* Opera/IE 8+ */
+  }
+</style>
   <span ng-show='panel.spyable' style="position:absolute;right:0px;top:0px" class='panelextra pointer'>
       <i bs-modal="'partials/modal.html'" class="icon-eye-open"></i>
   </span>
-  <div ng-show="!panel.multi">
+  <!--
+  <div>
     <form>
       <table class="form-horizontal">
         <tr>
@@ -29,4 +44,16 @@
       </table>
     </form>
   </div>
+  -->
+
+
+  <label class="small">Create new queries from <strong>{{panel.field}}</strong> ({{panel.mode}} mode)</label>
+  <div>
+    <form class="form-search" style="position:relative" ng-submit="get_data()">
+      <input class="search-query panel-derive input-block-level" bs-typeahead="panel.history" data-min-length=0 data-items=100 type="text" ng-model="panel.query"/>
+      <span class="end-derive">
+        <i class="icon-search pointer" ng-click="get_data()"></i>
+      </span
+    </form>
+  </div>
 </kibana-panel>

+ 26 - 40
panels/derivequeries/module.js

@@ -1,6 +1,6 @@
 /*
 
-  ## Termsquery
+  ## Derivequeries
 
   Broadcasts an array of queries based on the results of a terms facet
 
@@ -11,25 +11,19 @@
   * size :: how many queries to generate
   * fields :: a list of fields known to us
   * query_mode :: how to create query
-  
-  ### Group Events
-  #### Sends
-  * query :: Always broadcast as an array, even in multi: false
-  * get_time :: Request the time object from the timepicker
-  #### Receives
-  * query :: An array of queries. This is probably needs to be fixed.
-  * time :: populate index and time
-  * fields :: A list of fields known to us
+
 */
 
 angular.module('kibana.derivequeries', [])
-.controller('derivequeries', function($scope, eventBus) {
+.controller('derivequeries', function($scope, $rootScope, query, fields, dashboard, filterSrv) {
 
   // Set and populate defaults
   var _d = {
+    loading : false,
     status  : "Beta",
     label   : "Search",
     query   : "*",
+    ids     : [],
     group   : "default",
     field   : '_type',
     fields  : [],
@@ -43,26 +37,19 @@ angular.module('kibana.derivequeries', [])
   _.defaults($scope.panel,_d);
 
   $scope.init = function() {
-    eventBus.register($scope,'fields', function(event, fields) {
-      $scope.panel.fields = fields.all;
-    });
-    eventBus.register($scope,'time', function(event,time){set_time(time)});
-    eventBus.register($scope,'query', function(event, query) {
-      $scope.panel.query = _.isArray(query) ? query[0] : query;
-      $scope.get_data();
-    });
-    // Now that we're all setup, request the time from our group
-    eventBus.broadcast($scope.$id,$scope.panel.group,'get_time')
+    $scope.panel.fields = fields.list
   }
 
   $scope.get_data = function() {
     update_history($scope.panel.query);
+    
     // Make sure we have everything for the request to complete
-    if(_.isUndefined($scope.index) || _.isUndefined($scope.time))
+    if(dashboard.indices.length == 0) {
       return
+    }
 
     $scope.panel.loading = true;
-    var request = $scope.ejs.Request().indices($scope.index);
+    var request = $scope.ejs.Request().indices(dashboard.indices);
 
     // Terms mode
     request = request
@@ -73,9 +60,7 @@ angular.module('kibana.derivequeries', [])
         .facetFilter(ejs.QueryFilter(
           ejs.FilteredQuery(
             ejs.QueryStringQuery($scope.panel.query || '*'),
-            ejs.RangeFilter($scope.time.field)
-              .from($scope.time.from)
-              .to($scope.time.to)
+            filterSrv.getBoolFilter(filterSrv.ids)
             )))).size(0)
 
     $scope.populate_modal(request);
@@ -93,10 +78,22 @@ angular.module('kibana.derivequeries', [])
       } else if ($scope.panel.mode === 'OR') {
         var suffix = ' OR (' + $scope.panel.query + ')';
       }
+      var ids = [];
       _.each(results.facets.query.terms, function(v) {
-        data.push($scope.panel.field+':"'+v.term+'"'+suffix)
+        var _q = $scope.panel.field+':"'+v.term+'"'+suffix;
+        // if it isn't in the list, remove it
+        var _iq = query.findQuery(_q)
+        if(!_iq) {
+          ids.push(query.set({query:_q}));
+        } else {
+          ids.push(_iq.id);
+        }
       });
-      $scope.send_query(data)
+      _.each(_.difference($scope.panel.ids,ids),function(id){
+        query.remove(id)
+      })
+      $scope.panel.ids = ids;
+      dashboard.refresh();
     });
   }
 
@@ -114,23 +111,12 @@ angular.module('kibana.derivequeries', [])
     $scope.modal = {
       title: "Inspector",
       body : "<h5>Last Elasticsearch Query</h5><pre>"+
-          'curl -XGET '+config.elasticsearch+'/'+$scope.index+"/_search?pretty -d'\n"+
+          'curl -XGET '+config.elasticsearch+'/'+dashboard.indices+"/_search?pretty -d'\n"+
           angular.toJson(JSON.parse(request.toString()),true)+
         "'</pre>", 
     } 
   }
 
-  function set_time(time) {
-    $scope.time = time;
-    $scope.index = _.isUndefined(time.index) ? $scope.index : time.index
-    $scope.get_data();
-  }
-
-  $scope.send_query = function(query) {
-    var _query = _.isArray(query) ? query : [query]
-    eventBus.broadcast($scope.$id,$scope.panel.group,'query',_query)
-  }
-
   var update_history = function(query) {
     query = _.isArray(query) ? query : [query];
     if($scope.panel.remember > 0) {

+ 4 - 4
panels/fields/micropanel.html

@@ -1,8 +1,8 @@
 <a class="close" ng-click="dismiss()" href="">×</a>
 <h4>
   Micro Analysis of {{micropanel.field}} 
-  <i class="pointer icon-search" ng-click="build_search('_exists_',micropanel.field);dismiss();"></i>
-  <i class="pointer icon-ban-circle" ng-click="build_search('_missing_',micropanel.field);dismiss();"></i>
+  <i class="pointer icon-search" ng-click="fieldExists(micropanel.field,'must');dismiss();"></i>
+  <i class="pointer icon-ban-circle" ng-click="fieldExists(micropanel.field,'mustNot');dismiss();"></i>
   <br><small>{{micropanel.count}} events in the table set</small>
 </h4>
 <table style="width:480px" class='table table-bordered table-striped table-condensed'>
@@ -15,8 +15,8 @@
     <tr ng-repeat='field in micropanel.values'>
       <td>{{{true: "__blank__",false:field[0]}[field[0] == ""]}}</td>
       <td>
-        <i class="pointer icon-search" ng-click="build_search(micropanel.field,field[0]);dismiss();"></i>
-        <i class="pointer icon-ban-circle" ng-click="build_search(micropanel.field,field[0],true);dismiss();"></i>
+        <i class="pointer icon-search" ng-click="build_search(micropanel.field,field[0],'must');dismiss();"></i>
+        <i class="pointer icon-ban-circle" ng-click="build_search(micropanel.field,field[0],'mustNot');dismiss();"></i>
       </td>
       <td>{{field[1]}}</td>
     </tr>

+ 11 - 4
panels/fields/module.js

@@ -18,7 +18,7 @@
 
 */
 angular.module('kibana.fields', [])
-.controller('fields', function($scope, eventBus, $timeout) {
+.controller('fields', function($scope, eventBus, $timeout, dashboard, query, filterSrv) {
 
   // Set and populate defaults
   var _d = {
@@ -79,9 +79,16 @@ angular.module('kibana.fields', [])
     eventBus.broadcast($scope.$id,$scope.panel.group,"selected_fields",$scope.active)
   }
 
-  $scope.build_search = function(field, value,negate) {
-    $scope.panel.query = [add_to_query($scope.panel.query,field,value,negate)]
-    eventBus.broadcast($scope.$id,$scope.panel.group,'query',$scope.panel.query);
+  $scope.build_search = function(field,value,mandate) {
+    var query = field+":"+angular.toJson(value)
+    
+    filterSrv.set({type:'querystring',query:query,mandate:mandate})
+    dashboard.refresh();
+  }
+
+  $scope.fieldExists = function(field,mandate) {
+    filterSrv.set({type:'exists',field:field,mandate:mandate})
+    dashboard.refresh();
   }
 
   $scope.is_active = function(field) {

+ 7 - 0
panels/filtering/editor.html

@@ -0,0 +1,7 @@
+<div>
+  <div class="row-fluid">    
+    <div class="span12">
+      No options here
+    </div>
+  </div>
+</div>

+ 15 - 0
panels/filtering/meta.html

@@ -0,0 +1,15 @@
+<div>
+  <style>
+    .input-query-alias {
+      margin-bottom: 5px !important;
+    }
+  </style>
+  <a class="close" ng-click="render();dismiss();" href="">×</a>
+  <h6>Query Alias</h6>
+  <form>
+    <input class="input-medium input-query-alias" type="text" ng-model="queries.list[id].alias" placeholder='Alias...' />
+    <div>
+      <i ng-repeat="color in queries.colors" class="pointer" ng-class="{'icon-circle-blank':queries.list[id].color == color,'icon-circle':queries.list[id].color != color}" style="color:{{color}}" ng-click="queries.list[id].color = color;render();"> </i>
+    </div>
+  </form>
+</div>

+ 45 - 0
panels/filtering/module.html

@@ -0,0 +1,45 @@
+<kibana-panel ng-controller='filtering' ng-init="init()">
+  <style>
+    .filtering-container {
+      margin-top: 3px;
+    }
+    .filter-panel-filter {
+      display:inline-block;
+      vertical-align: top;
+      margin-left: 10px;
+      width: 200px;
+      padding: 5px;
+      border: #555 1px solid;
+      margin: 0px 5px 5px 0px;
+    }
+    .filter-must {
+      border-bottom: #7EB26D 3px solid;
+    }
+    .filter-mustNot {
+      border-bottom: #E24D42 3px solid;
+    }
+    .filter-should {
+      border-bottom: #EF843C 3px solid;
+    }
+    .filter-action {
+      float:right;
+      margin-bottom: 0px !important;
+      margin-left: 3px;
+    }
+
+  </style>
+
+  <div class='filtering-container'>
+    <div ng-repeat="id in filterSrv.ids" class="small filter-panel-filter">
+      <div class="filter-{{filterSrv.list[id].mandate}}">
+        <strong>{{filterSrv.list[id].type}}</strong> {{filterSrv.list[id].mandate}}
+        <i class="filter-action pointer icon-remove" bs-tooltip="'Remove'" ng-click="remove(id)"></i>
+        <i class="filter-action pointer" ng-class="{'icon-check': filterSrv.list[id].active,'icon-check-empty': !filterSrv.list[id].active}" bs-tooltip="'Toggle'" ng-click="toggle(id)"></i>
+
+      </div>
+      <ul class="unstyled">
+        <li ng-repeat="(key,value) in filterSrv.list[id]" ng-show="show_key(key)"><strong>{{key}}</strong> : {{value}}</li>
+      </ul>
+    </div>
+  </div>
+</kibana-panel>

+ 46 - 0
panels/filtering/module.js

@@ -0,0 +1,46 @@
+/*
+
+  ## filtering
+
+  An experimental for interacting with the filter service
+
+  ### Parameters
+
+*/
+
+angular.module('kibana.filtering', [])
+.controller('filtering', function($scope, filterSrv, $rootScope, dashboard) {
+
+  // Set and populate defaults
+  var _d = {
+    status  : "Experimental"
+  }
+  _.defaults($scope.panel,_d);
+
+  $scope.init = function() {
+    $scope.filterSrv = filterSrv
+  }
+
+  $scope.remove = function(id) {
+    filterSrv.remove(id);
+    dashboard.refresh();
+  }
+
+  $scope.toggle = function(id) {
+    filterSrv.list[id].active = !filterSrv.list[id].active;
+    dashboard.refresh();
+  }
+
+  $scope.refresh = function(query) {
+    $rootScope.$broadcast('refresh')
+  }
+
+  $scope.render = function(query) {
+    $rootScope.$broadcast('render')
+  }
+
+  $scope.show_key = function(key) {
+    return !_.contains(['type','id','alias','mandate','active'],key)
+  }
+
+});

+ 9 - 35
panels/histogram/editor.html

@@ -4,46 +4,20 @@
       <label class="small">Mode</label> 
       <select ng-change="set_refresh(true)" class="input-small" ng-model="panel.mode" ng-options="f for f in ['count','min','mean','max','total']"></select>
     </div>
-    <div class="span3" ng-show="panel.mode != 'count'">
-      <label class="small">Field</label>
+    <div class="span2">
+      <label class="small">Time Field</label>
       <form>
-        <input ng-change="set_refresh(true)" placeholder="Start typing" bs-typeahead="fields.list" type="text" class="input-small" ng-model="panel.value_field">
+        <input ng-change="set_refresh(true)" placeholder="Start typing" bs-typeahead="fields.list" type="text" class="input-small" ng-model="panel.time_field">
       </form>
     </div>
-    <div class="span5" ng-show="panel.mode != 'count'">
-      <label class="small">Note</label><small> In <strong>{{panel.mode}}</strong> mode the configured field <strong>must</strong> be a numeric type</small>
-    </div>
-  </div>
-  <div class="row-fluid">
-    <div class="span3">
-      <form style="margin-bottom: 0px">
-        <label class="small">Label</label>
-        <input type="text" placeholder="New Label" style="width:70%" ng-model="newlabel">
-      </form>
-    </div>
-    <div class="span8">
-      <label class="small">Query</label>
-      <form class="input-append" style="margin-bottom: 0px">
-        <input type="text" placeholder="New Query" style="width:80%" ng-model="newquery">
-        <button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery='';set_refresh(true)"><i class="icon-plus"></i></button>
-      </form>
-    </div>
-    <div class="span1">
-    </div>
-  </div>
-  <div class="row-fluid" ng-repeat="q in panel.query">        
-    <div class="span3">
-      <form style="margin-bottom: 0px">
-        <input type="text" style="width:70%" ng-model="q.label" ng-change="set_refresh(true)">
-      </form>
-    </div>
-    <div class="span8">
-      <form style="margin-bottom: 0px">
-        <input type="text" style="width:80%" ng-model="q.query" ng-change="set_refresh(true)">
+    <div class="span2" ng-show="panel.mode != 'count'">
+      <label class="small">Value Field</label>
+      <form>
+        <input ng-change="set_refresh(true)" placeholder="Start typing" bs-typeahead="fields.list" type="text" class="input-small" ng-model="panel.value_field">
       </form>
     </div>
-    <div class="span1">
-      <i class="icon-remove pointer" ng-click="remove_query(q)"></i>
+    <div class="span3" ng-show="panel.mode != 'count'">
+      <label class="small">Note</label><small> In <strong>{{panel.mode}}</strong> mode the configured field <strong>must</strong> be a numeric type</small>
     </div>
   </div>
   <h5>Chart Options</h5>

+ 24 - 6
panels/histogram/module.html

@@ -1,18 +1,36 @@
 <kibana-panel ng-controller='histogram' ng-init="init()" style="height:{{panel.height || row.height}}">
-  <span ng-show="panel.spyable" style="position:absolute;right:0px;top:0px" class='panelextra pointer'>
+  <style>
+    .histogram-legend {
+      display:inline-block;
+      padding-right:5px
+    }
+    .histogram-legend-dot {
+      display:inline-block;
+      height:10px;
+      width:10px;
+      border-radius:5px;  
+    }
+    .histogram-legend-item {
+      display:inline-block; 
+    }
+    .histogram-chart {
+      position:relative;
+    }
+  </style>
+  <span ng-show="panel.spyable" class='spy panelextra pointer'>
       <i bs-modal="'partials/modal.html'" class="icon-eye-open"></i>
   </span>
   <div>
   <span ng-show='panel.zoomlinks && data'>
-    <a class='small' ng-click='zoom(0.5)'><i class='icon-zoom-in'></i> Zoom In</a>
+    <!--<a class='small' ng-click='zoom(0.5)'><i class='icon-zoom-in'></i> Zoom In</a>-->
     <a class='small' ng-click='zoom(2)'><i class='icon-zoom-out'></i> Zoom Out</a> | 
   </span>
-  <span ng-show="panel.legend" ng-repeat='series in plot.getData()' style='display:inline-block;padding-right:5px'>
-    <div style="display:inline-block;background:{{series.color}};height:10px;width:10px;border-radius:5px;"></div>
-    <div class='small' style='display:inline-block'>{{series.label}} ({{series.hits}})</div>
+  <span ng-show="panel.legend" ng-repeat='series in data' class="histogram-legend">
+    <div class="histogram-legend-dot" style="background:{{series.info.color}};"></div>
+    <div class='small histogram-legend-item'>{{series.info.alias}} ({{series.hits}})</div>
   </span>
   <span ng-show="panel.legend" class="small"><span ng-show="panel.value_field && panel.mode != 'count'">{{panel.value_field}}</span> {{panel.mode}} per <strong>{{panel.interval}}</strong> | (<strong>{{hits}}</strong> hits)</span>
   </div>
   <center><img ng-show='panel.loading && _.isUndefined(data)' src="common/img/load_big.gif"></center>
-  <div histogram-chart params="{{panel}}" style="height:{{panel.height || row.height}};position:relative"></div>
+  <div histogram-chart class="histogram-chart" params="{{panel}}" style="height:{{panel.height || row.height}};"></div>
 </kibana-panel>         

+ 89 - 82
panels/histogram/module.js

@@ -32,17 +32,11 @@
   * x-axis :: Show x-axis labels and grid lines
   * y-axis :: Show y-axis labels and grid lines
   * interactive :: Allow drag to select time range
-  ### Group Events
-  #### Receives
-  * time :: An object containing the time range to use and the index(es) to query
-  * query :: An Array of queries, even if its only one
-  #### Sends
-  * get_time :: On panel initialization get time range to query
 
 */
 
 angular.module('kibana.histogram', [])
-.controller('histogram', function($scope, eventBus) {
+.controller('histogram', function($scope, eventBus, query, dashboard, filterSrv) {
 
   // Set and populate defaults
   var _d = {
@@ -50,11 +44,12 @@ angular.module('kibana.histogram', [])
     group       : "default",
     query       : [ {query: "*", label:"Query"} ],
     mode        : 'count',
+    time_field  : '@timestamp',
     value_field : null,
     auto_int    : true,
     resolution  : 100, 
     interval    : '5m',
-    fill        : 3,
+    fill        : 0,
     linewidth   : 3,
     timezone    : 'browser', // browser, utc or a standard timezone
     spyable     : true,
@@ -72,82 +67,55 @@ angular.module('kibana.histogram', [])
   _.defaults($scope.panel,_d)
 
   $scope.init = function() {
-    eventBus.register($scope,'time', function(event,time){$scope.set_time(time)});
-    
-    // Consider eliminating the check for array, this should always be an array
-    eventBus.register($scope,'query', function(event, query) {
-      if(_.isArray(query)) {
-        $scope.panel.query = _.map(query,function(q) {
-          return {query: q, label: q};
-        })
-      } else {
-        $scope.panel.query[0] = {query: query, label: query}
-      }
-      $scope.get_data();
-    });
 
-    // Now that we're all setup, request the time from our group if we don't 
-    // have it yet
-    if(_.isUndefined($scope.time))
-      eventBus.broadcast($scope.$id,$scope.panel.group,'get_time')
-  }
+    $scope.queries = query;
 
-  $scope.remove_query = function(q) {
-    $scope.panel.query = _.without($scope.panel.query,q);
-    $scope.get_data();
-  }
+    $scope.$on('refresh',function(){
+      $scope.get_data();
+    })
+
+    $scope.get_data()
 
-  $scope.add_query = function(label,query) {
-    if(!(_.isArray($scope.panel.query)))
-      $scope.panel.query = new Array();
-    $scope.panel.query.unshift({
-      query: query,
-      label: label, 
-    });
-    $scope.get_data();
   }
 
   $scope.get_data = function(segment,query_id) {
     delete $scope.panel.error
+
     // Make sure we have everything for the request to complete
-    if(_.isUndefined($scope.index) || _.isUndefined($scope.time))
+    if(dashboard.indices.length == 0) {
       return
+    }
 
+    var _range = $scope.range = filterSrv.timeRange('min');
+    
     if ($scope.panel.auto_int)
-      $scope.panel.interval = secondsToHms(calculate_interval($scope.time.from,$scope.time.to,$scope.panel.resolution,0)/1000);
-
+      $scope.panel.interval = secondsToHms(calculate_interval(_range.from,_range.to,$scope.panel.resolution,0)/1000);
+    
     $scope.panel.loading = true;
     var _segment = _.isUndefined(segment) ? 0 : segment
-    var request = $scope.ejs.Request().indices($scope.index[_segment]);
-
-    // Build the question part of the query
-    var queries = [];
-    _.each($scope.panel.query, function(v) {
-      queries.push($scope.ejs.FilteredQuery(
-        ejs.QueryStringQuery(v.query || '*'),
-        ejs.RangeFilter($scope.time.field)
-          .from($scope.time.from)
-          .to($scope.time.to))
-      )
-    });
+    var request = $scope.ejs.Request().indices(dashboard.indices[_segment]);
 
-    // Build the facet part, injecting the query in as a facet filter
-    _.each(queries, function(v) {
-
-      var facet = $scope.ejs.DateHistogramFacet("chart"+_.indexOf(queries,v))
+    // Build the query
+    _.each($scope.queries.ids, function(id) {
+      var query = $scope.ejs.FilteredQuery(
+        ejs.QueryStringQuery($scope.queries.list[id].query || '*'),
+        filterSrv.getBoolFilter(filterSrv.ids)
+      )
 
+      var facet = $scope.ejs.DateHistogramFacet(id)
+      
       if($scope.panel.mode === 'count') {
-        facet = facet.field($scope.time.field)
+        facet = facet.field($scope.panel.time_field)
       } else {
         if(_.isNull($scope.panel.value_field)) {
           $scope.panel.error = "In " + $scope.panel.mode + " mode a field must be specified";
           return
         }
-        facet = facet.keyField($scope.time.field).valueField($scope.panel.value_field)
+        facet = facet.keyField($scope.panel.time_field).valueField($scope.panel.value_field)
       }
-      facet = facet.interval($scope.panel.interval).facetFilter($scope.ejs.QueryFilter(v))
+      facet = facet.interval($scope.panel.interval).facetFilter($scope.ejs.QueryFilter(query))
       request = request.facet(facet).size(0)
-    })
+    });
 
     // Populate the inspector panel
     $scope.populate_modal(request);
@@ -157,6 +125,7 @@ angular.module('kibana.histogram', [])
 
     // Populate scope when we have results
     results.then(function(results) {
+
       $scope.panel.loading = false;
       if(_segment == 0) {
         $scope.hits = 0;
@@ -170,15 +139,24 @@ angular.module('kibana.histogram', [])
         return;
       }
 
-      // Make sure we're still on the same query
-      if($scope.query_id === query_id) {
+      // Convert facet ids to numbers
+      var facetIds = _.map(_.keys(results.facets),function(k){return parseInt(k);})
+
+      // Make sure we're still on the same query/queries
+      if($scope.query_id === query_id && 
+        _.intersection(facetIds,query.ids).length == query.ids.length
+        ) {
 
         var i = 0;
-        _.each(results.facets, function(v, k) {
+        _.each(query.ids, function(id) {
+          var v = results.facets[id];
 
           // Null values at each end of the time range ensure we see entire range
           if(_.isUndefined($scope.data[i]) || _segment == 0) {
-            var data = [[$scope.time.from.getTime(), null],[$scope.time.to.getTime(), null]];
+            var data = []
+            if(filterSrv.idsByType('time').length > 0) {
+              data = [[_range.from.getTime(), null],[_range.to.getTime(), null]];
+            }
             var hits = 0;
           } else {
             var data = $scope.data[i].data
@@ -197,15 +175,12 @@ angular.module('kibana.histogram', [])
           // Create the flot series object
           var series = { 
             data: {
-              label: $scope.panel.query[i].label || "query"+(parseInt(i)+1), 
+              info: $scope.queries.list[id],
               data: data,
               hits: hits
             },
           };
 
-          if (!(_.isUndefined($scope.panel.query[i].color)))
-            series.data.color = $scope.panel.query[i].color;
-          
           $scope.data[i] = series.data
 
           i++;
@@ -215,7 +190,7 @@ angular.module('kibana.histogram', [])
         $scope.$emit('render')
 
         // If we still have segments left, get them
-        if(_segment < $scope.index.length-1) {
+        if(_segment < dashboard.indices.length-1) {
           $scope.get_data(_segment+1,query_id)
         }
       
@@ -226,7 +201,33 @@ angular.module('kibana.histogram', [])
   // function $scope.zoom
   // factor :: Zoom factor, so 0.5 = cuts timespan in half, 2 doubles timespan
   $scope.zoom = function(factor) {
-    eventBus.broadcast($scope.$id,$scope.panel.group,'zoom',factor);
+    var _now = Date.now();
+    var _range = filterSrv.timeRange('min');
+    var _timespan = (_range.to.valueOf() - _range.from.valueOf());
+    var _center = _range.to.valueOf() - _timespan/2
+
+    var _to = (_center + (_timespan*factor)/2)
+    var _from = (_center - (_timespan*factor)/2)
+
+    // If we're not already looking into the future, don't.
+    if(_to > Date.now() && _range.to < Date.now()) {
+      var _offset = _to - Date.now()
+      _from = _from - _offset
+      _to = Date.now();
+    }
+
+    if(factor > 1) {
+      filterSrv.removeByType('time')
+    }
+    filterSrv.set({
+      type:'time',
+      from:moment.utc(_from),
+      to:moment.utc(_to),
+      field:$scope.panel.time_field
+    })
+    
+    dashboard.refresh();
+
   }
 
   // I really don't like this function, too much dom manip. Break out into directive?
@@ -234,7 +235,7 @@ angular.module('kibana.histogram', [])
     $scope.modal = {
       title: "Inspector",
       body : "<h5>Last Elasticsearch Query</h5><pre>"+
-          'curl -XGET '+config.elasticsearch+'/'+$scope.index+"/_search?pretty -d'\n"+
+          'curl -XGET '+config.elasticsearch+'/'+dashboard.indices+"/_search?pretty -d'\n"+
           angular.toJson(JSON.parse(request.toString()),true)+
         "'</pre>", 
     } 
@@ -251,14 +252,8 @@ angular.module('kibana.histogram', [])
     $scope.$emit('render');
   }
 
-  $scope.set_time = function(time) {
-    $scope.time = time;
-    $scope.index = time.index || $scope.index    
-    $scope.get_data();
-  }
-
 })
-.directive('histogramChart', function(eventBus) {
+.directive('histogramChart', function(dashboard, eventBus, filterSrv, $rootScope) {
   return {
     restrict: 'A',
     link: function(scope, elem, attrs, ctrl) {
@@ -276,6 +271,14 @@ angular.module('kibana.histogram', [])
       // Function for rendering panel
       function render_panel() {
  
+        // Populate from the query service
+        try {
+          _.each(scope.data,function(series) {
+            series.label = series.info.alias,
+            series.color = series.info.color
+          })
+        } catch(e) {return}
+
         // Set barwidth based on specified interval
         var barwidth = interval_to_seconds(scope.panel.interval)*1000
 
@@ -388,9 +391,13 @@ angular.module('kibana.histogram', [])
       });
 
       elem.bind("plotselected", function (event, ranges) {
-        scope.time.from = moment(ranges.xaxis.from);
-        scope.time.to   = moment(ranges.xaxis.to)
-        eventBus.broadcast(scope.$id,scope.panel.group,'set_time',scope.time)
+        var _id = filterSrv.set({
+          type  : 'time',
+          from  : moment.utc(ranges.xaxis.from),
+          to    : moment.utc(ranges.xaxis.to),
+          field : scope.panel.time_field
+        })
+        dashboard.refresh();
       });
     }
   };

+ 0 - 34
panels/hits/editor.html

@@ -26,38 +26,4 @@
       <label class="small">Labels</label><input type="checkbox" ng-model="panel.labels" ng-checked="panel.labels">
     </div>
   </div>
-  <h5>Queries</h5>    
-  <div class="row-fluid">
-    <div class="span3">
-      <form style="margin-bottom: 0px">
-       <label class="small">Label</label>
-        <input type="text" placeholder="New Label" style="width:70%" ng-model="newlabel">
-      </form>
-    </div>
-    <div class="span8">
-      <form class="input-append" style="margin-bottom: 0px">
-        <label class="small">Query</label>
-        <input type="text" placeholder="New Query" style="width:80%" ng-model="newquery">
-        <button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery='';set_refresh(true)"><i class="icon-plus"></i></button>
-      </form>
-    </div>
-    <div class="span1">
-    </div>
-  </div>
-  <div class="row-fluid" ng-repeat="q in panel.query">        
-    <div class="span3">
-      <form style="margin-bottom: 0px">
-        <input type="text" style="width:70%" ng-model="q.label" ng-change="set_refresh(true)">
-      </form>
-    </div>
-    <div class="span8">
-      <form class="input-append" style="margin-bottom: 0px">
-        <input type="text" style="width:80%" ng-model="q.query" ng-change="set_refresh(true)">
-        <button class="btn" ng-click="get_data()"><i class="icon-search"></i></button>
-      </form>
-    </div>
-    <div class="span1">
-      <i class="icon-remove pointer" ng-click="remove_query(q)"></i>
-    </div>
-  </div>
 </div>

+ 13 - 10
panels/hits/module.html

@@ -3,14 +3,14 @@
   <div ng-show="panel.counter_pos == 'above' && (panel.chart == 'bar' || panel.chart == 'pie')" id='{{$id}}-legend'>
     <!-- vertical legend -->
     <table class="small" ng-show="panel.arrangement == 'vertical'">  
-      <tr ng-repeat="query in plot.getData()">
-        <td><div style="display:inline-block;border-radius:5px;background:{{query.color}};height:10px;width:10px"></div></td> <td style="padding-right:10px;padding-left:10px;">{{query.label}}</td><td>{{query.data[0][1]}}</td>
+      <tr ng-repeat="query in data">
+        <td><div style="display:inline-block;border-radius:5px;background:{{query.info.color}};height:10px;width:10px"></div></td> <td style="padding-right:10px;padding-left:10px;">{{query.info.alias}}</td><td>{{query.data[0][1]}}</td>
       </tr>
     </table>
 
     <!-- horizontal legend -->
-    <div class="small" ng-show="panel.arrangement == 'horizontal'" ng-repeat="query in plot.getData()" style="float:left;padding-left: 10px;">
-     <span><div style="display:inline-block;border-radius:5px;background:{{query.color}};height:10px;width:10px"></div></span> {{query.label}} ({{query.data[0][1]}}) </span>
+    <div class="small" ng-show="panel.arrangement == 'horizontal'" ng-repeat="query in data" style="float:left;padding-left: 10px;">
+     <span><div style="display:inline-block;border-radius:5px;background:{{query.info.color}};height:10px;width:10px"></div></span> {{query.info.alias}} ({{query.data[0][1]}}) </span>
     </div><br>
 
   </div>
@@ -20,23 +20,26 @@
   <div ng-show="panel.chart == 'pie' || panel.chart == 'bar'" hits-chart params="{{panel}}" style="height:{{panel.height || row.height}};position:relative"></div>
 
   <div ng-show="panel.counter_pos == 'below' && (panel.chart == 'bar' || panel.chart == 'pie')" id='{{$id}}-legend'>
-
     <!-- vertical legend -->
     <table class="small" ng-show="panel.arrangement == 'vertical'">  
-      <tr ng-repeat="query in plot.getData()">
-        <td><div style="display:inline-block;border-radius:5px;background:{{query.color}};height:10px;width:10px"></div></td> <td style="padding-right:10px;padding-left:10px;">{{query.label}}</td><td>{{query.data[0][1]}}</td>
+      <tr ng-repeat="query in data">
+        <td><div style="display:inline-block;border-radius:5px;background:{{query.info.color}};height:10px;width:10px"></div></td> <td style="padding-right:10px;padding-left:10px;">{{query.info.alias}}</td><td>{{query.data[0][1]}}</td>
       </tr>
     </table>
 
     <!-- horizontal legend -->
-    <div class="small" ng-show="panel.arrangement == 'horizontal'" ng-repeat="query in plot.getData()" style="float:left;padding-left: 10px;">
-     <span><div style="display:inline-block;border-radius:5px;background:{{query.color}};height:10px;width:10px"></div></span> {{query.label}} ({{query.data[0][1]}}) </span>
+    <div class="small" ng-show="panel.arrangement == 'horizontal'" ng-repeat="query in data" style="float:left;padding-left: 10px;">
+     <span><div style="display:inline-block;border-radius:5px;background:{{query.info.color}};height:10px;width:10px"></div></span> {{query.info.alias}} ({{query.data[0][1]}}) </span>
     </div><br>
 
   </div>
 
   <div ng-show="panel.chart == 'total'"><div ng-style="panel.style" style="line-height:{{panel.style['font-size']}}">{{hits}}</div></div>
 
-  <span ng-show="panel.chart == 'list'"><span ng-style="panel.style" style="line-height:{{panel.style['font-size']}}" ng-repeat="query in data">{{query.label}} ({{query.hits}})<span></span><br ng-show="panel.arrangement == 'vertical' && panel.chart == 'list'">
+  <span ng-show="panel.chart == 'list'">
+    <div ng-style="panel.style" style="display:inline-block;line-height:{{panel.style['font-size']}}" ng-repeat="query in data">
+      <i class="icon-circle" style="color:{{query.info.color}}"></i> {{query.info.alias}} ({{query.hits}})
+    </div>
+  </span><br ng-show="panel.arrangement == 'vertical' && panel.chart == 'list'">
 
 </kibana-panel>         

+ 42 - 60
panels/hits/module.js

@@ -13,16 +13,10 @@
   * donut :: Only applies to 'pie' charts. Punches a hole in the chart for some reason
   * tilt :: Only 'pie' charts. Janky 3D effect. Looks terrible 90% of the time. 
   * lables :: Only 'pie' charts. Labels on the pie?
-  ### Group Events
-  #### Sends
-  * get_time :: On panel initialization get time range to query
-  #### Receives
-  * time :: An object containing the time range to use and the index(es) to query
-  * query :: An Array of queries, even if its only one
 
 */
 angular.module('kibana.hits', [])
-.controller('hits', function($scope, eventBus) {
+.controller('hits', function($scope, query, dashboard, filterSrv) {
 
   // Set and populate defaults
   var _d = {
@@ -30,8 +24,8 @@ angular.module('kibana.hits', [])
     query   : ["*"],
     group   : "default",
     style   : { "font-size": '10pt'},
-    arrangement : 'vertical',
-    chart       : 'none',
+    arrangement : 'horizontal',
+    chart       : 'bar',
     counter_pos : 'above',
     donut   : false,
     tilt    : false,
@@ -41,17 +35,12 @@ angular.module('kibana.hits', [])
 
   $scope.init = function () {
     $scope.hits = 0;
-    eventBus.register($scope,'time', function(event,time){
-      set_time(time)
-    });
-    eventBus.register($scope,'query', function(event, query) {
-      $scope.panel.query = _.map(query,function(q) {
-        return {query: q, label: q};
-      })
+   
+    $scope.$on('refresh',function(){
       $scope.get_data();
-    });
-    // Now that we're all setup, request the time from our group
-    eventBus.broadcast($scope.$id,$scope.panel.group,'get_time')
+    })
+    $scope.get_data();
+
   }
 
   $scope.get_data = function(segment,query_id) {
@@ -59,30 +48,24 @@ angular.module('kibana.hits', [])
     $scope.panel.loading = true;
 
     // Make sure we have everything for the request to complete
-    if(_.isUndefined($scope.index) || _.isUndefined($scope.time))
+    if(dashboard.indices.length == 0) {
       return
+    }
 
     var _segment = _.isUndefined(segment) ? 0 : segment
-    var request = $scope.ejs.Request().indices($scope.index[_segment]);
+    var request = $scope.ejs.Request().indices(dashboard.indices[_segment]);
     
     // Build the question part of the query
-    var queries = [];
-    _.each($scope.panel.query, function(v) {
-      queries.push($scope.ejs.FilteredQuery(
-        ejs.QueryStringQuery(v.query || '*'),
-        ejs.RangeFilter($scope.time.field)
-          .from($scope.time.from)
-          .to($scope.time.to))
-      )
-    });
-
-    // Build the facet part
-    _.each(queries, function(v) {
+    _.each(query.ids, function(id) {
+      var _q = $scope.ejs.FilteredQuery(
+        ejs.QueryStringQuery(query.list[id].query || '*'),
+        filterSrv.getBoolFilter(filterSrv.ids));
+    
       request = request
-        .facet($scope.ejs.QueryFacet("query"+_.indexOf(queries,v))
-          .query(v)
+        .facet($scope.ejs.QueryFacet(id)
+          .query(_q)
         ).size(0)
-    })
+    });
 
     // TODO: Spy for hits panel
     //$scope.populate_modal(request);
@@ -92,7 +75,6 @@ angular.module('kibana.hits', [])
 
     // Populate scope when we have results
     results.then(function(results) {
-
       $scope.panel.loading = false;
       if(_segment == 0) {
         $scope.hits = 0;
@@ -105,16 +87,25 @@ angular.module('kibana.hits', [])
         $scope.panel.error = $scope.parse_error(results.error);
         return;
       }
-      if($scope.query_id === query_id) {
+
+      // Convert facet ids to numbers
+      var facetIds = _.map(_.keys(results.facets),function(k){return parseInt(k);})
+
+      // Make sure we're still on the same query/queries
+      if($scope.query_id === query_id && 
+        _.intersection(facetIds,query.ids).length == query.ids.length
+        ) {
         var i = 0;
-        _.each(results.facets, function(v, k) {
+        _.each(query.ids, function(id) {
+          var v = results.facets[id]
           var hits = _.isUndefined($scope.data[i]) || _segment == 0 ? 
             v.count : $scope.data[i].hits+v.count
           $scope.hits += v.count
 
           // Create series
           $scope.data[i] = { 
-            label: $scope.panel.query[i].label || "query"+(parseInt(i)+1), 
+            info: query.list[id],
+            id: id,
             hits: hits,
             data: [[i,hits]]
           };
@@ -122,26 +113,13 @@ angular.module('kibana.hits', [])
           i++;
         });
         $scope.$emit('render');
-        if(_segment < $scope.index.length-1) 
+        if(_segment < dashboard.indices.length-1) 
           $scope.get_data(_segment+1,query_id)
         
       }
     });
   }
 
-  $scope.remove_query = function(q) {
-    $scope.panel.query = _.without($scope.panel.query,q);
-    $scope.get_data();
-  }
-
-  $scope.add_query = function(label,query) {
-    $scope.panel.query.unshift({
-      query: query,
-      label: label, 
-    });
-    $scope.get_data();
-  }
-
   $scope.set_refresh = function (state) { 
     $scope.refresh = state; 
   }
@@ -155,11 +133,10 @@ angular.module('kibana.hits', [])
 
   function set_time(time) {
     $scope.time = time;
-    $scope.index = _.isUndefined(time.index) ? $scope.index : time.index
     $scope.get_data();
   }
 
-}).directive('hitsChart', function(eventBus) {
+}).directive('hitsChart', function(query) {
   return {
     restrict: 'A',
     link: function(scope, elem, attrs, ctrl) {
@@ -177,6 +154,13 @@ angular.module('kibana.hits', [])
       // Function for rendering panel
       function render_panel() {
 
+        try {
+          _.each(scope.data,function(series) {
+            series.label = series.info.alias,
+            series.color = series.info.color
+          })
+        } catch(e) {return}
+
         var scripts = $LAB.script("common/lib/panels/jquery.flot.js").wait()
                           .script("common/lib/panels/jquery.flot.pie.js")
 
@@ -196,13 +180,12 @@ angular.module('kibana.hits', [])
                 yaxis: { show: true, min: 0, color: "#c8c8c8" },
                 xaxis: { show: false },
                 grid: {
-                  backgroundColor: '#272b30',
                   borderWidth: 0,
                   borderColor: '#eee',
                   color: "#eee",
                   hoverable: true,
                 },
-                colors: ['#86B22D','#BF6730','#1D7373','#BFB930','#BF3030','#77207D']
+                colors: query.colors
               })
             if(scope.panel.chart === 'pie')
               scope.plot = $.plot(elem, scope.data, {
@@ -218,7 +201,6 @@ angular.module('kibana.hits', [])
                       label: 'The Rest'
                     },
                     stroke: {
-                      color: '#272b30',
                       width: 0
                     },
                     label: { 
@@ -234,7 +216,7 @@ angular.module('kibana.hits', [])
                 },
                 //grid: { hoverable: true, clickable: true },
                 grid:   { hoverable: true, clickable: true },
-                colors: ['#86B22D','#BF6730','#1D7373','#BFB930','#BF3030','#77207D']
+                colors: query.colors
               });
 
             // Compensate for the height of the  legend. Gross

+ 0 - 7
panels/map/editor.html

@@ -11,13 +11,6 @@
         <input  bs-typeahead="fields.list" type="text" class="input-small" ng-model="panel.field">
       </form>
     </div>
-    <div class="span6">
-      <form class="input-append">
-        <h6>Query</h6>
-        <input type="text" ng-model="panel.query">
-        <button class="btn" ng-click="get_data();"><i class="icon-search"></i></button>
-      </form>
-    </div>
     <div class="span1"><h6>Map</h6> 
       <select ng-change="$emit('render')" class="input-small" ng-model="panel.map" ng-options="f for f in ['world','europe','usa']"></select>
     </div>

+ 62 - 2
panels/map/module.html

@@ -1,6 +1,66 @@
 <kibana-panel ng-controller='map' ng-init="init()">
-  <span ng-show="panel.spyable" style="position:absolute;right:0px;top:0px" class='panelextra pointer'>
+  <style>
+    .jvectormap-label {
+        position: absolute;
+        display: none;
+        visibility: hidden;
+        border: solid 1px #CDCDCD;
+        -webkit-border-radius: 3px;
+        -moz-border-radius: 3px;
+        border-radius: 3px;
+        background: #292929;
+        color: white;
+        font-family: sans-serif, Verdana;
+        font-size: smaller;
+        padding: 3px;
+    }
+
+    .jvectormap-zoomin, .jvectormap-zoomout {
+        position: absolute;
+        left: 10px;
+        -webkit-border-radius: 3px;
+        -moz-border-radius: 3px;
+        border-radius: 3px;
+        background: #292929;
+        padding: 3px;
+        color: white;
+        width: 10px;
+        height: 10px;
+        cursor: pointer;
+        line-height: 10px;
+        text-align: center;
+    }
+
+    .jvectormap {
+        position: relative;
+    }
+
+    .jvectormap-zoomin {
+        display: none;
+        top: 10px;
+    }
+
+    .jvectormap-zoomout {
+        display: none;
+        top: 30px;
+    }
+
+    .map-legend {
+        color   : #c8c8c8;
+        padding : 10px;
+        font-size: 11pt;
+        font-weight: 200;
+        background-color: #1f1f1f;
+        border-radius: 5px;
+        position: absolute;
+        right: 0px;
+        top: 15px;
+        display: none;
+        z-index: 99;
+    }
+  </style>
+  <span ng-show="panel.spyable" class='spy panelextra pointer'>
     <i bs-modal="'partials/modal.html'" class="icon-eye-open"></i>
   </span>
-  <div map params="{{panel}}" style="height:{{panel.height || row.height}}"></div>
+  <div class="jvectormap" map params="{{panel}}" style="height:{{panel.height || row.height}}"></div>
 </kibana-panel>

+ 23 - 41
panels/map/module.js

@@ -19,16 +19,11 @@
   * spyable :: Show the 'eye' icon that reveals the last ES query
   * index_limit :: This does nothing yet. Eventually will limit the query to the first
                    N indices
-  ### Group Events
-  #### Sends
-  * get_time :: On panel initialization get time range to query
-  #### Receives
-  * time :: An object containing the time range to use and the index(es) to query
-  * query :: An Array of queries, this panel uses only the first one
+
 */
 
 angular.module('kibana.map', [])
-.controller('map', function($scope, eventBus) {
+.controller('map', function($scope, $rootScope, query, dashboard, filterSrv) {
 
   // Set and populate defaults
   var _d = {
@@ -45,22 +40,24 @@ angular.module('kibana.map', [])
   _.defaults($scope.panel,_d)
 
   $scope.init = function() {
-    eventBus.register($scope,'time', function(event,time){set_time(time)});
-    eventBus.register($scope,'query', function(event, query) {
-      $scope.panel.query = _.isArray(query) ? query[0] : query;
-      $scope.get_data();
-    });
-    // Now that we're all setup, request the time from our group
-    eventBus.broadcast($scope.$id,$scope.panel.group,'get_time')
+    $scope.$on('refresh',function(){$scope.get_data()})
+    $scope.get_data();
   }
 
   $scope.get_data = function() {
+    
     // Make sure we have everything for the request to complete
-    if(_.isUndefined($scope.index) || _.isUndefined($scope.time))
+    if(dashboard.indices.length == 0) {
       return
+    }
 
     $scope.panel.loading = true;
-    var request = $scope.ejs.Request().indices($scope.index);
+    var request = $scope.ejs.Request().indices(dashboard.indices);
+
+    var boolQuery = ejs.BoolQuery();
+    _.each(query.list,function(q) {
+      boolQuery = boolQuery.should(ejs.QueryStringQuery(q.query || '*'))
+    })
 
     // Then the insert into facet and make the request
     var request = request
@@ -70,10 +67,8 @@ angular.module('kibana.map', [])
         .exclude($scope.panel.exclude)
         .facetFilter(ejs.QueryFilter(
           ejs.FilteredQuery(
-            ejs.QueryStringQuery($scope.panel.query || '*'),
-            ejs.RangeFilter($scope.time.field)
-              .from($scope.time.from)
-              .to($scope.time.to)
+            boolQuery,
+            filterSrv.getBoolFilter(filterSrv.ids)
             )))).size(0);
 
     $scope.populate_modal(request);
@@ -97,22 +92,15 @@ angular.module('kibana.map', [])
     $scope.modal = {
       title: "Inspector",
       body : "<h5>Last Elasticsearch Query</h5><pre>"+
-          'curl -XGET '+config.elasticsearch+'/'+$scope.index+"/_search?pretty -d'\n"+
+          'curl -XGET '+config.elasticsearch+'/'+dashboard.indices+"/_search?pretty -d'\n"+
           angular.toJson(JSON.parse(request.toString()),true)+
         "'</pre>", 
     } 
   }
 
-  function set_time(time) {
-    $scope.time = time;
-    $scope.index = _.isUndefined(time.index) ? $scope.index : time.index
-    $scope.get_data();
-  }
-
   $scope.build_search = function(field,value) {
-    $scope.panel.query = add_to_query($scope.panel.query,field,value,false)
-    $scope.get_data();
-    eventBus.broadcast($scope.$id,$scope.panel.group,'query',[$scope.panel.query]);
+    filterSrv.set({type:'querystring',mandate:'must',query:field+":"+value})
+    dashboard.refresh();
   }
 
 })
@@ -155,20 +143,12 @@ angular.module('kibana.map', [])
               }]
             },
             onRegionLabelShow: function(event, label, code){
-              $('.jvectormap-label').css({
-                "position"    : "absolute",
-                "display"     : "none",
-                'color'       : "#c8c8c8",
-                'padding'     : '10px',
-                'font-size'   : '11pt',
-                'font-weight' : 200,
-                'background-color': '#1f1f1f',
-                'border-radius': '5px'
-              })
+              elem.children('.map-legend').show()
               var count = _.isUndefined(scope.data[code]) ? 0 : scope.data[code];
-              $('.jvectormap-label').text(label.text() + ": " + count);
+              elem.children('.map-legend').text(label.text() + ": " + count);
             },
             onRegionOut: function(event, code) {
+              $('.map-legend').hide();
             },
             onRegionClick: function(event, code) {
               var count = _.isUndefined(scope.data[code]) ? 0 : scope.data[code];
@@ -176,6 +156,8 @@ angular.module('kibana.map', [])
                 scope.build_search(scope.panel.field,code)
             }
           });
+          elem.prepend('<span class="map-legend"></span>');
+          $('.map-legend').hide();
         })
       }
     }

+ 0 - 92
panels/map2/display/binning.js

@@ -1,92 +0,0 @@
-/**
- * Hexagonal binning
- * Rendered as normally projected svg paths, which mean they *do not*
- * clip on spheres appropriately.  To fix this, we would need to translate
- * the svg path into a geo-path
- */
-function displayBinning(scope, dr, dimensions) {
-
-    var hexbin = d3.hexbin()
-        .size(dimensions)
-        .radius(scope.panel.display.binning.hexagonSize);
-
-
-    var binPoints = [],
-        binnedPoints = [],
-        binRange = 0;
-
-
-    if (scope.panel.display.binning.enabled) {
-        /**
-         * primary field is just binning raw counts
-         *
-         * Secondary field is binning some metric like mean/median/total.  Hexbins doesn't support that,
-         * so we cheat a little and just add more points to compensate.
-         * However, we don't want to add a million points, so normalize against the largest value
-         */
-        if (scope.panel.display.binning.areaEncodingField === 'secondary') {
-            var max = Math.max.apply(Math, _.map(scope.data, function(k,v){return k;})),
-                scale = 50/max;
-
-            _.map(scope.data, function (k, v) {
-                var decoded = geohash.decode(v);
-                return _.map(_.range(0, k*scale), function(a,b) {
-                    binPoints.push(dr.projection([decoded.longitude, decoded.latitude]));
-                })
-            });
-
-        } else {
-            binPoints = dr.projectedPoints;
-        }
-
-        //bin and sort the points, so we can set the various ranges appropriately
-        binnedPoints = hexbin(binPoints).sort(function(a, b) { return b.length - a.length; });
-        binRange = binnedPoints[0].length;
-
-        //clean up some memory
-        binPoints = [];
-    } else {
-
-        //not enabled, so just set an empty array.  D3.exit will take care of the rest
-        binnedPoints = [];
-        binRange = 0;
-    }
-
-
-
-    var radius = d3.scale.sqrt()
-        .domain([0, binRange])
-        .range([0, scope.panel.display.binning.hexagonSize]);
-
-    var color = d3.scale.linear()
-        .domain([0,binRange])
-        .range(["white", "steelblue"])
-        .interpolate(d3.interpolateLab);
-
-
-    var hex = dr.g.selectAll(".hexagon")
-        .data(binnedPoints);
-
-    hex.enter().append("path")
-        .attr("d", function (d) {
-            if (scope.panel.display.binning.areaEncoding === false) {
-                return hexbin.hexagon();
-            } else {
-                return hexbin.hexagon(radius(d.length));
-            }
-        })
-        .attr("class", "hexagon")
-        .attr("transform", function (d) {
-            return "translate(" + d.x + "," + d.y + ")";
-        })
-        .style("fill", function (d) {
-            if (scope.panel.display.binning.colorEncoding === false) {
-                return color(binnedPoints[0].length / 2);
-            } else {
-                return color(d.length);
-            }
-        })
-        .attr("opacity", scope.panel.display.binning.hexagonAlpha);
-
-    hex.exit().remove();
-}

+ 0 - 28
panels/map2/display/bullseye.js

@@ -1,28 +0,0 @@
-/**
- * Renders bullseyes as geo-json poly gon entities
- * Allows for them to clip on spheres correctly
- */
-function displayBullseye(scope, dr) {
-
-    var degrees = 180 / Math.PI
-    var circle = d3.geo.circle();
-    var data = [];
-
-    if (scope.panel.display.bullseye.enabled) {
-        data =  [
-          circle.origin(parseFloat(scope.panel.display.bullseye.coord.lat), parseFloat(scope.panel.display.bullseye.coord.lon)).angle(1000 / 6371 * degrees)()
-        ];
-    }
-
-    var arcs = dr.g.selectAll(".arc")
-        .data(data);
-
-    arcs.enter().append("path")
-
-        .attr("d", dr.path)
-        .attr("class", "arc");
-
-    arcs.exit().remove();
-
-
-}

+ 0 - 44
panels/map2/display/geopoints.js

@@ -1,44 +0,0 @@
-/**
- * Renders geopoints as geo-json poly gon entities
- * Allows for them to clip on spheres correctly
- */
-function displayGeopoints(scope, dr) {
-
-    var points = [];
-    var circle = d3.geo.circle();
-    var degrees = 180 / Math.PI
-
-    if (scope.panel.display.geopoints.enabled) {
-        //points = dr.points;
-
-      points = _.map(dr.points, function(v) {
-        return {
-            type: "Point",
-            coordinates: [v[0], v[1]]
-        };
-      });
-
-    }
-
-
-    dr.geopoints = dr.g.selectAll("path.geopoint")
-      .data(points);
-
-
-
-    dr.geopoints.enter().append("path")
-      /*
-        .datum(function(d) {
-            return circle.origin([d[0], d[1]]).angle(scope.panel.display.geopoints.pointSize / 6371 * degrees)();
-        })
-        */
-      .attr("class", "geopoint")
-      .attr("d", dr.path);
-
-    dr.geopoints.exit().remove();
-
-
-
-
-
-}

+ 0 - 287
panels/map2/editor.html

@@ -1,287 +0,0 @@
-<style>
-    .tabDetails {
-        border-bottom: 1px solid #ddd;
-        padding-bottom:20px;
-    }
-    .tabDetails td {
-        padding-right: 10px;
-        padding-bottom:10px;
-    }
-</style>
-<div ng-controller="map2">
-    <div class="row-fluid" style="margin-bottom:20px">
-        <div class="span11">
-            The map panel is compatible with either Geopoints or two-letter country codes, depending on the graphing options.  Left click to drag/pan map, scroll wheel (or double click) to zoom.  Globes can be spun using ctrl-key + drag.
-        </div>
-    </div>
-
-
-    <div class="row-fluid">
-        <div class="span10">
-            <form class="form-horizontal">
-                <div class="control-group">
-                    <label class="control-label" for="panelfield">Primary Field</label>
-                    <div class="controls">
-                        <input type="text" id="panelfield" class="input"
-                               ng-model="panel.field"
-                               ng-change="get_data()" />
-                    </div>
-                </div>
-                <div class="control-group">
-                    <label class="control-label" for="panelsecondaryfield">Secondary Field</label>
-                    <div class="controls">
-                        <input type="text" id="panelsecondaryfield" class="input"
-                               ng-model="panel.secondaryfield"
-                               ng-change="get_data()"
-                               data-placement="right"
-                               placeholder="Optional"
-                               bs-tooltip="'Allows aggregating on Primary field, while counting stats on a secondary (e.g. Group By user_id, Sum(purchase_price)).'" />
-                    </div>
-                </div>
-                <div class="control-group">
-                    <label class="control-label" for="panelquery">Query</label>
-                    <div class="controls">
-                        <input type="text" id="panelquery" class="input" ng-model="panel.query">
-                    </div>
-                </div>
-            </form>
-        </div>
-    </div>
-    <div class="row-fluid">
-        <div class="span11">
-            <h4>Display Options</h4>
-        </div>
-        <!--
-            Rolling our own tab control here because the Angular-Strap Tab directive doesn't allow
-            updates to components inside, which is quite bizarre.  Or I just can't figure it out...
-        -->
-
-        <div class="span11">
-            <ul class="nav nav-tabs" ng-cloak="">
-                <li ng-repeat="tab in ['Geopoints', 'Binning', 'Choropleth', 'Bullseye', 'Data']" ng-class="{active:isActive(tab)}">
-                    <a ng-click="tabClick(tab)">{{tab}}</a>
-                </li>
-            </ul>
-        </div>
-    </div>
-
-    <div class="row-fluid tabDetails" ng-show="isActive('geopoints')">
-        <div class="span8 offset1">
-            <table>
-                <tbody  >
-                <tr>
-                    <td>Geopoints</td>
-                    <td>
-                        <button type="button" class="btn" bs-button
-                            data-placement="right"
-                            bs-tooltip="'Compatible with Geopoint Type'"
-                            ng-change="$emit('render')"
-                            ng-class="{'btn-success': panel.display.geopoints.enabled}"
-                            ng-model="panel.display.geopoints.enabled">{{panel.display.geopoints.enabled|enabledText}}</button>
-                    </td>
-                </tr>
-                <tr>
-                    <td>Point size</td>
-                    <td>
-                        <input type="text" style="width:100px"
-                            ng-change="$emit('render')"
-                            data-placement="right"
-                            bs-tooltip="'Controls the size of the geopoints on the map.'"
-                            ng-model="panel.display.geopoints.pointSize"
-                            value="{{panel.display.geopoints.pointSize}}" />
-                    </td>
-                </tr>
-                <tr>
-                    <td>Point Transparency</td>
-                    <td>
-                        <input type="text" style="width:100px"
-                               ng-change="$emit('render')"
-                               data-placement="right"
-                               bs-tooltip="'Controls the transparency of geopoints. Valid numbers are between 0.0 and 1.0'"
-                               ng-model="panel.display.geopoints.pointAlpha"
-                               value="{{panel.display.geopoints.pointAlpha}}" />
-                    </td>
-                </tr>
-                <tr>
-                    <td>Autosizing</td>
-                    <td>
-                        <input type="checkbox"
-                               ng-change="$emit('render')"
-                               data-placement="right"
-                               ng-model="panel.display.geopoints.autosize"
-                               bs-tooltip="'Allows point sizes to scale as you zoom in and out of the map.'" />
-                    </td>
-                </tr>
-                </tbody>
-            </table>
-        </div>
-    </div>
-
-    <div class="row-fluid tabDetails" ng-show="isActive('binning')">
-        <div class="span8 offset1">
-            <table>
-                <tbody  >
-                <tr>
-                    <td>Binning</td>
-                    <td>
-                        <button type="button" class="btn" bs-button
-                                data-placement="right"
-                                bs-tooltip="'Compatible with Geopoint Type'"
-                                ng-change="$emit('render')"
-                                ng-class="{'btn-success': panel.display.binning.enabled}"
-                                ng-model="panel.display.binning.enabled">{{panel.display.binning.enabled|enabledText}}</button>
-                    </td>
-                </tr>
-                <tr>
-                    <td>Hexagon size</td>
-                    <td>
-                        <input type="text" style="width:100px"
-                               ng-change="$emit('render')"
-                               data-placement="right"
-                               bs-tooltip="'Controls the size of the hexagonal binning'"
-                               ng-model="panel.display.binning.hexagonSize"
-                               value="{{panel.display.binning.hexagonSize}}" />
-                    </td>
-                </tr>
-                <tr>
-                    <td>Hexagon Transparency</td>
-                    <td>
-                        <input type="text" style="width:100px"
-                               ng-change="$emit('render')"
-                               data-placement="right"
-                               bs-tooltip="'Controls the transparency of hexagonal bins. Valid numbers are between 0.0 and 1.0'"
-                               ng-model="panel.display.binning.hexagonAlpha"
-                               value="{{panel.display.binning.hexagonAlpha}}" />
-                    </td>
-                </tr>
-                <tr>
-                    <td>
-                        <button type="button" class="btn" bs-button
-                                ng-change="$emit('render')"
-                                ng-class="{'btn-success': panel.display.binning.areaEncoding}"
-                                ng-model="panel.display.binning.areaEncoding">Area</button>
-                    </td>
-                    <td>
-                        <div class="btn-group" ng-model="panel.display.binning.areaEncodingField" bs-buttons-radio ng-change="$emit('render')">
-                            <button type="button" class="btn" value="primary">Primary Field</button>
-                            <button type="button" class="btn" value="secondary">Secondary Field</button>
-                        </div>
-                    </td>
-                </tr>
-                <tr>
-                    <td>
-                        <button type="button" class="btn" bs-button
-                                ng-change="$emit('render')"
-                                ng-class="{'btn-success': panel.display.binning.colorEncoding}"
-                                ng-model="panel.display.binning.colorEncoding">Color</button>
-                    </td>
-                    <td>
-                        <div class="btn-group" ng-model="panel.display.binning.colorEncodingField" bs-buttons-radio ng-change="$emit('render')">
-                            <button type="button" class="btn" value="primary">Primary Field</button>
-                            <button type="button" class="btn" value="secondary">Secondary Field</button>
-                        </div>
-                    </td>
-                </tr>
-                </tbody>
-            </table>
-        </div>
-    </div>
-
-    <div class="row-fluid tabDetails" ng-show="isActive('choropleth')">
-        <div class="span8 offset1">
-            <table>
-                <tbody  >
-                <tr>
-                    <td>Choropleth</td>
-                    <td>
-                        <button type="button" class="btn" bs-button
-                                data-placement="right"
-                                bs-tooltip="'Choropleths color country regions according to your selected field. Compatible with Country-Coded fields'"
-                                ng-change="$emit('render')"
-                                ng-class="{'btn-success': panel.display.choropleth.enabled}"
-                                ng-model="panel.display.choropleth.enabled">{{panel.display.choropleth.enabled|enabledText}}</button>
-                    </td>
-                </tr>
-                </tbody>
-            </table>
-        </div>
-    </div>
-
-    <div class="row-fluid tabDetails" ng-show="isActive('bullseye')">
-        <div class="span8 offset1">
-            <table>
-                <tbody  >
-                <tr>
-                    <td>Bullseye</td>
-                    <td>
-                        <button type="button" class="btn" bs-button
-                                ng-change="$emit('render')"
-                                ng-class="{'btn-success': panel.display.bullseye.enabled}"
-                                ng-model="panel.display.bullseye.enabled">{{panel.display.bullseye.enabled|enabledText}}</button>
-                    </td>
-                </tr>
-                <tr>
-                    <td>Bullseye Coordinates</td>
-                    <td>
-                        <input type="text" style="width:100px"
-                               ng-change="$emit('render')"
-                               placeholder="Latitude"
-                               data-placement="right"
-                               bs-tooltip="'Latitude of Bullseye'"
-                               ng-model="panel.display.bullseye.coord.lat"
-                               value="{{panel.display.bullseye.coord.lat}}" />
-
-                        <input type="text" style="width:100px"
-                               placeholder="Longitude"
-                               ng-change="$emit('render')"
-                               data-placement="right"
-                               bs-tooltip="'Longitude of Bullseye'"
-                               ng-model="panel.display.bullseye.coord.lon"
-                               value="{{panel.display.bullseye.coord.lon}}" />
-                    </td>
-                </tr>
-                </tbody>
-            </table>
-        </div>
-    </div>
-
-    <div class="row-fluid tabDetails" ng-show="isActive('data')">
-        <div class="span8 offset1">
-            <table>
-                <tbody  >
-                <tr>
-                    <td>Data Points</td>
-                    <td>
-                        <input type="text" style="width:100px"
-                               ng-change="get_data()"
-                               data-placement="right"
-                               bs-tooltip="'Controls the number of samples used in the map. Be careful with this value!'"
-                               ng-model="panel.display.data.samples"
-                               value="{{panel.display.data.samples}}" />
-                    </td>
-                </tr>
-                <tr>
-                    <td>Map Projection</td>
-                    <td>
-                        <select ng-model="panel.display.data.type" ng-options="option.id as option.text for option in panel.display.data.dropdown"></select>
-                    </td>
-                </tr>
-                </tbody>
-            </table>
-        </div>
-    </div>
-
-    <h5>Panel Spy</h5>
-
-    <div class="row-fluid">
-        <div class="span2">
-            <label class="small">Spyable</label> <input type="checkbox" ng-model=
-                "panel.spyable" ng-checked="panel.spyable">
-        </div>
-
-        <div class="span9 small">
-            The panel spy shows 'behind the scenes' information about a panel. It can be
-            accessed by clicking the in the top right of the panel.
-        </div>
-    </div>
-</div>

+ 0 - 1
panels/map2/lib/d3.hexbin.v0.min.js

@@ -1 +0,0 @@
-(function(){d3.hexbin=function(){function u(n){var r={};return n.forEach(function(n,t){var a=s.call(u,n,t)/o,e=Math.round(a),h=f.call(u,n,t)/i-(1&e?.5:0),c=Math.round(h),l=a-e;if(3*Math.abs(l)>1){var g=h-c,v=c+(c>h?-1:1)/2,M=e+(e>a?-1:1),m=h-v,d=a-M;g*g+l*l>m*m+d*d&&(c=v+(1&e?1:-1)/2,e=M)}var x=c+"-"+e,j=r[x];j?j.push(n):(j=r[x]=[n],j.x=(c+(1&e?.5:0))*i,j.y=e*o)}),d3.values(r)}function a(r){var t=0,u=0;return n.map(function(n){var a=Math.sin(n)*r,e=-Math.cos(n)*r,i=a-t,o=e-u;return t=a,u=e,[i,o]})}var e,i,o,h=1,c=1,f=r,s=t;return u.x=function(n){return arguments.length?(f=n,u):f},u.y=function(n){return arguments.length?(s=n,u):s},u.hexagon=function(n){return 1>arguments.length&&(n=e),"m"+a(n).join("l")+"z"},u.mesh=function(){for(var n=[],r=a(e).slice(0,4).join("l"),t=0,u=!1;c+e>t;t+=o,u=!u)for(var f=u?i/2:0;h>f;f+=i)n.push("M",f,",",t,"m",r);return n.join("")},u.size=function(n){return arguments.length?(h=+n[0],c=+n[1],u):[h,c]},u.radius=function(n){return arguments.length?(e=+n,i=2*e*Math.sin(Math.PI/3),o=1.5*e,u):e},u.radius(1)};var n=d3.range(0,2*Math.PI,Math.PI/3),r=function(n){return n[0]},t=function(n){return n[1]}})();

+ 0 - 137
panels/map2/lib/node-geohash.js

@@ -1,137 +0,0 @@
-/**
- * Copyright (c) 2011, Sun Ning.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- *
- */
-
-var BASE32_CODES = "0123456789bcdefghjkmnpqrstuvwxyz";
-var BASE32_CODES_DICT = {};
-for(var i=0; i<BASE32_CODES.length; i++) {
-    BASE32_CODES_DICT[BASE32_CODES.charAt(i)]=i;
-}
-
-var encode = function(latitude, longitude, numberOfChars){
-    numberOfChars = numberOfChars || 9;
-    var chars = [], bits = 0;
-    var hash_value = 0;
-
-    var maxlat = 90, minlat = -90;
-    var maxlon = 180, minlon = -180;
-
-    var mid;
-    var islon = true;
-    while(chars.length < numberOfChars) {
-        if (islon){
-            mid = (maxlon+minlon)/2;
-            if(longitude > mid){
-                hash_value = (hash_value << 1) + 1;
-                minlon=mid;
-            } else {
-                hash_value = (hash_value << 1) + 0;
-                maxlon=mid;
-            }
-        } else {
-            mid = (maxlat+minlat)/2;
-            if(latitude > mid ){
-                hash_value = (hash_value << 1) + 1;
-                minlat = mid;
-            } else {
-                hash_value = (hash_value << 1) + 0;
-                maxlat = mid;
-            }
-        }
-        islon = !islon;
-
-        bits++;
-        if (bits == 5) {
-            var code = BASE32_CODES[hash_value];
-            chars.push(code);
-            bits = 0;
-            hash_value = 0;
-        }
-    }
-    return chars.join('')
-};
-
-var decode_bbox = function(hash_string){
-    var islon = true;
-    var maxlat = 90, minlat = -90;
-    var maxlon = 180, minlon = -180;
-
-    var hash_value = 0;
-    for(var i=0,l=hash_string.length; i<l; i++) {
-        var code = hash_string[i].toLowerCase();
-        hash_value = BASE32_CODES_DICT[code];
-
-        for (var bits=4; bits>=0; bits--) {
-            var bit = (hash_value >> bits) & 1;
-            if (islon){
-                var mid = (maxlon+minlon)/2;
-                if(bit == 1){
-                    minlon = mid;
-                } else {
-                    maxlon = mid;
-                }
-            } else {
-                var mid = (maxlat+minlat)/2;
-                if(bit == 1){
-                    minlat = mid;
-                } else {
-                    maxlat = mid;
-                }
-            }
-            islon = !islon;
-        }
-    }
-    return [minlat, minlon, maxlat, maxlon];
-}
-
-var decode = function(hash_string){
-    var bbox = decode_bbox(hash_string);
-    var lat = (bbox[0]+bbox[2])/2;
-    var lon = (bbox[1]+bbox[3])/2;
-    var laterr = bbox[2]-lat;
-    var lonerr = bbox[3]-lon;
-    return {latitude:lat, longitude:lon,
-        error:{latitude:laterr, longitude:lonerr}};
-};
-
-/**
- * direction [lat, lon], i.e.
- * [1,0] - north
- * [1,1] - northeast
- * ...
- */
-var neighbor = function(hashstring, direction) {
-    var lonlat = decode(hashstring);
-    var neighbor_lat = lonlat.latitude
-        + direction[0] * lonlat.error.latitude * 2;
-    var neighbor_lon = lonlat.longitude
-        + direction[1] * lonlat.error.longitude * 2;
-    return encode(neighbor_lat, neighbor_lon, hashstring.length);
-}
-
-var geohash = {
-    'encode': encode,
-    'decode': decode,
-    'decode_bbox': decode_bbox,
-    'neighbor': neighbor,
-}
-module.exports = geohash;

+ 0 - 1
panels/map2/lib/queue.v1.min.js

@@ -1 +0,0 @@
-(function(){function n(n){function t(){for(;f=a<c.length&&n>p;){var u=a++,t=c[u],r=l.call(t,1);r.push(e(u)),++p,t[0].apply(null,r)}}function e(n){return function(u,l){--p,null==d&&(null!=u?(d=u,a=s=0/0,r()):(c[n]=l,--s?f||t():r()))}}function r(){null!=d?v(d):i?v(d,c):v.apply(null,[d].concat(c))}var o,f,i,c=[],a=0,p=0,s=0,d=null,v=u;return n||(n=1/0),o={defer:function(){return d||(c.push(arguments),++s,t()),o},await:function(n){return v=n,i=!1,s||r(),o},awaitAll:function(n){return v=n,i=!0,s||r(),o}}}function u(){}"undefined"==typeof module?self.queue=n:module.exports=n,n.version="1.0.4";var l=[].slice})();

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
panels/map2/lib/topojson.v1.min.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
panels/map2/lib/world-110m.json


+ 0 - 251
panels/map2/lib/world-country-names.tsv

@@ -1,251 +0,0 @@
-id	short	name
-4	AF	Afghanistan
-8	AL	Albania
-10	AQ	Antarctica
-12	DZ	Algeria
-16	AS	American Samoa
-20	AD	Andorra
-24	AO	Angola
-28	AG	Antigua and Barbuda
-31	AZ	Azerbaijan
-32	AR	Argentina
-36	AU	Australia
-40	AT	Austria
-44	BS	Bahamas
-48	BH	Bahrain
-50	BD	Bangladesh
-51	AM	Armenia
-52	BB	Barbados
-56	BE	Belgium
-60	BM	Bermuda
-64	BT	Bhutan
-68	BO	Bolivia, Plurinational State of
-70	BA	Bosnia and Herzegovina
-72	BW	Botswana
-74	BV	Bouvet Island
-76	BR	Brazil
-84	BZ	Belize
-86	IO	British Indian Ocean Territory
-90	SB	Solomon Islands
-92	VG	Virgin Islands, British
-96	BN	Brunei Darussalam
-100	BG	Bulgaria
-104	MM	Myanmar
-108	BI	Burundi
-112	BY	Belarus
-116	KH	Cambodia
-120	CM	Cameroon
-124	CA	Canada
-132	CV	Cape Verde
-136	KY	Cayman Islands
-140	CF	Central African Republic
-144	LK	Sri Lanka
-148	TD	Chad
-152	CL	Chile
-156	CN	China
-158	TW	Taiwan, Province of China
-162	CX	Christmas Island
-166	CC	Cocos (Keeling) Islands
-170	CO	Colombia
-174	KM	Comoros
-175	YT	Mayotte
-178	CG	Congo
-180	CD	Congo, the Democratic Republic of the
-184	CK	Cook Islands
-188	CR	Costa Rica
-191	HR	Croatia
-192	CU	Cuba
-196	CY	Cyprus
-203	CZ	Czech Republic
-204	BJ	Benin
-208	DK	Denmark
-212	DM	Dominica
-214	DO	Dominican Republic
-218	EC	Ecuador
-222	SV	El Salvador
-226	GQ	Equatorial Guinea
-231	ET	Ethiopia
-232	ER	Eritrea
-233	EE	Estonia
-234	FO	Faroe Islands
-238	FK	Falkland Islands (Malvinas)
-239	GS	South Georgia and the South Sandwich Islands
-242	FJ	Fiji
-246	FI	Finland
-248	AX	Åland Islands
-250	FR	France
-254	GF	French Guiana
-258	PF	French Polynesia
-260	TF	French Southern Territories
-262	DJ	Djibouti
-266	GA	Gabon
-268	GE	Georgia
-270	GM	Gambia
-275		Palestinian Territory, Occupied
-276	DE	Germany
-288	GH	Ghana
-292	GI	Gibraltar
-296	KI	Kiribati
-300	GR	Greece
-304	GL	Greenland
-308	GD	Grenada
-312	GP	Guadeloupe
-316	GU	Guam
-320	GT	Guatemala
-324	GN	Guinea
-328	GY	Guyana
-332	HT	Haiti
-334	HM	Heard Island and McDonald Islands
-336	VA	Holy See (Vatican City State)
-340	HN	Honduras
-344	HK	Hong Kong
-348	HU	Hungary
-352	IS	Iceland
-356	IN	India
-360	ID	Indonesia
-364	IR	Iran, Islamic Republic of
-368	IQ	Iraq
-372	IE	Ireland
-376	IL	Israel
-380	IT	Italy
-384		Côte d'Ivoire
-388	JM	Jamaica
-392	JP	Japan
-398	KZ	Kazakhstan
-400	JO	Jordan
-404	KE	Kenya
-408	KP	Korea, Democratic People's Republic of
-410	KR	Korea, Republic of
-414	KW	Kuwait
-417	KG	Kyrgyzstan
-418	LA	Lao People's Democratic Republic
-422	LB	Lebanon
-426	LS	Lesotho
-428	LV	Latvia
-430	LR	Liberia
-434	LY	Libya
-438	LI	Liechtenstein
-440	LT	Lithuania
-442	LU	Luxembourg
-446	MO	Macao
-450	MG	Madagascar
-454	MW	Malawi
-458	MY	Malaysia
-462	MV	Maldives
-466	ML	Mali
-470	MT	Malta
-474	MQ	Martinique
-478	MR	Mauritania
-480	MU	Mauritius
-484	MX	Mexico
-492	MC	Monaco
-496	MN	Mongolia
-498	MD	Moldova, Republic of
-499	ME	Montenegro
-500	MS	Montserrat
-504	MA	Morocco
-508	MZ	Mozambique
-512	OM	Oman
-516	NA	Namibia
-520	NR	Nauru
-524	NP	Nepal
-528	NL	Netherlands
-531		Curaçao
-533	AW	Aruba
-534	SX	Sint Maarten (Dutch part)
-535	BQ	Bonaire, Sint Eustatius and Saba
-540	NC	New Caledonia
-548	VU	Vanuatu
-554	NZ	New Zealand
-558	NI	Nicaragua
-562	NE	Niger
-566	NG	Nigeria
-570	NU	Niue
-574	NF	Norfolk Island
-578	NO	Norway
-580	MP	Northern Mariana Islands
-581	UM	United States Minor Outlying Islands
-583	FM	Micronesia, Federated States of
-584	MH	Marshall Islands
-585	PW	Palau
-586	PK	Pakistan
-591	PA	Panama
-598	PG	Papua New Guinea
-600	PY	Paraguay
-604	PE	Peru
-608	PH	Philippines
-612	PN	Pitcairn
-616	PL	Poland
-620	PT	Portugal
-624	GW	Guinea-Bissau
-626	TL	Timor-Leste
-630	PR	Puerto Rico
-634	QA	Qatar
-638		Réunion
-642	RO	Romania
-643	RU	Russian Federation
-646	RW	Rwanda
-652		Saint Barthélemy
-654	SH	Saint Helena, Ascension and Tristan da Cunha
-659	KN	Saint Kitts and Nevis
-660	AI	Anguilla
-662	LC	Saint Lucia
-663	MF	Saint Martin (French part)
-666	PM	Saint Pierre and Miquelon
-670	VC	Saint Vincent and the Grenadines
-674	SM	San Marino
-678	ST	Sao Tome and Principe
-682	SA	Saudi Arabia
-686	SN	Senegal
-688	RS	Serbia
-690	SC	Seychelles
-694	SL	Sierra Leone
-702	SG	Singapore
-703	SK	Slovakia
-704	VN	Viet Nam
-705	SI	Slovenia
-706	SO	Somalia
-710	ZA	South Africa
-716	ZW	Zimbabwe
-724	ES	Spain
-728	SS	South Sudan
-729	SD	Sudan
-732	EH	Western Sahara
-740	SR	Suriname
-744	SJ	Svalbard and Jan Mayen
-748	SZ	Swaziland
-752	SE	Sweden
-756	CH	Switzerland
-760	SY	Syrian Arab Republic
-762	TJ	Tajikistan
-764	TH	Thailand
-768	TG	Togo
-772	TK	Tokelau
-776	TO	Tonga
-780	TT	Trinidad and Tobago
-784	AE	United Arab Emirates
-788	TN	Tunisia
-792	TR	Turkey
-795	TM	Turkmenistan
-796	TC	Turks and Caicos Islands
-798	TV	Tuvalu
-800	UG	Uganda
-804	UA	Ukraine
-807	MK	Macedonia, the former Yugoslav Republic of
-818	EG	Egypt
-826	GB	United Kingdom
-831	GG	Guernsey
-832	JE	Jersey
-833	IM	Isle of Man
-834	TZ	Tanzania, United Republic of
-840	US	United States
-850	VI	Virgin Islands, U.S.
-854	BF	Burkina Faso
-858	UY	Uruguay
-860	UZ	Uzbekistan
-862	VE	Venezuela, Bolivarian Republic of
-876	WF	Wallis and Futuna
-882	WS	Samoa
-887	YE	Yemen
-894	ZM	Zambia
-		

+ 0 - 58
panels/map2/module.html

@@ -1,58 +0,0 @@
-<kibana-panel ng-controller='map2' ng-init="init()">
-    <style>
-        .overlay {
-            fill: none;
-            pointer-events: all;
-        }
-
-        .land {
-            fill: #D1D1D1;
-            stroke: #595959;
-            stroke-linejoin: round;
-            stroke-linecap: round;
-            stroke-width: .1px;
-        }
-
-        .boundary {
-            fill: none;
-            stroke: #000;
-            stroke-linejoin: round;
-            stroke-linecap: round;
-        }
-
-        .hexagon {
-            fill: none;
-            stroke: #000;
-            stroke-width: .1px;
-        }
-
-        .q1 { fill:rgb(247,251,255); }
-        .q2 { fill:rgb(222,235,247); }
-        .q3 { fill:rgb(198,219,239); }
-        .q4 { fill:rgb(158,202,225); }
-        .q5 { fill:rgb(107,174,214); }
-        .q6 { fill:rgb(66,146,198); }
-        .q7 { fill:rgb(33,113,181); }
-        .q8 { fill:rgb(8,81,156); }
-        .q9 { fill:rgb(8,48,107); }
-
-        .arc {
-            stroke: #f00;
-            stroke-width: .5px;
-            fill: none;
-        }
-
-        .geopoint {
-            stroke: #000;
-            stroke-width: .5px;
-            fill: #000;
-        }
-
-        .dropdown-menu{position:absolute;top:auto;left:auto;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;}
-    </style>
-  <span ng-show="panel.spyable" style="position:absolute;right:0px;top:0px" class='panelextra pointer'>
-    <i bs-modal="'partials/modal.html'" class="icon-eye-open"></i>
-  </span>
-
-  <div map2 params="{{panel}}" style="height:{{panel.height || row.height}}"></div>
-</kibana-panel>

+ 0 - 452
panels/map2/module.js

@@ -1,452 +0,0 @@
-angular.module('kibana.map2', [])
-  .controller('map2', function ($scope, eventBus, keylistener) {
-
-    // Set and populate defaults
-    var _d = {
-      status : "Broken",
-      query: "*",
-      map: "world",
-      colors: ['#C8EEFF', '#0071A4'],
-      size: 100,
-      exclude: [],
-      spyable: true,
-      group: "default",
-      index_limit: 0,
-      display: {
-        translate:[0, 0],
-        scale:-1,
-        data: {
-          samples: 1000,
-          type: "mercator",
-          dropdown:[
-            {
-              "text": "Mercator (Flat)",
-              id: "mercator"
-            },
-            {
-              text: "Orthographic (Sphere)",
-              id: "orthographic"
-            }
-          ]
-        },
-        geopoints: {
-          enabled: false,
-          enabledText: "Enabled",
-          pointSize: 0.3,
-          pointAlpha: 0.6
-        },
-        binning: {
-          enabled: false,
-          hexagonSize: 2,
-          hexagonAlpha: 1.0,
-          areaEncoding: true,
-          areaEncodingField: "primary",
-          colorEncoding: true,
-          colorEncodingField: "primary"
-        },
-        choropleth: {
-          enabled: false
-        },
-        bullseye: {
-          enabled: false,
-          coord: {
-            lat: 0,
-            lon: 0
-          }
-        }
-      },
-      activeDisplayTab:"Geopoints"
-    };
-
-    _.defaults($scope.panel, _d)
-
-    $scope.init = function () {
-      eventBus.register($scope, 'time', function (event, time) {
-        set_time(time)
-      });
-      eventBus.register($scope, 'query', function (event, query) {
-        $scope.panel.query = _.isArray(query) ? query[0] : query;
-        $scope.get_data();
-      });
-      // Now that we're all setup, request the time from our group
-      eventBus.broadcast($scope.$id, $scope.panel.group, 'get_time');
-
-      $scope.keylistener = keylistener;
-
-    };
-
-    $scope.get_data = function () {
-
-      // Make sure we have everything for the request to complete
-      if (_.isUndefined($scope.index) || _.isUndefined($scope.time))
-        return
-
-      $scope.panel.loading = true;
-      var request = $scope.ejs.Request().indices($scope.index);
-
-
-      var metric = 'count';
-
-      //Use a regular term facet if there is no secondary field
-      if (typeof $scope.panel.secondaryfield === "undefined") {
-        var facet = $scope.ejs.TermsFacet('map')
-          .field($scope.panel.field)
-          .size($scope.panel.display.data.samples)
-          .exclude($scope.panel.exclude)
-          .facetFilter(ejs.QueryFilter(
-            ejs.FilteredQuery(
-              ejs.QueryStringQuery($scope.panel.query || '*'),
-              ejs.RangeFilter($scope.time.field)
-                .from($scope.time.from)
-                .to($scope.time.to))));
-
-        metric = 'count';
-      } else {
-        //otherwise, use term stats
-        //NOTE: this will break if valueField is a geo_point
-        //      need to put in checks for that
-        var facet = $scope.ejs.TermStatsFacet('map')
-          .keyField($scope.panel.field)
-          .valueField($scope.panel.secondaryfield)
-          .size($scope.panel.display.data.samples)
-          .facetFilter(ejs.QueryFilter(
-            ejs.FilteredQuery(
-              ejs.QueryStringQuery($scope.panel.query || '*'),
-              ejs.RangeFilter($scope.time.field)
-                .from($scope.time.from)
-                .to($scope.time.to))));
-
-        metric = 'total';
-      }
-
-
-      // Then the insert into facet and make the request
-      var request = request.facet(facet).size(0);
-
-      $scope.populate_modal(request);
-
-      var results = request.doSearch();
-
-      // Populate scope when we have results
-      results.then(function (results) {
-        $scope.panel.loading = false;
-        $scope.hits = results.hits.total;
-        $scope.data = {};
-
-        _.each(results.facets.map.terms, function (v) {
-
-          if (!_.isNumber(v.term)) {
-            $scope.data[v.term.toUpperCase()] = v[metric];
-          } else {
-            $scope.data[v.term] = v[metric];
-          }
-        });
-
-        $scope.$emit('render')
-      });
-    };
-
-    // I really don't like this function, too much dom manip. Break out into directive?
-    $scope.populate_modal = function (request) {
-      $scope.modal = {
-        title: "Inspector",
-        body: "<h5>Last Elasticsearch Query</h5><pre>" + 'curl -XGET ' + config.elasticsearch + '/' + $scope.index + "/_search?pretty -d'\n" + angular.toJson(JSON.parse(request.toString()), true) + "'</pre>"
-      }
-    };
-
-    function set_time(time) {
-      $scope.time = time;
-      $scope.index = _.isUndefined(time.index) ? $scope.index : time.index
-      $scope.get_data();
-    }
-
-    $scope.build_search = function (field, value) {
-      $scope.panel.query = add_to_query($scope.panel.query, field, value, false)
-      $scope.get_data();
-      eventBus.broadcast($scope.$id, $scope.panel.group, 'query', $scope.panel.query);
-    };
-
-    $scope.isActive = function(tab) {
-      return (tab.toLowerCase() === $scope.panel.activeDisplayTab.toLowerCase());
-    }
-
-    $scope.tabClick = function(tab) {
-      $scope.panel.activeDisplayTab = tab;
-    }
-
-  })
-  .filter('enabledText', function() {
-    return function (value) {
-      if (value === true) {
-        return "Enabled";
-      } else {
-        return "Disabled";
-      }
-    }
-  })
-  .directive('map2', function () {
-    return {
-      restrict: 'A',
-      link: function (scope, elem, attrs) {
-
-        //directive level variables related to d3
-        var dr = {};
-
-        scope.initializing = false;
-
-
-        dr.worldData = null;
-        dr.worldNames = null;
-
-        /**
-         * Initialize the panels if new, or render existing panels
-         */
-        scope.init_or_render = function() {
-          if (typeof dr.svg === 'undefined') {
-            console.log("init");
-
-            //prevent duplicate initialization steps, if render is called again
-            //before the svg is setup
-            if (!scope.initializing) {
-              init_panel();
-            }
-          } else {
-            console.log("render");
-            render_panel();
-          }
-        };
-
-
-        /**
-         * Receive render events
-         */
-        scope.$on('render', function () {
-          scope.init_or_render();
-        });
-
-        /**
-         * On window resize, re-render the panel
-         */
-        angular.element(window).bind('resize', function () {
-          scope.init_or_render();
-        });
-
-
-        /**
-         * Load the various panel-specific scripts, map data, then initialize
-         * the svg and set appropriate D3 settings
-         */
-        function init_panel() {
-
-          scope.initializing = true;
-          // Using LABjs, wait until all scripts are loaded before rendering panel
-          var scripts = $LAB.script("common/lib/d3.v3.min.js?rand="+Math.floor(Math.random()*10000))
-            .script("panels/map2/lib/topojson.v1.min.js?rand="+Math.floor(Math.random()*10000))
-            .script("panels/map2/lib/node-geohash.js?rand="+Math.floor(Math.random()*10000))
-            .script("panels/map2/lib/d3.hexbin.v0.min.js?rand="+Math.floor(Math.random()*10000))
-            .script("panels/map2/lib/queue.v1.min.js?rand="+Math.floor(Math.random()*10000))
-            .script("panels/map2/display/binning.js?rand="+Math.floor(Math.random()*10000))
-            .script("panels/map2/display/geopoints.js?rand="+Math.floor(Math.random()*10000))
-            .script("panels/map2/display/bullseye.js?rand="+Math.floor(Math.random()*10000));
-
-          // Populate element. Note that jvectormap appends, does not replace.
-          scripts.wait(function () {
-
-            queue()
-              .defer(d3.json, "panels/map2/lib/world-110m.json")
-              .defer(d3.tsv, "panels/map2/lib/world-country-names.tsv")
-              .await(function(error, world, names) {
-                dr.worldData = world;
-                dr.worldNames = names;
-
-                //Better way to get these values?  Seems kludgy to use jQuery on the div...
-                var width = $(elem[0]).width(),
-                  height = $(elem[0]).height();
-
-                //scale to whichever dimension is smaller, helps to ensure the whole map is displayed
-                dr.scale = (width > height) ? (height/5) : (width/5);
-
-                dr.zoom = d3.behavior.zoom()
-                  .scaleExtent([1, 20])
-                  .on("zoom", translate_map);
-
-                //used by choropleth
-                //@todo change domain so that it reflects the domain of the data
-                dr.quantize = d3.scale.quantize()
-                  .domain([0, 1000])
-                  .range(d3.range(9).map(function(i) { return "q" + (i+1); }));
-
-                //Extract name and two-letter codes for our countries
-                dr.countries = topojson.feature(dr.worldData, dr.worldData.objects.countries).features;
-
-                dr.countries = dr.countries.filter(function(d) {
-                  return dr.worldNames.some(function(n) {
-                    if (d.id == n.id) {
-                      d.name = n.name;
-                      return d.short = n.short;
-                    }
-                  });
-                }).sort(function(a, b) {
-                    return a.name.localeCompare(b.name);
-                  });
-
-                //create the new svg
-                dr.svg = d3.select(elem[0]).append("svg")
-                  .attr("width", "100%")
-                  .attr("height", "100%")
-                  .attr("viewBox", "0 0 " + width + " " + height)
-                  .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
-                  .call(dr.zoom);
-                dr.g = dr.svg.append("g");
-
-                scope.initializing = false;
-                render_panel();
-              });
-          });
-        }
-
-
-        /**
-         * Render updates to the SVG. Typically happens when the data changes (time, query)
-         * or when new options are selected
-         */
-        function render_panel() {
-
-          var width = $(elem[0]).width(),
-            height = $(elem[0]).height();
-
-          //Projection is dependant on the map-type
-          if (scope.panel.display.data.type === 'mercator') {
-            dr.projection = d3.geo.mercator()
-              .translate([width/2, height/2])
-              .scale(dr.scale);
-
-          } else if (scope.panel.display.data.type === 'orthographic') {
-            dr.projection = d3.geo.orthographic()
-              .translate([width/2, height/2])
-              .scale(100)
-              .clipAngle(90);
-
-            //recenters the sphere more towards the US...not really necessary
-            dr.projection.rotate([100 / 2, 20 / 2, dr.projection.rotate()[2]]);
-
-          }
-
-          dr.path = d3.geo.path()
-            .projection(dr.projection).pointRadius(0.2);
-
-          console.log(scope.data);
-
-          //Geocoded points are decoded into lonlat
-          dr.points = _.map(scope.data, function (k, v) {
-            //console.log(k,v);
-            var decoded = geohash.decode(v);
-            return [decoded.longitude, decoded.latitude];
-          });
-
-          //And also projected projected to x/y.  Both sets of points are used
-          //by different functions
-          dr.projectedPoints = _.map(dr.points, function (coords) {
-            return dr.projection(coords);
-          });
-
-          dr.svg.select(".overlay").remove();
-
-          dr.svg.append("rect")
-            .attr("class", "overlay")
-            .attr("width", width)
-            .attr("height", height)
-            .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
-
-
-          //Draw the countries, if this is a choropleth, draw with fancy colors
-          var countryPath = dr.g.selectAll(".land")
-            .data(dr.countries);
-
-          countryPath.enter().append("path")
-            .attr("class", function(d) {
-              if (scope.panel.display.choropleth.enabled) {
-                return 'land ' + dr.quantize(scope.data[d.short]);
-              } else {
-                return 'land';
-              }
-            })
-            .attr("d", dr.path);
-
-          countryPath.exit().remove();
-
-          //If this is a sphere, set up drag and keypress listeners
-          if (scope.panel.display.data.type === 'orthographic') {
-            dr.svg.style("cursor", "move")
-              .call(d3.behavior.drag()
-                .origin(function() { var rotate = dr.projection.rotate(); return {x: 2 * rotate[0], y: -2 * rotate[1]}; })
-                .on("drag", function() {
-                  if (scope.keylistener.keyActive(17)) {
-                    dr.projection.rotate([d3.event.x / 2, -d3.event.y / 2, dr.projection.rotate()[2]]);
-
-                    //dr.svg.selectAll("path").attr("d", dr.path);
-                    dr.g.selectAll("path").attr("d", dr.path);
-
-                  }
-                   }));
-
-
-          }
-
-          //Special fix for when the user changes from mercator -> orthographic
-          //The globe won't redraw automatically, we need to force it
-          if (scope.panel.display.data.type === 'orthographic') {
-            dr.svg.selectAll("path").attr("d", dr.path);
-          }
-
-
-          /**
-           * Display option rendering
-           * Order is important to render order here!
-           */
-
-          //@todo fix this
-          var dimensions = [width, height];
-          displayBinning(scope, dr, dimensions);
-          displayGeopoints(scope, dr);
-          displayBullseye(scope, dr);
-
-
-
-
-          //If the panel scale is not default (e.g. the user has moved the maps around)
-          //set the scale and position to the last saved config
-          if (scope.panel.display.scale != -1) {
-            dr.zoom.scale(scope.panel.display.scale).translate(scope.panel.display.translate);
-            dr.g.style("stroke-width", 1 / scope.panel.display.scale).attr("transform", "translate(" + scope.panel.display.translate + ") scale(" + scope.panel.display.scale + ")");
-
-          }
-
-        }
-
-
-        /**
-         * On D3 zoom events, pan/zoom the map
-         * Only applies if the ctrl-key is not pressed, so it doesn't clobber
-         * sphere dragging
-         */
-        function translate_map() {
-
-          var width = $(elem[0]).width(),
-            height = $(elem[0]).height();
-
-          if (! scope.keylistener.keyActive(17)) {
-            var t = d3.event.translate,
-              s = d3.event.scale;
-            t[0] = Math.min(width / 2 * (s - 1), Math.max(width / 2 * (1 - s), t[0]));
-            t[1] = Math.min(height / 2 * (s - 1) + 230 * s, Math.max(height / 2 * (1 - s) - 230 * s, t[1]));
-            dr.zoom.translate(t);
-
-            scope.panel.display.translate = t;
-            scope.panel.display.scale = s;
-            dr.g.style("stroke-width", 1 / s).attr("transform", "translate(" + t + ") scale(" + s + ")");
-          }
-        }
-      }
-    };
-  });

+ 0 - 21
panels/parallelcoordinates/editor.html

@@ -1,21 +0,0 @@
-<div class="row-fluid" ng-controller="parallelcoordinates">
-    <div style="width:90%">
-        <form class="input-append">
-            <h6>Query</h6>
-            <input type="text" style="width:90%" ng-model="panel.query">
-            <button class="btn" ng-click="get_data();"><i class="icon-search"></i></button>
-        </form>
-    </div>
-</div>
-<h5>Panel Spy</h5>
-<div class="row-fluid">
-    <div class="span2">
-        <label class="small"> Spyable </label><input type="checkbox" ng-model="panel.spyable" ng-checked="panel.spyable">
-    </div>
-    <div class="span9 small">
-        The panel spy shows 'behind the scenes' information about a panel. It can
-        be accessed by clicking the <i class='icon-eye-open'></i> in the top right
-        of the panel.
-    </div>
-</div>
-

+ 0 - 42
panels/parallelcoordinates/module.html

@@ -1,42 +0,0 @@
-<style>
-    svg {
-        font-size: 14px;
-    }
-
-    .foregroundlines {
-        fill: none;
-        stroke-opacity: 0.3;
-        stroke-width: 1.5px;
-    }
-
-    .foregroundlines.fade {
-        display:none
-    }
-
-    .legend line {
-        stroke-width: 2px;
-    }
-
-    .brush .extent {
-        fill-opacity: .3;
-        stroke: #fff;
-        shape-rendering: crispEdges;
-    }
-
-    .axis line, .axis path {
-        fill: none;
-        stroke: #000;
-        shape-rendering: crispEdges;
-    }
-
-    .axis text {
-        text-shadow: 0 1px 0 #fff;
-        cursor: move;
-    }
-</style>
-<kibana-panel ng-controller='parallelcoordinates' ng-init="init()">
-  <span ng-show="panel.spyable" style="position:absolute;right:0px;top:0px" class='panelextra pointer'>
-    <i bs-modal="'partials/modal.html'" class="icon-eye-open"></i>
-  </span>
-    <div parallelcoordinates params="{{panel}}" style="height:{{panel.height || row.height}}"></div>
-</kibana-panel>

+ 0 - 514
panels/parallelcoordinates/module.js

@@ -1,514 +0,0 @@
-angular.module('kibana.parallelcoordinates', [])
-  .controller('parallelcoordinates', function ($scope, eventBus) {
-
-
-    $scope.activeDocs = [];
-
-    // Set and populate defaults
-    var _d = {
-      status  : "Broken",
-      query   : "*",
-      size    : 100, // Per page
-      pages   : 5,   // Pages available
-      offset  : 0,
-      sort    : ['@timestamp','desc'],
-      group   : "default",
-      style   : {'font-size': '9pt'},
-      fields  : [],
-      sortable: true,
-      spyable: true
-    }
-
-    _.defaults($scope.panel, _d)
-
-    $scope.init = function () {
-
-      $scope.set_listeners($scope.panel.group);
-      // Now that we're all setup, request the time from our group
-      eventBus.broadcast($scope.$id,$scope.panel.group,"get_time")
-
-      //and get the currently selected fields
-      eventBus.broadcast($scope.$id,$scope.panel.group,"get_fields")
-    };
-
-    $scope.set_listeners = function(group) {
-      eventBus.register($scope,'time',function(event,time) {
-        $scope.panel.offset = 0;
-        set_time(time)
-      });
-      eventBus.register($scope,'query',function(event,query) {
-        $scope.panel.offset = 0;
-        $scope.panel.query = _.isArray(query) ? query[0] : query;
-        $scope.get_data();
-      });
-      eventBus.register($scope,'sort', function(event,sort){
-        $scope.panel.sort = _.clone(sort);
-        $scope.get_data();
-      });
-      eventBus.register($scope,'selected_fields', function(event, fields) {
-        $scope.panel.fields = _.clone(fields)
-        $scope.$emit('render');
-      });
-    };
-
-
-    $scope.get_data = function (segment,query_id) {
-
-      // Make sure we have everything for the request to complete
-      if (_.isUndefined($scope.index) || _.isUndefined($scope.time))
-        return;
-
-      var _segment = _.isUndefined(segment) ? 0 : segment
-      $scope.segment = _segment;
-
-      $scope.panel.loading = true;
-      var request = $scope.ejs.Request().indices($scope.index[_segment])
-        .query(ejs.FilteredQuery(
-          ejs.QueryStringQuery($scope.panel.query || '*'),
-          ejs.RangeFilter($scope.time.field)
-            .from($scope.time.from)
-            .to($scope.time.to)
-        )
-        )
-        .size($scope.panel.size*$scope.panel.pages)
-        .sort($scope.panel.sort[0],$scope.panel.sort[1]);
-
-      $scope.populate_modal(request);
-
-      var results = request.doSearch();
-
-
-      // Populate scope when we have results
-      results.then(function (results) {
-        $scope.panel.loading = false;
-        if(_segment === 0) {
-          $scope.hits = 0;
-          $scope.data = [];
-          query_id = $scope.query_id = new Date().getTime()
-        }
-
-        // Check for error and abort if found
-        if(!(_.isUndefined(results.error))) {
-          $scope.panel.error = $scope.parse_error(results.error);
-          return;
-        }
-
-        // Check that we're still on the same query, if not stop
-        if($scope.query_id === query_id) {
-          $scope.data= $scope.data.concat(_.map(results.hits.hits, function(hit) {
-            return flatten_json(hit['_source']);
-          }));
-
-          $scope.hits += results.hits.total;
-
-          // Sort the data
-          $scope.data = _.sortBy($scope.data, function(v){
-            return v[$scope.panel.sort[0]]
-          });
-
-          // Reverse if needed
-          if($scope.panel.sort[1] == 'desc')
-            $scope.data.reverse();
-
-          // Keep only what we need for the set
-          $scope.data = $scope.data.slice(0,$scope.panel.size * $scope.panel.pages)
-
-        } else {
-          return;
-        }
-        $scope.$emit('render')
-      });
-
-
-
-    };
-
-    // I really don't like this function, too much dom manip. Break out into directive?
-    $scope.populate_modal = function (request) {
-      $scope.modal = {
-        title: "Inspector",
-        body: "<h5>Last Elasticsearch Query</h5><pre>" + 'curl -XGET ' + config.elasticsearch + '/' + $scope.index + "/_search?pretty -d'\n" + angular.toJson(JSON.parse(request.toString()), true) + "'</pre>"
-      }
-    };
-
-    function set_time(time) {
-      $scope.time = time;
-      $scope.index = _.isUndefined(time.index) ? $scope.index : time.index
-      $scope.get_data();
-    }
-
-
-    $scope.$watch('activeDocs', function(v) {
-      eventBus.broadcast($scope.$id,$scope.panel.group,"table_documents",
-        {query:$scope.panel.query,docs:$scope.activeDocs});
-    });
-
-  })
-  .directive('parallelcoordinates', function () {
-    return {
-      restrict: 'A',
-      link: function (scope, elem, attrs) {
-
-        //used to store a variety of directive-level variables
-        var directive = {};
-
-        scope.initializing = false;
-
-
-        /**
-         * Initialize the panels if new, or render existing panels
-         */
-        scope.init_or_render = function() {
-          if (typeof directive.svg === 'undefined') {
-
-            //prevent duplicate initialization steps, if render is called again
-            //before the svg is setup
-            if (!scope.initializing) {
-              init_panel();
-            }
-          } else {
-            render_panel();
-          }
-        };
-
-
-        /**
-         * Receive render events
-         */
-        scope.$on('render', function () {
-          scope.init_or_render();
-        });
-
-        /**
-         * On window resize, re-render the panel
-         */
-        angular.element(window).bind('resize', function () {
-          scope.init_or_render();
-        });
-
-
-        /**
-         * Load the various panel-specific scripts then initialize
-         * the svg and set appropriate D3 settings
-         */
-        function init_panel() {
-
-          directive.m = [80, 100, 80, 100];
-          directive.w = $(elem[0]).width() - directive.m[1] - directive.m[3];
-          directive.h = $(elem[0]).height() - directive.m[0] - directive.m[2];
-
-
-          scope.initializing = true;
-          // Using LABjs, wait until all scripts are loaded before rendering panel
-          var scripts = $LAB.script("common/lib/d3.v3.min.js?rand="+Math.floor(Math.random()*10000));
-
-          scripts.wait(function () {
-
-            directive.x = d3.scale.ordinal().domain(scope.panel.fields).rangePoints([0, directive.w]);
-            directive.y = {};
-
-            directive.line = d3.svg.line().interpolate('cardinal');
-            directive.axis = d3.svg.axis().orient("left");
-
-            var viewbox = "0 0 " + (directive.w + directive.m[1] + directive.m[3]) + " " + (directive.h + directive.m[0] + directive.m[2]);
-            directive.svg = d3.select(elem[0]).append("svg")
-              .attr("width", "100%")
-              .attr("height", "100%")
-              .attr("viewbox", viewbox)
-              .append("svg:g")
-              .attr("transform", "translate(" + directive.m[3] + "," + directive.m[0] + ")");
-
-            // Add foreground lines.
-            directive.foreground = directive.svg.append("svg:g")
-              .attr("class", "foreground");
-
-            scope.initializing = false;
-            render_panel();
-          });
-
-
-        }
-
-        // Returns the path for a given data point.
-        function path(d) {
-          return directive.line(scope.panel.fields.map(function(p) { return [directive.x(p), directive.y[p](d[p])]; }));
-        }
-
-        // Handles a brush event, toggling the display of foreground lines.
-        function brush() {
-          var actives = scope.panel.fields.filter(function(p) { return !directive.y[p].brush.empty(); }),
-            extents = actives.map(function(p) { return directive.y[p].brush.extent(); });
-
-          //.fade class hides the "inactive" lines, helps speed up rendering significantly
-          directive.foregroundLines.classed("fade", function(d) {
-            return !actives.every(function(p, i) {
-
-              var pointValue;
-
-              if (directive.ordinals[p] === true) {
-                pointValue = directive.y[p](d[p]);
-              } else {
-                pointValue = d[p];
-              }
-
-              var inside = extents[i][0] <= pointValue && pointValue <= extents[i][1];
-              return inside;
-            });
-          });
-
-          //activeDocs contains the actual doc records for selected lines.
-          //will be broadcast out to the table
-          var activeDocs = _.filter(scope.data, function(v) {
-            return actives.every(function(p,i) {
-              var inside = extents[i][0] <= v[p] && v[p] <= extents[i][1];
-              return inside;
-            });
-          })
-
-          scope.$apply(function() {
-            scope.activeDocs = activeDocs;
-          });
-        }
-
-
-        //Drag functions are used for dragging the axis aroud
-        function dragstart(d) {
-          directive.i = scope.panel.fields.indexOf(d);
-        }
-
-        function drag(d) {
-          directive.x.range()[directive.i] = d3.event.x;
-          scope.panel.fields.sort(function(a, b) { return directive.x(a) - directive.x(b); });
-          directive.foregroundLines.attr("transform", function(d) { return "translate(" + directive.x(d) + ")"; });
-          directive.traits.attr("transform", function(d) { return "translate(" + directive.x(d) + ")"; });
-          directive.brushes.attr("transform", function(d) { return "translate(" + directive.x(d) + ")"; });
-          directive.axisLines.attr("transform", function(d) { return "translate(" + directive.x(d) + ")"; });
-          directive.foregroundLines.attr("d", path);
-        }
-
-        function dragend(d) {
-          directive.x.domain(scope.panel.fields).rangePoints([0, directive.w]);
-          var t = d3.transition().duration(500);
-          t.selectAll(".trait").attr("transform", function(d) { return "translate(" + directive.x(d) + ")"; });
-          t.selectAll(".axis").attr("transform", function(d) { return "translate(" + directive.x(d) + ")"; });
-          t.selectAll(".brush").attr("transform", function(d) { return "translate(" + directive.x(d) + ")"; });
-          t.selectAll(".foregroundlines").attr("d", path);
-        }
-
-
-
-
-        /**
-         * Render updates to the SVG. Typically happens when the data changes (time, query)
-         * or when new options are selected
-         */
-        function render_panel() {
-
-
-          //update the svg if the size has changed
-          directive.w = $(elem[0]).width() - directive.m[1] - directive.m[3];
-          directive.h = $(elem[0]).height() - directive.m[0] - directive.m[2];
-          directive.svg.attr("viewbox", "0 0 " + (directive.w + directive.m[1] + directive.m[3]) + " " + (directive.h + directive.m[0] + directive.m[2]));
-
-
-          directive.x = d3.scale.ordinal().domain(scope.panel.fields).rangePoints([0, directive.w]);
-          directive.y = {};
-
-          directive.line = d3.svg.line().interpolate('cardinal');
-          directive.axis = d3.svg.axis().orient("left").ticks(5);
-          directive.ordinals = {};
-
-          scope.panel.fields.forEach(function(d) {
-            var firstField = scope.data[0][d];
-
-            if (_.isString(firstField)) {
-              if (isValidDate(new Date(firstField))) {
-
-                //convert date timestamps to actual dates
-                _.map(scope.data, function(v) {
-                  v[d] = new Date(v[d]);
-                });
-
-                var extents = d3.extent(scope.data, function(p) { return p[d]; });
-
-                directive.y[d] = d3.time.scale()
-                  .domain([extents[0],extents[1]])
-                  .range([directive.h, 0]);
-
-              } else {
-                directive.ordinals[d] = true;
-
-                var value = function(v) { return v[d]; };
-                var values = _.map(_.uniq(scope.data, value),value);
-
-                directive.y[d] = d3.scale.ordinal()
-                  .domain(values)
-                  .rangePoints([directive.h, 0]);
-
-              }
-
-            } else if (_.isNumber(firstField)) {
-              directive.y[d] = d3.scale.linear()
-                .domain(d3.extent(scope.data, function(p) { return +p[d]; }))
-                .range([directive.h, 0]);
-
-            } else if (_.isDate(firstField)) {
-              //this section is only used after timestamps have been parsed into actual date objects...
-              //avoids reparsing
-
-              var extents = d3.extent(scope.data, function(p) { return p[d]; });
-
-              directive.y[d] = d3.time.scale()
-                .domain([extents[0],extents[1]])
-                .range([directive.h, 0]);
-
-            }
-
-            directive.y[d].brush = d3.svg.brush()
-              .y(directive.y[d])
-              .on("brush", brush);
-          });
-
-          //setup the colors for the lines
-          setColors();
-
-          //pull out the actively selected columns for rendering the axis/lines
-          var activeData = _.map(scope.data, function(d) {
-            var t = {};
-            _.each(scope.panel.fields, function(f) {
-              t[f] = d[f];
-            });
-            return t;
-          });
-
-
-          //Lines
-          directive.foregroundLines = directive.foreground
-            .selectAll(".foregroundlines")
-            .data(activeData, function(d, i){
-              var id = "";
-              _.each(d, function(v) {
-                id += i + "_" + v;
-              });
-              return id;
-            });
-          directive.foregroundLines
-            .enter().append("svg:path")
-            .attr("d", path)
-            .attr("class", "foregroundlines")
-            .attr("style", function(d) {
-              return "stroke:" + directive.colors(d[scope.panel.fields[0]]) + ";";
-            });
-          directive.foregroundLines.exit().remove();
-
-
-
-          //Axis group
-          directive.traits = directive.svg.selectAll(".trait")
-            .data(scope.panel.fields, String);
-          directive.traits
-            .enter().append("svg:g")
-            .attr("class", "trait")
-            .attr("transform", function(d) { return "translate(" + directive.x(d) + ")"; });
-          directive.traits
-            .exit().remove();
-
-
-          //brushes used to select lines
-          directive.brushes = directive.svg.selectAll(".brush")
-            .data(scope.panel.fields, String);
-          directive.brushes
-            .enter()
-            .append("svg:g")
-            .attr("class", "brush")
-            .each(function(d) {
-              d3.select(this)
-                .call(directive.y[d].brush)
-                .attr("transform", function(d) { return "translate(" + directive.x(d) + ")"; });
-            })
-            .selectAll("rect")
-            .attr("x", -8)
-            .attr("width", 16);
-
-          //this section is repeated because enter() only works on "new" data, but we always need to
-          //update the brushes if things change.  This just calls the brushing function, so it doesn't
-          //affect currently active rects
-          directive.brushes
-            .each(function(d) {
-              d3.select(this)
-                .call(directive.y[d].brush)
-                .attr("transform", function(d) { return "translate(" + directive.x(d) + ")"; });
-            });
-          directive.brushes
-            .exit().remove();
-
-
-          //vertical axis and labels
-          directive.axisLines =  directive.svg.selectAll(".axis")
-            .data(scope.panel.fields, String);
-          directive.axisLines
-            .enter()
-            .append("svg:g")
-            .attr("class", "axis")
-            .each(function(d) {
-              d3.select(this)
-                .call(directive.axis.scale(directive.y[d]))
-                .attr("transform", function(d) { return "translate(" + directive.x(d) + ")"; });
-            }).call(d3.behavior.drag()
-              .origin(function(d) { return {x: directive.x(d)}; })
-              .on("dragstart", dragstart)
-              .on("drag", drag)
-              .on("dragend", dragend))
-
-            .append("svg:text")
-            .attr("text-anchor", "middle")
-            .attr("y", -9)
-            .text(String);
-          directive.axisLines
-            .exit().remove();
-
-          //Simulate a dragend in case there is new data and we need to rearrange
-          dragend();
-
-        }
-
-        function setColors() {
-
-          var firstPanelField = scope.data[0][scope.panel.fields[0]];
-          var extents = d3.extent(scope.data, function(p) { return p[scope.panel.fields[0]]; });
-
-          if (_.isString(firstPanelField)) {
-
-            var value = function(v) { return v[firstPanelField]; };
-            var values = _.map(_.uniq(scope.data, value),value);
-
-            values = scope.data;
-            directive.colors = d3.scale.ordinal()
-              .domain(values)
-              .range(d3.range(values.length).map(d3.scale.linear()
-                .domain([0, values.length - 1])
-                .range(["red", "blue"])
-                .interpolate(d3.interpolateLab)));
-
-          } else if (_.isNumber(firstPanelField)) {
-            directive.colors = d3.scale.linear()
-              .domain([extents[0],extents[1]])
-              .range(["#4580FF", "#FF9245"]);
-
-          } else if (_.isDate(firstPanelField)) {
-            directive.colors = d3.time.scale()
-              .domain([extents[0],extents[1]])
-              .range(["#4580FF", "#FF9245"]);
-          }
-
-        }
-
-        function isValidDate(d) {
-          if ( Object.prototype.toString.call(d) !== "[object Date]" )
-            return false;
-          return !isNaN(d.getTime());
-        }
-
-      }
-    };
-  });

+ 52 - 73
panels/pie/editor.html

@@ -1,83 +1,62 @@
-<div class="row-fluid" ng-switch="panel.mode">
-<div class="span3">
-  <label class="small">Mode</label> 
-  <select class="input-small" ng-change="set_mode(panel.mode)" ng-model="panel.mode" ng-options="f for f in ['terms','goal']"></select>
-</div> 
-  <div ng-switch-when="terms">
+<div>
+  <div class="row-fluid" ng-switch="panel.mode">
     <div class="row-fluid">
-      <div class="span3">
-        <form style="margin-bottom: 0px">
+      <div class="span2">
+        <label class="small">Mode</label> 
+        <select class="input-small" ng-change="set_mode(panel.mode);set_refresh(true)" ng-model="panel.mode" ng-options="f for f in ['terms','goal']"></select>
+      </div> 
+    </div>
+    <div ng-switch-when="terms">
+      <div class="row-fluid">
+        <div class="span2">
           <label class="small">Field</label>
-          <input type="text" style="width:90%" bs-typeahead="fields.list" ng-model="panel.query.field">
-        </form>
-      </div>
-      <div class="span5">
-        <form class="input-append" style="margin-bottom: 0px">
-          <label class="small">Query</label>
-          <input type="text" style="width:80%" ng-model="panel.query.query">
-          <button class="btn" ng-click="get_data()"><i class="icon-search"></i></button>
-        </form>
-      </div>
-    </div>  
-    <div class="row-fluid">
-      <div class="span3">
-        <label class="small">Length</label>
-        <input type="number" style="width:80%" ng-model="panel.size" ng-change="get_data()">
-      </div>
-      <div class="span8">
-        <form class="input-append" style="margin-bottom: 0px">
+          <input type="text" class="input-small" bs-typeahead="fields.list" ng-model="panel.query.field" ng-change="set_refresh(true)">
+        </div>
+        <div class="span2">
+          <label class="small">Length</label>
+          <input class="input-small" type="number" ng-model="panel.size" ng-change="set_refresh(true)">
+        </div>
+        <div class="span6">
           <label class="small">Exclude Terms(s) (comma seperated)</label>
-          <input array-join type="text" style="width:90%"  ng-model='panel.exclude'></input>
-          <button class="btn" ng-click="get_data()"><i class="icon-search"></i></button>
-        </form>
-      </div>
+          <input array-join type="text" ng-model='panel.exclude'></input>
+        </div>
+      </div>  
     </div>
-  </div>
-  <div ng-switch-when="goal">
-    <div class="row-fluid">
-      <div class="span3">
-        <label class="small">Mode</label> 
-        <select class="input-small" ng-change="set_mode(panel.mode)" ng-model="panel.mode" ng-options="f for f in ['terms','goal']"></select>
-      </div>   
-      <div class="span2">
-        <form style="margin-bottom: 0px">
-          <label class="small">Goal</label>
-          <input type="number" style="width:90%" ng-model="panel.query.goal">
-        </form>
-      </div>
-      <div class="span5">
-        <form class="input-append" style="margin-bottom: 0px">
-          <label class="small">Query</label>
-          <input type="text" style="width:80%" ng-model="panel.query.query">
-          <button class="btn" ng-click="get_data()"><i class="icon-search"></i></button>
-        </form>
+    <div ng-switch-when="goal">
+      <div class="row-fluid">
+        <div class="span2">
+          <form style="margin-bottom: 0px">
+            <label class="small">Goal</label>
+            <input type="number" style="width:90%" ng-model="panel.query.goal" ng-change="set_refresh(true)">
+          </form>
+        </div>
       </div>
     </div>
   </div>
-</div>
-<div class="row-fluid">    
-  <div class="span1">
-    <label class="small"> Donut </label><input type="checkbox" ng-model="panel.donut" ng-checked="panel.donut">
-  </div>
-  <div class="span1">
-    <label class="small"> Tilt </label><input type="checkbox" ng-model="panel.tilt" ng-checked="panel.tilt">
-  </div>
-  <div class="span1">
-    <label class="small"> Labels </label><input type="checkbox" ng-model="panel.labels" ng-checked="panel.labels">
-  </div>
-  <div class="span3"> 
-    <label class="small">Legend</label> 
-    <select class="input-small" ng-model="panel.legend" ng-options="f for f in ['above','below','none']"></select></span>
-  </div>
-</div>
-<h5>Panel Spy</h5>
-<div class="row-fluid">
-  <div class="span2"> 
-    <label class="small"> Spyable </label><input type="checkbox" ng-model="panel.spyable" ng-checked="panel.spyable">
+  <div class="row-fluid">    
+    <div class="span1">
+      <label class="small"> Donut </label><input type="checkbox" ng-model="panel.donut" ng-checked="panel.donut">
+    </div>
+    <div class="span1">
+      <label class="small"> Tilt </label><input type="checkbox" ng-model="panel.tilt" ng-checked="panel.tilt">
+    </div>
+    <div class="span1">
+      <label class="small"> Labels </label><input type="checkbox" ng-model="panel.labels" ng-checked="panel.labels">
+    </div>
+    <div class="span3"> 
+      <label class="small">Legend</label> 
+      <select class="input-small" ng-model="panel.legend" ng-options="f for f in ['above','below','none']"></select></span>
+    </div>
   </div>
-  <div class="span9 small">
-    The panel spy shows 'behind the scenes' information about a panel. It can
-    be accessed by clicking the <i class='icon-eye-open'></i> in the top right
-    of the panel.
+  <h5>Panel Spy</h5>
+  <div class="row-fluid">
+    <div class="span2"> 
+      <label class="small"> Spyable </label><input type="checkbox" ng-model="panel.spyable" ng-checked="panel.spyable">
+    </div>
+    <div class="span9 small">
+      The panel spy shows 'behind the scenes' information about a panel. It can
+      be accessed by clicking the <i class='icon-eye-open'></i> in the top right
+      of the panel.
+    </div>
   </div>
 </div>

+ 44 - 69
panels/pie/module.js

@@ -23,27 +23,20 @@
                       doesn't have a field
   * spyable :: Show the 'eye' icon that displays the last ES query for this panel
 
-  ### Group Events
-  #### Sends
-  * get_time :: On panel initialization get time range to query
-  #### Receives
-  * time :: An object containing the time range to use and the index(es) to query
-  * query :: An Array of queries, this panel will use the first in the array
-
 */
 
 angular.module('kibana.pie', [])
-.controller('pie', function($scope, eventBus) {
+.controller('pie', function($scope, $rootScope, query, dashboard, filterSrv) {
 
   // Set and populate defaults
   var _d = {
     status  : "Deprecating Soon",
-    query   : { field:"_all", query:"*", goal: 1}, 
+    query   : { field:"_type", goal: 100}, 
     size    : 10,
     exclude : [],
     donut   : false,
     tilt    : false,
-    legend  : true,
+    legend  : "above",
     labels  : true,
     mode    : "terms",
     group   : "default",
@@ -53,30 +46,7 @@ angular.module('kibana.pie', [])
   _.defaults($scope.panel,_d)
 
   $scope.init = function() {
-    eventBus.register($scope,'time', function(event,time){set_time(time)});
-    eventBus.register($scope,'query', function(event, query) {
-      $scope.panel.query.query = _.isArray(query) ? query[0] : query;
-      $scope.get_data();
-    });
-    // Now that we're all setup, request the time from our group
-    eventBus.broadcast($scope.$id,$scope.panel.group,'get_time')
-  }
-
-
-  $scope.remove_query = function(q) {
-    if($scope.panel.mode !== 'query') 
-      return false;
-    $scope.panel.query = _.without($scope.panel.query,q);
-    $scope.get_data();
-  }
-
-  $scope.add_query = function(label,query) {
-    if($scope.panel.mode !== 'query') 
-      return false;
-    $scope.panel.query.unshift({
-      query: query,
-      label: label, 
-    });
+    $scope.$on('refresh',function(){$scope.get_data()})
     $scope.get_data();
   }
 
@@ -84,21 +54,40 @@ angular.module('kibana.pie', [])
     switch(mode)
     {
     case 'terms':
-      $scope.panel.query = {query:"*",field:"_all"};
+      $scope.panel.query = {field:"_all"};
       break;
     case 'goal':
-      $scope.panel.query = {query:"*",goal:100};
+      $scope.panel.query = {goal:100};
       break;
     }
   }
 
+  $scope.set_refresh = function (state) { 
+    $scope.refresh = state; 
+  }
+
+  $scope.close_edit = function() {
+    if($scope.refresh)
+      $scope.get_data();
+    $scope.refresh =  false;
+    $scope.$emit('render');
+  }
+
   $scope.get_data = function() {
+    
     // Make sure we have everything for the request to complete
-    if(_.isUndefined($scope.index) || _.isUndefined($scope.time))
+    if(dashboard.indices.length == 0) {
       return
+    } 
 
     $scope.panel.loading = true;
-    var request = $scope.ejs.Request().indices($scope.index);
+    var request = $scope.ejs.Request().indices(dashboard.indices);
+
+    // This could probably be changed to a BoolFilter 
+    var boolQuery = ejs.BoolQuery();
+    _.each(query.list,function(q) {
+      boolQuery = boolQuery.should(ejs.QueryStringQuery(q.query || '*'))
+    })
 
     // Terms mode
     if ($scope.panel.mode == "terms") {
@@ -109,10 +98,8 @@ angular.module('kibana.pie', [])
           .exclude($scope.panel.exclude)
           .facetFilter(ejs.QueryFilter(
             ejs.FilteredQuery(
-              ejs.QueryStringQuery($scope.panel.query.query || '*'),
-              ejs.RangeFilter($scope.time.field)
-                .from($scope.time.from)
-                .to($scope.time.to)
+              boolQuery,
+              filterSrv.getBoolFilter(filterSrv.ids)
               )))).size(0)
 
       $scope.populate_modal(request);
@@ -141,11 +128,8 @@ angular.module('kibana.pie', [])
     // Goal mode
     } else {
       request = request
-        .query(ejs.QueryStringQuery($scope.panel.query.query || '*'))
-        .filter(ejs.RangeFilter($scope.time.field)
-          .from($scope.time.from)
-          .to($scope.time.to)
-          .cache(false))
+        .query(boolQuery)
+        .filter(filterSrv.getBoolFilter(filterSrv.ids))
         .size(0)
       
       $scope.populate_modal(request);
@@ -169,26 +153,14 @@ angular.module('kibana.pie', [])
     $scope.modal = {
       title: "Inspector",
       body : "<h5>Last Elasticsearch Query</h5><pre>"+
-          'curl -XGET '+config.elasticsearch+'/'+$scope.index+"/_search?pretty -d'\n"+
+          'curl -XGET '+config.elasticsearch+'/'+dashboard.indices+"/_search?pretty -d'\n"+
           angular.toJson(JSON.parse(request.toString()),true)+
         "'</pre>", 
     } 
   }
 
-  $scope.build_search = function(field,value) {
-    $scope.panel.query.query = add_to_query($scope.panel.query.query,field,value,false)
-    $scope.get_data();
-    eventBus.broadcast($scope.$id,$scope.panel.group,'query',[$scope.panel.query.query]);
-  }
-
-  function set_time(time) {
-    $scope.time = time;
-    $scope.index = _.isUndefined(time.index) ? $scope.index : time.index
-    $scope.get_data();
-  }
-  
 })
-.directive('pie', function() {
+.directive('pie', function(query, filterSrv, dashboard) {
   return {
     restrict: 'A',
     link: function(scope, elem, attrs) {
@@ -228,8 +200,8 @@ angular.module('kibana.pie', [])
             show: scope.panel.labels,
             radius: 2/3,
             formatter: function(label, series){
-              return '<div ng-click="build_search(panel.query.field,\''+label+'\') "style="font-size:8pt;text-align:center;padding:2px;color:white;">'+
-                label+'<br/>'+Math.round(series.percent)+'%</div>';
+              return '<div "style="font-size:8pt;text-align:center;padding:2px;color:white;">'+
+                series.info.alias+'<br/>'+Math.round(series.percent)+'%</div>';
             },
             threshold: 0.1 
           }
@@ -258,7 +230,7 @@ angular.module('kibana.pie', [])
             clickable: true 
           },
           legend: { show: false },
-          colors: ['#86B22D','#BF6730','#1D7373','#BFB930','#BF3030','#77207D']
+          colors: query.colors
         };
 
         // Populate element
@@ -269,7 +241,7 @@ angular.module('kibana.pie', [])
         }
       }
 
-      function piett(x, y, contents) {
+      function tt(x, y, contents) {
         var tooltip = $('#pie-tooltip').length ? 
           $('#pie-tooltip') : $('<div id="pie-tooltip"></div>');
 
@@ -287,16 +259,19 @@ angular.module('kibana.pie', [])
       }
 
       elem.bind("plotclick", function (event, pos, object) {
-        if (!object)
+        if (!object) {
           return;
-        if(scope.panel.mode === 'terms')
-          scope.build_search(scope.panel.query.field,object.series.label);
+        }
+        if(scope.panel.mode === 'terms') {
+          filterSrv.set({type:'terms',field:scope.panel.query.field,value:object.series.label})
+          dashboard.refresh();
+        }
       });
 
       elem.bind("plothover", function (event, pos, item) {
         if (item) {
           var percent = parseFloat(item.series.percent).toFixed(1) + "%";
-          piett(pos.pageX, pos.pageY, "<div style='vertical-align:middle;display:inline-block;background:"+item.series.color+";height:15px;width:15px;border-radius:10px;'></div> " + 
+          tt(pos.pageX, pos.pageY, "<div style='vertical-align:middle;display:inline-block;background:"+item.series.color+";height:15px;width:15px;border-radius:10px;'></div> " + 
             (item.series.label||"")+ " " + percent);
         } else {
           $("#pie-tooltip").remove();

+ 7 - 0
panels/query/editor.html

@@ -0,0 +1,7 @@
+<div>
+  <div class="row-fluid">    
+    <div class="span12">
+      No options here
+    </div>
+  </div>
+</div>

+ 15 - 0
panels/query/meta.html

@@ -0,0 +1,15 @@
+<div>
+  <style>
+    .input-query-alias {
+      margin-bottom: 5px !important;
+    }
+  </style>
+  <a class="close" ng-click="render();dismiss();" href="">×</a>
+  <h6>Query Alias</h6>
+  <form>
+    <input class="input-medium input-query-alias" type="text" ng-model="queries.list[id].alias" placeholder='Alias...' />
+    <div>
+      <i ng-repeat="color in queries.colors" class="pointer" ng-class="{'icon-circle-blank':queries.list[id].color == color,'icon-circle':queries.list[id].color != color}" style="color:{{color}}" ng-click="queries.list[id].color = color;render();"> </i>
+    </div>
+  </form>
+</div>

+ 51 - 0
panels/query/module.html

@@ -0,0 +1,51 @@
+<kibana-panel ng-controller='query' ng-init="init()">
+<style>
+  .short-query {
+    display:inline-block;
+    margin-left: 10px;
+  }
+  .begin-query {
+    position:absolute;
+    left:15px;
+    top:5px;
+  }
+  .end-query {
+    position:absolute;
+    right:15px;
+    top:5px;
+  }
+  .panel-query {
+    padding-left: 35px !important;
+    height: 31px !important;
+    -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
+    -moz-box-sizing: border-box;    /* Firefox, other Gecko */
+    box-sizing: border-box;         /* Opera/IE 8+ */
+  }
+  .form-search:hover .has-remove {
+    padding-left: 50px !important;
+  }
+  .remove-query {
+    opacity: 0;
+  }
+  .last-query {
+    padding-right: 45px !important;
+  }
+  .form-search:hover .remove-query {
+    opacity: 1;
+  }
+</style>
+  <label class="small">{{panel.label}}</label>
+  <div ng-repeat="id in queries.ids" ng-class="{'short-query': queries.ids.length>1}">
+    <form class="form-search" style="position:relative" ng-submit="refresh()">
+      <span class="begin-query">
+        <i class="icon-circle pointer" data-unique="1" bs-popover="'panels/query/meta.html'" data-placement="right" style="color:{{queries.list[id].color}}"></i>
+        <i class="icon-remove-sign pointer remove-query" ng-show="queries.ids.length>1" ng-click="queries.remove(id);refresh()"></i>
+      </span>
+      <input class="search-query panel-query" ng-class="{'input-block-level': queries.ids.length==1,'last-query': $last,'has-remove': queries.ids.length>1}" bs-typeahead="panel.history" data-min-length=0 data-items=100 type="text" ng-model="queries.list[id].query"/>
+      <span class="end-query">
+        <i class="icon-search pointer" ng-click="refresh()" ng-show="$last"></i>
+        <i class="icon-plus pointer" ng-click="queries.set({})" ng-show="$last"></i>
+      </span>
+    </form>
+  </div>
+</kibana-panel>

+ 60 - 0
panels/query/module.js

@@ -0,0 +1,60 @@
+/*
+
+  ## query
+
+  An experimental panel for the query service
+
+  ### Parameters
+  * label ::  The label to stick over the field 
+  * query ::  A string or an array of querys. String if multi is off, array if it is on
+              This should be fixed, it should always be an array even if its only 
+              one element
+*/
+
+angular.module('kibana.query', [])
+.controller('query', function($scope, query, $rootScope) {
+
+  // Set and populate defaults
+  var _d = {
+    status  : "Experimental",
+    label   : "Search",
+    query   : "*",
+    group   : "default",
+    history : [],
+    remember: 10 // max: 100, angular strap can't take a variable for items param
+  }
+  _.defaults($scope.panel,_d);
+
+  $scope.queries = query;
+
+  $scope.init = function() {
+  }
+
+  $scope.refresh = function(query) {
+    $rootScope.$broadcast('refresh')
+  }
+
+  $scope.render = function(query) {
+    $rootScope.$broadcast('render')
+  }
+
+  $scope.add_query = function() {
+    if (_.isArray($scope.panel.query))
+      $scope.panel.query.push("")
+    else {
+      $scope.panel.query = new Array($scope.panel.query)
+      $scope.panel.query.push("")
+    }
+  }
+
+  var update_history = function(query) {
+    if($scope.panel.remember > 0) {
+      $scope.panel.history = _.union(query.reverse(),$scope.panel.history)
+      var _length = $scope.panel.history.length
+      if(_length > $scope.panel.remember) {
+        $scope.panel.history = $scope.panel.history.slice(0,$scope.panel.remember)
+      }
+    }
+  }
+
+});

+ 0 - 5
panels/sort/module.html

@@ -1,5 +0,0 @@
-<kibana-panel ng-controller='sort' ng-init="init()" style="white-space: nowrap;">
-  <label><small>{{panel.label}}</small></label>
-  <select style="width:85%" ng-model="panel.sort[0]" ng-change="set_sort()" ng-options="f for f in fields"></select>
-  <i ng-click="toggle_sort()" class="pointer" ng-class="{'icon-chevron-up': panel.sort[1] == 'asc','icon-chevron-down': panel.sort[1] == 'desc'}"></i>
-</kibana-panel>

+ 0 - 51
panels/sort/module.js

@@ -1,51 +0,0 @@
-/*
-
-  ## Sort
-
-  This will probably be removed in the near future since it only interacts with 
-  the table panel and the table panel already implements all of its functionality.
-  It only interacts with the table panel in any case
-
-  ### Parameters
-  * label ::  The label to stick over the drop down
-  * sort :: An array where the first elemetn is the field to sort on an the second
-            is the direction ('asc' or 'desc')
-  ### Group Events
-  #### Sends
-  * sort :: An array where the first elemetn is the field to sort on an the second
-            is the direction ('asc' or 'desc')
-  #### Receives
-  * fields :: An array containing the fields in a table. This will be concat'd + 
-              uniqued with the curent list. 
-
-*/
-
-angular.module('kibana.sort', [])
-.controller('sort', function($scope, eventBus) {
-
-  // Set and populate defaults
-  var _d = {
-    status  : "Stable",
-    label   : "Sort",
-    sort    : ['_score','desc'],
-    group   : "default"
-  }
-  _.defaults($scope.panel,_d);
-
-  $scope.init = function() {
-    $scope.fields = [];
-    eventBus.register($scope,'fields',function(event, fields) {
-      $scope.panel.sort = _.clone(fields.sort);
-      $scope.fields     = _.union(fields.all,$scope.fields);
-    });
-  }
-
-  $scope.set_sort = function() {
-    eventBus.broadcast($scope.$id,$scope.panel.group,"sort",$scope.panel.sort)
-  }
-
-  $scope.toggle_sort = function() {
-    $scope.panel.sort[1] = $scope.panel.sort[1] == 'asc' ? 'desc' : 'asc';
-    $scope.set_sort();
-  }
-})

+ 0 - 17
panels/stringquery/editor.html

@@ -1,17 +0,0 @@
-<div>
-  <div class="row-fluid">    
-    <div class="span2">
-      <label class="small">Multi-query</label><input type="checkbox" ng-change="set_multi(panel.multi) "ng-model="panel.multi" ng-checked="panel.multi">
-    </div>
-    <div class="span3" ng-show="panel.multi">
-      <label class="small">Arrangement</label> 
-      <select class="input-small" ng-model="panel.multi_arrange" ng-options="f for f in ['vertical','horizontal']"></select>
-    </div>
-  </div>
-  <div class="row-fluid" ng-show="panel.multi">    
-    <div class="span12">
-      <h5>A note on multi query panels</h5>
-      <p>You turned on multi panel support: <i>cool</i>. Be aware that not all panels support display of multiple queries. Panels that don't support receiving several queries at one time will (should) display the results of the <strong>first query in the list</strong>. Further, some panels might not support receiving multiple queries in all modes. For example, the <strong>pie panel</strong> supports multiple queries only in <strong>query mode</strong>. In its other modes it will display the results of the first query in the list</p>
-    </div>
-  </div>
-</div>

+ 0 - 49
panels/stringquery/module.html

@@ -1,49 +0,0 @@
-<kibana-panel ng-controller='stringquery' ng-init="init()">
-  <div ng-show="!panel.multi">
-    <form>
-      <table class="form-horizontal">
-        <tr><td><label><small>{{panel.label}}</small></label></td></tr>
-        <tr>
-          <td width="97%" style="padding-right:20px">
-            <span style="position:relative">
-              <i class="icon-remove-sign pointer" style="position:absolute;left:10px;top:3px" ng-show="panel.query.length > 0" ng-click="panel.query='';send_query(panel.query)"></i>
-              <input bs-typeahead="panel.history" data-min-length=0 data-items=100 type="text" style="text-indent:20px;width:100%" ng-model="panel.query">
-            </span>
-          </td>
-          <td style="margin-left:20px" width="1%">
-            <button style="margin-top:0px" type="submit" class="btn btn-success btn-small" ng-click="send_query(panel.query)"><i class="icon-search"></i></button>
-          </td>
-        <tr>
-      </table>
-    </form>
-  </div>
-  <div class='row-fluid' ng-show="panel.multi && panel.multi_arrange == 'horizontal'">
-        <span ng-repeat="q in panel.query">
-          <span style="margin-bottom:0px;margin-right:5px;display:inline-block;position:relative">
-            <i class="icon-remove-sign pointer" style="position:absolute;left:10px;top:8px" ng-show="panel.query.length > 1" ng-click="remove_query($index);send_query(panel.query)"></i>
-            <input bs-typeahead="panel.history" data-min-length=0 data-items=100 style="margin-bottom:5px; text-indent: 20px;" type="text"  ng-model="panel.query[$index]" ng-model-onblur style="width:90%">
-            <br>
-          </span>
-        </span>
-        <br>
-      <button type="submit" class="btn btn-info" ng-click="send_query(panel.query)"><i class="icon-search"></i> Search</button>
-      <button type="submit" class="btn" ng-click="add_query();"><i class="icon-plus"></i></button>
-  </div>
-  <div ng-show="panel.multi && panel.multi_arrange == 'vertical'">
-    <form>
-      <table class="form-horizontal" style="margin-bottom:5px;">
-        <tr><td class="small">Queries</td></tr>      
-        <tr ng-repeat="q in panel.query" style="margin-bottom:10px">
-          <td width="99%">
-            <span style="position:relative">
-              <i class="icon-remove-sign pointer" style="position:absolute;left:10px;top:3px" ng-show="panel.query.length > 1" ng-click="remove_query($index);send_query(panel.query)"></i>
-              <input bs-typeahead="panel.history" data-min-length=0 data-items=100 type="text"  ng-model="panel.query[$index]" ng-model-onblur style="text-indent: 20px;width:100%">
-            </span>
-          </td>
-          <td width="1%" style="visibility:hidden"><button class="btn btn-info" type="submit"></button></td>
-        </tr>
-      </table>
-      <button type="submit" class="btn btn-info" ng-click="send_query(panel.query)"><i class="icon-search"></i> Search</button>
-      <button type="submit" class="btn" ng-click="send_query(panel.query);add_query();"><i class="icon-plus"></i> Add Query</button>
-  </form>
-</kibana-panel>

+ 0 - 81
panels/stringquery/module.js

@@ -1,81 +0,0 @@
-/*
-
-  ## Stringquery
-
-  Broadcasts a query object to other panels
-
-  ### Parameters
-  * label ::  The label to stick over the field 
-  * query ::  A string or an array of querys. String if multi is off, array if it is on
-              This should be fixed, it should always be an array even if its only 
-              one element
-  * multi :: Allow input of multiple queries? true/false
-  * multi_arrange :: How to arrange multu query string panels, 'vertical' or 'horizontal'
-  ### Group Events
-  #### Sends
-  * query :: Always broadcast as an array, even in multi: false
-  #### Receives
-  * query :: An array of queries. This is probably needs to be fixed.
-
-*/
-
-angular.module('kibana.stringquery', [])
-.controller('stringquery', function($scope, eventBus) {
-
-  // Set and populate defaults
-  var _d = {
-    status  : "Stable",
-    label   : "Search",
-    query   : "*",
-    group   : "default",
-    multi   : false,
-    multi_arrange: 'horizontal',
-    history : [],
-    remember: 10 // max: 100, angular strap can't take a variable for items param
-  }
-  _.defaults($scope.panel,_d);
-
-  $scope.init = function() {
-    // If we're in multi query mode, they all get wiped out if we receive a 
-    // query. Query events must be exchanged as arrays.
-    eventBus.register($scope,'query',function(event,query) {
-      $scope.panel.query = query;
-      update_history(query);
-    });   
-  }
-
-  $scope.send_query = function(query) {
-    var _query = _.isArray(query) ? query : [query];
-    update_history(_query);
-    eventBus.broadcast($scope.$id,$scope.panel.group,'query',_query);
-  }
-
-  $scope.add_query = function() {
-    if (_.isArray($scope.panel.query))
-      $scope.panel.query.push("")
-    else {
-      $scope.panel.query = new Array($scope.panel.query)
-      $scope.panel.query.push("")
-    }
-  }
-
-  $scope.set_multi = function(multi) {
-    $scope.panel.query = multi ? 
-      new Array($scope.panel.query) : $scope.panel.query[0];
-  }
-
-  $scope.remove_query = function(index) {
-    $scope.panel.query.splice(index,1);
-  }
-
-  var update_history = function(query) {
-    if($scope.panel.remember > 0) {
-      $scope.panel.history = _.union(query.reverse(),$scope.panel.history)
-      var _length = $scope.panel.history.length
-      if(_length > $scope.panel.remember) {
-        $scope.panel.history = $scope.panel.history.slice(0,$scope.panel.remember)
-      }
-    }
-  }
-
-});

+ 0 - 17
panels/table/editor.html

@@ -1,12 +1,3 @@
-  <div class="row-fluid">    
-    <div style="width:90%">
-      <form class="input-append">
-        <h6>Query</h6>
-        <input type="text" style="width:90%" ng-model="panel.query">
-        <button class="btn" ng-click="get_data();"><i class="icon-search"></i></button>
-      </form>
-    </div>
-  </div>
   <div class="row-fluid">    
     <div class="span4">
       <form class="input-append">
@@ -77,14 +68,6 @@
       <select class="input-small" ng-model="panel.overflow" ng-options="f.value as f.key for f in [{key:'scroll',value:'height'},{key:'expand',value:'min-height'}]"></select>
     </div>
   </div>
-  <!--<div class="row-fluid" ng-show='panel.sortable'>
-    <div class="span11"> 
-      <h6>A note about sorting</h6>
-      Allowing sorting can incur a significant performance penalty if using timestamped indices. 
-      Kibana will be unable to query your indices sequentially and thus must query them all at
-      once. Only enable sorting if your cluster is stout enough to handle it.
-    </div>
-  </div>-->
   <h5>Panel Spy</h5>
   <div class="row-fluid">
     <div class="span2"> 

+ 26 - 36
panels/table/module.js

@@ -29,7 +29,7 @@
 */
 
 angular.module('kibana.table', [])
-.controller('table', function($scope, eventBus, fields) {
+.controller('table', function($rootScope, $scope, eventBus, fields, query, dashboard, filterSrv) {
 
   // Set and populate defaults
   var _d = {
@@ -55,20 +55,11 @@ angular.module('kibana.table', [])
 
     $scope.set_listeners($scope.panel.group)
 
-    // Now that we're all setup, request the time from our group
-    eventBus.broadcast($scope.$id,$scope.panel.group,"get_time")
+    $scope.get_data();
   }
 
   $scope.set_listeners = function(group) {
-    eventBus.register($scope,'time',function(event,time) {
-      $scope.panel.offset = 0;
-      set_time(time)
-    });
-    eventBus.register($scope,'query',function(event,query) {
-      $scope.panel.offset = 0;
-      $scope.panel.query = _.isArray(query) ? query[0] : query;
-      $scope.get_data();
-    });
+    $scope.$on('refresh',function(){$scope.get_data()})
     eventBus.register($scope,'sort', function(event,sort){
       $scope.panel.sort = _.clone(sort);
       $scope.get_data();
@@ -77,7 +68,7 @@ angular.module('kibana.table', [])
       $scope.panel.fields = _.clone(fields)
     });
     eventBus.register($scope,'table_documents', function(event, docs) {
-        $scope.panel.query = docs.query;
+        query.list[query.ids[0]].query = docs.query;
         $scope.data = docs.docs;
     });
   }
@@ -116,32 +107,37 @@ angular.module('kibana.table', [])
   }
 
   $scope.build_search = function(field,value,negate) {
-    $scope.panel.query = add_to_query($scope.panel.query,field,value,negate)
+    var query = field+":"+angular.toJson(value)
+    filterSrv.set({type:'querystring',query:query,mandate:(negate ? 'mustNot':'must')})
     $scope.panel.offset = 0;
-    $scope.get_data();
-    eventBus.broadcast($scope.$id,$scope.panel.group,'query',[$scope.panel.query]);
+    dashboard.refresh();
   }
 
   $scope.get_data = function(segment,query_id) {
     $scope.panel.error =  false;
 
     // Make sure we have everything for the request to complete
-    if(_.isUndefined($scope.index) || _.isUndefined($scope.time))
+    if(dashboard.indices.length == 0) {
       return
+    }
     
     $scope.panel.loading = true;
 
     var _segment = _.isUndefined(segment) ? 0 : segment
     $scope.segment = _segment;
 
-    var request = $scope.ejs.Request().indices($scope.index[_segment])
-      .query(ejs.FilteredQuery(
-        ejs.QueryStringQuery($scope.panel.query || '*'),
-        ejs.RangeFilter($scope.time.field)
-          .from($scope.time.from)
-          .to($scope.time.to)
-        )
-      )
+    var request = $scope.ejs.Request().indices(dashboard.indices[_segment])
+
+    var boolQuery = ejs.BoolQuery();
+    _.each(query.list,function(q) {
+      boolQuery = boolQuery.should(ejs.QueryStringQuery(q.query || '*'))
+    })
+
+    request = request.query(
+      ejs.FilteredQuery(
+        boolQuery,
+        filterSrv.getBoolFilter(filterSrv.ids)
+      ))
       .highlight(
         ejs.Highlight($scope.panel.highlight)
         .fragmentSize(2147483647) // Max size of a 32bit unsigned int
@@ -205,10 +201,10 @@ angular.module('kibana.table', [])
       // If we're not sorting in reverse chrono order, query every index for
       // size*pages results
       // Otherwise, only get size*pages results then stop querying
-      if(
-          ($scope.data.length < $scope.panel.size*$scope.panel.pages || 
-            !(($scope.panel.sort[0] === $scope.time.field) && $scope.panel.sort[1] === 'desc')) && 
-          _segment+1 < $scope.index.length
+      if($scope.data.length < $scope.panel.size*$scope.panel.pages
+        //($scope.data.length < $scope.panel.size*$scope.panel.pages
+         // || !(($scope.panel.sort[0] === $scope.time.field) && $scope.panel.sort[1] === 'desc'))
+        && _segment+1 < dashboard.indices.length
       ) {
         $scope.get_data(_segment+1,$scope.query_id)
       }
@@ -244,7 +240,7 @@ angular.module('kibana.table', [])
     });
     eventBus.broadcast($scope.$id,$scope.panel.group,"table_documents", 
       {
-        query: $scope.panel.query,
+        query: query.list[query.ids[0]].query,
         docs : _.pluck($scope.data,'_source'),
         index: $scope.index
       });
@@ -261,12 +257,6 @@ angular.module('kibana.table', [])
   }
 
 
-  function set_time(time) {
-    $scope.time = time;
-    $scope.index = _.isUndefined(time.index) ? $scope.index : time.index
-    $scope.get_data();
-  }
-
 })
 .filter('highlight', function() {
   return function(text) {

+ 0 - 34
panels/timepicker/editor.html

@@ -8,40 +8,6 @@
       <input type="text" class="input-small" ng-model="panel.timefield">
     </div>
   </div>
-  <div class="row-fluid">    
-    <h5>Index Settings</h5>
-    <div ng-show="panel.index_interval != 'none'" class="row-fluid"> 
-       <div class="span12">
-        <p class="small">
-          Time stamped indices use your selected time range to create a list of 
-          indices that match a specified timestamp pattern. This can be very 
-          efficient for some data sets (eg, logs) For example, to match the 
-          default logstash index pattern you might use 
-          <code>[logstash-]YYYY.MM.DD</code>. The [] in "[logstash-]" are 
-          important as they instruct Kibana not to treat those letters as a 
-          pattern.
-        </p>
-        <p class="small">
-          See <a href="http://momentjs.com/docs/#/displaying/format/">http://momentjs.com/docs/#/displaying/format/</a>
-          for documentation on date formatting.
-        </p>
-
-       </div>
-    </div>
-    <div class="row-fluid"> 
-      <div class="span2">
-        <h6>Timestamp</h6><select class="input-mini" ng-model="panel.index_interval" ng-options='f for f in ["none","hour","day","week","month","year"]'></select>
-      </div>
-      <div class="span5">
-        <h6>Index <span ng-show="panel.index_interval != 'none'">pattern <small>Absolutes in []</small></span></h6>
-        <input type="text" class="input-medium" ng-model="panel.index">
-      </div>
-      <div class="span4">
-        <h6>Failover Index <small>If index not found</small></h6>
-        <input type="text" class="input-medium" ng-model="panel.defaultindex">
-      </div>
-    </div>
-  </div>
   <div class="row-fluid">
     <h5>Relative mode <small>settings</small></h5>  
     <div class="span6">

+ 13 - 3
panels/timepicker/module.html

@@ -1,5 +1,5 @@
 <kibana-panel ng-controller='timepicker' ng-init="init()">
-  <div class="row-fluid" ng-switch="panel.mode">
+  <div class="row-fluid" ng-switch="panel.mode" ng-show="filterSrv.idsByType('time').length > 0">
     <div ng-switch-when="absolute">
       <div class="span5">
         <form class="nomargin">
@@ -47,17 +47,27 @@
       </div>
     </div>
   </div>
+  <div class="row-fluid" ng-show="filterSrv.idsByType('time').length < 1">
+    <div>
+      <div class="span11">
+        <h4>No time filter present</h4>
+      </div>
+    </div>
+  </div>
   <div class="row-fluid nomargin">
-    <div class="span12 small">
+    <div class="span12 small" ng-show="filterSrv.idsByType('time').length > 0">
       <a ng-click="set_mode('relative')" ng-class="{'strong': (panel.mode == 'relative')}">Relative</a> | 
       <a ng-click="set_mode('absolute')" ng-class="{'strong': (panel.mode == 'absolute')}">Absolute</a> | 
       <a ng-click="set_mode('since')"    ng-class="{'strong': (panel.mode == 'since')}">Since</a>
-      <span ng-hide="panel.mode == 'absolute'"> | 
+      <span ng-hide="panel.mode == 'absolute' || panel.mode == 'none'"> | 
         <input type="checkbox" ng-model="panel.refresh.enable" ng-change='refresh();'> Auto-refresh 
         <span ng-class="{'ng-cloak': !panel.refresh.enable}">
           every <a data-title="<small>Auto-refresh Settings</small>" data-placement="bottom" bs-popover="'panels/timepicker/refreshctrl.html'">{{panel.refresh.interval}}s</a>.
         </span>
       </span>
     </div>
+    <div class="span12 small" ng-show="filterSrv.idsByType('time').length < 1">
+      <a class='btn btn-small' ng-click="time_apply()">Create a time filter</a> 
+    </div>
   </div>
 </kibana-panel>

+ 61 - 76
panels/timepicker/module.js

@@ -11,24 +11,14 @@
   * time_options :: An array of possible time options. Default: ['5m','15m','1h','6h','12h','24h','2d','7d','30d']
   * timespan :: The default options selected for the relative view. Default: '15m'
   * timefield :: The field in which time is stored in the document.
-  * index :: Index pattern to match. Literals should be double quoted. Default: '_all'
-  * defaultindex :: Index to failover to if index not found
-  * index_interval :: Time between timestamped indices (can be 'none') for static index
   * refresh: Object containing refresh parameters
     * enable :: true/false, enable auto refresh by default. Default: false
     * interval :: Seconds between auto refresh. Default: 30
     * min :: The lowest interval a user may set
-
-  ### Group Events
-  #### Sends
-  * time :: Object Includes from, to and index
-  #### Receives
-  * get_time :: Receives an object containing a $id, broadcasts back to it.
-
 */
 
 angular.module('kibana.timepicker', [])
-.controller('timepicker', function($scope, eventBus, $timeout, timer, $http, kbnIndex) {
+.controller('timepicker', function($scope, $rootScope, $timeout, timer, $http, dashboard, filterSrv) {
 
   // Set and populate defaults
   var _d = {
@@ -37,9 +27,6 @@ angular.module('kibana.timepicker', [])
     time_options  : ['5m','15m','1h','6h','12h','24h','2d','7d','30d'],
     timespan      : '15m',
     timefield     : '@timestamp',
-    index         : '_all',
-    defaultindex  : "_all",
-    index_interval: "none",
     timeformat    : "",
     group         : "default",
     refresh       : {
@@ -57,6 +44,7 @@ angular.module('kibana.timepicker', [])
     // Private refresh interval that we can use for view display without causing
     // unnecessary refreshes during changes
     $scope.refresh_interval = $scope.panel.refresh.interval
+    $scope.filterSrv = filterSrv;
 
     // Init a private time object with Date() objects depending on mode
     switch($scope.panel.mode) {
@@ -80,41 +68,33 @@ angular.module('kibana.timepicker', [])
         break;
     }
     $scope.time.field = $scope.panel.timefield;
-    $scope.time_apply();
+    // These 3 statements basicly do everything time_apply() does
+    set_timepicker($scope.time.from,$scope.time.to)
+    update_panel()
+    set_time_filter($scope.time)
+    dashboard.refresh();
+
 
     // Start refresh timer if enabled
     if ($scope.panel.refresh.enable)
       $scope.set_interval($scope.panel.refresh.interval);
 
-    // In the case that a panel is not ready to receive a time event, it may
-    // request one be sent by broadcasting a 'get_time' with its _id to its group
-    // This panel can handle multiple groups
-    eventBus.register($scope,"get_time", function(event,id) {
-      eventBus.broadcast($scope.$id,id,'time',compile_time($scope.time))
-    });
-
     // In case some other panel broadcasts a time, set us to an absolute range
-    eventBus.register($scope,"set_time", function(event,time) {
-      $scope.panel.mode = 'absolute';
-      set_timepicker(moment(time.from),moment(time.to))
-      $scope.time_apply()
-    });
-    
-    eventBus.register($scope,"zoom", function(event,factor) {
-      var _timespan = ($scope.time.to.valueOf() - $scope.time.from.valueOf());
-      try {
-        if($scope.panel.mode != 'absolute') {
-          $scope.panel.mode = 'since'
-          set_timepicker(moment($scope.time.to.valueOf() - _timespan*factor),$scope.time.to)
-        } else {
-          var _center = $scope.time.to.valueOf() - _timespan/2
-          set_timepicker(moment(_center - (_timespan*factor)/2),
-                         moment(_center + (_timespan*factor)/2))        
+    $scope.$on('refresh', function() {
+      if(filterSrv.idsByType('time').length > 0) {
+        var time = filterSrv.timeRange('min')
+
+        if($scope.time.from.diff(moment.utc(time.from)) != 0 
+          || $scope.time.to.diff(moment.utc(time.to)) != 0)
+        {
+          $scope.set_mode('absolute');
+
+          // These 3 statements basicly do everything time_apply() does
+          set_timepicker(moment(time.from),moment(time.to))
+          $scope.time = $scope.time_calc();
+          update_panel()
         }
-      } catch (e) {
-        console.log(e)
-      }     
-      $scope.time_apply();
+      }
     });
   }
 
@@ -146,10 +126,26 @@ angular.module('kibana.timepicker', [])
     }
   }
 
+  var update_panel = function() {
+    // Update panel's string representation of the time object.Don't update if
+    // we're in relative mode since we dont want to store the time object in the
+    // json for relative periods
+    if($scope.panel.mode !== 'relative') {
+      $scope.panel.time = { 
+        from : $scope.time.from.format("MM/DD/YYYY HH:mm:ss"),
+        to : $scope.time.to.format("MM/DD/YYYY HH:mm:ss"),
+      };
+    } else {
+      delete $scope.panel.time;
+    }
+  }
+
   $scope.set_mode = function(mode) {
     $scope.panel.mode = mode;
     $scope.panel.refresh.enable = mode === 'absolute' ? 
       false : $scope.panel.refresh.enable
+
+    update_panel();
   }
 
   $scope.to_now = function() {
@@ -201,54 +197,43 @@ angular.module('kibana.timepicker', [])
     };
   }
 
-  $scope.time_apply = function() {   
+  $scope.time_apply = function() { 
     $scope.panel.error = "";   
     // Update internal time object
+
+    // Remove all other time filters
+    filterSrv.removeByType('time')
+
     $scope.time = $scope.time_calc();
     $scope.time.field = $scope.panel.timefield
+    update_panel()
 
-    // Get indices for the time period, then broadcast time range and index list
-    // in a single object. Not sure if I like this.
-    if($scope.panel.index_interval !== 'none') {
-      kbnIndex.indices($scope.time.from,
-        $scope.time.to,
-        $scope.panel.index,
-        $scope.panel.index_interval
-      ).then(function (p) {
-        if(p.length > 0) {
-          $scope.time.index = p;
-          eventBus.broadcast($scope.$id,$scope.panel.group,'time',compile_time($scope.time))
-        } else {
-          $scope.panel.error = "Could not match index pattern to any ElasticSearch indices"
-        }
-      });
-    } else {
-      $scope.time.index = [$scope.panel.index];
-      eventBus.broadcast($scope.$id,$scope.panel.group,'time',compile_time($scope.time))
-    }
+    set_time_filter($scope.time)
+    dashboard.refresh();
 
-    // Update panel's string representation of the time object.Don't update if
-    // we're in relative mode since we dont want to store the time object in the
-    // json for relative periods
-    if($scope.panel.mode !== 'relative') {
-      $scope.panel.time = { 
-        from : $scope.time.from.format("MM/DD/YYYY HH:mm:ss"),
-        to : $scope.time.to.format("MM/DD/YYYY HH:mm:ss"),
-        index : $scope.time.index,
-      };
+  };
+
+
+  function set_time_filter(time) {
+    time.type = 'time'
+    // Check if there's a time filter we remember, if not, set one and remember it
+    if(!_.isUndefined($scope.panel.filter_id) && 
+      !_.isUndefined(filterSrv.list[$scope.panel.filter_id]) && 
+      filterSrv.list[$scope.panel.filter_id].type == 'time') 
+    {
+      filterSrv.set(compile_time(time),$scope.panel.filter_id)
     } else {
-      delete $scope.panel.time;
+      $scope.panel.filter_id = filterSrv.set(compile_time(time))
     }
-  };
+    return $scope.panel.filter_id;
+  }
 
-  // Prefer to pass around Date() objects in the EventBus since interacting with
+  // Prefer to pass around Date() objects since interacting with
   // moment objects in libraries that are expecting Date()s can be tricky
   function compile_time(time) {
     time = _.clone(time)
     time.from = time.from.toDate()
     time.to   = time.to.toDate()
-    time.interval = $scope.panel.index_interval
-    time.pattern = $scope.panel.index 
     return time;
   }
 

+ 0 - 32
panels/trends/editor.html

@@ -26,36 +26,4 @@
       <select class="input-small" ng-model="panel.arrangement" ng-options="f for f in ['horizontal','vertical']"></select></span>
     </div>
   </div>
-
-  <h5>Queries</h5>    
-  <div class="row-fluid">
-    <div class="span3">
-      <form style="margin-bottom: 0px">
-       <label class="small">Label</label>
-        <input type="text" placeholder="New Label" style="width:70%" ng-model="newlabel">
-      </form>
-    </div>
-    <div class="span8">
-      <form class="input-append" style="margin-bottom: 0px">
-        <label class="small">Query</label>
-        <input type="text" placeholder="New Query" style="width:80%" ng-model="newquery">
-        <button class="btn" ng-click="add_query(newlabel,newquery);newlabel='';newquery='';set_refresh(true)"><i class="icon-plus"></i></button>
-      </form>
-    </div>
-    <div class="span1">
-    </div>
-  </div>
-  <div class="row-fluid" ng-repeat="q in panel.query">        
-    <div class="span3">
-      <form style="margin-bottom: 0px">
-        <input type="text" style="width:70%" ng-model="q.label" ng-change="set_refresh(true)">
-      </form>
-    </div>
-    <div class="span8">
-        <input type="text" style="width:80%" ng-model="q.query" ng-change="set_refresh(true)">
-    </div>
-    <div class="span1">
-      <i class="icon-remove pointer" ng-click="remove_query(q)"></i>
-    </div>
-  </div>
 </div>

+ 2 - 1
panels/trends/module.html

@@ -1,10 +1,11 @@
 <kibana-panel ng-controller='trends' ng-init="init()">
 
   <div ng-style="panel.style" style="line-height:{{panel.style['font-size']}};display:inline-block;padding-right: 5px;" ng-repeat="query in trends">
+    <i class="icon-circle" style="color:{{query.info.color}}"></i>
     <span ng-class="{'text-success': query.hits.new >= query.hits.old, 'text-error': query.hits.old > query.hits.new}" class='strong'>
       <i class='large' ng-class="{'icon-caret-up': query.hits.new >= query.hits.old, 'icon-caret-down': query.hits.old > query.hits.new}"></i> {{query.percent}}% 
     </span>
-    <span class="tiny pointer light" bs-tooltip="'Then: '+query.hits.old+', Now: '+query.hits.new">({{query.label}})</span>
+    <span class="tiny pointer light" bs-tooltip="'Then: '+query.hits.old+', Now: '+query.hits.new" ng-show="query.info.alias != ''">({{query.info.alias}})</span>
     <br ng-show="panel.arrangement == 'vertical'">
   </div>
 </kibana-panel>         

+ 37 - 64
panels/trends/module.js

@@ -19,7 +19,7 @@
 
 */
 angular.module('kibana.trends', [])
-.controller('trends', function($scope, eventBus, kbnIndex) {
+.controller('trends', function($scope, kbnIndex, query, dashboard, filterSrv) {
 
   // Set and populate defaults
   var _d = {
@@ -34,17 +34,10 @@ angular.module('kibana.trends', [])
 
   $scope.init = function () {
     $scope.hits = 0;
-    eventBus.register($scope,'time', function(event,time){
-      set_time(time)
-    });
-    eventBus.register($scope,'query', function(event, query) {
-      $scope.panel.query = _.map(query,function(q) {
-        return {query: q, label: q};
-      })
-      $scope.get_data();
-    });
-    // Now that we're all setup, request the time from our group
-    eventBus.broadcast($scope.$id,$scope.panel.group,'get_time')
+
+    $scope.$on('refresh',function(){$scope.get_data()})
+
+    $scope.get_data();
   }
 
   $scope.get_data = function(segment,query_id) {
@@ -52,8 +45,11 @@ angular.module('kibana.trends', [])
     $scope.panel.loading = true;
 
     // Make sure we have everything for the request to complete
-    if(_.isUndefined($scope.index) || _.isUndefined($scope.time))
+    if(dashboard.indices.length == 0) {
       return
+    } else {
+      $scope.index = dashboard.indices
+    }
 
     $scope.old_time = {
       from : new Date($scope.time.from.getTime() - interval_to_seconds($scope.panel.ago)*1000),
@@ -64,43 +60,31 @@ angular.module('kibana.trends', [])
     var request = $scope.ejs.Request();
 
     // Build the question part of the query
-    var queries = [];
-    _.each($scope.panel.query, function(v) {
-      queries.push($scope.ejs.FilteredQuery(
-        ejs.QueryStringQuery(v.query || '*'),
-        ejs.RangeFilter($scope.time.field)
-          .from($scope.time.from)
-          .to($scope.time.to))
-      )
-    });
+    _.each(query.ids, function(id) {
+      var q = $scope.ejs.FilteredQuery(
+        ejs.QueryStringQuery(query.list[id].query || '*'),
+        filterSrv.getBoolFilter(filterSrv.ids))
 
-    // Build the facet part
-    _.each(queries, function(v) {
       request = request
-        .facet($scope.ejs.QueryFacet("new"+_.indexOf(queries,v))
-          .query(v)
+        .facet($scope.ejs.QueryFacet(id)
+          .query(q)
         ).size(0)
-    })
+    });
 
-    var queries = [];
-    _.each($scope.panel.query, function(v) {
-      queries.push($scope.ejs.FilteredQuery(
-        ejs.QueryStringQuery(v.query || '*'),
+    // And again for the old time period
+    _.each(query.ids, function(id) {
+      var q = $scope.ejs.FilteredQuery(
+        ejs.QueryStringQuery(query.list[id].query || '*'),
         ejs.RangeFilter($scope.time.field)
           .from($scope.old_time.from)
           .to($scope.old_time.to))
-      )
-    });
-
-    // Build the facet part
-    _.each(queries, function(v) {
       request = request
-        .facet($scope.ejs.QueryFacet("old"+_.indexOf(queries,v))
-          .query(v)
+        .facet($scope.ejs.QueryFacet("old_"+id)
+          .query(q)
         ).size(0)
-    })
+    });
 
-    // TODO: Spy for hits panel
+    // TODO: Spy for trend panel
     //$scope.populate_modal(request);
 
     // If we're on the first segment we need to get our indices
@@ -134,11 +118,19 @@ angular.module('kibana.trends', [])
           $scope.panel.error = $scope.parse_error(results.error);
           return;
         }
-        if($scope.query_id === query_id) {
+
+        // Convert facet ids to numbers
+        var facetIds = _.map(_.keys(results.facets),function(k){if(!isNaN(k)){return parseInt(k)}})
+
+        // Make sure we're still on the same query/queries
+        if($scope.query_id === query_id && 
+          _.intersection(facetIds,query.ids).length == query.ids.length
+          ) {
           var i = 0;
-          _.each($scope.panel.query, function(k) {
-            var n = results.facets['new'+i].count
-            var o = results.facets['old'+i].count
+          _.each(query.ids, function(id) {
+            var v = results.facets[id]
+            var n = results.facets[id].count
+            var o = results.facets['old_'+id].count
 
             var hits = {
               new : _.isUndefined($scope.data[i]) || _segment == 0 ? n : $scope.data[i].hits.new+n,        
@@ -152,7 +144,7 @@ angular.module('kibana.trends', [])
               '?' : Math.round(percentage(hits.old,hits.new)*100)/100
             // Create series
             $scope.data[i] = { 
-              label: $scope.panel.query[i].label || "query"+(parseInt(i)+1), 
+              info: query.list[id],
               hits: {
                 new : hits.new,
                 old : hits.old
@@ -177,19 +169,6 @@ angular.module('kibana.trends', [])
     return x == 0 ? null : 100*(y-x)/x
   }
 
-  $scope.remove_query = function(q) {
-    $scope.panel.query = _.without($scope.panel.query,q);
-    $scope.get_data();
-  }
-
-  $scope.add_query = function(label,query) {
-    $scope.panel.query.unshift({
-      query: query,
-      label: label, 
-    });
-    $scope.get_data();
-  }
-
   $scope.set_refresh = function (state) { 
     $scope.refresh = state; 
   }
@@ -201,10 +180,4 @@ angular.module('kibana.trends', [])
     $scope.$emit('render');
   }
 
-  function set_time(time) {
-    $scope.time = time;
-    $scope.index = time.index || $scope.index
-    $scope.get_data();
-  }
-
 })

+ 1 - 2
partials/dashboard.html

@@ -1,8 +1,7 @@
 
 <div class="row-fluid container" style="margin-top:10px">
   <!-- Rows -->
-  <div ng-controller="dashcontrol" ng-init="init()"></div>
-  <div class="row-fluid kibana-row" ng-controller="RowCtrl" ng-repeat="(row_name, row) in dashboards.rows" ng-style="row_style(row)">
+  <div class="row-fluid kibana-row" ng-controller="RowCtrl" ng-repeat="(row_name, row) in dashboard.current.rows" ng-style="row_style(row)">
     <div class="row-control">
       <div class="row-fluid row-header" style="padding:0px;margin:0px;height:0px">
         <div style="vertical-align:bottom">

+ 62 - 24
partials/dasheditor.html

@@ -1,38 +1,56 @@
 <div class="modal-header">
   <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
-  <h3>{{dashboards.title}} <small> editor</small></h3>
+  <h3>{{dashboard.current.title}} <small> editor</small></h3>
 </div>
 <div class="modal-body">
   <h4>Dashboard Control</h4>
   <div class="row-fluid">
     <div class="span8">
-      <label class="small">Title</label><input type="text" class="input-large" ng-model='dashboards.title'></input>
+      <label class="small">Title</label><input type="text" class="input-large" ng-model='dashboard.current.title'></input>
     </div>
     <div class="span1"> 
-      <label class="small"> Editable </label><input type="checkbox" ng-model="dashboards.editable" ng-checked="dashboards.editable" />
+      <label class="small"> Editable </label><input type="checkbox" ng-model="dashboard.current.editable" ng-checked="dashboard.current.editable" />
     </div>
   </div>
-  <div class="row-fluid">
-    <div class="span12">
-      <h4>Rows</h4>
-      <table class="table table-condensed table-striped">
-        <thead>
-          <th>Title</th>
-          <th>Delete</th>
-          <th>Move</th>
-        </thead>
-        <tr ng-repeat="row in dashboards.rows">
-          <td>{{row.title}}</td>
-          <td><i ng-click="dashboards.rows = _.without(dashboards.rows,row)" class="pointer icon-remove"></i></td>
-          <td><i ng-click="_.move(dashboards.rows,$index,$index-1)" ng-hide="$first" class="pointer icon-arrow-up"></i></td>
-          <td><i ng-click="_.move(dashboards.rows,$index,$index+1)" ng-hide="$last" class="pointer icon-arrow-down"></i></td>
-        </tr>
-      </table>
+  <div class="row-fluid">    
+    <h4>Index Settings</h4>
+    <div ng-show="dashboard.current.index.interval != 'none'" class="row-fluid"> 
+       <div class="span12">
+        <p class="small">
+          Time stamped indices use your selected time range to create a list of 
+          indices that match a specified timestamp pattern. This can be very 
+          efficient for some data sets (eg, logs) For example, to match the 
+          default logstash index pattern you might use 
+          <code>[logstash-]YYYY.MM.DD</code>. The [] in "[logstash-]" are 
+          important as they instruct Kibana not to treat those letters as a 
+          pattern.
+        </p>
+        <p class="small">
+          See <a href="http://momentjs.com/docs/#/displaying/format/">http://momentjs.com/docs/#/displaying/format/</a>
+          for documentation on date formatting.
+        </p>
+
+       </div>
+    </div>
+    <div class="row-fluid"> 
+      <div class="span3">
+        <h6>Timestamping</h6><select class="input-small" ng-model="dashboard.current.index.interval" ng-options='f for f in ["none","hour","day","week","month","year"]'></select>
+      </div>
+      <div class="span5" ng-show="dashboard.current.index.interval != 'none'">
+        <h6>Index pattern <small>Absolutes in []</small></h6>
+        <input type="text" class="input-medium" ng-model="dashboard.current.index.pattern">
+      </div>
+      <div class="span4">
+        <h6>Default Index <small ng-show="dashboard.current.index.interval != 'none'">If index not found</small></h6>
+        <input type="text" class="input-medium" ng-model="dashboard.current.index.default">
+      </div>
     </div>
   </div>
-  <h4>New row</h4>
-    <div class="row-fluid">
-      <div class="span8">      
+  <hr/>
+  <h4>Rows</h4>
+  <div class="row-fluid">
+    <form>
+      <div class="span5">      
         <label class="small">Title</label>
         <input type="text" class="input-large" ng-model='row.title'></input>
       </div>
@@ -44,10 +62,30 @@
         <label class="small"> Editable </label>
         <input type="checkbox" ng-model="row.editable" ng-checked="row.editable" />
       </div>
+      <div class="span4">
+        <label>&nbsp</label>
+        <button ng-click="add_row(dashboard.current,row); reset_row();" class="btn btn-primary">Create Row</button>
+      </div>
+    </form>
+  </div>
+  <div class="row-fluid">
+    <div class="span12">
+      <table class="table table-condensed table-striped">
+        <thead>
+          <th>Title</th>
+          <th>Delete</th>
+          <th>Move</th>
+        </thead>
+        <tr ng-repeat="row in dashboard.current.rows">
+          <td>{{row.title}}</td>
+          <td><i ng-click="dashboard.current.rows = _.without(dashboard.current.rows,row)" class="pointer icon-remove"></i></td>
+          <td><i ng-click="_.move(dashboard.current.rows,$index,$index-1)" ng-hide="$first" class="pointer icon-arrow-up"></i></td>
+          <td><i ng-click="_.move(dashboard.current.rows,$index,$index+1)" ng-hide="$last" class="pointer icon-arrow-down"></i></td>
+        </tr>
+      </table>
     </div>
-    <button ng-click="add_row(dashboards,row); reset_row();" class="btn btn-primary">Create Row</button><br>
   </div>
 </div>
 <div class="modal-footer">
-  <button type="button" class="btn btn-success" ng-click="dismiss();reset_panel();">Close</button>
+  <button type="button" class="btn btn-success" ng-click="dismiss();reset_panel();dashboard.refresh()">Close</button>
 </div>

+ 0 - 3
partials/panelgeneral.html

@@ -2,9 +2,6 @@
     <div class="span4">
       <label class="small">Title</label><input type="text" class="input-medium" ng-model='panel.title'></input>
     </div>
-    <div class="span4">
-      <label class="small">Group(s) (comma seperated)</label><input array-join type="text" class="input-medium" ng-model='panel.group'></input>
-    </div>
     <div class="span2" ng-hide="panel.sizeable == false">
       <label class="small">Span</label> <select class="input-mini" ng-model="panel.span" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10,11,12]"></select>
     </div>

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác