Explorar el Código

Added highlighting to table panel, remove debug statement from derivequeries

Rashid Khan hace 12 años
padre
commit
6aa72fa994

+ 1 - 0
common/css/main.css

@@ -60,6 +60,7 @@
   margin: 0px;
 }
 
+
 [ng\:cloak], [ng-cloak], .ng-cloak {
   display: none !important;
 }

+ 3 - 0
panels/bbuzz/editor.html

@@ -0,0 +1,3 @@
+<div>
+  Nothing to see here folks
+</div>

BIN
panels/bbuzz/img/0.jpg


BIN
panels/bbuzz/img/1.jpg


BIN
panels/bbuzz/img/2.jpg


BIN
panels/bbuzz/img/3.jpg


BIN
panels/bbuzz/img/4.jpg


BIN
panels/bbuzz/img/5.jpg


BIN
panels/bbuzz/img/6.jpg


+ 58 - 0
panels/bbuzz/module.html

@@ -0,0 +1,58 @@
+<kibana-panel ng-controller='bbuzz' ng-init="init()">
+
+<table class="table table-condensed" style="vertical-align: middle">
+  <tr ng-style="cluster_color(cluster.status)">
+    <td>
+      <span style="font-size:2.5em;line-height:50px">
+        <strong>Cluster:</strong> {{cluster.cluster_name}}
+      </span>
+    </td>
+    <td></td>
+    <td style="text-align:right;vertical-align:middle">
+      <form class="input-append" style="text-align:right">
+        <input ng-model="settings.replicas" type="number" class="input-mini">
+        <button class="btn btn-info" ng-click="set_replicas(settings.replicas)"><i class="icon-copy"></i></button>
+      </form>
+    </td>
+  </tr>
+  <tr ng-style="cluster_color('nothing')" style="vertical-align:middle" ng-repeat="(name,node) in nodes.info">
+    <td style='font-size:1.5em;vertical-align:middle'>
+      <img style='font-size:1.5em;width:25px;height:25px;border-radius:20px;border:1px solid #000;margin-right: 10px' src="{{bbuzz.picture(node.transport_address)}}"> 
+    {{node.name}} / {{bbuzz.ip_address(node.transport_address)}}
+    </td>
+    <td> 
+      P
+      <div ng-repeat='(index,shards) in nodes.routing[name]' bs-tooltip="index" style='display:inline-block;'>
+        <div ng-repeat='shard in primary(shards,true)' ng-class='shard_class(shard.state)' style='display:inline-block;font-size:7pt;line-height:10px;width:10px;height:10px;border-radius:10px;color:#fff;text-align:center;background:{{bbuzz.index_color(index)}};border:0px;margin-right: 2px'></div>
+      </div>
+      <br>
+      R 
+      <div ng-repeat='(index,shards) in nodes.routing[name]' bs-tooltip="index"  style='display:inline-block;'>
+        <div ng-repeat='shard in primary(shards,false)' ng-class='shard_class(shard.state)' style='display:inline-block;font-size:7pt;line-height:10px;width:10px;height:10px;border-radius:10px;color:#fff;text-align:center;background:{{bbuzz.index_color(index)}};border:0px;margin-right: 2px'></div>
+      </div><br>
+    </td>
+    <td style="text-align: right;vertical-align: middle">
+      <button ng-click="node_mode(bbuzz.ip_address(node.transport_address),'stop')" style="font-size:1.5em; margin-right:30px" class='icon-signout btn btn-danger'></button>
+    </td>
+  </tr>
+</table>
+<table style="vertical-align: middle" ng-show="nodes.dead.length > 0" class='table table-condensed'>
+  <tr ng-style="cluster_color('red')">
+    <td colspan='100'>
+      <span style="line-height:15px">
+        Missing nodes
+      </span>
+    </td>
+  </tr>
+  <tr ng-style="cluster_color('nothing')" ng-repeat="name in nodes.dead">
+    <td colspan="2">
+      <img style='width:30px;height:30px;border-radius:30px;border:2px solid #000;margin-right: 10px' src="{{bbuzz.picture(name)}}"> 
+    {{bbuzz.ip_address(name)}} {{nodes.status[name]}}
+    </td>
+    <td style="text-align: right">
+      <button ng-click="node_mode(bbuzz.ip_address(name),'start')" style="font-size:1.5em; margin-right:30px" class='icon-signin btn btn-info'></button>
+    </td>
+  </tr>
+</table>
+
+</kibana-panel>         

+ 372 - 0
panels/bbuzz/module.js

@@ -0,0 +1,372 @@
+/*
+
+  ## bbuzz
+
+  A special panel for my Berlin Buzzwords talk
+
+  ### Parameters
+  * None
+  ### 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
+
+*/
+angular.module('kibana.bbuzz', [])
+.controller('bbuzz', function($scope, eventBus, $http, bbuzz) {
+
+  // Set and populate defaults
+  var _d = {
+    group   : "default",
+  }
+  _.defaults($scope.panel,_d)
+
+  $scope.init = function () {
+    $scope.nodes = {};
+    $scope.settings = {};
+    $scope.indices = [];
+    $scope.bbuzz = bbuzz
+    $scope.colors = bbuzz.colors(20)
+    eventBus.register($scope,'time', function(event,time){
+      set_time(time)
+    });
+    // Now that we're all setup, request the time from our group
+    eventBus.broadcast($scope.$id,$scope.panel.group,'get_time')
+  }
+
+  $scope.set_replicas = function(replicas) {
+    $http.put(config.elasticsearch+'/_settings', {
+      "index" : {"number_of_replicas" : replicas}
+    }).success(function (data) {
+      $scope.get_data();
+    });
+  }
+
+  $scope.status = function(ip) {
+    var something = $http({
+      url: "http://"+ip+':4567/status',
+      method: "GET"
+    }).error(function(data, status, headers, config) {
+      $scope.error = status;
+    });
+
+    return something.then(function(p) {
+      return p.data
+    });
+  }
+
+  $scope.node_mode = function(ip,mode) {
+    $http({method: 'GET', url: "http://"+ip+':4567/'+mode}).
+    success(function(data, status, headers, config) {
+      control.log(data)
+      $scope.get_data();
+    }).
+    error(function(data, status, headers, config) {
+      console.log(data, status)
+      $scope.get_data();
+    });
+  }
+
+  $scope.get_data = function() {
+
+    // Get cluster health 
+    if (!_.isNumber($scope.settings.replicas)) {
+      $http({method: 'GET', url: config.elasticsearch+'/_settings'}).
+      success(function(data, status, headers, config) {
+        $scope.settings.replicas = parseInt(_.pairs(data)[0][1].settings['index.number_of_replicas'])
+      }).
+      error(function(data, status, headers, config) {
+        console.log('Could not contact elasticsearch at: '+config.elasticsearch)
+      });
+    }
+
+    // Get cluster health 
+    $http({method: 'GET', url: config.elasticsearch+'/_cluster/health'}).
+    success(function(data, status, headers, config) {
+      $scope.cluster = data 
+    }).
+    error(function(data, status, headers, config) {
+      console.log('Could not contact elasticsearch at: '+config.elasticsearch)
+    });
+
+    // And nodes list
+    $http({method: 'GET', url: config.elasticsearch+'/_cluster/state'}).
+    success(function(data, status, headers, config) {
+      var i = []
+      _.each(data.routing_nodes.nodes, function(v,node) {
+        i[node] = _.groupBy(v,'index')
+      })
+      $scope.nodes.routing = i;
+      $scope.nodes.info = data.nodes
+      $scope.nodes.live = _.pluck(data.nodes,'transport_address'),
+      $scope.nodes.dead = _.difference(bbuzz.get_nodes(),_.pluck(data.nodes,'transport_address'))
+      //$scope.nodes.dead = ['inet[/10.126.115.79:9300]'];
+      $scope.indices = _.union($scope.indices,_.keys(i))
+
+      _.each($scope.nodes.dead,function(node) {
+        $http({
+          url: "http://"+bbuzz.ip_address(node)+':4567/status',
+          method: "GET"
+        }).success(function(data, status, headers, config) {
+          if(_.isUndefined($scope.nodes.status))
+            $scope.nodes.status = {};
+          $scope.nodes.status[node] = 'data';
+        })
+      })
+
+    }).
+    error(function(data, status, headers, config) {
+      console.log('Could not contact elasticsearch at: '+config.elasticsearch)
+    });
+  }
+
+  // returns list of shards sorted by index
+  $scope.sorted_shards = function(shards) {
+    return _.sortBy(shards, function(shard) {
+      return shard.index
+    })
+  }
+
+  $scope.primary = function(shards,primary) {
+    return _.filter(shards, function(shard) {
+      return shard.primary === primary ? true: false;
+    })
+  }
+
+  $scope.shard_class = function(state) {
+    switch(state)
+    {
+    case 'INITIALIZING':
+      return ['animated','flash','infinite']
+    case 'RELOCATING':
+      return ['faded']
+    default:
+      return ''
+    }
+  }
+
+  $scope.cluster_class = function(state) {
+    switch(state)
+    {
+    case 'green':
+      return 'btn-success'
+    case 'yellow':
+      return 'btn-warning'
+    case 'red': 
+      return 'btn-danger'
+    default:
+      return 'btn-inverse'
+    }
+  }
+
+  $scope.cluster_color = function(state) {
+    switch(state)
+    {
+    case 'green':
+      var style = {color: '#FFF', bg: '#5BB75B', light: '#62C462', dark: '#51A351'}
+      break;
+    case 'yellow':
+      var style = {color: '#FFF', bg: '#FAA732', light: '#FBB450', dark: '#F89406'}
+      break;
+    case 'red': 
+      var style = {color: '#FFF', bg: "#DA4F49", light: '#EE5F5B', dark: '#BD362F'}
+      break;
+    default:
+      var style = {color: '#000', bg: "#F5F5F5", light: '#FFFFFF', dark: '#E6E6E6'}
+      break;
+    }
+    return { 
+      'color': style.color,
+      'margin-bottom': '0px',
+      'font-weight': 200,
+      'background-color': style.bg,
+      'background-image': 'linear-gradient(to bottom, '+style.light+', '+style.dark+')',
+      'background-repeat': 'repeat-x',
+      'border-color': 'rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25)',
+      'border-style': 'solid',
+      'border-width': '1px',
+      'box-shadow': '0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 1px 2px rgba(0, 0, 0, 0.05)',
+      'padding': '10px'
+    };
+  }
+
+  function set_time(time) {
+    $scope.time = time;
+    $scope.get_data();
+  }
+
+}).directive('started', function(eventBus) {
+  return {
+    restrict: 'C',
+    link: function(scope, elem, attrs, ctrl) {
+      console.log('shard')
+
+      // Receive render events
+      scope.$on('render',function(){
+        render_panel();
+      });
+  
+      // Re-render if the window is resized
+      angular.element(window).bind('resize', function(){
+        render_panel();
+      });
+
+      // Function for rendering panel
+      function render_panel() {
+
+        var scripts = $LAB.script("common/lib/panels/jquery.flot.js")
+
+        // Populate element.
+        scripts.wait(function(){
+          try {
+
+          } catch(e) {
+            elem.text(e)
+          }
+        })
+      }
+
+    }
+  };
+}).service('bbuzz', function($timeout) {
+  var nodes = []; 
+  var indices = [];
+
+  this.index_id = function(id) {
+    if (!_.contains(indices,id))
+      indices.push(id)
+    return _.indexOf(indices,id)
+  }
+
+  this.index_color = function(id) {
+    var i = this.index_id(id)
+    var colors = this.colors(indices.length)
+    return colors[i];
+  }
+
+  this.ip_address = function(transport_address) {
+    return transport_address.split(/[\/:]/)[1]
+  }
+
+  this.node_id = function(id) {
+    if (!_.contains(nodes,id))
+      nodes.push(id)
+    return _.indexOf(nodes,id)
+  }
+
+  this.get_nodes = function() {
+    return nodes;
+  }
+
+  this.picture = function(id) {
+    return 'panels/bbuzz/img/'+this.node_id(id)+'.jpg'
+  }
+
+  this.colors = function(count) {
+    // produce colors as needed
+    var seed = ['#86B22D','#BF6730','#1D7373','#BFB930','#BF3030','#77207D']
+    var colors = [], variation = 0;
+    i = 0;
+    while (colors.length < count) {
+      var c;
+      if (seed.length == i) // check degenerate case
+        c = new Color(100, 100, 100);
+      else
+        c = parseColor(seed[i]);
+
+      // vary color if needed
+      var sign = variation % 2 == 1 ? -1 : 1;
+      var factor = 1 + sign * Math.ceil(variation / 2) * 0.2;
+      c.scale(factor, factor, factor);
+
+      // FIXME: if we're getting to close to something else,
+      // we should probably skip this one
+      colors.push(c.toString());
+     
+      ++i;
+      if (i >= seed.length) {
+        i = 0;
+        ++variation;
+      }
+    }
+    return colors;
+  }
+
+  function parseColor(str) {
+    var result;
+    // Look for #a0b1c2
+    if (result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))
+      return new Color(parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16));
+    // Look for #fff
+    if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))
+      return new Color(parseInt(result[1]+result[1], 16), parseInt(result[2]+result[2], 16), parseInt(result[3]+result[3], 16));
+  }
+
+  // color helpers, inspiration from the jquery color animation
+  // plugin by John Resig
+  function Color (r, g, b, a) {
+       
+    var rgba = ['r','g','b','a'];
+    var x = 4; //rgba.length
+       
+    while (-1<--x) {
+      this[rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0);
+    }
+       
+    this.toString = function() {
+      if (this.a >= 1.0) {
+        return "rgb("+[this.r,this.g,this.b].join(",")+")";
+      } else {
+        return "rgba("+[this.r,this.g,this.b,this.a].join(",")+")";
+      }
+    };
+
+    this.scale = function(rf, gf, bf, af) {
+      x = 4; //rgba.length
+      while (-1<--x) {
+        if (arguments[x] != null)
+          this[rgba[x]] *= arguments[x];
+        }
+        return this.normalize();
+      };
+
+      this.adjust = function(rd, gd, bd, ad) {
+        x = 4; //rgba.length
+        while (-1<--x) {
+          if (arguments[x] != null)
+            this[rgba[x]] += arguments[x];
+          }
+          return this.normalize();
+        };
+
+      this.clone = function() {
+        return new Color(this.r, this.b, this.g, this.a);
+      };
+
+      var limit = function(val,minVal,maxVal) {
+        return Math.max(Math.min(val, maxVal), minVal);
+      };
+
+      this.normalize = function() {
+        this.r = clamp(0, parseInt(this.r), 255);
+        this.g = clamp(0, parseInt(this.g), 255);
+        this.b = clamp(0, parseInt(this.b), 255);
+        this.a = clamp(0, this.a, 1);
+        return this;
+      };
+
+      this.normalize();
+    }
+
+    function clamp(min, value, max) {
+      if (value < min)
+        return min;
+      else if (value > max)
+        return max;
+      else
+        return value;
+    }
+
+
+})

+ 1 - 2
panels/derivequeries/module.js

@@ -83,7 +83,6 @@ angular.module('kibana.derivequeries', [])
       _.each(results.facets.query.terms, function(v) {
         data.push($scope.panel.field+':"'+v.term+'"')
       });
-      console.log(data)
       $scope.send_query(data)
     });
   }
@@ -121,4 +120,4 @@ angular.module('kibana.derivequeries', [])
 
 
 
-});
+});

+ 14 - 1
panels/table/editor.html

@@ -16,10 +16,23 @@
       </form>
     </div>
     <div class="span8">
-      <h6>Selected fields <small>Click to remove</small></h6>
+      <h6>Columns <small>Click to remove</small></h6>
       <span style="margin-left:3px" ng-click="toggle_field(field)" ng-repeat="field in $parent.panel.fields" class="label remove pointer">{{field}} </span>
     </div>
   </div>
+  <div class="row-fluid">    
+    <div class="span4">
+      <form class="input-append">
+        <h6>Add field</h6>
+        <input bs-typeahead="fields.list" type="text" class="input-small" ng-model='newhighlight' ng-change="set_refresh(true)">
+        <button class="btn" ng-click="toggle_highlight(newhighlight);newhighlight=''"><i class="icon-plus"></i></button>
+      </form>
+    </div>
+    <div class="span8">
+      <h6>Highlighted fields <small>Click to remove</small></h6>
+      <span style="margin-left:3px" ng-click="toggle_highlight(field);set_refresh(true)" ng-repeat="field in $parent.panel.highlight" class="label remove pointer">{{field}} </span>
+    </div>
+  </div>
   <h5>Options</h5>
   <div class="row-fluid">
     <div class="span1"> 

+ 4 - 4
panels/table/module.html

@@ -35,8 +35,8 @@
       
       </thead>
       <tbody ng-repeat="row in data.slice(panel.offset,panel.offset+panel.size)" ng-class-odd="'odd'">
-        <tr ng-click="toggle_details(row)">
-          <td ng-repeat="field in panel.fields">{{row[field]}}</td>
+        <tr ng-click="toggle_details(row)" class="pointer">
+          <td ng-repeat="field in panel.fields" ng-bind-html-unsafe="(row.highlight[field]||row._source[field]) | highlight"></td>
         </tr>
         <tr ng-show="row.kibana.details">
           <td colspan=1000>
@@ -46,7 +46,7 @@
                 <th>Action</th>
                 <th>Value</th>
               </thead>
-              <tr ng-repeat="(key,value) in row.kibana.details" ng-class-odd="'odd'">
+              <tr ng-repeat="(key,value) in row.kibana.details._source" ng-class-odd="'odd'">
                 <td>{{key}}</td>
                 <td>
                   <i class='icon-search pointer' ng-click="build_search(key,value)"></i> 
@@ -59,7 +59,7 @@
         </tr>
       </tbody>
     </table>
-    <div class="row-fluid" ng-show="panel.paging">>
+    <div class="row-fluid" ng-show="panel.paging">
       <div class="span1 offset3" style="text-align:right">
         <i ng-click="panel.offset = 0" ng-show="panel.offset > 0" class='icon-circle-arrow-left pointer'></i>
         <i ng-click="panel.offset = (panel.offset - panel.size)" ng-show="panel.offset > 0" class='icon-arrow-left pointer'></i>

+ 49 - 7
panels/table/module.js

@@ -39,6 +39,7 @@ angular.module('kibana.table', [])
     group   : "default",
     style   : {'font-size': '9pt'},
     fields  : [],
+    highlight : [],
     sortable: true,
     header  : true,
     paging  : true, 
@@ -92,6 +93,13 @@ angular.module('kibana.table', [])
     broadcast_results();
   }
 
+  $scope.toggle_highlight = function(field) {
+    if (_.indexOf($scope.panel.highlight,field) > -1) 
+      $scope.panel.highlight = _.without($scope.panel.highlight,field)
+    else
+      $scope.panel.highlight.push(field)
+  }  
+
   $scope.toggle_details = function(row) {
     row.kibana = row.kibana || {};
     row.kibana.details = !row.kibana.details ? $scope.without_kibana(row) : false;
@@ -129,6 +137,12 @@ angular.module('kibana.table', [])
           .to($scope.time.to)
         )
       )
+      .highlight(
+        ejs.Highlight($scope.panel.highlight)
+        .fragmentSize(2147483647) // Max size of a 32bit unsigned int
+        .preTags('@start-highlight@')
+        .postTags('@end-highlight@')
+      )
       .size($scope.panel.size*$scope.panel.pages)
       .sort($scope.panel.sort[0],$scope.panel.sort[1]);
 
@@ -155,14 +169,17 @@ angular.module('kibana.table', [])
       // 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']);
+          return {
+            _source   : flatten_json(hit['_source']),
+            highlight : flatten_json(hit['highlight']||{})
+          }
         }));
         
         $scope.hits += results.hits.total;
 
         // Sort the data
         $scope.data = _.sortBy($scope.data, function(v){
-          return v[$scope.panel.sort[0]]
+          return v._source[$scope.panel.sort[0]]
         });
         
         // Reverse if needed
@@ -177,7 +194,7 @@ angular.module('kibana.table', [])
       }
       
       // This breaks, use $scope.data for this
-      $scope.all_fields = get_all_fields($scope.data);
+      $scope.all_fields = get_all_fields(_.pluck($scope.data,'_source'));
       broadcast_results();
 
       // If we're not sorting in reverse chrono order, query every index for
@@ -205,9 +222,10 @@ angular.module('kibana.table', [])
   }
 
   $scope.without_kibana = function (row) {
-    row = _.clone(row)
-    delete row.kibana
-    return row
+    return { 
+      _source   : row._source,
+      highlight : row.highlight
+    }
   } 
 
   // Broadcast a list of all fields. Note that receivers of field array 
@@ -222,15 +240,39 @@ angular.module('kibana.table', [])
     eventBus.broadcast($scope.$id,$scope.panel.group,"table_documents", 
       {
         query: $scope.panel.query,
-        docs : $scope.data,
+        docs : _.pluck($scope.data,'_source'),
         index: $scope.index
       });
   }
 
+  $scope.set_refresh = function (state) { 
+    $scope.refresh = state; 
+  }
+
+  $scope.close_edit = function() {
+    if($scope.refresh)
+      $scope.get_data();
+    $scope.refresh =  false;
+  }
+
+
   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) {
+    if (text.toString().length) {
+      return text.toString().
+        replace(/&/g, '&amp;').
+        replace(/</g, '&lt;').
+        replace(/>/g, '&gt;').
+        replace(/@start-highlight@/g, '<code class="highlight">').
+        replace(/@end-highlight@/g, '</code>')
+    }
+    return '';
+  }
 });