Introduction
Introduction Statistics Contact Development Disclaimer Help
Sync up recent work - warvox - VoIP based wardialing tool, forked from rapid7/w…
Log
Files
Refs
README
---
commit 6fba0686fb93cf3157e42be1dff58a5b9cace5b0
parent 876a89b6d4cde9a8e6df003f1aa0c08565040afc
Author: HD Moore <[email protected]>
Date: Sun, 6 Jan 2013 21:34:24 -0600
Sync up recent work
Diffstat:
A app/assets/images/search.png | 0
M app/assets/javascripts/application… | 158 +++++++++++++++++++++++++++…
A app/assets/javascripts/dataTables.… | 44 +++++++++++++++++++++++++++…
A app/assets/javascripts/dataTables.… | 54 +++++++++++++++++++++++++++…
A app/assets/javascripts/dataTables.… | 15 +++++++++++++++
A app/assets/javascripts/dataTables_… | 133 +++++++++++++++++++++++++++…
A app/assets/javascripts/jobs/view_r… | 52 +++++++++++++++++++++++++++…
A app/assets/javascripts/jquery.tabl… | 215 +++++++++++++++++++++++++++…
D app/assets/stylesheets/application… | 12 ------------
A app/assets/stylesheets/application… | 74 +++++++++++++++++++++++++++…
M app/assets/stylesheets/bootstrap_a… | 22 ++++++++++++++++++++++
M app/controllers/jobs_controller.rb | 115 ++++++++++++++++++++++++++++-…
M app/helpers/application_helper.rb | 153 +++++++++++++++++++++++++++++…
M app/models/job.rb | 13 +++++++++----
A app/views/jobs/_view_results.json.… | 20 ++++++++++++++++++++
M app/views/jobs/view_results.html.e… | 52 +++++++++++++++++----------…
M app/views/layouts/application.html… | 22 ++++++++++++----------
M config/environments/development.rb | 6 ++++--
M config/routes.rb | 11 ++++++-----
A db/migrate/20130106000000_add_inde… | 29 +++++++++++++++++++++++++++…
M db/schema.rb | 19 ++++++++++++++++++-
M lib/warvox/jobs/analysis.rb | 9 ++++++---
22 files changed, 1159 insertions(+), 69 deletions(-)
---
diff --git a/app/assets/images/search.png b/app/assets/images/search.png
Binary files differ.
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/app…
@@ -7,4 +7,162 @@
//= require bootstrap-lightbox
//= require dataTables/jquery.dataTables
//= require dataTables/jquery.dataTables.bootstrap
+//= require dataTables.hiddenTitle
+//= require dataTables.filteringDelay
+//= require dataTables.fnReloadAjax
+//= require jquery.table
+//= require dataTables_overrides
//= require highcharts
+
+
+
+
+function getParameterByName(name)
+{
+ name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
+ var regexS = "[\\?&]" + name + "=([^&#]*)";
+ var regex = new RegExp(regexS);
+ var results = regex.exec(window.location.href);
+ if(results == null)
+ return "";
+ else
+ return decodeURIComponent(results[1].replace(/\+/g, " "));
+}
+
+
+/*
+ * If the given select element is set to "", disables every other element
+ * inside the select's form.
+ */
+function disable_fields_if_select_is_blank(select) {
+ var formElement = Element.up(select, "form");
+ var fields = formElement.getElements();
+
+ Element.observe(select, "change", function(e) {
+ var v = select.getValue();
+ for (var i in fields) {
+ if (fields[i] != select && fields[i].type && fields[i]…
+ if (v != "") {
+ fields[i].disabled = true
+ } else {
+ fields[i].disabled = false;
+ }
+ }
+ }
+ });
+}
+
+function enable_fields_with_checkbox(checkbox, div) {
+ var fields;
+
+ if (!div) {
+ div = Element.up(checkbox, "fieldset")
+ }
+
+ f = function(e) {
+ fields = div.descendants();
+ var v = checkbox.getValue();
+ for (var i in fields) {
+ if (fields[i] != checkbox && fields[i].type && fields[…
+ if (!v) {
+ fields[i].disabled = true
+ } else {
+ fields[i].disabled = false;
+ }
+ }
+ }
+ }
+ f();
+ Element.observe(checkbox, "change", f);
+}
+
+function placeholder_text(field, text) {
+ var formElement = Element.up(field, "form");
+ var submitButton = Element.select(formElement, 'input[type="submit"]')…
+
+ if (field.value == "") {
+ field.value = text;
+ field.setAttribute("class", "placeholder");
+ }
+
+ Element.observe(field, "focus", function(e) {
+ field.setAttribute("class", "");
+ if (field.value == text) {
+ field.value = "";
+ }
+ });
+ Element.observe(field, "blur", function(e) {
+ if (field.value == "") {
+ field.setAttribute("class", "placeholder");
+ field.value = text;
+ }
+ });
+ submitButton.observe("click", function(e) {
+ if (field.value == text) {
+ field.value = "";
+ }
+ });
+}
+
+
+function submit_checkboxes_to(path, token) {
+ var f = document.createElement('form');
+ f.style.display = 'none';
+
+ /* Set the post destination */
+ f.method = "POST";
+ f.action = path;
+
+ /* Create the authenticity_token */
+ var s = document.createElement('input');
+ s.setAttribute('type', 'hidden');
+ s.setAttribute('name', 'authenticity_token');
+ s.setAttribute('value', token);
+ f.appendChild(s);
+
+ /* Copy the checkboxes from the host form */
+ $("input[type=checkbox]").each(function(i,e) {
+ if (e.checked) {
+ var c = document.createElement('input');
+ c.setAttribute('type', 'hidden');
+ c.setAttribute('name', e.getAttribute('name') );
+ c.setAttribute('value', e.getAttribute('value') );
+ f.appendChild(c);
+ }
+ })
+
+ /* Look for hidden variables in checkbox form */
+ $("input[type=hidden]").each(function(i,e) {
+ if ( e.getAttribute('name').indexOf("[]") != -1 ) {
+ var c = document.createElement('input');
+ c.setAttribute('type', 'hidden');
+ c.setAttribute('name', e.getAttribute('name') );
+ c.setAttribute('value', e.getAttribute('value') );
+ f.appendChild(c);
+ }
+ })
+
+ /* Copy the search field from the host form */
+ $("input#search").each(function (i,e) {
+ if (e.getAttribute("class") != "placeholder") {
+ var c = document.createElement('input');
+ c.setAttribute('type', 'hidden');
+ c.setAttribute('name', e.getAttribute('name') );
+ c.setAttribute('value', e.value );
+ f.appendChild(c);
+ }
+ });
+
+ /* Append to the main form body */
+ document.body.appendChild(f);
+ f.submit();
+ return false;
+}
+
+
+// Look for the other half of this in app/coffeescripts/forms.coffee
+function enableSubmitButtons() {
+ $("form.formtastic input[type='submit']").each(function(elmt) {
+ elmt.removeClassName('disabled'); elmt.removeClassName('submitting');
+ });
+}
diff --git a/app/assets/javascripts/dataTables.filteringDelay.js b/app/assets/j…
@@ -0,0 +1,44 @@
+jQuery.fn.dataTableExt.oApi.fnSetFilteringDelay = function ( oSettings, iDelay…
+ /*
+ * Inputs: object:oSettings - dataTables settings object - automa…
+ * integer:iDelay - delay in milliseconds
+ * Usage: $('#example').dataTable().fnSetFilteringDelay(250);
+ * Author: Zygimantas Berziunas (www.zygimantas.com) and Allan Ja…
+ * License: GPL v2 or BSD 3 point style
+ * Contact: zygimantas.berziunas /AT\ hotmail.com
+ */
+ var
+ _that = this,
+ iDelay = (typeof iDelay == 'undefined') ? 250 : iDelay;
+
+ this.each( function ( i ) {
+ jQuery.fn.dataTableExt.iApiIndex = i;
+ var
+ $this = this,
+ oTimerId = null,
+ sPreviousSearch = null,
+ anControl = jQuery( 'input', _that.fnSettings().aanFea…
+
+ anControl.unbind( 'keyup' ).bind( 'keyup', function() {
+ var $$this = $this;
+
+ if (sPreviousSearch === null || sPreviousSearch != anC…
+ window.clearTimeout(oTimerId);
+ sPreviousSearch = anControl.val();
+ oTimerId = window.setTimeout(function() {
+ jQuery.fn.dataTableExt.iApiIndex = i;
+ _that.fnFilter( anControl.val() );
+ }, iDelay);
+ }
+ });
+
+ return this;
+ } );
+ return this;
+}
+
+/* Example call
+$(document).ready(function() {
+ $('.dataTable').dataTable().fnSetFilteringDelay();
+} ); */
+
diff --git a/app/assets/javascripts/dataTables.fnReloadAjax.js b/app/assets/jav…
@@ -0,0 +1,53 @@
+jQuery.fn.dataTableExt.oApi.fnReloadAjax = function ( oSettings, sNewSource, f…
+{
+ if ( typeof sNewSource != 'undefined' && sNewSource != null ) {
+ oSettings.sAjaxSource = sNewSource;
+ }
+
+ // Server-side processing should just call fnDraw
+ if ( oSettings.oFeatures.bServerSide ) {
+ this.fnDraw();
+ return;
+ }
+
+ this.oApi._fnProcessingDisplay( oSettings, true );
+ var that = this;
+ var iStart = oSettings._iDisplayStart;
+ var aData = [];
+
+ this.oApi._fnServerParams( oSettings, aData );
+
+ oSettings.fnServerData.call( oSettings.oInstance, oSettings.sAjaxSource, aDa…
+ /* Clear the old information from the table */
+ that.oApi._fnClearTable( oSettings );
+
+ /* Got the data - add it to the table */
+ var aData = (oSettings.sAjaxDataProp !== "") ?
+ that.oApi._fnGetObjectDataFn( oSettings.sAjaxDataProp )( json ) : json;
+
+ for ( var i=0 ; i<aData.length ; i++ )
+ {
+ that.oApi._fnAddData( oSettings, aData[i] );
+ }
+
+ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+
+ if ( typeof bStandingRedraw != 'undefined' && bStandingRedraw === true )
+ {
+ oSettings._iDisplayStart = iStart;
+ that.fnDraw( false );
+ }
+ else
+ {
+ that.fnDraw();
+ }
+
+ that.oApi._fnProcessingDisplay( oSettings, false );
+
+ /* Callback user function - for event handlers etc */
+ if ( typeof fnCallback == 'function' && fnCallback != null )
+ {
+ fnCallback( oSettings );
+ }
+ }, oSettings );
+};
+\ No newline at end of file
diff --git a/app/assets/javascripts/dataTables.hiddenTitle.js b/app/assets/java…
@@ -0,0 +1,15 @@
+jQuery.fn.dataTableExt.oSort['title-numeric-asc'] = function(a,b) {
+ var x = a.match(/title="*(-?[0-9]+)/)[1];
+ var y = b.match(/title="*(-?[0-9]+)/)[1];
+ x = parseFloat( x );
+ y = parseFloat( y );
+ return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+};
+
+jQuery.fn.dataTableExt.oSort['title-numeric-desc'] = function(a,b) {
+ var x = a.match(/title="*(-?[0-9]+)/)[1];
+ var y = b.match(/title="*(-?[0-9]+)/)[1];
+ x = parseFloat( x );
+ y = parseFloat( y );
+ return ((x < y) ? 1 : ((x > y) ? -1 : 0));
+};
diff --git a/app/assets/javascripts/dataTables_overrides.js b/app/assets/javasc…
@@ -0,0 +1,133 @@
+$.extend( $.fn.dataTableExt.oStdClasses, {
+ "sWrapper": "dataTables_wrapper form-inline"
+} );
+
+
+/* API method to get paging information */
+$.fn.dataTableExt.oApi.fnPagingInfo = function ( oSettings )
+{
+ return {
+ "iStart": oSettings._iDisplayStart,
+ "iEnd": oSettings.fnDisplayEnd(),
+ "iLength": oSettings._iDisplayLength,
+ "iTotal": oSettings.fnRecordsTotal(),
+ "iFilteredTotal": oSettings.fnRecordsDisplay(),
+ "iPage": Math.ceil( oSettings._iDisplayStart / oSetti…
+ "iTotalPages": Math.ceil( oSettings.fnRecordsDisplay() / oS…
+ };
+};
+
+/* Bootstrap style pagination control */
+$.extend( $.fn.dataTableExt.oPagination, {
+ "bootstrap": {
+ "fnInit": function( oSettings, nPaging, fnDraw ) {
+ var oLang = oSettings.oLanguage.oPaginate;
+ var fnClickHandler = function ( e ) {
+ e.preventDefault();
+ if ( oSettings.oApi._fnPageChange(oSettings, e…
+ fnDraw( oSettings );
+ }
+ };
+
+ $(nPaging).addClass('pagination').append(
+ '<ul>'+
+ '<li class="prev disabled"><a href="#"…
+ '<li class="next disabled"><a href="#"…
+ '</ul>'
+ );
+ var els = $('a', nPaging);
+ $(els[0]).bind( 'click.DT', { action: "previous" }, fn…
+ $(els[1]).bind( 'click.DT', { action: "next" }, fnClic…
+ },
+
+ "fnUpdate": function ( oSettings, fnDraw ) {
+ var iListLength = 5;
+ var oPaging = oSettings.oInstance.fnPagingInfo();
+ var an = oSettings.aanFeatures.p;
+ var i, j, sClass, iStart, iEnd, iHalf=Math.floor(iList…
+
+ if ( oPaging.iTotalPages < iListLength) {
+ iStart = 1;
+ iEnd = oPaging.iTotalPages;
+ }
+ else if ( oPaging.iPage <= iHalf ) {
+ iStart = 1;
+ iEnd = iListLength;
+ } else if ( oPaging.iPage >= (oPaging.iTotalPages-iHal…
+ iStart = oPaging.iTotalPages - iListLength + 1;
+ iEnd = oPaging.iTotalPages;
+ } else {
+ iStart = oPaging.iPage - iHalf + 1;
+ iEnd = iStart + iListLength - 1;
+ }
+
+ for ( i=0, iLen=an.length ; i<iLen ; i++ ) {
+ // Remove the middle elements
+ $('li:gt(0)', an[i]).filter(':not(:last)').rem…
+
+ // Add the new list items and their event hand…
+ for ( j=iStart ; j<=iEnd ; j++ ) {
+ sClass = (j==oPaging.iPage+1) ? 'class…
+ $('<li '+sClass+'><a href="#">'+j+'</a…
+ .insertBefore( $('li:last', an…
+ .bind('click', function (e) {
+ e.preventDefault();
+ oSettings._iDisplaySta…
+ fnDraw( oSettings );
+ } );
+ }
+
+ // Add / remove disabled classes from the stat…
+ if ( oPaging.iPage === 0 ) {
+ $('li:first', an[i]).addClass('disable…
+ } else {
+ $('li:first', an[i]).removeClass('disa…
+ }
+
+ if ( oPaging.iPage === oPaging.iTotalPages-1 |…
+ $('li:last', an[i]).addClass('disabled…
+ } else {
+ $('li:last', an[i]).removeClass('disab…
+ }
+ }
+ }
+ }
+} );
+
+
+/*
+ * TableTools Bootstrap compatibility
+ * Required TableTools 2.1+
+ */
+if ( $.fn.DataTable.TableTools ) {
+ // Set the classes that TableTools uses to something suitable for Boot…
+ $.extend( true, $.fn.DataTable.TableTools.classes, {
+ "container": "DTTT btn-group",
+ "buttons": {
+ "normal": "btn",
+ "disabled": "disabled"
+ },
+ "collection": {
+ "container": "DTTT_dropdown dropdown-menu",
+ "buttons": {
+ "normal": "",
+ "disabled": "disabled"
+ }
+ },
+ "print": {
+ "info": "DTTT_print_info modal"
+ },
+ "select": {
+ "row": "active"
+ }
+ } );
+
+ // Have the collection use a bootstrap compatible dropdown
+ $.extend( true, $.fn.DataTable.TableTools.DEFAULTS.oTags, {
+ "collection": {
+ "container": "ul",
+ "button": "li",
+ "liner": "a"
+ }
+ } );
+}
diff --git a/app/assets/javascripts/jobs/view_results.coffee b/app/assets/javas…
@@ -0,0 +1,52 @@
+jQuery ($) ->
+ $ ->
+ resultsPath = $('#results-path').html()
+ $resultsTable = $('#results-table')
+
+ # Enable DataTable for the results list.
+ $resultsDataTable = $resultsTable.table
+ analysisTab: true
+ controlBarLocation: $('.analysis-control-bar')
+ searchInputHint: 'Search Calls'
+ searchable: true
+ datatableOptions:
+ "sDom": "<'row'<'span6'l><'span6'f>r>t<'row'<'span6'i><'span6'p>>",
+ "sPaginationType": "bootstrap",
+ "oLanguage":
+ "sEmptyTable": "No results for this job."
+ "sAjaxSource": resultsPath
+ "aaSorting": [[1, 'asc']]
+ "aoColumns": [
+ {"mDataProp": "checkbox", "bSortable": false}
+ {"mDataProp": "number"}
+ {"mDataProp": "caller_id"}
+ {"mDataProp": "provider"}
+ {"mDataProp": "answered"}
+ {"mDataProp": "busy"}
+ {"mDataProp": "audio_length"}
+ {"mDataProp": "ring_length"}
+ ]
+
+ # Gray out the table during loads.
+ $("#results-table_processing").watch 'visibility', ->
+ if $(this).css('visibility') == 'visible'
+ $resultsTable.css opacity: 0.6
+ else
+ $resultsTable.css opacity: 1
+
+ # Display the search bar when the search icon is clicked
+ $('.button .search').click (e) ->
+ $filter = $('.dataTables_filter')
+ $input = $('.dataTables_filter input')
+ if $filter.css('bottom').charAt(0) == '-' # if (css matches -42px)
+ # input box is visible, hide it
+ # only allow user to hide if there is no search string
+ if !$input.val() || $input.val().length < 1
+ $filter.css('bottom', '99999999px')
+ else # input box is invisible, display it
+ $filter.css('bottom', '-42px')
+ $input.focus() # auto-focus input
+ e.preventDefault()
+
+ searchVal = $('.dataTables_filter input').val()
+ $('.button .search').click() if searchVal && searchVal.length > 0
diff --git a/app/assets/javascripts/jquery.table.coffee b/app/assets/javascript…
@@ -0,0 +1,215 @@
+# table plugin
+#
+# Adds sorting and other dynamic functions to tables.
+jQuery ($) ->
+ $.table =
+ defaults:
+ searchable: true
+ searchInputHint: 'Search'
+ sortableClass: 'sortable'
+ setFilteringDelay: false
+ datatableOptions:
+ "bStateSave": true
+ "oLanguage":
+ "sSearch": ""
+ "sProcessing": "Loading..."
+ "fnDrawCallback": ->
+ $.table.controlBar.buttons.enable()
+ "sDom": '<"control-bar"f><"list-table-header clearfix"l>t<"list-table-…
+ "sPaginationType": "full_numbers"
+ "fnInitComplete": (oSettings, json) ->
+ # if old search term saved, display it
+ searchTerm = getParameterByName 'search'
+ # FIX ME
+ $searchBox = $('#search', $(this).parents().eq(3))
+
+ if searchTerm
+ $searchBox.val searchTerm
+ $searchBox.focus()
+
+ # insert the cancel button to the left of the search box
+ $searchBox.before('<a class="cancel-search" href="#"></a>')
+ $a = $('.cancel-search')
+ table = this
+ searchTerm = $searchBox.val()
+ searchBox = $searchBox.eq(0)
+ $a.hide() if (!searchTerm || searchTerm.length < 1)
+
+ $a.click (e) -> # called when red X is clicked
+ $(this).hide()
+ table.fnFilter ''
+ $(searchBox).blur() # blur to trigger filler text
+ e.preventDefault() # Other control code can be found in…
+ # bind to fnFilter() calls
+ # do this by saving fnFilter to fnFilterOld & overriding
+ table['fnFilterOld'] = table.fnFilter
+ table.fnFilter = (str) ->
+ $a = jQuery('.cancel-search')
+ if str && str.length > 0
+ $a.show()
+ else
+ $a.hide()
+ table.fnFilterOld(str)
+
+ window.setTimeout ( =>
+ this.fnFilter(searchTerm)
+ ), 0
+
+ $('.button a.search').click() if searchTerm
+
+ analysisTabOptions:
+ "aLengthMenu": [[10, 50, 100, 250, 500, -1], [10, 50, 100, 250, 5…
+ "iDisplayLength": 10
+ "bProcessing": true
+ "bServerSide": true
+ "bSortMulti": false
+
+ checkboxes:
+ bind: ->
+ # TODO: This and any other 'table.list' selectors that appear in the p…
+ # code will trigger all sortable tables visible on the page.
+ $("table.list thead tr th input[type='checkbox']").live 'click', (e) ->
+ $checkboxes = $("input[type='checkbox']", "table.list tbody tr td:nt…
+ if $(this).attr 'checked'
+ $checkboxes.attr 'checked', true
+ else
+ $checkboxes.attr 'checked', false
+
+ controlBar:
+ buttons:
+ # Disables/enables buttons based on number of checkboxes selected,
+ # and the class name.
+ enable: ->
+ numChecked = $("tbody tr td input[type='checkbox']", "table.list").f…
+ disable = ($button) ->
+ $button.addClass 'disabled'
+ $button.children('input').attr 'disabled', 'disabled'
+ enable = ($button) ->
+ $button.removeClass 'disabled'
+ $button.children('input').removeAttr 'disabled'
+
+ switch numChecked
+ when 0
+ disable $('.btn.single', '.control-bar')
+ disable $('.btn.multiple','.control-bar')
+ disable $('.btn.any', '.control-bar')
+ when 1
+ enable $('.btn.single', '.control-bar')
+ disable $('.btn.multiple','.control-bar')
+ enable $('.btn.any', '.control-bar')
+ else
+ disable $('.btn.single', '.control-bar')
+ enable $('.btn.multiple','.control-bar')
+ enable $('.btn.any', '.control-bar')
+
+ show:
+ bind: ->
+ # Show button
+ $showButton = $('span.button a.show', '.control-bar')
+ if $showButton.length
+ $showButton.click (e) ->
+ unless $showButton.parent('span').hasClass 'disabled'
+ $("table.list tbody tr td input[type='checkbox']").filter(':…
+ hostHref = $("table.list tbody tr td input[type='checkbox']")
+ .filter(':checked')
+ .parents('tr')
+ .children('td:nth-child(2)')
+ .children('a')
+ .attr('href')
+ window.location = hostHref
+ e.preventDefault()
+
+ edit:
+ bind: ->
+ # Settings button
+ $editButton = $('span.button a.edit', '.control-bar')
+ if $editButton.length
+ $editButton.click (e) ->
+ unless $editButton.parent('span').hasClass 'disabled'
+ $("table.list tbody tr td input[type='checkbox']").filter(':…
+ hostHref = $("table.list tbody tr td input[type='checkbox']")
+ .filter(':checked')
+ .parents('tr')
+ .children('td:nth-child(2)')
+ .children('span.settings-url')
+ .html()
+ window.location = hostHref
+ e.preventDefault()
+
+ bind: (options) ->
+ # Move the buttons into the control bar.
+ $('.control-bar').prepend($('.control-bar-items').html())
+ $('.control-bar-items').remove()
+
+ # Move the control bar to a new location, if specified.
+ if !!options.controlBarLocation
+ $('.control-bar').appendTo(options.controlBarLocation)
+
+ this.enable()
+ this.show.bind()
+ this.edit.bind()
+
+ bind: (options) ->
+ this.buttons.bind(options)
+ # Redraw the buttons with each checkbox click.
+ $("input[type='checkbox']", "table.list").live 'click', (e) =>
+ this.buttons.enable()
+
+ searchField:
+ # Add an input hint to the search field.
+ addInputHint: (options, $table) ->
+ if options.searchable
+ # if the searchbar is in a control bar, expand selector scope to inc…
+ searchScope = $table.parents().eq(3) if !!options.controlBarLocation
+ searchScope ||= $table.parents().eq(2) # otherwise limit scope to j…
+ $searchInput = $('.dataTables_filter input', searchScope)
+ # We'll need this id set for the checkbox functions.
+ $searchInput.attr 'id', 'search'
+ $searchInput.attr 'placeholder', options.searchInputHint
+ # $searchInput.inputHint()
+
+ bind: ($table, options) ->
+ $tbody = $table.children('tbody')
+ dataTable = null
+ # Turn the table into a DataTable.
+ if $table.hasClass options.sortableClass
+ # Don't mess with the search input if there's no control bar.
+ unless $('.control-bar-items').length
+ options.datatableOptions["sDom"] = '<"list-table-header clearfix"lfr…
+
+ datatableOptions = options.datatableOptions
+ # If we're loading under the Analysis tab, then load the standard
+ # Analysis tab options.
+ if options.analysisTab
+ $.extend(datatableOptions, options.analysisTabOptions)
+ options.setFilteringDelay = true
+ options.controlBarLocation = $('.analysis-control-bar')
+
+ dataTable = $table.dataTable(datatableOptions)
+ $table.data('dataTableObject', dataTable)
+ dataTable.fnSetFilteringDelay(500) if options.setFilteringDelay
+
+ # If we're loading under the Analysis tab, then load the standard Anal…
+ if options.analysisTab
+ # Gray out the table during loads.
+ $("##{$table.attr('id')}_processing").watch 'visibility', ->
+ if $(this).css('visibility') == 'visible'
+ $table.css opacity: 0.6
+ else
+ $table.css opacity: 1
+
+ # Checking a host_ids checkbox should also check the invisible relat…
+ $table.find('tbody tr td input[type=checkbox].hosts').live 'change',…
+ $(this).siblings('input[type=checkbox]').attr('checked', $(this).a…
+
+ this.checkboxes.bind()
+ this.controlBar.bind(options)
+ # Add an input hint to the search field.
+ this.searchField.addInputHint(options, $table)
+ # Keep width at 100%.
+ $table.css('width', '100%')
+
+ $.fn.table = (options) ->
+ settings = $.extend true, {}, $.table.defaults, options
+ $table = $(this)
+ return this.each -> $.table.bind($table, settings)
diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/styleshee…
@@ -1,12 +0,0 @@
-/*
- *= require bootstrap_and_overrides
- */
-
-/*
- *= require_self
- *= require formtastic
- *= require formtastic-bootstrap
- *= require formtastic-overrides
- *= require bootstrap-lightbox
- *= require dataTables/jquery.dataTables.bootstrap
-*/
diff --git a/app/assets/stylesheets/application.css.scss.erb b/app/assets/style…
@@ -0,0 +1,74 @@
+/*
+ *= require bootstrap_and_overrides
+ */
+
+/*
+ *= require_self
+ *= require formtastic
+ *= require formtastic-bootstrap
+ *= require formtastic-overrides
+ *= require bootstrap-lightbox
+ *= require dataTables/jquery.dataTables.bootstrap
+*/
+
+
+table.list {
+ td.actions {
+ vertical-align: middle;
+ }
+
+ td.dataTables_empty {
+ text-shadow: none !important;
+ }
+
+ td a.datatables-search {
+ color: blue;
+
+ &:hover{
+ text-decoration: underline;
+ }
+ }
+ thead tr {
+ background-size: 100% 100%;
+ background-color: #eeeeee;
+ }
+}
+
+.dataTables_filter {
+ padding: 0px;
+ width: auto !important;
+
+ input {
+ background-image: url(<%= asset_path 'search.png' %>);
+ background-position: 160px 6px;
+ background-repeat: no-repeat;
+ height: 18px;
+ padding-left: 5px;
+ width: 170px;
+ }
+}
+
+.dataTables_info {
+ font-size: 11px;
+ font-color: #666666;
+}
+
+.dataTables_length label {
+ font-weight: bold;
+}
+
+.dataTables_length select {
+ font-weight: bold;
+}
+
+.control-bar {
+ padding: 5px;
+ text-align: center;
+}
+
+.control-bar table {
+ width: 320px;
+ border: 0;
+ margin-left: auto;
+ margin-right: auto;
+}
diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/asse…
@@ -38,6 +38,28 @@ body {
@navbarBackground: #ea5709;
@navbarBackgroundHighlight: #4A1C04;
+
+// Datatables
+
+.paginate_disabled_previous {
+ display: none;
+}
+
+.paginate_disabled_next {
+ display: none;
+}
+
+.paginate_enabled_previous {
+ color: red;
+ margin-right: 20px;
+}
+
+.paginate_enabled_next {
+ color: green;
+}
+
+// End of DataTables
+
.call-detail {
font-size: 10px;
}
diff --git a/app/controllers/jobs_controller.rb b/app/controllers/jobs_controll…
@@ -1,5 +1,7 @@
class JobsController < ApplicationController
+ require 'shellwords'
+
def index
@reload_interval = 20000
@@ -42,11 +44,6 @@ class JobsController < ApplicationController
def view_results
@job = Job.find(params[:id])
- @results = @job.calls.paginate(
- :page => params[:page],
- :order => 'number ASC',
- :per_page => 30
- )
@call_results = {
:Timeout => @job.calls.count(:conditions => { :answered => fa…
@@ -54,6 +51,78 @@ class JobsController < ApplicationController
:Answered => @job.calls.count(:conditions => { :answered => tr…
}
+ sort_by = params[:sort_by] || 'number'
+ sort_dir = params[:sort_dir] || 'asc'
+
+ @results = []
+ @results_total_count = @job.calls.count()
+
+ if request.format.json?
+ if params[:iDisplayLength] == '-1'
+ @results_per_page = nil
+ else
+ @results_per_page = (params[:iDisplayLength] || 20).to_i
+ end
+ @results_offset = (params[:iDisplayStart] || 0).to_i
+
+ calls_search
+ @results = @job.calls.includes(:provider).where(@search_conditions).limi…
+ @results_total_display_count = @job.calls.includes(:provider).where(@sea…
+ end
+
+ respond_to do |format|
+ format.html
+ format.json { render :partial => 'view_results', :results => @results, :…
+ end
+ end
+
+ # Generate a SQL sort by option based on the incoming DataTables paramater.
+ #
+ # Returns the SQL String.
+ def calls_sort_option
+ column = case params[:iSortCol_0].to_s
+ when '1'
+ 'number'
+ when '2'
+ 'caller_id'
+ when '3'
+ 'providers.name'
+ when '4'
+ 'answered'
+ when '5'
+ 'busy'
+ when '6'
+ 'audio_length'
+ when '7'
+ 'ring_length'
+ end
+ column + ' ' + (params[:sSortDir_0] =~ /^A/i ? 'asc' : 'desc') if column
+ end
+
+ def calls_search
+ @search_conditions = []
+ terms = params[:sSearch].to_s
+ terms = Shellword.shellwords(terms) rescue terms.split(/\s+/)
+ where = ""
+ param = []
+ glue = ""
+ terms.each do |w|
+ where << glue
+ case w
+ when 'answered'
+ where << "answered = ? "
+ param << true
+ when 'busy'
+ where << "busy = ? "
+ param << true
+ else
+ where << "( number ILIKE ? OR caller_id ILIKE …
+ param << "%#{w}%"
+ param << "%#{w}%"
+ end
+ glue = "AND " if glue.empty?
+ @search_conditions = [ where, *param ]
+ end
end
def new_dialer
@@ -64,12 +133,29 @@ class JobsController < ApplicationController
@job.project = Project.last
end
+ if params[:result_ids]
+ nums = ""
+ Call.find_each(:conditions => { :id => params[:result_ids] }) do |…
+ nums << call.number + "\n"
+ end
+ @job.range = nums
+ end
+
+
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @job }
end
end
+ def purge_calls
+ @job = Job.find(params[:id])
+ Call.delete_all(:id => params[:result_ids])
+ CallMedium.delete_all(:call_id => params[:result_ids])
+ flash[:notice] = "Purged #{params[:result_ids].length} calls"
+ redirect_to view_results_path(@job.project_id, @job.id)
+ end
+
def dialer
@job = Job.new(params[:job])
@job.created_by = current_user.login
@@ -114,10 +200,21 @@ class JobsController < ApplicationController
def analyze_job
@job = Job.find(params[:id])
- @new = Job.new({
- :task => 'analysis', :scope => 'job', :target_id => @job.id,
- :project_id => @project.id, :status => 'submitted'
- })
+
+ # Handle analysis of specific call IDs via checkbox submission
+ if params[:result_ids]
+ @new = Job.new({
+ :task => 'analysis', :scope => 'calls', :target_ids =>…
+ :project_id => @project.id, :status => 'submitted'
+ })
+ else
+ # Otherwise analyze the entire Job
+ @new = Job.new({
+ :task => 'analysis', :scope => 'job', :target_id => @j…
+ :project_id => @project.id, :status => 'submitted'
+ })
+ end
+
respond_to do |format|
if @new.schedule
flash[:notice] = 'Analysis job was successfully created.'
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper…
@@ -92,6 +92,159 @@ module ApplicationHelper
else
job.status.to_s.capitalize
end
+ end
+
+ #
+ # Includes any javascripts specific to this view. The hosts/show view
+ # will automatically include any javascripts at public/javascripts/hos…
+ #
+ # @return [void]
+ def include_view_javascript
+ #
+ # Sprockets treats index.js as special, so the js for the inde…
+ # http://guides.rubyonrails.org/asset_pipeline.html#using-inde…
+ #
+
+ controller_action_name = controller.action_name
+
+ if controller_action_name == 'index'
+ safe_action_name = '_index'
+ else
+ safe_action_name = controller_action_name
+ end
+
+ include_view_javascript_named(safe_action_name)
+ end
+
+ # Includes the named javascript for this controller if it exists.
+ #
+ # @return [void]
+ def include_view_javascript_named(name)
+
+ controller_path = controller.controller_path
+ extensions = ['.coffee', '.js.coffee']
+ javascript_controller_pathname = Rails.root.join('app', 'asset…
+ pathnames = extensions.collect { |extension|
+ javascript_controller_pathname.join("#{name}#{extensio…
+ }
+
+ if pathnames.any?(&:exist?)
+ path = File.join(controller_path, name)
+ content_for(:view_javascript) do
+ javascript_include_tag path
+ end
+ end
+ end
+
+
+
+ #
+ # Generate pagination links
+ #
+ # Parameters:
+ # :name:: the kind of the items we're paginating
+ # :items:: the collection of items currently on the page
+ # :count:: total count of items to paginate
+ # :offset:: offset from the beginning where +items+ starts within the total
+ # :page:: current page
+ # :num_pages:: total number of pages
+ #
+ def page_links(opts={})
+ link_method = opts[:link_method]
+ if not link_method or not respond_to? link_method
+ raise RuntimeError.new("Need a method for generating links")
+ end
+ name = opts[:name] || ""
+ items = opts[:items] || []
+ count = opts[:count] || 0
+ offset = opts[:offset] || 0
+ page = opts[:page] || 1
+ num_pages = opts[:num_pages] || 1
+
+ page_list = ""
+ 1.upto(num_pages) do |p|
+ if p == page
+ page_list << content_tag(:span, :class=>"current") { h page }
+ else
+ page_list << self.send(link_method, p, { :page => p })
+ end
+ end
+ content_tag(:div, :id => "page_links") do
+ content_tag(:span, :class => "index") do
+ if items.size > 0
+ "#{offset + 1}-#{offset + items.size} of #{h pluralize(count, name)}…
+ else
+ h(name.pluralize)
+ end.html_safe
+ end +
+ if num_pages > 1
+ self.send(link_method, '', { :page => 0 }, { :class => 'start' }) +
+ self.send(link_method, '', { :page => page-1 }, {:class => 'prev' …
+ page_list +
+ self.send(link_method, '', { :page => [page+1,num_pages].min }, { …
+ self.send(link_method, '', { :page => num_pages }, { :class => 'en…
+ else
+ ""
+ end
+ end
+ end
+
+ def submit_checkboxes_to(name, path, html={})
+ if html[:confirm]
+ confirm = html.delete(:confirm)
+ link_to(name, "#", html.merge({:onclick => "if(confirm…
+ else
+ link_to(name, "#", html.merge({:onclick => "submit_che…
+ end
+ end
+
+ # Scrub out data that can break the JSON parser
+ #
+ # data - The String json to be scrubbed.
+ #
+ # Returns the String json with invalid data removed.
+ def json_data_scrub(data)
+ data.to_s.gsub(/[\x00-\x1f]/){ |x| "\\x%.2x" % x.unpack("C*")[…
+ end
+ # Returns the properly escaped sEcho parameter that DataTables expects.
+ def echo_data_tables
+ h(params[:sEcho]).to_json.html_safe
end
+
+ # Generate the markup for the call's row checkbox.
+ # Returns the String markup html, escaped for json.
+ def call_checkbox_tag(call)
+ check_box_tag("result_ids[]", call.id, false, :id => nil).to_j…
+ end
+
+ def call_number_html(call)
+ json_data_scrub(h(call.number)).to_json.html_safe
+ end
+
+ def call_caller_id_html(call)
+ json_data_scrub(h(call.caller_id)).to_json.html_safe
+ end
+
+ def call_provider_html(call)
+ json_data_scrub(h(call.provider.name)).to_json.html_safe
+ end
+
+ def call_answered_html(call)
+ json_data_scrub(h(call.answered ? "Yes" : "No")).to_json.html_…
+ end
+
+ def call_busy_html(call)
+ json_data_scrub(h(call.busy ? "Yes" : "No")).to_json.html_safe
+ end
+
+ def call_audio_length_html(call)
+ json_data_scrub(h(call.audio_length.to_s)).to_json.html_safe
+ end
+
+ def call_ring_length_html(call)
+ json_data_scrub(h(call.ring_lenght.to_s)).to_json.html_safe
+ end
+
+
end
diff --git a/app/models/job.rb b/app/models/job.rb
@@ -23,8 +23,8 @@ class Job < ActiveRecord::Base
record.errors[:lines] << "Lines should…
end
when 'analysis'
- unless ['job', 'project', 'global'].include?(r…
- record.errors[:scope] << "Scope must b…
+ unless ['calls', 'job', 'project', 'global'].i…
+ record.errors[:scope] << "Scope must b…
end
if record.scope == "job" and Job.where(:id => …
record.errors[:job_id] << "The job_id …
@@ -32,6 +32,9 @@ class Job < ActiveRecord::Base
if record.scope == "project" and Project.where…
record.errors[:project_id] << "The pro…
end
+ if record.scope == "calls" and (record.target_…
+ record.errors[:target_ids] << "The tar…
+ end
when 'import'
else
record.errors[:base] << "Invalid task specifie…
@@ -64,8 +67,9 @@ class Job < ActiveRecord::Base
attr_accessor :scope
attr_accessor :force
attr_accessor :target_id
+ attr_accessor :target_ids
- attr_accessible :scope, :force, :target_id
+ attr_accessible :scope, :force, :target_id, :target_ids
validates_with JobValidator
@@ -102,7 +106,8 @@ class Job < ActiveRecord::Base
self.args = Marshal.dump({
:scope => self.scope, # job / pr…
:force => !!(self.force), # true / f…
- :target_id => self.target_id.to_i # job_id o…
+ :target_id => self.target_id.to_i, # job_id o…
+ :target_ids => self.target_ids.map{|x| x.to_i }
})
return self.save
else
diff --git a/app/views/jobs/_view_results.json.erb b/app/views/jobs/_view_resul…
@@ -0,0 +1,20 @@
+{
+ "sEcho": <%= echo_data_tables %>,
+ "iTotalRecords": <%= @results_total_count.to_json %>,
+ "iTotalDisplayRecords": <%= @results_total_display_count.to_json %>,
+ "aaData": [
+ <% @results.each_with_index do |result, index| -%>
+ {
+ "DT_RowId": <%= dom_id(result).to_json.html_safe%>,
+ "checkbox": <%= call_checkbox_tag(result) %>,
+ "number": <%= call_number_html(result) %>,
+ "caller_id": <%= call_caller_id_html(result) %>,
+ "provider": <%= call_provider_html(result) %>,
+ "answered": <%= call_answered_html(result) %>,
+ "busy": <%= call_busy_html(result) %>,
+ "audio_length": <%= call_audio_length_html(result) %>,
+ "ring_length": <%= call_audio_length_html(result) %>
+ }<%= ',' unless index == (@results.size - 1) %>
+ <% end -%>
+ ]
+}
diff --git a/app/views/jobs/view_results.html.erb b/app/views/jobs/view_results…
@@ -1,10 +1,9 @@
-<% if @results.length > 0 %>
-
+<% include_view_javascript %>
<h1 class='title'>Call Results for Scan #<%[email protected]%></h1>
-<table width='100%' align='center' border=0 cellspacing=0 cellpadding=6>
+<table class='table table-striped table-condensed'>
<tr>
<td align='center'>
<%= render :partial => 'shared/graphs/call_results' %>
@@ -12,13 +11,35 @@
</tr>
</table>
-<br/>
-<%= will_paginate @results, :renderer => BootstrapPagination::Rails %>
-<table class='table table-striped table-condensed' width='90%' id='results'>
+
+<%= form_tag do %>
+<div class="control-bar">
+<table width='100%' border=0 cellpadding=6>
+<tbody><tr>
+<td>
+ <%= submit_checkboxes_to(raw('<i class="icon-refresh"></i> Scan'), new…
+</td><td>
+ <%= submit_checkboxes_to(raw('<i class="icon-cog"></i> Analyze'), anal…
+</td><td>
+ <%= submit_checkboxes_to(raw('<i class="icon-trash"></i> Delete'), pur…
+</td><td>
+ <a class="btn btn-mini any" href="#"><i class="icon-trash"></i> Purge<…
+</td>
+</tr></tbody></table>
+
+</div>
+
+
+<div class="analysis-control-bar"> </div>
+
+<span id="results-path" class="invisible"><%= view_results_path(@project, @job…
+
+<table id='results-table' class='table table-striped table-condensed sortable …
<thead>
<tr>
+ <th><%= check_box_tag "all_results", false %></th>
<th>Number</th>
<th>Source CID</th>
<th>Provider</th>
@@ -28,25 +49,10 @@
<th>Ring Time</th>
</tr>
</thead>
- <tbody>
-<% @results.each do |call| %>
- <tr>
- <td><%= call.number %></td>
- <td><%= call.caller_id %></td>
- <td><%= call.provider.name %></td>
- <td><%= call.answered ? "Yes" : "No" %></td>
- <td><%= call.busy %></td>
- <td><%= call.audio_length %></td>
- <td><%= call.ring_length %></td>
- </tr>
-<% end %>
+ <tbody id="results-list">
</tbody>
</table>
-<%= will_paginate @results, :renderer => BootstrapPagination::Rails %>
-
-<% else %>
-
-<h1 class='title'>No Results</h1>
+</div>
<% end %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/applica…
@@ -1,18 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= content_for?(:title) ? yield(:title) : "WarVOX v#{WarVOX::VERSI…
- <%= csrf_meta_tags %>
+ <%= csrf_meta_tags %>
- <!--[if lt IE 9]>
- <%= javascript_include_tag "html5" %>
- <![endif]-->
+ <!--[if lt IE 9]>
+ <%= javascript_include_tag "html5" %>
+ <![endif]-->
+
+<%= javascript_include_tag "application" %>
+<%= yield :view_javascript %>
+<%= stylesheet_link_tag "application", :media => "all" %>
+<%= yield :view_stylesheets %>
- <%= javascript_include_tag "application" %>
- <%= stylesheet_link_tag "application", :media => "all" %>
<%= favicon_link_tag '/assets/apple-touch-icon-144x144-precomposed.png', :…
<%= favicon_link_tag '/assets/apple-touch-icon-114x114-precomposed.png', :…
@@ -90,7 +93,6 @@
<% end %>
<% end %>
-
<div class="row">
<div class="span12 content">
<div class="content">
diff --git a/config/environments/development.rb b/config/environments/developme…
@@ -25,15 +25,17 @@ Web::Application.configure do
# Raise exception on mass assignment protection for Active Record models
config.active_record.mass_assignment_sanitizer = :strict
+ config.log_level = :debug
+
# Log the query plan for queries taking more than this (works
# with SQLite, MySQL, and PostgreSQL)
- config.active_record.auto_explain_threshold_in_seconds = 0.5
+ config.active_record.auto_explain_threshold_in_seconds = 0.75
# Do not compress assets
config.assets.compress = false
# Expands the lines which load the assets
- config.assets.debug = true
+ config.assets.debug = false
config.serve_static_assets = true
end
diff --git a/config/routes.rb b/config/routes.rb
@@ -8,11 +8,12 @@ Web::Application.routes.draw do
match '/projects/:project_id/all' => 'projects#index', :…
- match '/jobs/dial' => 'jobs#new_dialer', :as => :new_dialer_job
- match '/jobs/dialer' => 'jobs#dialer', :as => :dialer_job
- match '/jobs/analyze' => 'jobs#new_analyzer', :as => :new_analyzer_job
- match '/jobs/analyzer' => 'jobs#analyzer', :as => :analyzer_job
- match '/jobs/:id/stop' => 'jobs#stop', :as => :stop_job
+ match '/jobs/dial' => 'jobs#new_dialer', :as => :new_dialer_job
+ match '/jobs/dialer' => 'jobs#dialer', :as => :dialer_job
+ match '/jobs/analyze' => 'jobs#new_analyzer', :as => :new_analyzer_…
+ match '/jobs/analyzer' => 'jobs#analyzer', :as => :analyzer_job
+ match '/jobs/:id/stop' => 'jobs#stop', :as => :stop_job
+ match '/jobs/:id/calls/purge' => "jobs#purge_calls", :as => :purge_calls_j…
match '/projects/:project_id/scans' => 'jobs#results', :as => :res…
match '/projects/:project_id/scans/:id' => 'jobs#view_results', :as =>…
diff --git a/db/migrate/20130106000000_add_indexes.rb b/db/migrate/201301060000…
@@ -0,0 +1,29 @@
+class AddIndexes < ActiveRecord::Migration
+ def up
+ add_index :jobs, :project_id
+ add_index :lines, :number
+ add_index :lines, :project_id
+ add_index :line_attributes, :line_id
+ add_index :line_attributes, :project_id
+ add_index :calls, :number
+ add_index :calls, :job_id
+ add_index :calls, :provider_id
+ add_index :call_media, :call_id
+ add_index :call_media, :project_id
+ add_index :signature_fp, :signature_id
+ end
+
+ def down
+ remove_index :jobs, :project_id
+ remove_index :lines, :number
+ remove_index :lines, :project_id
+ remove_index :line_attributes, :line_id
+ remove_index :line_attributes, :project_id
+ remove_index :calls, :number
+ remove_index :calls, :job_id
+ remove_index :calls, :provider_id
+ remove_index :call_media, :call_id
+ remove_index :call_media, :project_id
+ remove_index :signature_fp, :signature_id
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control syste…
-ActiveRecord::Schema.define(:version => 20121228171549) do
+ActiveRecord::Schema.define(:version => 20130106000000) do
add_extension "intarray"
@@ -27,6 +27,9 @@ ActiveRecord::Schema.define(:version => 20121228171549) do
t.binary "png_sig_freq"
end
+ add_index "call_media", ["call_id"], :name => "index_call_media_on_call_id"
+ add_index "call_media", ["project_id"], :name => "index_call_media_on_projec…
+
create_table "calls", :force => true do |t|
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
@@ -49,6 +52,10 @@ ActiveRecord::Schema.define(:version => 20121228171549) do
t.integer "fprint", :array => true
end
+ add_index "calls", ["job_id"], :name => "index_calls_on_job_id"
+ add_index "calls", ["number"], :name => "index_calls_on_number"
+ add_index "calls", ["provider_id"], :name => "index_calls_on_provider_id"
+
create_table "jobs", :force => true do |t|
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
@@ -65,6 +72,8 @@ ActiveRecord::Schema.define(:version => 20121228171549) do
t.integer "progress", :default => 0
end
+ add_index "jobs", ["project_id"], :name => "index_jobs_on_project_id"
+
create_table "line_attributes", :force => true do |t|
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
@@ -75,6 +84,9 @@ ActiveRecord::Schema.define(:version => 20121228171549) do
t.string "content_type", :default => "text"
end
+ add_index "line_attributes", ["line_id"], :name => "index_line_attributes_on…
+ add_index "line_attributes", ["project_id"], :name => "index_line_attributes…
+
create_table "lines", :force => true do |t|
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
@@ -84,6 +96,9 @@ ActiveRecord::Schema.define(:version => 20121228171549) do
t.text "notes"
end
+ add_index "lines", ["number"], :name => "index_lines_on_number"
+ add_index "lines", ["project_id"], :name => "index_lines_on_project_id"
+
create_table "projects", :force => true do |t|
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
@@ -122,6 +137,8 @@ ActiveRecord::Schema.define(:version => 20121228171549) do
t.integer "fprint", :array => true
end
+ add_index "signature_fp", ["signature_id"], :name => "index_signature_fp_on_…
+
create_table "signatures", :force => true do |t|
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
diff --git a/lib/warvox/jobs/analysis.rb b/lib/warvox/jobs/analysis.rb
@@ -65,11 +65,11 @@ class Analysis < Base
end
case @conf[:scope]
- when 'call'
+ when 'calls':
if @conf[:force]
- query = {:id => @conf[:target_id], :answered =…
+ query = {:id => @conf[:target_ids], :answered …
else
- query = {:id => @conf[:target_id], :answered =…
+ query = {:id => @conf[:target_ids], :answered …
end
when 'job'
if @conf[:force]
@@ -89,6 +89,9 @@ class Analysis < Base
else
query = {:answered => true, :busy => false, :a…
end
+ else
+ # Bail if we don't have a valid scope
+ return
end
# Build a list of call IDs, as find_each() gets confused if th…
You are viewing proxied material from jay.scot. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.