Remove ubaplyer (too buggy) - warvox - VoIP based wardialing tool, forked from rapid7/warvox.
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
       ---
 (DIR) commit d1ca9925b77d6c463f3e85de96b17e219f8f40cf
 (DIR) parent 6fba0686fb93cf3157e42be1dff58a5b9cace5b0
 (HTM) Author: HD Moore <hd_moore@rapid7.com>
       Date:   Mon,  7 Jan 2013 03:27:01 -0600
       
       Remove ubaplyer (too buggy)
       
       Diffstat:
         M Gemfile                             |       2 +-
         M Gemfile.lock                        |      31 ++++++++++---------------------
         M app/assets/javascripts/application… |       1 -
         M app/assets/javascripts/dataTables.… |       9 ++++-----
         M app/assets/javascripts/dataTables_… |       1 +
         D app/assets/javascripts/jquery.ubap… |     223 ------------------------------
         D app/assets/javascripts/jquery.ubap… |       5 -----
         M app/assets/stylesheets/application… |       4 ++++
         M app/assets/stylesheets/bootstrap_a… |      51 -------------------------------
         M app/controllers/analyze_controller… |      90 +++++++++++++++++++++++--------
         M app/controllers/jobs_controller.rb  |       5 ++++-
         M app/helpers/application_helper.rb   |      30 +++---------------------------
         M app/views/analyze/view.html.erb     |      60 ++++++++++++++++---------------
         M app/views/jobs/index.html.erb       |       2 +-
         M app/views/jobs/results.html.erb     |       4 ++--
         M app/views/jobs/view_results.html.e… |      13 +++----------
         M app/views/layouts/application.html… |      12 ------------
         M app/views/shared/_audio_player.htm… |       1 -
         M app/views/shared/_call_info.html.e… |       7 ++++++-
         M lib/warvox/jobs/analysis.rb         |       4 ++--
       
       20 files changed, 140 insertions(+), 415 deletions(-)
       ---
 (DIR) diff --git a/Gemfile b/Gemfile
       @@ -31,7 +31,7 @@ gem 'formtastic'
        gem 'formtastic-bootstrap'
        gem 'rails_bootstrap_navbar'
        
       -gem "therubyracer", :group => :assets, :platform => :ruby
       +# gem "therubyracer", :group => :assets, :platform => :ruby
        
        gem 'will_paginate', '~> 3.0'
        gem 'will_paginate-bootstrap'
 (DIR) diff --git a/Gemfile.lock b/Gemfile.lock
       @@ -1,6 +1,6 @@
        GIT
          remote: git://github.com/dockyard/postgres_ext.git
       -  revision: a0ac550ecc63f5413b31e808798443a7b7b250eb
       +  revision: d114fea48bd27d7d35040b774e479058e3910566
          specs:
            postgres_ext (0.1.0)
              activerecord (~> 3.2.0)
       @@ -37,9 +37,9 @@ GEM
              i18n (~> 0.6)
              multi_json (~> 1.0)
            arel (3.0.2)
       -    authlogic (3.1.0)
       -      activerecord (>= 3.0.7)
       -      activerecord (>= 3.0.7)
       +    authlogic (3.2.0)
       +      activerecord (>= 3.0.0)
       +      activesupport (>= 3.0.0)
            builder (3.0.4)
            coffee-rails (3.2.2)
              coffee-script (>= 2.2.0)
       @@ -48,7 +48,6 @@ GEM
              coffee-script-source
              execjs
            coffee-script-source (1.4.0)
       -    commonjs (0.2.6)
            daemons (1.1.9)
            dynamic_form (1.1.4)
            erubis (2.7.0)
       @@ -63,18 +62,13 @@ GEM
            hike (1.2.1)
            i18n (0.6.1)
            journey (1.0.4)
       -    jquery-datatables-rails (1.11.1)
       +    jquery-datatables-rails (1.11.2)
              jquery-rails
            jquery-rails (2.1.4)
              railties (>= 3.0, < 5.0)
              thor (>= 0.14, < 2.0)
       -    json (1.7.5)
       +    json (1.7.6)
            kissfft (0.0.1)
       -    less (2.2.2)
       -      commonjs (~> 0.2.6)
       -    less-rails (2.2.4)
       -      actionpack (>= 3.1)
       -      less (~> 2.2.0)
            librex (0.0.68)
            mail (2.4.4)
              i18n (>= 0.4.0)
       @@ -85,7 +79,7 @@ GEM
            pg (0.11.0)
            pg_array_parser (0.0.8)
            polyglot (0.3.3)
       -    rack (1.4.1)
       +    rack (1.4.2)
            rack-cache (1.2)
              rack (>= 0.4)
            rack-ssl (1.3.2)
       @@ -114,8 +108,7 @@ GEM
            rake (10.0.3)
            rdoc (3.12)
              json (~> 1.4)
       -    ref (1.0.2)
       -    sass (3.2.4)
       +    sass (3.2.5)
            sass-rails (3.2.5)
              railties (~> 3.2.0)
              sass (>= 3.1.10)
       @@ -124,8 +117,6 @@ GEM
              hike (~> 1.2)
              rack (~> 1.0)
              tilt (~> 1.1, != 1.3.0)
       -    therubyracer (0.11.0)
       -      ref
            thin (1.5.0)
              daemons (>= 1.0.9)
              eventmachine (>= 0.12.6)
       @@ -135,11 +126,10 @@ GEM
            treetop (1.4.12)
              polyglot
              polyglot (>= 0.3.1)
       -    twitter-bootstrap-rails (2.1.4)
       +    twitter-bootstrap-rails (2.2.0)
              actionpack (>= 3.1)
       -      less-rails (~> 2.2.3)
       +      execjs
              railties (>= 3.1)
       -      therubyracer (>= 0.10.2)
            tzinfo (0.3.35)
            uglifier (1.3.0)
              execjs (>= 0.3.0)
       @@ -167,7 +157,6 @@ DEPENDENCIES
          rails-settings-cached
          rails_bootstrap_navbar
          sass-rails (~> 3.2.3)
       -  therubyracer
          thin
          twitter-bootstrap-rails
          uglifier (>= 1.0.3)
 (DIR) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
       @@ -2,7 +2,6 @@
        // This file is automatically included by javascript_include_tag :defaults
        //= require jquery
        //= require jquery_ujs
       -//= require jquery.ubaplayer
        //= require twitter/bootstrap
        //= require bootstrap-lightbox
        //= require dataTables/jquery.dataTables
 (DIR) diff --git a/app/assets/javascripts/dataTables.fnReloadAjax.js b/app/assets/javascripts/dataTables.fnReloadAjax.js
       @@ -20,18 +20,18 @@ jQuery.fn.dataTableExt.oApi.fnReloadAjax = function ( oSettings, sNewSource, fnC
          oSettings.fnServerData.call( oSettings.oInstance, oSettings.sAjaxSource, aData, function(json) {
            /* 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;
       @@ -50,4 +50,4 @@ jQuery.fn.dataTableExt.oApi.fnReloadAjax = function ( oSettings, sNewSource, fnC
              fnCallback( oSettings );
            }
          }, oSettings );
       -};
       -\ No newline at end of file
       +};
 (DIR) diff --git a/app/assets/javascripts/dataTables_overrides.js b/app/assets/javascripts/dataTables_overrides.js
       @@ -19,6 +19,7 @@ $.fn.dataTableExt.oApi.fnPagingInfo = function ( oSettings )
        
        /* Bootstrap style pagination control */
        $.extend( $.fn.dataTableExt.oPagination, {
       +
                "bootstrap": {
                        "fnInit": function( oSettings, nPaging, fnDraw ) {
                                var oLang = oSettings.oLanguage.oPaginate;
 (DIR) diff --git a/app/assets/javascripts/jquery.ubaplayer.js b/app/assets/javascripts/jquery.ubaplayer.js
       @@ -1,223 +0,0 @@
       -/*! UbaPlayer - v1.0.1 - 2012-06-06
       -* https://github.com/brianhadaway/UbaPlayer
       -* Copyright (c) 2012 Brian Hadaway; Licensed MIT, GPL */
       -
       -(function($){
       -        var defaults = {
       -                audioButtonClass:        "audioButton",
       -                autoPlay:        null,
       -                codecs:        [{name:"OGG", codec: 'audio/ogg; codecs="vorbis"'}, {name:"MP3", codec: 'audio/mpeg'}],
       -                continuous:        false,
       -                extension:        null,
       -                flashAudioPlayerPath:        "libs/swf/player.swf",
       -                flashExtension:        ".mp3",
       -                flashObjectID:        "audioPlayer",
       -                loadingClass:        "loading",
       -                loop:        false,
       -                playerContainer:        "player",
       -                playingClass:        "playing",
       -                swfobjectPath:        "libs/swfobject/swfobject.js",
       -                volume:        0.5
       -        },
       -        currentTrack,
       -        isPlaying = false,
       -        isFlash = false,
       -        audio,
       -        $buttons,
       -        $tgt,
       -        $el,
       -        playTrack,
       -        resumeTrack,
       -        pauseTrack,
       -        methods = {
       -                play: function(element){
       -                        $tgt = element;
       -                        currentTrack = _methods.getFileNameWithoutExtension($tgt.attr("href"));
       -                        isPlaying = true;
       -                        $tgt.addClass(defaults.loadingClass);
       -                        $buttons.removeClass(defaults.playingClass);
       -
       -                        if(isFlash){
       -                                if(audio) {
       -                                        _methods.removeListeners(window);
       -                                }
       -                                audio = document.getElementById(defaults.flashObjectID);
       -                                _methods.addListeners(window);
       -                                audio.playFlash(currentTrack + defaults.extension);
       -                        } else {
       -                                if(audio) {
       -                                        audio.pause();
       -                                        _methods.removeListeners(audio);
       -                                }
       -                                audio = new Audio("");
       -                                _methods.addListeners(audio);
       -                                audio.id = "audio";
       -                                audio.loop = defaults.loop ? "loop" : "";
       -                                audio.volume = defaults.volume;
       -                                audio.src = currentTrack + defaults.extension;
       -                                audio.play();
       -                        }
       -                },
       -
       -                pause: function(){
       -                        if(isFlash){
       -                                audio.pauseFlash();
       -                        } else {
       -                                audio.pause();
       -                        }
       -
       -                        $tgt.removeClass(defaults.playingClass);
       -                        isPlaying = false;
       -                },
       -
       -                resume: function(){
       -                        if(isFlash){
       -                                audio.playFlash();
       -                        } else {
       -                                audio.play();
       -                        }
       -                        $tgt.addClass(defaults.playingClass);
       -                        isPlaying = true;
       -                },
       -
       -                playing: function(){
       -                        return isPlaying;
       -                }
       -        },
       -        
       -        _methods = {
       -                init: function( options ){
       -                        var types;
       -
       -                        //set defaults
       -                        $.extend(defaults, options);
       -                        $el = this;
       -
       -                        //listen for clicks on the controls
       -                        $(".controls").bind("click",function(event){
       -                                _methods.updateTrackState(event);
       -                                return false;
       -                        });
       -                        $buttons = $("."+defaults.audioButtonClass);
       -
       -                        types = defaults.codecs;
       -                        for(var i = 0, ilen = types.length; i < ilen; i++){
       -                                var type = types[i];
       -                                if(_methods.canPlay(type)){
       -                                        defaults.extension = [".", type.name.toLowerCase()].join("");
       -                                        break;
       -                                }
       -                        }
       -
       -                        if(!defaults.extension || isFlash){
       -                                isFlash = true;
       -                                defaults.extension = defaults.flashExtension;
       -                        }
       -
       -                        if(isFlash){
       -                                $el.html("<div id='" + defaults.playerContainer + "'/>");
       -                                $.getScript(defaults.swfobjectPath,function(){
       -                                        swfobject.embedSWF(defaults.flashAudioPlayerPath, defaults.playerContainer, "0", "0", "9.0.0", "swf/expressInstall.swf", false, false, {id:defaults.flashObjectID}, _methods.swfLoaded);
       -                                });
       -                        } else {
       -                                if(defaults.autoPlay){
       -                                        methods.play(defaults.autoPlay);
       -                                }
       -                        }
       -                },
       -
       -                updateTrackState: function( evt ){
       -                        $tgt = $(evt.target);
       -                        if(!$tgt.hasClass("audioButton")){
       -                                return;
       -                        }
       -                        if(!audio || (audio && currentTrack !== _methods.getFileNameWithoutExtension($tgt.attr("href")))){
       -                                methods.play($tgt);
       -                        } else if(!isPlaying) {
       -                                methods.resume();
       -                        } else {
       -                                methods.pause();
       -                        }
       -                },
       -
       -                addListeners: function(elem) {
       -                        $(elem).bind({"canplay" : _methods.onLoaded,
       -                                                "error" : _methods.onError,
       -                                                "ended" : _methods.onEnded});
       -                },
       -
       -                removeListeners: function(elem) {
       -                        $(elem).unbind({"canplay" : _methods.onLoaded,
       -                                                "error" : _methods.onError,
       -                                                "ended" : _methods.onEnded});
       -                },
       -
       -                onLoaded: function () {
       -                        $buttons.removeClass(defaults.loadingClass);
       -                        $tgt.addClass(defaults.playingClass);
       -
       -                        audio.play();
       -                },
       -
       -                onError: function () {
       -                        $buttons.removeClass(defaults.loadingClass);
       -                        if(isFlash){
       -                                _methods.removeListeners(window);
       -                        } else {
       -                                _methods.removeListeners(audio);
       -                        }
       -                },
       -
       -                onEnded: function () {
       -                        isPlaying = false;
       -                        $tgt.removeClass(defaults.playingClass);
       -                        currentTrack = "";
       -                        if(isFlash){
       -                                _methods.removeListeners(window);
       -                        } else {
       -                                _methods.removeListeners(audio);
       -                        }
       -
       -                        if(defaults.continuous){
       -                                var $next = $tgt.next().length ? $tgt.next() : $(defaults.audioButtonClass).eq(0);
       -                                methods.play($next);
       -                        }
       -
       -                },
       -
       -                canPlay: function(type) {
       -                        if(!document.createElement("audio").canPlayType){
       -                                return false;
       -                        } else {
       -                                return document.createElement("audio").canPlayType(type.codec).match(/maybe|probably/i) ? true : false;
       -                        }
       -                },
       -
       -                swfLoaded: function(){
       -                        if(defaults.autoPlay){
       -                                setTimeout(function(){methods.play(defaults.autoPlay);}, 500);
       -                        }
       -                },
       -
       -                getFileNameWithoutExtension: function(fileName){
       -                        //this function take a full file name and returns an extensionless file name
       -                        //ex. entering foo.mp3 returns foo
       -                        //ex. entering foo returns foo (no change)
       -
       -                        var fileNamePieces = fileName.split('.');
       -                        fileNamePieces.pop();
       -                        return fileNamePieces.join(".");
       -                }
       -        };
       -
       -        $.fn.ubaPlayer = function(method){
       -                if(methods[method]){
       -                        return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
       -                } else if ( typeof method === "object" || ! method ) {
       -                        return _methods.init.apply( this, arguments );
       -                } else {
       -                        $.error( "Method " +  method + " does not exist on jquery.ubaPlayer" );
       -                }
       -        };
       -
       -})(jQuery);
 (DIR) diff --git a/app/assets/javascripts/jquery.ubaplayer.min.js b/app/assets/javascripts/jquery.ubaplayer.min.js
       @@ -1,4 +0,0 @@
       -/*! UbaPlayer - v1.0.1 - 2012-06-06
       -* https://github.com/brianhadaway/UbaPlayer
       -* Copyright (c) 2012 Brian Hadaway; Licensed MIT, GPL */
       -(function(a){var b={audioButtonClass:"audioButton",autoPlay:null,codecs:[{name:"OGG",codec:'audio/ogg; codecs="vorbis"'},{name:"MP3",codec:"audio/mpeg"}],continuous:!1,extension:null,flashAudioPlayerPath:"libs/swf/player.swf",flashExtension:".mp3",flashObjectID:"audioPlayer",loadingClass:"loading",loop:!1,playerContainer:"player",playingClass:"playing",swfobjectPath:"libs/swfobject/swfobject.js",volume:.5},c,d=!1,e=!1,f,g,h,i,j,k,l,m={play:function(a){h=a,c=n.getFileNameWithoutExtension(h.attr("href")),d=!0,h.addClass(b.loadingClass),g.removeClass(b.playingClass),e?(f&&n.removeListeners(window),f=document.getElementById(b.flashObjectID),n.addListeners(window),f.playFlash(c+b.extension)):(f&&(f.pause(),n.removeListeners(f)),f=new Audio(""),n.addListeners(f),f.id="audio",f.loop=b.loop?"loop":"",f.volume=b.volume,f.src=c+b.extension,f.play())},pause:function(){e?f.pauseFlash():f.pause(),h.removeClass(b.playingClass),d=!1},resume:function(){e?f.playFlash():f.play(),h.addClass(b.playingClass),d=!0},playing:function(){return d}},n={init:function(c){var d;a.extend(b,c),i=this,a(".controls").bind("click",function(a){return n.updateTrackState(a),!1}),g=a("."+b.audioButtonClass),d=b.codecs;for(var f=0,h=d.length;f<h;f++){var j=d[f];if(n.canPlay(j)){b.extension=[".",j.name.toLowerCase()].join("");break}}if(!b.extension||e)e=!0,b.extension=b.flashExtension;e?(i.html("<div id='"+b.playerContainer+"'/>"),a.getScript(b.swfobjectPath,function(){swfobject.embedSWF(b.flashAudioPlayerPath,b.playerContainer,"0","0","9.0.0","swf/expressInstall.swf",!1,!1,{id:b.flashObjectID},n.swfLoaded)})):b.autoPlay&&m.play(b.autoPlay)},updateTrackState:function(b){h=a(b.target);if(!h.hasClass("audioButton"))return;!f||f&&c!==n.getFileNameWithoutExtension(h.attr("href"))?m.play(h):d?m.pause():m.resume()},addListeners:function(b){a(b).bind({canplay:n.onLoaded,error:n.onError,ended:n.onEnded})},removeListeners:function(b){a(b).unbind({canplay:n.onLoaded,error:n.onError,ended:n.onEnded})},onLoaded:function(){g.removeClass(b.loadingClass),h.addClass(b.playingClass),f.play()},onError:function(){g.removeClass(b.loadingClass),e?n.removeListeners(window):n.removeListeners(f)},onEnded:function(){d=!1,h.removeClass(b.playingClass),c="",e?n.removeListeners(window):n.removeListeners(f);if(b.continuous){var g=h.next().length?h.next():a(b.audioButtonClass).eq(0);m.play(g)}},canPlay:function(a){return document.createElement("audio").canPlayType?document.createElement("audio").canPlayType(a.codec).match(/maybe|probably/i)?!0:!1:!1},swfLoaded:function(){b.autoPlay&&setTimeout(function(){m.play(b.autoPlay)},500)},getFileNameWithoutExtension:function(a){var b=a.split(".");return b.pop(),b.join(".")}};a.fn.ubaPlayer=function(b){if(m[b])return m[b].apply(this,Array.prototype.slice.call(arguments,1));if(typeof b=="object"||!b)return n.init.apply(this,arguments);a.error("Method "+b+" does not exist on jquery.ubaPlayer")}})(jQuery);
       -\ No newline at end of file
 (DIR) diff --git a/app/assets/stylesheets/application.css.scss.erb b/app/assets/stylesheets/application.css.scss.erb
       @@ -48,6 +48,10 @@ table.list {
          }
        }
        
       +.dataTables_processing {
       +        visibility: hidden;
       +}
       +
        .dataTables_info {
                font-size: 11px;
                font-color: #666666;
 (DIR) diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/bootstrap_and_overrides.css.less
       @@ -65,57 +65,6 @@ body {
        }
        
        
       -// Start Ubaplayer
       -.audio-player {
       -        list-style: none;
       -        margin: 0;
       -        padding: 0;
       -}
       -
       -#fallback {
       -        display: none;
       -}
       -
       -.audioButton {
       -        background: url("/assets/ubaplayer-btn.png") no-repeat 0 7px;
       -        display: block;
       -        cursor: pointer;
       -        margin-left: 10px;
       -        padding: 7px 10px 15px 25px;
       -        color: @darkGray;
       -        font-size: 18px;
       -        font-weight: bold;
       -}
       -
       -.audioButton:hover {
       -        background-position: 0 -33px;
       -        color: @darkGray;
       -        text-decoration: none;
       -}
       -
       -.audioButton.playing {
       -        background-position: 0 -73px;
       -        color: @darkGray;
       -}
       -
       -.audioButton.playing:hover {
       -        background-position: 0 -113px;
       -        color: @darkGray;
       -}
       -
       -.audioButton.loading,
       -.audioButton.loading:hover {
       -        background: url("/assets/ubaplayer-loading.gif") no-repeat 0 7px;
       -        text-indent: 0!important;
       -}
       -
       -#player {
       -        display: none;
       -}
       -
       -// End of Ubaplayer
       -
       -
        .fwd_match_span {
                width: 100px;
                padding-left: 20px;
 (DIR) diff --git a/app/controllers/analyze_controller.rb b/app/controllers/analyze_controller.rb
       @@ -25,31 +25,34 @@ class AnalyzeController < ApplicationController
        
                @lines_by_type = res_types
        
       -        if(@shown and @shown != 'all')
       -                @results = Call.where(:job_id => @job_id).paginate(
       -                        :page => params[:page],
       -                        :order => 'number ASC',
       -                        :per_page => 10,
       -                        :conditions => [ 'answered = ? and analysis_completed_at IS NOT NULL and busy = ? and line_type = ?', true, false, @shown ]
       -                )
       -        else
       -                @results = Call.where(:job_id => @job_id).paginate(
       -                        :page => params[:page],
       -                        :order => 'number ASC',
       -                        :per_page => 10,
       -                        :conditions => [ 'answered = ? and analysis_completed_at IS NOT NULL and busy = ?', true, false ]
       -                )
       -        end
       -
       -        @filters = []
       -        @filters << { :scope => "all", :label => "All" }
       -        res_types.keys.each do |t|
       -                @filters << { :scope => t.to_s.downcase, :label => t.to_s }
       -        end
       +    sort_by  = params[:sort_by] || 'number'
       +    sort_dir = params[:sort_dir] || 'asc'
       +
       +    @results = []
       +    @results_total_count = @job.calls.where("job_id = ? AND analysis_completed_at IS NOT NULL", @job_id).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).limit(@results_per_page).offset(@results_offset).order(calls_sort_option)
       +      @results_total_display_count = @job.calls.includes(:provider).where(@search_conditions).count()
       +    end
       +
       +        respond_to do |format|
       +      format.html
       +      format.json {
       +              render :content_type => 'application/json', :json => render_to_string(:partial => 'view', :results => @results, :lines_by_type => @lines_by_type )
       +      }
       +    end
        
          end
        
       -
          def view_matches
                  @result = Call.find(params[:call_id])
                  @job_id = @result.job_id
       @@ -104,4 +107,47 @@ class AnalyzeController < ApplicationController
          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'
       +               'line_type'
       +             when '3'
       +               'peak_freq'
       +             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 = "job_id = ? AND analysis_completed_at IS NOT NULL "
       +        param = [ @job_id ]
       +        glue  = "AND "
       +        terms.each do |w|
       +                where << glue
       +                case w
       +                        when /^F(\d+)$/i   # F2100 = peak frequency between 2095hz and 2105hz
       +                                freq = $1.to_i
       +                                where << "( peak_freq > ? AND peak_freq < ? ) "
       +                                param << freq - 5.0
       +                                param << freq + 5.0
       +                        else
       +                                where << "( number ILIKE ? OR caller_id ILIKE ? OR line_type ILIKE ? ) "
       +                                param << "%#{w}%"
       +                                param << "%#{w}%"
       +                                param << "%#{w}%"
       +                end
       +                glue = "AND " if glue.empty?
       +                @search_conditions = [ where, *param ]
       +        end
       +  end
       +
       +
        end
 (DIR) diff --git a/app/controllers/jobs_controller.rb b/app/controllers/jobs_controller.rb
       @@ -72,7 +72,10 @@ class JobsController < ApplicationController
        
                respond_to do |format|
              format.html
       -      format.json { render :partial => 'view_results', :results => @results, :call_results => @call_results }
       +
       +     format.json {
       +              render :content_type => 'application/json', :json => render_to_string(:partial => 'view_results', :results => @results, :call_results => @call_results )
       +      }
            end
          end
        
 (DIR) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
       @@ -56,33 +56,6 @@ module ApplicationHelper
                        end
                end
        
       -        def format_call_type_details(call)
       -                        ttip = raw("<div class='task_args_formatted'>")
       -
       -
       -                        ttip << raw("<div class='task_args_var'>Call Time:</div> ")
       -                        ttip << raw("<div class='task_args_val'>") + h(call.created_at.strftime("%Y-%m-%d %H:%M:%S %Z")) + raw("&nbsp;</div>")
       -
       -                        ttip << raw("<div class='task_args_var'>CallerID:</div> ")
       -                        ttip << raw("<div class='task_args_val'>") + h(call.caller_id) + raw("&nbsp;</div>")
       -
       -                        ttip << raw("<div class='task_args_var'>Provider:</div> ")
       -                        ttip << raw("<div class='task_args_val'>") + h(call.provider.name) + raw("&nbsp;</div>")
       -
       -
       -                        ttip << raw("<div class='task_args_var'>Audio:</div> ")
       -                        ttip << raw("<div class='task_args_val'>") + h(call.audio_length.to_s) + raw("&nbsp;</div>")
       -
       -
       -                        ttip << raw("<div class='task_args_var'>Ring:</div> ")
       -                        ttip << raw("<div class='task_args_val'>") + h(call.ring_length.to_s) + raw("&nbsp;</div>")
       -
       -                        ttip << raw("</div>\n")
       -                        outp = raw("<span class='xpopover' rel='popover' data-title=\"#{h call.number.to_s }\" data-content=\"#{ttip}\"><strong>#{h call.line_type.upcase }</strong></span>")
       -                        outp
       -        end
       -
       -
                def format_job_status(job)
                        case job.status
                        when 'error'
       @@ -137,6 +110,9 @@ module ApplicationHelper
                end
        
        
       +        def escape_javascript_dq(str)
       +                escape_javascript(str.strip).gsub("\\'", "'").gsub("\t", "    ")
       +        end
        
          #
          # Generate pagination links
 (DIR) diff --git a/app/views/analyze/view.html.erb b/app/views/analyze/view.html.erb
       @@ -1,6 +1,8 @@
       +<% include_view_javascript %>
       +
        <h1 class='title'>Analysis of Scan #<%= @job_id %></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/lines_by_type' %>
       @@ -8,42 +10,42 @@
        </tr>
        </table>
        
       -<%= will_paginate @results, :renderer => BootstrapPagination::Rails %>
        
       -<div class='pull-right'>
       -        <%= select_tag_for_filter(@filters, params) %>
       +<%= 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_dialer_job_path, { :class => "btn any" }) %>
       +</td>
       +<td>
       +        <%= submit_checkboxes_to(raw('<i class="icon-trash"></i> Delete'), purge_calls_job_path, { :class => "btn any ", :confirm => 'Purge selected calls?' }) %>
       +</td>
       +</tr></tbody></table>
       +
        </div>
        
       -<table class='table table-striped table-condensed'>
       +
       +<div class="analysis-control-bar"> </div>
       +
       +<span id="results-path" class="invisible"><%= view_analyze_path(@project, @job.id, :format => :json) %></span>
       +
       +<table id='results-table' class='table table-striped table-condensed sortable list' >
          <thead>
          <tr>
       +        <th><%= check_box_tag "all_results", false %></th>
            <th>Number</th>
            <th>Type</th>
       -          <th>Signal</th>
       -          <th>Actions</th>
       +    <th>Signal</th>
          </tr>
          </thead>
       -  <tbody>
       -<%  @results.each do |call| %>
       -  <tr>
       -    <td align='center'>
       -                <%= render :partial => 'shared/call_info', :locals => { :call => call } %>
       -        </td>
       -        <td align='center'>
       -                <%= format_call_type_details(call) %>
       -        </td>
       -          <td align='center'>
       -                <span class='pull-left'><%= render :partial => 'shared/lightbox_sig', :locals => { :call => call } %></span>
       -                <%= render :partial => 'shared/lightbox_freq', :locals => { :call => call } %>
       -        </td>
       -        <td>
       -                <% if call.fprint and call.fprint.length > 0 %>
       -                        <a class="btn" href="<%= view_matches_path(call.project_id, call.job_id, call.id) %>" rel="tooltip" title="Search for Matches"><i class="icon-search"></i></a>
       -                <% end %>
       -        </td>
       -  </tr>
       -<% end %>
       -</tbody>
       +  <tbody id="results-list">
       +  </tbody>
        </table>
        
       -<%= will_paginate @results, :renderer => BootstrapPagination::Rails %>
       +<% end %>
       +
       +<% if false %>
       +        <a class="btn" href="<%= view_matches_path(call.project_id, call.job_id, call.id) %>" rel="tooltip" title="Search for Matches"><i class="icon-search"></i></a>
       +<% end %>
 (DIR) diff --git a/app/views/jobs/index.html.erb b/app/views/jobs/index.html.erb
       @@ -59,7 +59,7 @@
                    <% if job.task == "dialer" %>
                            <a class="btn" href="<%= view_results_path(job.project,job) %>" rel="tooltip" title="View Current Stats" ><i class="icon-zoom-in"></i></a>
                    <% end %>
       -            <% if job.task == "analysis" %>
       +            <% if job.task == "analysis" and job.details[:scope].to_s != "calls" %>
                            <a class="btn" href="<%= view_analyze_path(job.project,job.details[:target_id]||job.id) %>" rel="tooltip" title="View Call Analysis"><i class="icon-eye-open"></i></a>
                    <% end %>
                        <a class="btn" href="<%= stop_job_path(job) %>" data-confirm="Terminate this job?" rel="nofollow tooltip" title="Terminate Job"><i class="icon-stop"></i></a>
 (DIR) diff --git a/app/views/jobs/results.html.erb b/app/views/jobs/results.html.erb
       @@ -45,8 +45,8 @@
        
                    <% end %>
            </td>
       -    <td><span rel="tooltip" class="xtooltip" title="<%= pct_answered %>% answered"><%= cnt_answered %> / <%= cnt_dialed %></span></td>
       -    <td><span rel="tooltip" class="xtooltip" title="<%= pct_analyzed %>% analyzed"><%= cnt_analyzed%></span></td>
       +    <td><span rel="tooltip" class="xtooltip" title="<%= pct_answered %>% answered"><%= number_with_delimiter(cnt_answered) %> / <%= number_with_delimiter(cnt_dialed) %></span></td>
       +    <td><span rel="tooltip" class="xtooltip" title="<%= pct_analyzed %>% analyzed"><%= number_with_delimiter(cnt_analyzed) %></span></td>
        
        
                <td><%= job.created_at.strftime("%Y-%m-%d %H:%M:%S") %></td>
 (DIR) diff --git a/app/views/jobs/view_results.html.erb b/app/views/jobs/view_results.html.erb
       @@ -2,7 +2,6 @@
        
        <h1 class='title'>Call Results for Scan #<%=@job.id%></h1>
        
       -
        <table class='table table-striped table-condensed'>
        <tr>
                <td align='center'>
       @@ -12,20 +11,16 @@
        </table>
        
        
       -
       -
        <%= 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_dialer_job_path, { :class => "btn btn-mini any" }) %>
       +        <%= submit_checkboxes_to(raw('<i class="icon-refresh"></i> Scan'), new_dialer_job_path, { :class => "btn btn any" }) %>
        </td><td>
       -        <%= submit_checkboxes_to(raw('<i class="icon-cog"></i> Analyze'), analyze_job_path, { :class => "btn btn-mini any" }) %>
       +        <%= submit_checkboxes_to(raw('<i class="icon-cog"></i> Analyze'), analyze_job_path, { :class => "btn btn any" }) %>
        </td><td>
       -        <%= submit_checkboxes_to(raw('<i class="icon-trash"></i> Delete'), purge_calls_job_path, { :class => "btn btn-mini any", :confirm => 'Purge selected calls?' }) %>
       -</td><td>
       -        <a class="btn btn-mini any" href="#"><i class="icon-trash"></i> Purge</button>
       +        <%= submit_checkboxes_to(raw('<i class="icon-trash"></i> Delete'), purge_calls_job_path, { :class => "btn any", :confirm => 'Purge selected calls?' }) %>
        </td>
        </tr></tbody></table>
        
       @@ -53,6 +48,4 @@
          </tbody>
        </table>
        
       -</div>
       -
        <% end %>
 (DIR) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
       @@ -35,16 +35,6 @@
                                        delay: { show: 300, hide: 300 },
                                        animation: false,
                                });
       -
       -                        var ap = $("#audio_player");
       -                        if (ap && null != ap) {
       -                                $("#ubaPlayer").ubaPlayer({
       -                                        codecs: [{name:"MP3", codec: 'audio/mpeg'}],
       -                                        flashAudioPlayerPath: "/assets/player.swf",
       -                                        swfobjectPath: "/assets/swfobject.js",
       -                                        playerContainer: "audio_player"
       -                                });
       -                        }
                        });
                <% end %>
          </head>
       @@ -105,8 +95,6 @@
                          <p>WarVOX v<%=WarVOX::VERSION %> &copy; Rapid7, Inc. 2009-2013</p>
                  </footer>
        
       -                <div id="audio_player"></div>
       -
                </div>
              </div>
            </div> <!-- /container -->
 (DIR) diff --git a/app/views/shared/_audio_player.html.erb b/app/views/shared/_audio_player.html.erb
       @@ -1 +0,0 @@
       -<ul class="controls audio-player"><li><a class="audioButton" href="<%=resource_analyze_path(call.id, "mp3")%>"><%= call.number %></a></li>
 (DIR) diff --git a/app/views/shared/_call_info.html.erb b/app/views/shared/_call_info.html.erb
       @@ -1 +1,6 @@
       -<%= render :partial => 'shared/audio_player', :locals => { :call => call } %>
       +<h2>
       +<object type="application/x-shockwave-flash" data="/assets/musicplayer.swf?song_url=<%=resource_analyze_path(call.id, "mp3") %>" width="17" height="17">
       +        <param name="movie" value="/assets/musicplayer.swf?song_url=<%=resource_analyze_path(call.id, "mp3") %>" />
       +</object>
       +<%= call.number %>
       +</h2>
 (DIR) diff --git a/lib/warvox/jobs/analysis.rb b/lib/warvox/jobs/analysis.rb
       @@ -65,7 +65,7 @@ class Analysis < Base
                        end
        
                        case @conf[:scope]
       -                when 'calls':
       +                when 'calls'
                                if @conf[:force]
                                        query = {:id => @conf[:target_ids], :answered => true, :busy => false}
                                else
       @@ -91,7 +91,7 @@ class Analysis < Base
                                end
                        else
                                # Bail if we don't have a valid scope
       -                        return        
       +                        return
                        end
        
                        # Build a list of call IDs, as find_each() gets confused if the DB changes mid-iteration