You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spot.apache.org by ev...@apache.org on 2017/01/25 18:36:57 UTC

[45/49] incubator-spot git commit: Ingest summary supporting 3 use cases, Netflow, DNS and Proxy

Ingest summary supporting 3 use cases, Netflow, DNS and Proxy


Project: http://git-wip-us.apache.org/repos/asf/incubator-spot/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-spot/commit/46470640
Tree: http://git-wip-us.apache.org/repos/asf/incubator-spot/tree/46470640
Diff: http://git-wip-us.apache.org/repos/asf/incubator-spot/diff/46470640

Branch: refs/heads/master
Commit: 46470640ef882297df1109d4f3484f017cf63d95
Parents: f0619ae
Author: Diego Ortiz Huerta <di...@intel.com>
Authored: Mon Dec 12 10:08:56 2016 -0800
Committer: Everardo Lopez Sandoval (Intel) <el...@elopezsa-mac02.ra.intel.com>
Committed: Fri Jan 20 17:01:02 2017 -0800

----------------------------------------------------------------------
 .../ui/flow/js/constants/NetflowConstants.js    |   1 -
 spot-oa/ui/ingest-summary.html                  |  37 +-
 .../js/components/IngestSummaryPanel.react.js   | 418 +++++++++----------
 spot-oa/ui/js/components/OptionPicker.react.js  |  43 ++
 spot-oa/ui/js/constants/SpotConstants.js        |  10 +-
 spot-oa/ui/js/ingest-summary.js                 |  66 +--
 spot-oa/ui/js/stores/IngestSummaryStore.js      | 107 +++--
 spot-oa/ui/package.json                         |   2 +-
 8 files changed, 378 insertions(+), 306 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-spot/blob/46470640/spot-oa/ui/flow/js/constants/NetflowConstants.js
----------------------------------------------------------------------
diff --git a/spot-oa/ui/flow/js/constants/NetflowConstants.js b/spot-oa/ui/flow/js/constants/NetflowConstants.js
index c0fc7a8..4de19e1 100755
--- a/spot-oa/ui/flow/js/constants/NetflowConstants.js
+++ b/spot-oa/ui/flow/js/constants/NetflowConstants.js
@@ -5,7 +5,6 @@ var NetflowConstants = {
   API_VISUAL_DETAILS: '../../data/flow/${date}/chord-${ip}.tsv',
   API_COMMENTS: '../../data/flow/${date}/threats.csv',
   API_INCIDENT_PROGRESSION: '../../data/flow/${date}/threat-dendro-${ip}.json',
-  API_INGEST_SUMMARY: '../data/flow/ingest_summary/is_${year}${month}.csv',
   API_IMPACT_ANALYSIS: '../../data/flow/${date}/stats-${ip}.json',
   API_GLOBE_VIEW: '../../data/flow/${date}/globe-${ip}.json',
   API_WORLD_110M: '../flow/world-110m.json',

http://git-wip-us.apache.org/repos/asf/incubator-spot/blob/46470640/spot-oa/ui/ingest-summary.html
----------------------------------------------------------------------
diff --git a/spot-oa/ui/ingest-summary.html b/spot-oa/ui/ingest-summary.html
index e694609..1c4a100 100755
--- a/spot-oa/ui/ingest-summary.html
+++ b/spot-oa/ui/ingest-summary.html
@@ -34,32 +34,43 @@
             height: 100%;
         }
 
-        #spot-is-header {
+        .is-chart svg {
             width: 100%;
-            position: absolute;
-            top: 0;
-            left: 0;
-            z-index: 2;
-            height: auto;
+            height: 100%;
         }
 
-        #spot-is, #spot-is-summary {
-            height: 100%;
+        .is-chart svg .header text {
+            text-anchor: middle;
+            fill: #82837e;
+        }
+
+        .is-chart svg .header text tspan.bold {
+            font-weight: bold;
         }
 
-        .axis {
+        .is-chart .axis {
             shape-rendering: crispEdges;
         }
 
-        .axis path, .axis line {
+        .is-chart .axis path, .is-chart .axis line {
             fill: none;
         }
 
-        rect.pane {
-            cursor: e-resize;
-            fill: none;
+        .is-chart .pipeline {
             pointer-events: all;
         }
+
+        .is-chart .pipeline.zoom-in {
+            cursor: zoom-in;
+        }
+
+        .is-chart .pipeline.zoom-out {
+            cursor: zoom-out;
+        }
+
+        .is-chart .pipeline.e-resize {
+            cursor: e-resize;
+        }
     </style>
 </head>
 <body>

http://git-wip-us.apache.org/repos/asf/incubator-spot/blob/46470640/spot-oa/ui/js/components/IngestSummaryPanel.react.js
----------------------------------------------------------------------
diff --git a/spot-oa/ui/js/components/IngestSummaryPanel.react.js b/spot-oa/ui/js/components/IngestSummaryPanel.react.js
index cc951ad..06c83bd 100755
--- a/spot-oa/ui/js/components/IngestSummaryPanel.react.js
+++ b/spot-oa/ui/js/components/IngestSummaryPanel.react.js
@@ -3,234 +3,216 @@ const d3 = require('d3');
 const React = require('react');
 const ReactDOM = require('react-dom');
 
+const ContentLoaderMixin = require('./ContentLoaderMixin.react');
+const ChartMixin = require('./ChartMixin.react');
 const DateUtils = require('../utils/DateUtils');
 const InSumActions = require('../actions/InSumActions');
 
-const NetflowIngestSummaryStore = require('../../flow/js/stores/IngestSummaryStore');
-
-function initialDraw() {
-  var rootNode, format, x, y, xAxis, yAxis, area, svg, rect, total, minDate, maxDate, maxFlows, numberFormat;
-
-  rootNode = d3.select(ReactDOM.findDOMNode(this));
-
-  // graph dimensions
-  var m = [100, 50, 50, 80], // Margin
-      w = $(rootNode.node()).width() - m[1] - m[3], // Width
-      h = $(rootNode.node()).height() - m[0] - m[2]; // Height
-
-  format = d3.time.format("%Y-%m-%d %H:%M");
-
-  // Scales.
-  x = d3.time.scale().range([0, w]); // get X function
-  y = d3.scale.linear().range([h, 0]); // get Y function
-  xAxis = d3.svg.axis().scale(x).orient("bottom"); // Get the X axis (Time)
-  yAxis = d3.svg.axis().scale(y).orient("left"); // Get Y Axis (Netflows)
-
-  // An area generator.
-  area = d3.svg.area()
-        .x(function (d) {
-            return x(d.date);
-        })
-        .y0(h)
-        .y1(function (d) {
-            if (!isNaN(d.total))
-                return y(d.total);
-            else
-                return y(0);
-        });
+const IngestSummaryStore = require('../stores/IngestSummaryStore');
 
-  rootNode.select('svg').remove();
-
-  // define the Main SVG
-  svg = rootNode.select('#' + this.props.id + '-summary').append("svg")
-    .attr("width", w + m[1] + m[3])
-    .attr("height", h + m[0] + m[2])
-        .append("g")
-        .attr("transform", "translate(" + m[3] + "," + m[0] + ")")
-
-  // Append the clipPath to avoid the Area overlapping
-  svg.append("clipPath")
-        .attr("id", "clip")
-        .append("rect")
-          .attr("x", x(0))
-          .attr("y", y(1))
-          .attr("width", x(1) - x(0))
-          .attr("height", y(0) - y(1));
-
-  // Append the Y Axis group
-  svg.append("g")
-    .attr("class", "y axis");
-
-  // Append the X axis group
-  svg.append("g")
-    .attr("class", "x axis")
-    .attr("transform", "translate(0," + h + ")");
-
-  // Append a pane rect, which will help us to add the zoom functionality
-  rect = svg.append("rect")
-        .attr("class", "pane")
-        .attr("width", w)
-        .attr("height", h);
-
-  this.state.data.forEach(function (dataSet)
-  {
-    var a;
-
-    a = [{date: minDate}];
-    a.push.apply(a, dataSet);
-    minDate = d3.min(a, function (d) { return d.date; });
-    a[0] = {date: maxDate, flows: maxFlows};
-    maxDate = d3.max(a, function (d) { return d.date; });
-    maxFlows = d3.max(a, function (d) { return d.total; })
-  });
-
-  !minDate && (minDate = DateUtils.parseDate(NetflowIngestSummaryStore.getStartDate()));
-  !maxDate && (maxDate = DateUtils.parseDate(NetflowIngestSummaryStore.getEndDate()));
-
-  // bind the data to the X and Y generators
-  x.domain([minDate, maxDate]);
-  y.domain([0, maxFlows]);
-
-  // Bind the data to our path element.
-  svg.selectAll("path.area").data(this.state.data).enter().insert('path', 'g')
-                                                .attr('class', 'area')
-                                                .attr('clip-path', 'url(#clip)')
-                                                .style('fill', '#0071c5')
-                                                .attr('d', function (d) {
-                                                    return area(d);
-                                                });
-
-  //Add the pane rect the zoom behavior
-  rect.call(d3.behavior.zoom().x(x)
-      .scaleExtent([0.3, 2300]) // these are magic numbers to avoid the grap be zoomable in/out to the infinity
-      .on("zoom", zoom.bind(this)));
-
-  function draw () {
-    var total, minDate, maxDate, numberFormat;
-
-    svg.select("g.x.axis").call(xAxis);
-    svg.select("g.y.axis").call(yAxis);
-    svg.selectAll("path.area").attr("d", function (d) { return area(d); });
-    numberFormat = d3.format(",d"); // number formatter (comma separated number i.e. 100,000,000)
-
-    rootNode.select('#' + this.props.id + '-range').html("Seeing total flows <strong>from:</strong> " + x.domain().map(format).join(" <strong>to:</strong> "));
-
-    //Calculate the total flows between the displayed date range
-
-    total = 0;
-    minDate = x.domain()[0];
-    maxDate = x.domain()[1];
-
-    // Go to the first millisecond on dates
-    minDate.setSeconds(0);minDate.setMilliseconds(0);
-    maxDate.setSeconds(59);maxDate.setMilliseconds(0);
-
-    svg.selectAll("path.area").data().forEach(function (pathData)
-    {
-      pathData.forEach(function (record)
-      {
-        // Discard records outside displayed date range
-        if (record.date >= minDate && record.date <= maxDate) {
-          total += +record.total;
-        }
-      });
-    });
-
-    rootNode.select('#' + this.props.id + '-total').html("<strong>Total netflows in range:</strong> " + numberFormat(total));
-  }
-
-  /*
-      Zoom event handler
-  */
-  function zoom() {
-    if (d3.event.sourceEvent.type == "wheel") {
-      if (d3.event.sourceEvent.wheelDelta < 0)
-         rect.style("cursor", "zoom-out");
-      else
-         rect.style("cursor", "zoom-in");
-    }
-    else if (d3.event.sourceEvent.type == "mousemove") {
-      rect.style("cursor", "e-resize");
-    }
+const MARGIN = [80, 50, 50, 100];
+const TIME_FORMATER = d3.time.format('%Y-%m-%d %H:%M');
+const NUMBER_FORMATER = d3.format(',d');
 
-    draw.call(this);
-  }
+const IngestSummaryPanel = React.createClass({
+    mixins: [ContentLoaderMixin, ChartMixin],
+    buildChart() {
+        // Scales
+        this.xScale = d3.time.scale();
+        this.yScale = d3.scale.linear();
 
-  draw.call(this);
-}
+        // Axis
+        this.xAxis = d3.svg.axis().scale(this.xScale).orient('bottom'); // Time
+        this.yAxis = d3.svg.axis().scale(this.yScale).orient('left'); // Totals
 
-var IngestSummaryPanel = React.createClass({
-  propTypes: {
-    id: React.PropTypes.string
-  },
-  getDefaultProperties: function () {
-    return {
-      id: 'spot-is'
-    };
-  },
-  getInitialState: function ()
-  {
-    return {loading: true};
-  },
-  render:function()
-  {
-    var content;
-
-    if (this.state.error)
-    {
-      content = (
-        <div className="text-center text-danger">
-          {this.state.error}
-        </div>
-      );
-    }
-    else if (this.state.loading)
-    {
-      content = (
-        <div className="spot-loader">
-            Loading <span className="spinner"></span>
-        </div>
-      );
-    }
-    else
-    {
-      content = (
-        <div id={this.props.id} className="text-center">
-          <div id={this.props.id + '-header'}>
-            <p id={this.props.id + '-range'}></p>
-            <p id={this.props.id + '-total'}></p>
-            <p id={this.props.id + '-istructions'} className="small">** Zoom in/out using mouse wheel or two fingers in track pad <br /> ** Move across the x-axis by clicking anywhere in the graph and dragging to left or right</p>
-          </div>
-          <div id={this.props.id + '-summary'}></div>
-        </div>
-      );
-    }
+        // An area generator.
+        this.area = d3.svg.area()
+              .x(d => this.xScale(d.date))
+              .y1(d => (isNaN(d.total) ? this.yScale(0) : this.yScale(d.total)));
 
-    return (
-      <div>{content}</div>
-    )
-  },
-  componentDidMount: function()
-  {
-    NetflowIngestSummaryStore.addChangeDataListener(this._onChange);
-    window.addEventListener('resize', this.buildGraph);
-  },
-  componentWillUnmount: function ()
-  {
-    NetflowIngestSummaryStore.removeChangeDataListener(this._onChange);
-    window.removeEventListener('resize', this.buildGraph);
+        let d3svg = d3.select(this.svg);
+
+        const d3header = d3svg.append('g').attr('class', 'header');
+
+        d3header.append('text')
+            .attr('transform', 'translate(0,15)')
+            .html('Seeing data <tspan class="bold">from</tspan > <tspan class="min-date" /> <tspan class="bold"> to </tspan> <tspan class="max-date" />');
+
+        d3header.append('text')
+            .attr('transform', 'translate(0,30)')
+            .html('<tspan class="bold">Total</tspan> records ingested: <tspan class="total" />');
+
+        d3header.append('text')
+            .attr('transform', 'translate(0,45)')
+            .text('** Zoom in/out using mouse wheel or two fingers in track pad');
+        d3header.append('text')
+            .attr('transform', 'translate(0,60)')
+            .text('** ** Move across the x-axis by clicking anywhere in the graph and dragging to left or right');
+
+        this.updateLegends(this.state.minDate, this.state.maxDate, this.state.total);
+
+        this.canvas = d3svg.append('g')
+            .attr('transform', `translate(${MARGIN[3]},${MARGIN[0]})`);
+
+        // Append the clipPath to avoid drawing not seen data
+        this.clipRect = d3svg.append('defs')
+            .append('clipPath')
+                .attr('id', 'clip')
+                .append('rect')
+                    .attr('x',0)
+                    .attr('y', 0);
+
+        this.d3xAxis = this.canvas.append('g').attr('class', 'x axis');
+        this.d3yAxis = this.canvas.append('g').attr('class', 'y axis');
+        this.pipelineCanvas = this.canvas.append('g').attr('class', 'pipeline');
+
+        this.d3zoom = d3.behavior.zoom()
+            .scaleExtent([0.3, 2300]) // these are magic numbers to avoid the grap be zoomable in/out to the infinity
+            .on('zoom', this.zoom)
+
+        this.pipelineCanvas.call(this.d3zoom);
+
+        this.pipelineColor = d3.scale.category10().domain(Object.keys(IngestSummaryStore.PIPELINES));
+    },
+    draw() {
+        let $svg = $(this.svg);
+
+        this.width = $svg.width() - MARGIN[1] - MARGIN[3];
+        this.height = $svg.height() - MARGIN[0] - MARGIN[2];
+
+        d3.select(this.svg).select('.header').attr('transform', `translate(${this.width/2},0)`);
+
+        this.xScale.range([0, this.width]).domain([this.state.minDate, this.state.maxDate]);
+        this.yScale.range([this.height, 0]).domain([0, this.state.maxTotal]);
+
+        this.d3zoom.x(this.xScale)
+
+        this.area.y0(this.height);
+
+        this.clipRect
+            .attr('width', this.width)
+            .attr('height', this.height);
+
+        this.d3xAxis.attr('transform', `translate(0,${this.height})`);
+
+        this.drawPaths();
+    },
+    drawPaths() {
+        this.d3xAxis.call(this.xAxis);
+        this.d3yAxis.call(this.yAxis);
+
+        let total = 0;
+        const [minDate, maxDate] = this.xScale.domain();
+
+        // Go to the first millisecond on dates
+        minDate.setSeconds(0);minDate.setMilliseconds(0);
+        maxDate.setSeconds(59);maxDate.setMilliseconds(0);
+
+        const pipelineData = this.state.data.map(currentMonthData => {
+            // Filter records outside current date range
+            return currentMonthData.filter(record => {
+                const included = record.date>=minDate && record.date<=maxDate;
+
+                // Sum records included in range only
+                if (included) total += record.total;
+
+                return included;
+            });
+        }).filter(monthData => monthData.length>0); // Filter out empty months
+
+        this.drawPipeline(pipelineData);
+
+        this.updateLegends(minDate, maxDate, total);
+    },
+    drawPipeline(data) {
+        const pipelineSel = {};
+
+        pipelineSel.update = this.pipelineCanvas.selectAll('path.area').data(data);
+
+        pipelineSel.enter = pipelineSel.update.enter();
+        pipelineSel.exit = pipelineSel.update.exit();
+
+        pipelineSel.enter.append('path')
+            .attr('class', 'area')
+            .style('fill', this.pipelineColor(IngestSummaryStore.getPipeline()));
+
+        pipelineSel.update.attr('d', d => this.area(d));
+
+        pipelineSel.exit.remove();
+    },
+    updateLegends(minDate, maxDate, total) {
+        const minDateStr = TIME_FORMATER(minDate);
+        const maxDateStr = TIME_FORMATER(maxDate);
+        const totalStr = NUMBER_FORMATER(total);
+
+        const d3header = d3.select(this.svg).select('.header');
+
+        d3header.select('.min-date').text(minDateStr);
+        d3header.select('.max-date').text(maxDateStr);
+        d3header.select('.total').text(totalStr);
+    },
+    zoom() {
+        if (d3.event.sourceEvent.type == 'wheel') {
+            this.pipelineCanvas.classed('zoom-out', d3.event.sourceEvent.wheelDelta < 0);
+            this.pipelineCanvas.classed('zoom-in', d3.event.sourceEvent.wheelDelta >= 0);
+            this.pipelineCanvas.classed('e-resize', false);
+      }
+      else if (d3.event.sourceEvent.type == 'mousemove') {
+        this.pipelineCanvas.classed('e-resize', true);
+        this.pipelineCanvas.classed('zoom-out', false);
+        this.pipelineCanvas.classed('zoom-in', false);
+      }
+
+      this.drawPaths();
   },
-  componentDidUpdate: function ()
-  {
-    if (!this.state.loading && !this.state.error && this.state.data)
-    {
-      this.buildGraph();
+    componentDidMount() {
+        IngestSummaryStore.addChangeDataListener(this._onChange);
+    },
+    componentWillUnmount() {
+        IngestSummaryStore.removeChangeDataListener(this._onChange);
+    },
+    _onChange() {
+        const storeData = IngestSummaryStore.getData();
+
+        if (storeData.error) {
+            this.replaceState({error: storeData.error});
+        }
+        else if (!storeData.loading && storeData.data) {
+            this.replaceState(this._getStateFromData(storeData.data));
+        }
+        else {
+            this.replaceState({loading: storeData.loading});
+        }
+    },
+    _getStateFromData(data) {
+        let total, maxTotal, minDate, maxDate;
+
+        total = 0;
+        maxTotal = 0;
+        minDate = null;
+        maxDate = null;
+
+        data.forEach(function (monthData) {
+          monthData.forEach(function (record) {
+              minDate = d3.min([minDate, record.date]);
+              maxDate = d3.max([maxDate, record.date]);
+              maxTotal = d3.max([maxTotal, +record.total]);
+              total += +record.total;
+          });
+        });
+
+        !minDate && (minDate = DateUtils.parseDate(IngestSummaryStore.getStartDate()));
+        !maxDate && (maxDate = DateUtils.parseDate(IngestSummaryStore.getEndDate()));
+
+        return {
+            loading: false,
+            total,
+            maxTotal,
+            minDate,
+            maxDate,
+            data: data
+        };
     }
-  },
-  buildGraph: initialDraw,
-  _onChange: function () {
-    this.replaceState(NetflowIngestSummaryStore.getData());
-  }
 });
 
 module.exports = IngestSummaryPanel;

http://git-wip-us.apache.org/repos/asf/incubator-spot/blob/46470640/spot-oa/ui/js/components/OptionPicker.react.js
----------------------------------------------------------------------
diff --git a/spot-oa/ui/js/components/OptionPicker.react.js b/spot-oa/ui/js/components/OptionPicker.react.js
new file mode 100644
index 0000000..f8e748d
--- /dev/null
+++ b/spot-oa/ui/js/components/OptionPicker.react.js
@@ -0,0 +1,43 @@
+const React = require('react');
+
+const RadioPicker = React.createClass({
+    propTypes: {
+        id: React.PropTypes.string,
+        name: React.PropTypes.string,
+        options: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
+        value: React.PropTypes.string
+    },
+    getDefaultProps() {
+        return {
+            id: null,
+            name: null,
+            value: null
+        };
+    },
+    getInitialState() {
+        const state = {};
+
+        state.value = this.props.value || (this.props.options.length>0 ? this.props.options[0] : null);
+
+        return state;
+    },
+    render() {
+        const options = Object.keys(this.props.options).map(option => {
+            return <option value={option} selected={this.state.value==option}>
+                {this.props.options[option]}
+            </option>;
+        });
+
+        return <select id={this.props.id} className="form-control" name={this.props.name} onChange={this.onChange}>
+            {options}
+        </select>;
+    },
+    onChange(e) {
+        const value = e.target.value;
+        this.setState({value});
+
+        this.props.onChange && this.props.onChange(value);
+    }
+});
+
+module.exports = RadioPicker;

http://git-wip-us.apache.org/repos/asf/incubator-spot/blob/46470640/spot-oa/ui/js/constants/SpotConstants.js
----------------------------------------------------------------------
diff --git a/spot-oa/ui/js/constants/SpotConstants.js b/spot-oa/ui/js/constants/SpotConstants.js
index 35e23c4..e4a711d 100755
--- a/spot-oa/ui/js/constants/SpotConstants.js
+++ b/spot-oa/ui/js/constants/SpotConstants.js
@@ -1,4 +1,7 @@
-var SpotConstants = {
+const SpotConstants = {
+  PIPELINE_NETFLOW: 'flow',
+  PIPELINE_DNS: 'dns',
+  PIPELINE_PROXY: 'proxy',
   // Search Actions
   UPDATE_FILTER: 'UPDATE_FILTER',
   UPDATE_DATE: 'UPDATE_DATE',
@@ -18,6 +21,7 @@ var SpotConstants = {
   IMPACT_ANALYSIS_PANEL:'Impact Analysis',
   GLOBE_VIEW_PANEL:'Map View | Globe',
   TIMELINE_PANEL:'Timeline',
+  INGEST_SUMMARY_PANEL:'Ingest Summary',
   // Edge Investigation
   MAX_SUSPICIOUS_ROWS: 250,
   RELOAD_SUSPICIOUS: 'RELOAD_SUSPICIOUS',
@@ -31,10 +35,10 @@ var SpotConstants = {
   RELOAD_COMMENTS: 'RELOAD_COMMENTS',
   SELECT_COMMENT: 'SELECT_COMMENT',
   // INGEST SUMMARY
+  API_INGEST_SUMMARY: '../data/${pipeline}/ingest_summary/is_${year}${month}.csv',
+  RELOAD_INGEST_SUMMARY: 'RELOAD_INGEST_SUMMARY',
   START_DATE: 'start-date',
   END_DATE: 'end-date',
-  // Ingest summary Actions
-  RELOAD_INGEST_SUMMARY: 'RELOAD_INGEST_SUMMARY',
   // Server Paths
   NOTEBOOKS_PATH: '/notebooks/ipynb'
 };

http://git-wip-us.apache.org/repos/asf/incubator-spot/blob/46470640/spot-oa/ui/js/ingest-summary.js
----------------------------------------------------------------------
diff --git a/spot-oa/ui/js/ingest-summary.js b/spot-oa/ui/js/ingest-summary.js
index c42ed41..04f2872 100755
--- a/spot-oa/ui/js/ingest-summary.js
+++ b/spot-oa/ui/js/ingest-summary.js
@@ -3,12 +3,14 @@ const ReactDOM = require('react-dom');
 
 const SpotActions = require('./actions/SpotActions');
 const InSumActions = require('./actions/InSumActions');
+const IngestSummaryStore = require('./stores/IngestSummaryStore');
 const SpotConstants = require('./constants/SpotConstants');
 const SpotUtils = require('./utils/SpotUtils');
 const DateUtils = require('./utils/DateUtils');
 
 // Build and Render Toolbar
 const DateInput = require('./components/DateInput.react');
+const OptionPicker = require('./components/OptionPicker.react');
 
 // Find out period
 var startDate, endDate, today;
@@ -41,32 +43,48 @@ if (endDate < startDate)
   endDate = today;
 }
 
+const PIPELINES = IngestSummaryStore.PIPELINES;
+const DEFAULT_PIPELINE = Object.keys(PIPELINES)[0];
+
+const loadPipeline = function loadPipeline(pipeline) {
+    IngestSummaryStore.setPipeline(pipeline);
+    InSumActions.reloadSummary();
+}
+
 ReactDOM.render(
-  (
     <form className="form-inline">
-      <div className="form-group">
-        <label htmlFor="startDatePicker">Period:</label>
-        <div className="input-group input-group-xs">
-          <div className="input-group-addon">
-            <span className="glyphicon glyphicon-calendar" aria-hidden="true"></span>
-          </div>
-          <DateInput id="startDatePicker" name={SpotConstants.START_DATE} value={startDate}/>
+        <div className="form-group">
+            <label htmlFor="pipeline-picker">Source: </label>
+            <div className="input-group input-group-xs">
+                <OptionPicker
+                    id="pipeline-picker"
+                    options={PIPELINES}
+                    value={DEFAULT_PIPELINE}
+                    onChange={loadPipeline} />
+            </div>
+        </div>
+        <div className="form-group">
+            <label htmlFor="startDatePicker">Period:</label>
+            <div className="input-group input-group-xs">
+                <div className="input-group-addon">
+                    <span className="glyphicon glyphicon-calendar" aria-hidden="true"></span>
+                </div>
+                <DateInput id="startDatePicker" name={SpotConstants.START_DATE} value={startDate}/>
+            </div>
         </div>
-      </div>
-      <div className="form-group">
-        <label htmlFor="endDatePicker"> - </label>
-        <div className="input-group input-group-xs">
-          <DateInput id="endDatePicker" name={SpotConstants.END_DATE} value={endDate} />
-          <div className="input-group-btn">
-            <button className="btn btn-default" type="button" title="Reload" onClick={InSumActions.reloadSummary}>
-              <span className="glyphicon glyphicon-repeat" aria-hidden="true"></span>
-            </button>
-          </div>
+        <div className="form-group">
+            <label htmlFor="endDatePicker"> - </label>
+            <div className="input-group input-group-xs">
+                <DateInput id="endDatePicker" name={SpotConstants.END_DATE} value={endDate} />
+                <div className="input-group-btn">
+                    <button className="btn btn-default" type="button" title="Reload" onClick={InSumActions.reloadSummary}>
+                        <span className="glyphicon glyphicon-repeat" aria-hidden="true"></span>
+                    </button>
+                </div>
+            </div>
         </div>
-      </div>
-    </form>
-  ),
-  document.getElementById('nav_form')
+    </form>,
+    document.getElementById('nav_form')
 );
 
 // Build and Render Edge Investigation's panels
@@ -79,7 +97,7 @@ ReactDOM.render(
   <div id="spot-content">
     <PanelRow maximized>
       <Panel title="Ingest Summary" container header={false} className="col-md-12">
-        <IngestSummaryPanel id="spot-is" />
+        <IngestSummaryPanel className="is-chart" />
       </Panel>
     </PanelRow>
   </div>,
@@ -91,4 +109,4 @@ SpotActions.setDate(startDate, SpotConstants.START_DATE);
 SpotActions.setDate(endDate, SpotConstants.END_DATE);
 
 // Load data
-InSumActions.reloadSummary();
+loadPipeline(DEFAULT_PIPELINE);

http://git-wip-us.apache.org/repos/asf/incubator-spot/blob/46470640/spot-oa/ui/js/stores/IngestSummaryStore.js
----------------------------------------------------------------------
diff --git a/spot-oa/ui/js/stores/IngestSummaryStore.js b/spot-oa/ui/js/stores/IngestSummaryStore.js
index 9e4ccb5..ca8a439 100755
--- a/spot-oa/ui/js/stores/IngestSummaryStore.js
+++ b/spot-oa/ui/js/stores/IngestSummaryStore.js
@@ -1,34 +1,55 @@
-var assign = require('object-assign');
-var d3 = require('d3');
-
-var SpotDispatcher = require('../../../js/dispatchers/SpotDispatcher');
-var SpotConstants = require('../../../js/constants/SpotConstants');
-var NetflowConstants = require('../constants/NetflowConstants');
-var DateUtils = require('../../../js/utils/DateUtils');
-var RestStore = require('../../../js/stores/RestStore');
-
-var START_DATE_FILTER = NetflowConstants.START_DATE;
-var END_DATE_FILTER = NetflowConstants.END_DATE;
-var CURRENT_DATE_FILTER = 'current_date';
-
-var requestQueue = [];
-var requestErrors = [];
-
-var IngestSummaryStore = assign(new RestStore(NetflowConstants.API_INGEST_SUMMARY), {
+const assign = require('object-assign');
+const d3 = require('d3');
+
+const SpotDispatcher = require('../dispatchers/SpotDispatcher');
+const SpotConstants = require('../constants/SpotConstants');
+const DateUtils = require('../utils/DateUtils');
+const RestStore = require('../stores/RestStore');
+
+const PIPELINE_FILTER = 'pipeline';
+const CURRENT_YEAR_FILTER = 'year';
+const CURRENT_MONTH_FILTER = 'month';
+
+const requestQueue = [];
+const requestErrors = [];
+
+const IngestSummaryStore = assign(new RestStore(SpotConstants.API_INGEST_SUMMARY), {
+    PIPELINES: {
+        [SpotConstants.PIPELINE_NETFLOW]: 'Netflow',
+        [SpotConstants.PIPELINE_DNS]: 'Dns',
+        [SpotConstants.PIPELINE_PROXY]: 'Proxy'
+    },
     errorMessages: {
         404: 'No details available'
     },
-    setStartDate: function (date) {
-        this.setRestFilter(START_DATE_FILTER, date);
+    setStartDate(date) {
+        this._startDate = date;
+    },
+    getStartDate() {
+        return this._startDate;
+    },
+    setEndDate(date) {
+        this._endDate = date;
     },
-    getStartDate: function () {
-        return this.getRestFilter(START_DATE_FILTER);
+    getEndDate() {
+        return this._endDate;
     },
-    setEndDate: function (date) {
-        this.setRestFilter(END_DATE_FILTER, date);
+    setPipeline(pipeline) {
+        this.setRestFilter(PIPELINE_FILTER, pipeline);
     },
-    getEndDate: function () {
-        return this.getRestFilter(END_DATE_FILTER);
+    getPipeline() {
+        return this.getRestFilter(PIPELINE_FILTER);
+    },
+    setCurrentDate(date) {
+        this.setRestFilter(CURRENT_YEAR_FILTER, date.getFullYear())
+
+        const month = date.getMonth() + 1 + "";
+        this.setRestFilter(CURRENT_MONTH_FILTER, month.length==1 ? `0${month}`:month);
+
+        this._currentDate = date;
+    },
+    getCurrentDate() {
+        return this._currentDate;
     },
     /**
      *  Start asking the server for CSV data to create the chart
@@ -36,8 +57,8 @@ var IngestSummaryStore = assign(new RestStore(NetflowConstants.API_INGEST_SUMMAR
     requestSummary: function () {
         var startDate, endDate, date, delta, startRequests, i, month;
 
-        startDate = DateUtils.parseDate(this.getRestFilter(START_DATE_FILTER));
-        endDate = DateUtils.parseDate(this.getRestFilter(END_DATE_FILTER));
+        startDate = DateUtils.parseDate(this.getStartDate());
+        endDate = DateUtils.parseDate(this.getEndDate());
 
         // Find out how many request need to be made
         delta = (endDate.getFullYear() - startDate.getFullYear()) * 12 + (endDate.getMonth() - startDate.getMonth());
@@ -58,17 +79,10 @@ var IngestSummaryStore = assign(new RestStore(NetflowConstants.API_INGEST_SUMMAR
         startRequests && this.dequeue();
     },
     dequeue: function () {
-        var date, year, month;
-
         if (requestQueue.length == 0) return;
 
-        date = requestQueue.shift();
-        this.setRestFilter(CURRENT_DATE_FILTER, date);
-        year = date.getFullYear();
-        month = date.getMonth() + 1 + "";
-        month = month.length == 1 ? "0" + month : month;
-
-        this.setEndpoint(NetflowConstants.API_INGEST_SUMMARY.replace('${year}', year).replace('${month}', month));
+        const date = requestQueue.shift();
+        this.setCurrentDate(date);
 
         this.reload();
     },
@@ -91,10 +105,10 @@ var IngestSummaryStore = assign(new RestStore(NetflowConstants.API_INGEST_SUMMAR
             requestErrors.push(data);
         }
         else if (data.data) {
-            parse = d3.time.format("%Y-%m-%d %H:%M").parse; // Date formatting parser
-            startDate = DateUtils.parseDate(this.getRestFilter(START_DATE_FILTER));
-            endDate = DateUtils.parseDate(this.getRestFilter(END_DATE_FILTER));
-            date = DateUtils.parseDate(this.getRestFilter(CURRENT_DATE_FILTER));
+            parse = d3.time.format("%Y-%m-%d %H:%M:%S%Z").parse; // Date formatting parser
+            startDate = DateUtils.parseDate(this.getStartDate());
+            endDate = DateUtils.parseDate(this.getEndDate());
+            date = DateUtils.parseDate(this.getCurrentDate());
 
             if (date.getFullYear() == startDate.getFullYear() && date.getMonth() == startDate.getMonth()) {
                 dayFilter = startDate.getDate();
@@ -112,8 +126,8 @@ var IngestSummaryStore = assign(new RestStore(NetflowConstants.API_INGEST_SUMMAR
 
             // Parse dates and numbers.
             data.data.forEach(function (d) {
-                d.date = parse(d.date);
-                d.flows = +d.flows;
+                d.date = parse(`${d.date}:00-0000`);
+                d.total = +d.total;
             });
 
             // Sort the data by date ASC
@@ -122,13 +136,14 @@ var IngestSummaryStore = assign(new RestStore(NetflowConstants.API_INGEST_SUMMAR
             });
 
             if (!this._data.data) this._data.data = [];
+
             this._data.data.push(data.data);
         }
 
         this._data.loading = requestQueue.length > 0;
 
         if (!this._data.loading) {
-            if (this._data.data.length==0) {
+            if (this._data.data && this._data.data.length==0) {
                 // Broadcast first found error
                 this._data = requestErrors[0];
             }
@@ -144,15 +159,15 @@ SpotDispatcher.register(function (action) {
     switch (action.actionType) {
         case SpotConstants.UPDATE_DATE:
             switch (action.name) {
-                case NetflowConstants.START_DATE:
+                case SpotConstants.START_DATE:
                     IngestSummaryStore.setStartDate(action.date);
                     break;
-                case NetflowConstants.END_DATE:
+                case SpotConstants.END_DATE:
                     IngestSummaryStore.setEndDate(action.date);
                     break;
             }
             break;
-        case NetflowConstants.RELOAD_INGEST_SUMMARY:
+        case SpotConstants.RELOAD_INGEST_SUMMARY:
             IngestSummaryStore.requestSummary();
             break;
     }

http://git-wip-us.apache.org/repos/asf/incubator-spot/blob/46470640/spot-oa/ui/package.json
----------------------------------------------------------------------
diff --git a/spot-oa/ui/package.json b/spot-oa/ui/package.json
index 724544d..3863c64 100644
--- a/spot-oa/ui/package.json
+++ b/spot-oa/ui/package.json
@@ -36,7 +36,7 @@
   "scripts": {
     "test": "jest",
     "postinstall": "npm run build-all",
-    "watch-ingest-summary": "watchify js/ingest-summary.js -o js/ingest-summary.bundle.min.js -v -d",
+    "watch-ingest-summary": "NODE_ENV=development watchify js/ingest-summary.js -o js/ingest-summary.bundle.min.js -v -d",
     "build-all": "npm run build-flow && npm run build-dns && npm run build-proxy && npm run build-ingest-summary",
     "build-flow": "cd flow/ && npm run build-all && cd ../",
     "build-dns": "cd dns/ && npm run build-all && cd ../",