Просмотр исходного кода

Merge pull request #219 from rashidkpc/query_service

Refactor of queries, filters, dashboard service
Rashid Khan 12 лет назад
Родитель
Сommit
fdb3492825
79 измененных файлов с 1734 добавлено и 4988 удалено
  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)  
 

Разница между файлами не показана из-за своего большого размера
+ 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;}

Разница между файлами не показана из-за своего большого размера
+ 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})();

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
panels/map2/lib/topojson.v1.min.js


Разница между файлами не показана из-за своего большого размера
+ 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>

Некоторые файлы не были показаны из-за большого количества измененных файлов