| Swap lightbox, working results pages - warvox - VoIP based wardialing tool, for… | |
| Log | |
| Files | |
| Refs | |
| README | |
| --- | |
| commit 9974997874fab6f83540def599d2031acacbf5f1 | |
| parent 70379df629033bdd61080eba61894c2e9b745c62 | |
| Author: HD Moore <[email protected]> | |
| Date: Wed, 2 Jan 2013 02:49:15 -0600 | |
| Swap lightbox, working results pages | |
| Diffstat: | |
| M app/assets/javascripts/application… | 2 +- | |
| A app/assets/javascripts/bootstrap-l… | 354 +++++++++++++++++++++++++++… | |
| D app/assets/javascripts/jquery.ligh… | 472 ---------------------------… | |
| M app/assets/stylesheets/application… | 1 + | |
| A app/assets/stylesheets/bootstrap-l… | 65 +++++++++++++++++++++++++++… | |
| M app/controllers/analyze_controller… | 4 ++-- | |
| M app/controllers/calls_controller.rb | 59 +----------------------------… | |
| M app/controllers/jobs_controller.rb | 98 ++++++++++++++++++-----------… | |
| M app/models/job.rb | 59 +++++++++++++++++++++++------… | |
| M app/views/analyze/view.html.erb | 33 ++++++++++-------------------… | |
| M app/views/calls/index.html.erb | 22 +++++++++++----------- | |
| M app/views/jobs/index.html.erb | 2 +- | |
| A app/views/jobs/results.html.erb | 64 +++++++++++++++++++++++++++++… | |
| M app/views/layouts/application.html… | 3 ++- | |
| A app/views/shared/_lightbox_freq.ht… | 13 +++++++++++++ | |
| A app/views/shared/_lightbox_sig.htm… | 13 +++++++++++++ | |
| A app/views/shared/lightbox_sig.html… | 17 +++++++++++++++++ | |
| M bin/analyze_result.rb | 10 +++++++--- | |
| M config/routes.rb | 18 +++++++++--------- | |
| M lib/warvox/jobs/analysis.rb | 50 +++++++++++++++++++----------… | |
| M lib/warvox/jobs/base.rb | 4 ++++ | |
| 21 files changed, 708 insertions(+), 655 deletions(-) | |
| --- | |
| diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/app… | |
| @@ -3,7 +3,7 @@ | |
| //= require jquery | |
| //= require jquery_ujs | |
| //= require twitter/bootstrap | |
| -//= require jquery.lightbox-0.5 | |
| +//= require bootstrap-lightbox | |
| //= require dataTables/jquery.dataTables | |
| //= require dataTables/jquery.dataTables.bootstrap | |
| //= require highcharts | |
| diff --git a/app/assets/javascripts/bootstrap-lightbox.js b/app/assets/javascri… | |
| @@ -0,0 +1,353 @@ | |
| +/*!========================================================= | |
| +* bootstrap-lightbox v0.4.1 - 11/20/2012 | |
| +* http://jbutz.github.com/bootstrap-lightbox/ | |
| +* HEAVILY based off bootstrap-modal.js | |
| +* ========================================================== | |
| +* Copyright (c) 2012 Jason Butz (http://jasonbutz.info) | |
| +* | |
| +* Licensed under the Apache License, Version 2.0 (the "License"); | |
| +* you may not use this file except in compliance with the License. | |
| +* You may obtain a copy of the License at | |
| +* | |
| +* http://www.apache.org/licenses/LICENSE-2.0 | |
| +* | |
| +* Unless required by applicable law or agreed to in writing, software | |
| +* distributed under the License is distributed on an "AS IS" BASIS, | |
| +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| +* See the License for the specific language governing permissions and | |
| +* limitations under the License. | |
| +* ========================================================= */ | |
| + | |
| + | |
| +!function ($) { | |
| + // browser:true, jquery:true, node:true, laxbreak:true | |
| + "use strict"; // jshint ;_; | |
| + | |
| + | |
| +/* LIGHTBOX CLASS DEFINITION | |
| + * ========================= */ | |
| + | |
| + var Lightbox = function (element, options) | |
| + { | |
| + this.options = options; | |
| + this.$element = $(element) | |
| + .delegate('[data-dismiss="lightbox"]', 'click.dismiss.… | |
| + | |
| + this.options.remote && this.$element.find('.lightbox-body').lo… | |
| + | |
| + this.cloneSize(); | |
| + } | |
| + | |
| + Lightbox.prototype = { | |
| + constructor: Lightbox, | |
| + | |
| + toggle: function () | |
| + { | |
| + return this[!this.isShown ? 'show' : 'hide'](); | |
| + }, | |
| + | |
| + show: function () | |
| + { | |
| + var that = this; | |
| + var e = $.Event('show') | |
| + | |
| + this.$element.trigger(e); | |
| + | |
| + if (this.isShown || e.isDefaultPrevented()) return; | |
| + | |
| + | |
| + this.isShown = true; | |
| + | |
| + this.escape(); | |
| + | |
| + this.backdrop(function () | |
| + { | |
| + var transition = $.support.transition && that.… | |
| + | |
| + if (!that.$element.parent().length) | |
| + { | |
| + that.$element.appendTo(document.body);… | |
| + } | |
| + | |
| + that.$element | |
| + .show(); | |
| + | |
| + if (transition) | |
| + { | |
| + that.$element[0].offsetWidth; // force… | |
| + } | |
| + | |
| + that.$element | |
| + .addClass('in') | |
| + .attr('aria-hidden', false); | |
| + | |
| + that.enforceFocus(); | |
| + | |
| + transition ? | |
| + that.$element.one($.support.transition… | |
| + (function(){ that.centerImage(); that.… | |
| + | |
| + }); | |
| + }, | |
| + hide: function (e) | |
| + { | |
| + e && e.preventDefault(); | |
| + | |
| + var that = this; | |
| + | |
| + e = $.Event('hide'); | |
| + | |
| + this.$element.trigger(e); | |
| + | |
| + if (!this.isShown || e.isDefaultPrevented()) return; | |
| + | |
| + this.isShown = false; | |
| + | |
| + this.escape(); | |
| + | |
| + $(document).off('focusin.lightbox'); | |
| + | |
| + this.$element | |
| + .removeClass('in') | |
| + .attr('aria-hidden', true); | |
| + | |
| + $.support.transition && this.$element.hasClass('fade')… | |
| + this.hideWithTransition() : | |
| + this.hideLightbox(); | |
| + }, | |
| + enforceFocus: function () | |
| + { | |
| + var that = this; | |
| + $(document).on('focusin.lightbox', function (e) | |
| + { | |
| + if (that.$element[0] !== e.target && !that.$el… | |
| + { | |
| + that.$element.focus(); | |
| + } | |
| + }); | |
| + }, | |
| + escape: function () | |
| + { | |
| + var that = this; | |
| + if (this.isShown && this.options.keyboard) | |
| + { | |
| + this.$element.on('keyup.dismiss.lightbox', fun… | |
| + { | |
| + e.which == 27 && that.hide(); | |
| + }); | |
| + } | |
| + else if (!this.isShown) | |
| + { | |
| + this.$element.off('keyup.dismiss.lightbox'); | |
| + } | |
| + }, | |
| + hideWithTransition: function () | |
| + { | |
| + var that = this; | |
| + var timeout = setTimeout(function () | |
| + { | |
| + that.$element.off($.support.transition.end); | |
| + that.hideLightbox(); | |
| + }, 500); | |
| + | |
| + this.$element.one($.support.transition.end, function () | |
| + { | |
| + clearTimeout(timeout); | |
| + that.hideLightbox(); | |
| + }); | |
| + }, | |
| + hideLightbox: function (that) | |
| + { | |
| + this.$element | |
| + .hide() | |
| + .trigger('hidden'); | |
| + | |
| + this.backdrop(); | |
| + }, | |
| + removeBackdrop: function () | |
| + { | |
| + this.$backdrop.remove(); | |
| + this.$backdrop = null; | |
| + }, | |
| + backdrop: function (callback) | |
| + { | |
| + var that = this; | |
| + var animate = this.$element.hasClass('fade') ? 'fade' … | |
| + | |
| + if (this.isShown && this.options.backdrop) | |
| + { | |
| + var doAnimate = $.support.transition && animat… | |
| + | |
| + this.$backdrop = $('<div class="modal-backdrop… | |
| + .appendTo(document.body); | |
| + | |
| + this.$backdrop.click( | |
| + this.options.backdrop == 'static' ? | |
| + $.proxy(this.$element[0].focus… | |
| + $.proxy(this.hide, this) | |
| + ); | |
| + | |
| + if (doAnimate) this.$backdrop[0].offsetWidth; … | |
| + | |
| + this.$backdrop.addClass('in'); | |
| + | |
| + doAnimate ? | |
| + this.$backdrop.one($.support.transitio… | |
| + callback(); | |
| + | |
| + } | |
| + else if (!this.isShown && this.$backdrop) | |
| + { | |
| + this.$backdrop.removeClass('in'); | |
| + | |
| + $.support.transition && this.$element.hasClass… | |
| + this.$backdrop.one($.support.transitio… | |
| + this.removeBackdrop(); | |
| + | |
| + } | |
| + else if (callback) | |
| + { | |
| + callback(); | |
| + } | |
| + }, | |
| + centerImage: function() | |
| + { | |
| + var that = this; | |
| + var resizedOffs = 0; | |
| + var $img; | |
| + | |
| + that.h = that.$element.height(); | |
| + that.w = that.$element.width(); | |
| + | |
| + if(that.options.resizeToFit) | |
| + { | |
| + | |
| + resizedOffs = 10; | |
| + $img = that.$element.find('.lightbox-content')… | |
| + // Save original filesize | |
| + if(!$img.data('osizew')) $img.data('osizew', $… | |
| + if(!$img.data('osizeh')) $img.data('osizeh', $… | |
| + | |
| + var osizew = $img.data('osizew'); | |
| + var osizeh = $img.data('osizeh'); | |
| + | |
| + // Resize for window dimension < than image | |
| + // Reset previous | |
| + $img.css('max-width', 'none'); | |
| + $img.css('max-height', 'none'); | |
| + | |
| + | |
| + var sOffs = 40; // STYLE ? | |
| + if(that.$element.find('.lightbox-header').leng… | |
| + $img.css('max-width', $(window).width() - sOff… | |
| + $img.css('max-height', $(window).height() - sO… | |
| + | |
| + that.w = $img.width(); | |
| + that.h = $img.height(); | |
| + } | |
| + | |
| + that.$element.css({ | |
| + "position": "fixed", | |
| + "left": ( $(window).width() / 2 ) - ( that.w … | |
| + "top": ( $(window).height() / 2 ) - ( that.h … | |
| + }); | |
| + that.enforceFocus(); | |
| + }, | |
| + cloneSize: function() // The cloneSize function is only run on… | |
| + { | |
| + var that = this; | |
| + // Clone the element and append it to the body | |
| + // this allows us to get an idea for the size of the … | |
| + that.$clone = that.$element.filter(':first').clone() | |
| + .css( | |
| + { | |
| + 'position': 'absolute', | |
| + 'top' : -2000, | |
| + 'display' : 'block', | |
| + 'visibility': 'visible', | |
| + 'opacity': 100 | |
| + }) | |
| + .removeClass('fade') | |
| + .appendTo('body'); | |
| + | |
| + that.h = that.$clone.height(); | |
| + that.w = that.$clone.width(); | |
| + that.$clone.remove(); | |
| + | |
| + // try and center the element based on the | |
| + // height and width retrieved from the clone | |
| + that.$element.css({ | |
| + "position": "fixed", | |
| + "left": ( $(window).width() / 2 ) - ( that.w … | |
| + "top": ( $(window).height() / 2 ) - ( that.h … | |
| + }); | |
| + } | |
| + } | |
| + | |
| + | |
| +/* LIGHTBOX PLUGIN DEFINITION | |
| + * ======================= */ | |
| + | |
| + $.fn.lightbox = function (option) | |
| + { | |
| + return this.each(function () | |
| + { | |
| + var $this = $(this); | |
| + var data = $this.data('lightbox'); | |
| + var options = $.extend({}, $.fn.lightbox.defaults, $th… | |
| + if (!data) $this.data('lightbox', (data = new Lightbox… | |
| + | |
| + if (typeof option == 'string') | |
| + data[option]() | |
| + else if (options.show) | |
| + data.show() | |
| + }); | |
| + }; | |
| + | |
| + $.fn.lightbox.defaults = { | |
| + backdrop: true, | |
| + keyboard: true, | |
| + show: true, | |
| + resizeToFit: true | |
| + }; | |
| + | |
| + $.fn.lightbox.Constructor = Lightbox; | |
| + | |
| + | |
| +/* LIGHTBOX DATA-API | |
| + * ================== */ | |
| + | |
| + $(document).on('click.lightbox.data-api', '[data-toggle="lightbox"]', … | |
| + { | |
| + var $this = $(this); | |
| + var href = $this.attr('href'); | |
| + var $target = $($this.attr('data-target') || (href && href.rep… | |
| + var option = $target.data('lightbox') ? 'toggle' : $.extend({ … | |
| + var img = $this.attr('data-image') || false; | |
| + var $imgElem; | |
| + | |
| + e.preventDefault(); | |
| + | |
| + if(img) | |
| + { | |
| + $target.data('original-content', $target.find('.lightb… | |
| + $target.find('.lightbox-content').html('<img border="0… | |
| + } | |
| + | |
| + $target | |
| + .lightbox(option) | |
| + .one('hide', function () | |
| + { | |
| + $this.focus() | |
| + }) | |
| + .one('hidden',function () | |
| + { | |
| + if( img ) | |
| + { | |
| + $target.find('.lightbox-content').html… | |
| + img = undefined; | |
| + } | |
| + }); | |
| + }) | |
| + | |
| +}(window.jQuery); | |
| +\ No newline at end of file | |
| diff --git a/app/assets/javascripts/jquery.lightbox-0.5.js b/app/assets/javascr… | |
| @@ -1,472 +0,0 @@ | |
| -/** | |
| - * jQuery lightBox plugin | |
| - * This jQuery plugin was inspired and based on Lightbox 2 by Lokesh Dhakar (h… | |
| - * and adapted to me for use like a plugin from jQuery. | |
| - * @name jquery-lightbox-0.5.js | |
| - * @author Leandro Vieira Pinho - http://leandrovieira.com | |
| - * @version 0.5 | |
| - * @date April 11, 2008 | |
| - * @category jQuery plugin | |
| - * @copyright (c) 2008 Leandro Vieira Pinho (leandrovieira.com) | |
| - * @license CCAttribution-ShareAlike 2.5 Brazil - http://creativecommons.org/l… | |
| - * @example Visit http://leandrovieira.com/projects/jquery/lightbox/ for more … | |
| - */ | |
| - | |
| -// Offering a Custom Alias suport - More info: http://docs.jquery.com/Plugins/… | |
| -(function($) { | |
| - /** | |
| - * $ is an alias to jQuery object | |
| - * | |
| - */ | |
| - $.fn.lightBox = function(settings) { | |
| - // Settings to configure the jQuery lightBox plugin how you li… | |
| - settings = jQuery.extend({ | |
| - // Configuration related to overlay | |
| - overlayBgColor: '#000', … | |
| - overlayOpacity: 0.8, … | |
| - // Configuration related to navigation | |
| - fixedNavigation: false, … | |
| - // Configuration related to images | |
| - imageLoading: '/assets/lightbox… | |
| - imageBtnPrev: '/assets/lightbox… | |
| - imageBtnNext: '/assets/lightbox… | |
| - imageBtnClose: '/assets/lightbo… | |
| - imageBlank: '/assets/li… | |
| - // Configuration related to container image box | |
| - containerBorderSize: 10, … | |
| - containerResizeSpeed: 400, // (i… | |
| - // Configuration related to texts in caption. For exam… | |
| - txtImage: 'Image', … | |
| - txtOf: 'of', … | |
| - // Configuration related to keyboard navigation | |
| - keyToClose: 'c', … | |
| - keyToPrev: 'p', … | |
| - keyToNext: 'n', … | |
| - // Don't alter these variables in any way | |
| - imageArray: [], | |
| - activeImage: 0 | |
| - },settings); | |
| - // Caching the jQuery object with all elements matched | |
| - var jQueryMatchedObj = this; // This, in this context, refer t… | |
| - /** | |
| - * Initializing the plugin calling the start function | |
| - * | |
| - * @return boolean false | |
| - */ | |
| - function _initialize() { | |
| - _start(this,jQueryMatchedObj); // This, in this contex… | |
| - return false; // Avoid the browser following the link | |
| - } | |
| - /** | |
| - * Start the jQuery lightBox plugin | |
| - * | |
| - * @param object objClicked The object (link) whick the user h… | |
| - * @param object jQueryMatchedObj The jQuery object with all e… | |
| - */ | |
| - function _start(objClicked,jQueryMatchedObj) { | |
| - // Hime some elements to avoid conflict with overlay i… | |
| - $('embed, object, select').css({ 'visibility' : 'hidde… | |
| - // Call the function to create the markup structure; s… | |
| - _set_interface(); | |
| - // Unset total images in imageArray | |
| - settings.imageArray.length = 0; | |
| - // Unset image active information | |
| - settings.activeImage = 0; | |
| - // We have an image set? Or just an image? Let's see i… | |
| - if ( jQueryMatchedObj.length == 1 ) { | |
| - settings.imageArray.push(new Array(objClicked.… | |
| - } else { | |
| - // Add an Array (as many as we have), with hre… | |
| - for ( var i = 0; i < jQueryMatchedObj.length; … | |
| - settings.imageArray.push(new Array(jQu… | |
| - } | |
| - } | |
| - while ( settings.imageArray[settings.activeImage][0] !… | |
| - settings.activeImage++; | |
| - } | |
| - // Call the function that prepares image exibition | |
| - _set_image_to_view(); | |
| - } | |
| - /** | |
| - * Create the jQuery lightBox plugin interface | |
| - * | |
| - * The HTML markup will be like that: | |
| - <div id="jquery-overlay"></div> | |
| - <div id="jquery-lightbox"> | |
| - <div id="lightbox-container-image-box"> | |
| - <div id="lightbox-container-image"> | |
| - <img src="../fotos/XX.jpg" id=… | |
| - <div id="lightbox-nav"> | |
| - <a href="#" id="lightb… | |
| - <a href="#" id="lightb… | |
| - </div> | |
| - <div id="lightbox-loading"> | |
| - <a href="#" id="lightb… | |
| - <img src="..//… | |
| - </a> | |
| - </div> | |
| - </div> | |
| - </div> | |
| - <div id="lightbox-container-image-data-box"> | |
| - <div id="lightbox-container-image-data… | |
| - <div id="lightbox-image-detail… | |
| - <span id="lightbox-ima… | |
| - <span id="lightbox-ima… | |
| - </div> | |
| - <div id="lightbox-secNav"> | |
| - <a href="#" id="lightb… | |
| - <img src="..//… | |
| - </a> | |
| - </div> | |
| - </div> | |
| - </div> | |
| - </div> | |
| - * | |
| - */ | |
| - function _set_interface() { | |
| - // Apply the HTML markup into body tag | |
| - $('body').append('<div id="jquery-overlay"></div><div … | |
| - // Get page sizes | |
| - var arrPageSizes = ___getPageSize(); | |
| - // Style overlay and show it | |
| - $('#jquery-overlay').css({ | |
| - backgroundColor: settings.overlayBgColo… | |
| - opacity: settings.overl… | |
| - width: arrPageS… | |
| - height: arrPage… | |
| - }).fadeIn(); | |
| - // Get page scroll | |
| - var arrPageScroll = ___getPageScroll(); | |
| - // Calculate top and left offset for the jquery-lightb… | |
| - $('#jquery-lightbox').css({ | |
| - top: arrPageScroll[1] + (arrPageSizes[3… | |
| - left: arrPageScroll[0] | |
| - }).show(); | |
| - // Assigning click events in elements to close overlay | |
| - $('#jquery-overlay,#jquery-lightbox').click(function()… | |
| - _finish(); | |
| - }); | |
| - // Assign the _finish function to lightbox-loading-lin… | |
| - $('#lightbox-loading-link,#lightbox-secNav-btnClose').… | |
| - _finish(); | |
| - return false; | |
| - }); | |
| - // If window was resized, calculate the new overlay di… | |
| - $(window).resize(function() { | |
| - // Get page sizes | |
| - var arrPageSizes = ___getPageSize(); | |
| - // Style overlay and show it | |
| - $('#jquery-overlay').css({ | |
| - width: arrPageSizes[0], | |
| - height: arrPageSizes[1] | |
| - }); | |
| - // Get page scroll | |
| - var arrPageScroll = ___getPageScroll(); | |
| - // Calculate top and left offset for the jquer… | |
| - $('#jquery-lightbox').css({ | |
| - top: arrPageScroll[1] + (arrPag… | |
| - left: arrPageScroll[0] | |
| - }); | |
| - }); | |
| - } | |
| - /** | |
| - * Prepares image exibition; doing a image???s preloader to ca… | |
| - * | |
| - */ | |
| - function _set_image_to_view() { // show the loading | |
| - // Show the loading | |
| - $('#lightbox-loading').show(); | |
| - if ( settings.fixedNavigation ) { | |
| - $('#lightbox-image,#lightbox-container-image-d… | |
| - } else { | |
| - // Hide some elements | |
| - $('#lightbox-image,#lightbox-nav,#lightbox-nav… | |
| - } | |
| - // Image preload process | |
| - var objImagePreloader = new Image(); | |
| - objImagePreloader.onload = function() { | |
| - $('#lightbox-image').attr('src',settings.image… | |
| - // Perfomance an effect in the image container… | |
| - _resize_container_image_box(objImagePreloader.… | |
| - // clear onLoad, IE behaves irratically… | |
| - objImagePreloader.onload=function(){}; | |
| - }; | |
| - objImagePreloader.src = settings.imageArray[settings.a… | |
| - }; | |
| - /** | |
| - * Perfomance an effect in the image container resizing it | |
| - * | |
| - * @param integer intImageWidth The image???s width that will … | |
| - * @param integer intImageHeight The image???s height that wil… | |
| - */ | |
| - function _resize_container_image_box(intImageWidth,intImageHei… | |
| - // Get current width and height | |
| - var intCurrentWidth = $('#lightbox-container-image-box… | |
| - var intCurrentHeight = $('#lightbox-container-image-bo… | |
| - // Get the width and height of the selected image plus… | |
| - var intWidth = (intImageWidth + (settings.containerBor… | |
| - var intHeight = (intImageHeight + (settings.containerB… | |
| - // Diferences | |
| - var intDiffW = intCurrentWidth - intWidth; | |
| - var intDiffH = intCurrentHeight - intHeight; | |
| - // Perfomance the effect | |
| - $('#lightbox-container-image-box').animate({ width: in… | |
| - if ( ( intDiffW == 0 ) && ( intDiffH == 0 ) ) { | |
| - if ( $.browser.msie ) { | |
| - ___pause(250); | |
| - } else { | |
| - ___pause(100); | |
| - } | |
| - } | |
| - $('#lightbox-container-image-data-box').css({ width: i… | |
| - $('#lightbox-nav-btnPrev,#lightbox-nav-btnNext').css({… | |
| - }; | |
| - /** | |
| - * Show the prepared image | |
| - * | |
| - */ | |
| - function _show_image() { | |
| - $('#lightbox-loading').hide(); | |
| - $('#lightbox-image').fadeIn(function() { | |
| - _show_image_data(); | |
| - _set_navigation(); | |
| - }); | |
| - _preload_neighbor_images(); | |
| - }; | |
| - /** | |
| - * Show the image information | |
| - * | |
| - */ | |
| - function _show_image_data() { | |
| - $('#lightbox-container-image-data-box').slideDown('fas… | |
| - $('#lightbox-image-details-caption').hide(); | |
| - if ( settings.imageArray[settings.activeImage][1] ) { | |
| - $('#lightbox-image-details-caption').html(sett… | |
| - } | |
| - // If we have a image set, display 'Image X of X' | |
| - if ( settings.imageArray.length > 1 ) { | |
| - $('#lightbox-image-details-currentNumber').htm… | |
| - } | |
| - } | |
| - /** | |
| - * Display the button navigations | |
| - * | |
| - */ | |
| - function _set_navigation() { | |
| - $('#lightbox-nav').show(); | |
| - | |
| - // Instead to define this configuration in CSS file, w… | |
| - $('#lightbox-nav-btnPrev,#lightbox-nav-btnNext').css({… | |
| - | |
| - // Show the prev button, if not the first image in set | |
| - if ( settings.activeImage != 0 ) { | |
| - if ( settings.fixedNavigation ) { | |
| - $('#lightbox-nav-btnPrev').css({ 'back… | |
| - .unbind() | |
| - .bind('click',function() { | |
| - settings.activeImage =… | |
| - _set_image_to_view(); | |
| - return false; | |
| - }); | |
| - } else { | |
| - // Show the images button for Next but… | |
| - $('#lightbox-nav-btnPrev').unbind().ho… | |
| - $(this).css({ 'background' : '… | |
| - },function() { | |
| - $(this).css({ 'background' : '… | |
| - }).show().bind('click',function() { | |
| - settings.activeImage = setting… | |
| - _set_image_to_view(); | |
| - return false; | |
| - }); | |
| - } | |
| - } | |
| - | |
| - // Show the next button, if not the last image in set | |
| - if ( settings.activeImage != ( settings.imageArray.len… | |
| - if ( settings.fixedNavigation ) { | |
| - $('#lightbox-nav-btnNext').css({ 'back… | |
| - .unbind() | |
| - .bind('click',function() { | |
| - settings.activeImage =… | |
| - _set_image_to_view(); | |
| - return false; | |
| - }); | |
| - } else { | |
| - // Show the images button for Next but… | |
| - $('#lightbox-nav-btnNext').unbind().ho… | |
| - $(this).css({ 'background' : '… | |
| - },function() { | |
| - $(this).css({ 'background' : '… | |
| - }).show().bind('click',function() { | |
| - settings.activeImage = setting… | |
| - _set_image_to_view(); | |
| - return false; | |
| - }); | |
| - } | |
| - } | |
| - // Enable keyboard navigation | |
| - _enable_keyboard_navigation(); | |
| - } | |
| - /** | |
| - * Enable a support to keyboard navigation | |
| - * | |
| - */ | |
| - function _enable_keyboard_navigation() { | |
| - $(document).keydown(function(objEvent) { | |
| - _keyboard_action(objEvent); | |
| - }); | |
| - } | |
| - /** | |
| - * Disable the support to keyboard navigation | |
| - * | |
| - */ | |
| - function _disable_keyboard_navigation() { | |
| - $(document).unbind(); | |
| - } | |
| - /** | |
| - * Perform the keyboard actions | |
| - * | |
| - */ | |
| - function _keyboard_action(objEvent) { | |
| - // To ie | |
| - if ( objEvent == null ) { | |
| - keycode = event.keyCode; | |
| - escapeKey = 27; | |
| - // To Mozilla | |
| - } else { | |
| - keycode = objEvent.keyCode; | |
| - escapeKey = objEvent.DOM_VK_ESCAPE; | |
| - } | |
| - // Get the key in lower case form | |
| - key = String.fromCharCode(keycode).toLowerCase(); | |
| - // Verify the keys to close the ligthBox | |
| - if ( ( key == settings.keyToClose ) || ( key == 'x' ) … | |
| - _finish(); | |
| - } | |
| - // Verify the key to show the previous image | |
| - if ( ( key == settings.keyToPrev ) || ( keycode == 37 … | |
| - // If we???re not showing the first image, cal… | |
| - if ( settings.activeImage != 0 ) { | |
| - settings.activeImage = settings.active… | |
| - _set_image_to_view(); | |
| - _disable_keyboard_navigation(); | |
| - } | |
| - } | |
| - // Verify the key to show the next image | |
| - if ( ( key == settings.keyToNext ) || ( keycode == 39 … | |
| - // If we???re not showing the last image, call… | |
| - if ( settings.activeImage != ( settings.imageA… | |
| - settings.activeImage = settings.active… | |
| - _set_image_to_view(); | |
| - _disable_keyboard_navigation(); | |
| - } | |
| - } | |
| - } | |
| - /** | |
| - * Preload prev and next images being showed | |
| - * | |
| - */ | |
| - function _preload_neighbor_images() { | |
| - if ( (settings.imageArray.length -1) > settings.active… | |
| - objNext = new Image(); | |
| - objNext.src = settings.imageArray[settings.act… | |
| - } | |
| - if ( settings.activeImage > 0 ) { | |
| - objPrev = new Image(); | |
| - objPrev.src = settings.imageArray[settings.act… | |
| - } | |
| - } | |
| - /** | |
| - * Remove jQuery lightBox plugin HTML markup | |
| - * | |
| - */ | |
| - function _finish() { | |
| - $('#jquery-lightbox').remove(); | |
| - $('#jquery-overlay').fadeOut(function() { $('#jquery-o… | |
| - // Show some elements to avoid conflict with overlay i… | |
| - $('embed, object, select').css({ 'visibility' : 'visib… | |
| - } | |
| - /** | |
| - / THIRD FUNCTION | |
| - * getPageSize() by quirksmode.com | |
| - * | |
| - * @return Array Return an array with page width, height and w… | |
| - */ | |
| - function ___getPageSize() { | |
| - var xScroll, yScroll; | |
| - if (window.innerHeight && window.scrollMaxY) { | |
| - xScroll = window.innerWidth + window.scrollMax… | |
| - yScroll = window.innerHeight + window.scrollMa… | |
| - } else if (document.body.scrollHeight > document.body.… | |
| - xScroll = document.body.scrollWidth; | |
| - yScroll = document.body.scrollHeight; | |
| - } else { // Explorer Mac...would also work in Explorer… | |
| - xScroll = document.body.offsetWidth; | |
| - yScroll = document.body.offsetHeight; | |
| - } | |
| - var windowWidth, windowHeight; | |
| - if (self.innerHeight) { // all except Explorer | |
| - if(document.documentElement.clientWidth){ | |
| - windowWidth = document.documentElement… | |
| - } else { | |
| - windowWidth = self.innerWidth; | |
| - } | |
| - windowHeight = self.innerHeight; | |
| - } else if (document.documentElement && document.docume… | |
| - windowWidth = document.documentElement.clientW… | |
| - windowHeight = document.documentElement.client… | |
| - } else if (document.body) { // other Explorers | |
| - windowWidth = document.body.clientWidth; | |
| - windowHeight = document.body.clientHeight; | |
| - } | |
| - // for small pages with total height less then height … | |
| - if(yScroll < windowHeight){ | |
| - pageHeight = windowHeight; | |
| - } else { | |
| - pageHeight = yScroll; | |
| - } | |
| - // for small pages with total width less then width of… | |
| - if(xScroll < windowWidth){ | |
| - pageWidth = xScroll; | |
| - } else { | |
| - pageWidth = windowWidth; | |
| - } | |
| - arrayPageSize = new Array(pageWidth,pageHeight,windowW… | |
| - return arrayPageSize; | |
| - }; | |
| - /** | |
| - / THIRD FUNCTION | |
| - * getPageScroll() by quirksmode.com | |
| - * | |
| - * @return Array Return an array with x,y page scroll values. | |
| - */ | |
| - function ___getPageScroll() { | |
| - var xScroll, yScroll; | |
| - if (self.pageYOffset) { | |
| - yScroll = self.pageYOffset; | |
| - xScroll = self.pageXOffset; | |
| - } else if (document.documentElement && document.docume… | |
| - yScroll = document.documentElement.scrollTop; | |
| - xScroll = document.documentElement.scrollLeft; | |
| - } else if (document.body) {// all other Explorers | |
| - yScroll = document.body.scrollTop; | |
| - xScroll = document.body.scrollLeft; | |
| - } | |
| - arrayPageScroll = new Array(xScroll,yScroll); | |
| - return arrayPageScroll; | |
| - }; | |
| - /** | |
| - * Stop the code execution from a escified time in milisecond | |
| - * | |
| - */ | |
| - function ___pause(ms) { | |
| - var date = new Date(); | |
| - curDate = null; | |
| - do { var curDate = new Date(); } | |
| - while ( curDate - date < ms); | |
| - }; | |
| - // Return the jQuery object for chaining. The unbind method is… | |
| - return this.unbind('click').click(_initialize); | |
| - }; | |
| -})(jQuery); // Call and execute the function immediately passing the jQuery ob… | |
| diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/styleshee… | |
| @@ -7,5 +7,6 @@ | |
| *= require formtastic | |
| *= require formtastic-bootstrap | |
| *= require formtastic-overrides | |
| + *= require bootstrap-lightbox | |
| *= require dataTables/jquery.dataTables.bootstrap | |
| */ | |
| diff --git a/app/assets/stylesheets/bootstrap-lightbox.css b/app/assets/stylesh… | |
| @@ -0,0 +1,65 @@ | |
| +/*!========================================================= | |
| +* bootstrap-lightbox v0.4.1 - 11/20/2012 | |
| +* http://jbutz.github.com/bootstrap-lightbox/ | |
| +* HEAVILY based off bootstrap-modal.js | |
| +* ========================================================== | |
| +* Copyright (c) 2012 Jason Butz (http://jasonbutz.info) | |
| +* | |
| +* Licensed under the Apache License, Version 2.0 (the "License"); | |
| +* you may not use this file except in compliance with the License. | |
| +* You may obtain a copy of the License at | |
| +* | |
| +* http://www.apache.org/licenses/LICENSE-2.0 | |
| +* | |
| +* Unless required by applicable law or agreed to in writing, software | |
| +* distributed under the License is distributed on an "AS IS" BASIS, | |
| +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| +* See the License for the specific language governing permissions and | |
| +* limitations under the License. | |
| +* ========================================================= */ | |
| +.lightbox { | |
| + background-color: transparent; | |
| + text-align: center; | |
| + line-height: 0; | |
| + z-index: 1050; | |
| + position: relative; | |
| + top: 70px; | |
| + outline: none; | |
| +} | |
| +.lightbox .hide { | |
| + display: none; | |
| +} | |
| +.lightbox .in { | |
| + display: block; | |
| +} | |
| +.lightbox-content { | |
| + display: inline-block; | |
| + padding: 10px; | |
| + background-color: #ffffff; | |
| + border: 1px solid #999; | |
| + border: 1px solid rgba(0, 0, 0, 0.3); | |
| + *border: 1px solid #999; | |
| + /* IE6-7 */ | |
| + | |
| + -webkit-border-radius: 6px; | |
| + -moz-border-radius: 6px; | |
| + border-radius: 6px; | |
| + -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); | |
| + -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); | |
| + box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); | |
| + -webkit-background-clip: padding-box; | |
| + -moz-background-clip: padding-box; | |
| + background-clip: padding-box; | |
| +} | |
| +.lightbox-header .close { | |
| + color: white; | |
| + margin-right: -16px; | |
| + margin-top: -16px; | |
| + font-size: 2em; | |
| + opacity: .8; | |
| + filter: alpha(opacity=80); | |
| +} | |
| +.lightbox-header .close :hover { | |
| + opacity: .4; | |
| + filter: alpha(opacity=40); | |
| +} | |
| diff --git a/app/controllers/analyze_controller.rb b/app/controllers/analyze_co… | |
| @@ -30,14 +30,14 @@ class AnalyzeController < ApplicationController | |
| :page => params[:page], | |
| :order => 'number ASC', | |
| :per_page => 10, | |
| - :conditions => [ 'completed = ? and processed = ? and … | |
| + :conditions => [ 'answered = ? and analysis_completed_… | |
| ) | |
| else | |
| @results = Call.where(:job_id => @job_id).paginate( | |
| :page => params[:page], | |
| :order => 'number ASC', | |
| :per_page => 10, | |
| - :conditions => [ 'completed = ? and processed = ? and … | |
| + :conditions => [ 'answered = ? and analysis_completed_… | |
| ) | |
| end | |
| diff --git a/app/controllers/calls_controller.rb b/app/controllers/calls_contro… | |
| @@ -3,11 +3,10 @@ class CallsController < ApplicationController | |
| # GET /calls | |
| # GET /calls.xml | |
| def index | |
| - @jobs = Job.where(:status => 'answered').paginate( | |
| + @jobs = @project.jobs.where('task = ? AND completed_at IS NOT NULL', 'dial… | |
| :page => params[:page], | |
| :order => 'id DESC', | |
| :per_page => 30 | |
| - | |
| ) | |
| respond_to do |format| | |
| @@ -16,62 +15,6 @@ class CallsController < ApplicationController | |
| end | |
| end | |
| - # GET /calls/1/reanalyze | |
| - def reanalyze | |
| - Call.update_all(['processed = ?', false], ['job_id = ?', params[:id]… | |
| - j = Job.find(params[:id]) | |
| - j.processed = false | |
| - j.save | |
| - | |
| - redirect_to :action => 'analyze' | |
| - end | |
| - | |
| - # GET /calls/1/process | |
| - # GET /calls/1/process.xml | |
| - def analyze | |
| - @job_id = params[:id] | |
| - @job = Job.find(@job_id) | |
| - | |
| - if(@job.processed) | |
| - redirect_to :controller => 'analyze', :action => 'view', :id =… | |
| - return | |
| - end | |
| - | |
| - @dial_data_total = Call.count( | |
| - :conditions => [ 'job_id = ? and answered = ?', @job_id, true ] | |
| - ) | |
| - | |
| - @dial_data_done = Call.count( | |
| - :conditions => [ 'job_id = ? and processed = ?', @job_id, true… | |
| - ) | |
| - | |
| - ltypes = Call.find( :all, :select => 'DISTINCT line_type', :conditions… | |
| - res_types = {} | |
| - | |
| - ltypes.each do |k| | |
| - next if not k | |
| - res_types[k.capitalize.to_sym] = Call.count( | |
| - :conditions => ['job_id = ? and line_type = ?', @job_i… | |
| - ) | |
| - end | |
| - | |
| - @lines_by_type = res_types | |
| - | |
| - @dial_data_todo = Call.where(:job_id => @job_id).paginate( | |
| - :page => params[:page], | |
| - :order => 'number ASC', | |
| - :per_page => 50, | |
| - :conditions => [ 'answered = ? and processed = ? and busy = ?'… | |
| - ) | |
| - | |
| - if @dial_data_todo.length > 0 | |
| - res = @job.schedule(:analysis) | |
| - unless res | |
| - flash[:error] = "Unable to launch analysis job" | |
| - end | |
| - end | |
| - end | |
| - | |
| # GET /calls/1/view | |
| # GET /calls/1/view.xml | |
| def view | |
| diff --git a/app/controllers/jobs_controller.rb b/app/controllers/jobs_controll… | |
| @@ -15,6 +15,28 @@ class JobsController < ApplicationController | |
| end | |
| end | |
| + def results | |
| + | |
| + @jobs = @project.jobs.where('task = ? AND completed_at IS NOT NULL', 'dial… | |
| + :page => params[:page], | |
| + :order => 'id DESC', | |
| + :per_page => 30 | |
| + ) | |
| + | |
| + respond_to do |format| | |
| + format.html # index.html.erb | |
| + format.xml { render :xml => @calls } | |
| + end | |
| + end | |
| + | |
| + def view_results | |
| + @job = Job.find(params[:id]) | |
| + @calls = @job.calls.paginate( | |
| + :page => params[:page], | |
| + :order => 'id DESC', | |
| + :per_page => 30 | |
| + ) | |
| + end | |
| def new_dialer | |
| @job = Job.new | |
| @@ -53,55 +75,51 @@ class JobsController < ApplicationController | |
| end | |
| end | |
| - def stop | |
| - @job = Job.find(params[:id]) | |
| - @job.stop | |
| - flash[:notice] = "Job has been cancelled" | |
| - redirect_to :action => 'index' | |
| + def reanalyze_job | |
| + @job = Job.find(params[:id]) | |
| + @new = Job.new({ | |
| + :task => 'analysis', :scope => 'job', :target_id => @job.id, :… | |
| + :project_id => @project.id, :status => 'submitted' | |
| + }) | |
| + respond_to do |format| | |
| + if @new.schedule | |
| + flash[:notice] = 'Analysis job was successfully created.' | |
| + format.html { redirect_to jobs_path } | |
| + format.xml { render :xml => @job, :status => :created } | |
| + else | |
| + flash[:notice] = 'Analysis job could not run: ' + @new.errors.in… | |
| + format.html { redirect_to results_path(@project) } | |
| + format.xml { render :xml => @job.errors, :status => :unprocessable_en… | |
| + end | |
| + end | |
| end | |
| - def create | |
| - | |
| - @job = Job.new(params[:job]) | |
| - | |
| - if(Provider.find_all_by_enabled(true).length == 0) | |
| - @job.errors.add(:base, "No providers have been configured or e… | |
| - respond_to do |format| | |
| - format.html { render :action => "new" } | |
| - format.xml { render :xml => @job.errors, :status => :… | |
| - end | |
| - return | |
| - end | |
| - | |
| - @job.status = 'submitted' | |
| - @job.progress = 0 | |
| - @job.started_at = nil | |
| - @job.completed_at = nil | |
| - @job.range = @job_range.gsub(/[^0-9X:,\n]/m, '') | |
| - @job.cid_mask = @cid_mask.gsub(/[^0-9X]/m, '') if @job.cid_mask !=… | |
| - | |
| - if(@job.range_file.to_s != "") | |
| - @job.range = @job.range_file.read.gsub(/[^0-9X:,\n]/m, '') | |
| - end | |
| - | |
| + 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' | |
| + }) | |
| respond_to do |format| | |
| - if @job.save | |
| - flash[:notice] = 'Job was successfully created.' | |
| - | |
| - res = @job.schedule(:dialer) | |
| - unless res | |
| - flash[:error] = "Unable to launch dialer job" | |
| - end | |
| - | |
| - format.html { redirect_to :action => 'index' } | |
| - format.xml { render :xml => @job, :status => :created, :location => @… | |
| + if @new.schedule | |
| + flash[:notice] = 'Analysis job was successfully created.' | |
| + format.html { redirect_to jobs_path } | |
| + format.xml { render :xml => @job, :status => :created } | |
| else | |
| - format.html { render :action => "new" } | |
| + flash[:notice] = 'Analysis job could not run: ' + @new.errors.in… | |
| + format.html { redirect_to results_path(@project) } | |
| format.xml { render :xml => @job.errors, :status => :unprocessable_en… | |
| end | |
| end | |
| end | |
| + def stop | |
| + @job = Job.find(params[:id]) | |
| + @job.stop | |
| + flash[:notice] = "Job has been cancelled" | |
| + redirect_to :action => 'index' | |
| + end | |
| + | |
| def destroy | |
| @job = Job.find(params[:id]) | |
| @job.destroy | |
| diff --git a/app/models/job.rb b/app/models/job.rb | |
| @@ -23,6 +23,15 @@ class Job < ActiveRecord::Base | |
| record.errors[:lines] << "Lines should… | |
| end | |
| when 'analysis' | |
| + unless ['job', 'project', 'global'].include?(r… | |
| + record.errors[:scope] << "Scope must b… | |
| + end | |
| + if record.scope == "job" and Job.where(:id => … | |
| + record.errors[:job_id] << "The job_id … | |
| + end | |
| + if record.scope == "project" and Job.where(:id… | |
| + record.errors[:project_id] << "The pro… | |
| + end | |
| when 'import' | |
| else | |
| record.errors[:base] << "Invalid task specifie… | |
| @@ -31,22 +40,12 @@ class Job < ActiveRecord::Base | |
| end | |
| - has_many :calls | |
| - belongs_to :project | |
| - validates_with JobValidator | |
| - | |
| - def stop | |
| - self.class.update_all({ :status => 'cancelled'}, { :id => self… | |
| - end | |
| + # XXX: Purging a single job will be slow, but deleting the project is … | |
| + has_many :calls, :dependent => :destroy | |
| - def update_progress(pct) | |
| - if pct >= 100 | |
| - self.class.update_all({ :progress => pct, :completed_a… | |
| - else | |
| - self.class.update_all({ :progress => pct }, { :id => s… | |
| - end | |
| - end | |
| + belongs_to :project | |
| + attr_accessible :task, :status | |
| validates_presence_of :project_id | |
| @@ -62,6 +61,30 @@ class Job < ActiveRecord::Base | |
| attr_accessible :range, :seconds, :lines, :cid_mask | |
| + attr_accessor :scope | |
| + attr_accessor :force | |
| + attr_accessor :target_id | |
| + | |
| + attr_accessible :scope, :force, :target_id | |
| + | |
| + | |
| + validates_with JobValidator | |
| + | |
| + def stop | |
| + self.class.update_all({ :status => 'cancelled'}, { :id => self… | |
| + end | |
| + | |
| + def update_progress(pct) | |
| + if pct >= 100 | |
| + self.class.update_all({ :progress => pct, :completed_a… | |
| + else | |
| + self.class.update_all({ :progress => pct }, { :id => s… | |
| + end | |
| + end | |
| + | |
| + def details | |
| + Marshal.load(self.args) rescue {} | |
| + end | |
| def schedule | |
| case task | |
| @@ -75,7 +98,13 @@ class Job < ActiveRecord::Base | |
| }) | |
| return self.save | |
| when 'analysis' | |
| - # | |
| + self.status = 'submitted' | |
| + self.args = Marshal.dump({ | |
| + :scope => self.scope, # job / pr… | |
| + :force => !!(self.force), # true / f… | |
| + :target_id => self.target_id.to_i # job_id o… | |
| + }) | |
| + return self.save | |
| else | |
| raise ::RuntimeError, "Unsupported Job type" | |
| end | |
| diff --git a/app/views/analyze/view.html.erb b/app/views/analyze/view.html.erb | |
| @@ -10,7 +10,7 @@ | |
| </tr> | |
| </table> | |
| -<%= raw(will_paginate @results) %> | |
| +<%= will_paginate @results, :renderer => BootstrapPagination::Rails %> | |
| <table class='table table-striped table-bordered' width='90%'> | |
| <thead> | |
| @@ -27,34 +27,27 @@ | |
| <object | |
| type="application/x-shockwave-flash" | |
| - data="/assets/musicplayer.swf?song_url=<%=resource_ana… | |
| + data="/assets/musicplayer.swf?song_url=<%=resource_ana… | |
| width="20" | |
| height="17" | |
| style="margin-bottom: -5px;" | |
| > | |
| - <param name="movie" value="/assets/musicplayer.swf?son… | |
| + <param name="movie" value="/assets/musicplayer.swf?son… | |
| <param name="wmode" value="transparent"></param> | |
| </object> | |
| <b><%= call.number %></b> | |
| <hr width='100%' size='1'/> | |
| - CallerID: <%= call.cid%><br/> | |
| + CallerID: <%= call.caller_id%><br/> | |
| Provider: <%=h call.provider.name %><br/> | |
| - Audio: <%=h call.seconds %> Seconds<br/> | |
| - Ringer: <%=h call.ringtime %> Seconds<br/> | |
| + Audio: <%=h call.audio_length %> Seconds<br/> | |
| + Ringer: <%=h call.ring_length %> Seconds<br/> | |
| </td> | |
| <td align='center'> | |
| <b><%=h call.line_type.upcase %></b><br/> | |
| - <a href="<%=resource_analyze_path(@job_id, call.id, "big_sig_d… | |
| - <a href="<%=resource_analyze_path(@job_id, call.id, "big_freq"… | |
| - <% (call.signatures||"").split("\n").each do |s| | |
| - sid,mat,name = s.split(':', 3) | |
| - str = [mat.to_i * 6.4, 255].min | |
| - col = ("%.2x" % (255 - str)) * 3 | |
| - %> | |
| - <div style="color: #<%= col%>;"><%=h name%> (<%=h sid … | |
| - <% end %> | |
| + <%= render :partial => 'shared/lightbox_sig', :locals => { :ca… | |
| + <%= render :partial => 'shared/lightbox_freq', :locals => { :c… | |
| <% if call.fprint and call.fprint.length > 0 %> | |
| - <a href="<%=view_matches_path(call.id)%>">View Matches… | |
| + <a href="<%=view_matches_path(@project, call.id)%>">Vi… | |
| <% end %> | |
| </td> | |
| </tr> | |
| @@ -62,10 +55,4 @@ | |
| </tbody> | |
| </table> | |
| -<%= raw(will_paginate @results) %> | |
| - | |
| -<script type="text/javascript"> | |
| -$(function() { | |
| - $('a.lightbox').lightBox(); | |
| -}); | |
| -</script> | |
| +<%= will_paginate @results, :renderer => BootstrapPagination::Rails %> | |
| diff --git a/app/views/calls/index.html.erb b/app/views/calls/index.html.erb | |
| @@ -1,7 +1,7 @@ | |
| <% if @jobs.length > 0 %> | |
| <h1 class='title'>Completed Jobs</h1> | |
| -<%= raw(will_paginate @jobs) %> | |
| +<%= will_paginate @jobs, :renderer => BootstrapPagination::Rails %> | |
| <table class='table table-striped table-bordered' width='90%'> | |
| <thead> | |
| <tr> | |
| @@ -15,22 +15,22 @@ | |
| </thead> | |
| <tbody> | |
| -<% @jobs.sort{|a,b| b.id <=> a.id}.each do |job| %> | |
| +<% @jobs.each do |job| %> | |
| <tr> | |
| - <td><%=h job.id %></td> | |
| - <td><%=h job.range %></td> | |
| - <td><%=h job.cid_mask %></td> | |
| - <td><%=h ( | |
| - Call.count(:conditions => ['job_id = ? and processed = ?', job… | |
| + <td><%= job.id %></td> | |
| + <td><%= job.range %></td> | |
| + <td><%= job.cid_mask %></td> | |
| + <td><%= ( | |
| + job.calls.where("analysis_completed_at IS NOT NULL").count.to_… | |
| "/" + | |
| - Call.count(:conditions => ['job_id = ?', job.id]).to_s | |
| + job.calls.count.to_s | |
| )%></td> | |
| - <td><%=h job.started_at.localtime.strftime("%Y-%m-%d %H:%M:%S") %></td> | |
| + <td><%= job.started_at.localtime.strftime("%Y-%m-%d %H:%M:%S") %></td> | |
| <td> | |
| <a class="btn btn-mini" href="<%= view_call_path(@project,job) %>" rel… | |
| - <% if(job.analysis_completed_at) %> | |
| + <% if job.calls.where("analysis_completed_at IS NOT NULL").cou… | |
| <a class="btn btn-mini" href="<%= analyze_call_path(@p… | |
| <a class="btn btn-mini" href="<%= reanalyze_call_path(… | |
| <% else %> | |
| @@ -45,7 +45,7 @@ | |
| </tbody> | |
| </table> | |
| -<%= raw(will_paginate @jobs) %> | |
| +<%= will_paginate @jobs, :renderer => BootstrapPagination::Rails %> | |
| <% else %> | |
| diff --git a/app/views/jobs/index.html.erb b/app/views/jobs/index.html.erb | |
| @@ -1,4 +1,4 @@ | |
| -<% if(@submitted_jobs.length > 0) %> | |
| +<% if @submitted_jobs.length > 0 %> | |
| <h1 class='title'>Submitted Jobs</h1> | |
| diff --git a/app/views/jobs/results.html.erb b/app/views/jobs/results.html.erb | |
| @@ -0,0 +1,64 @@ | |
| +<% if @jobs.length > 0 %> | |
| +<h1 class='title'>Completed Jobs</h1> | |
| + | |
| +<%= will_paginate @jobs, :renderer => BootstrapPagination::Rails %> | |
| +<table class='table table-striped table-bordered' width='90%'> | |
| + <thead> | |
| + <tr> | |
| + <th>ID</th> | |
| + <th>Range</th> | |
| + <th>CallerID</th> | |
| + <th>Connected</th> | |
| + <th>Date</th> | |
| + <th>User</th> | |
| + <th>Actions</th> | |
| + </tr> | |
| + </thead> | |
| + <tbody> | |
| + | |
| +<% @jobs.each do |job| | |
| + | |
| + cnt_dialed = job.calls.count.to_i | |
| + cnt_answered = job.calls.where("answered = ? and busy = ?", true, fals… | |
| + pct_answered = 0 | |
| + unless cnt_dialed == 0 | |
| + pct_answered = ((cnt_answered.to_f / cnt_dialed.to_f) * 100).t… | |
| + end | |
| +%> | |
| + <tr> | |
| + <td><%= job.id %></td> | |
| + <td><%= job.details[:range].to_s %></td> | |
| + <td><%= job.details[:cid_mask].to_s %></td> | |
| + <td><span rel="tooltip" title="<%= pct_answered %>% answered"><%= cnt_answ… | |
| + | |
| + | |
| + <td><%= job.created_at.strftime("%Y-%m-%d %H:%M:%s") %></td> | |
| + <td><%= job.created_by %></td> | |
| + <td> | |
| + <a class="btn btn-mini" href="<%= view_results_path(@project,job) %>" … | |
| + | |
| + <% if job.calls.where("analysis_completed_at IS NOT NULL").cou… | |
| + <a class="btn btn-mini" href="<%= view_analyze_path(@p… | |
| + <a class="btn btn-mini" href="<%= reanalyze_job_path(@… | |
| + <% else %> | |
| + <a class="btn btn-mini" href="<%= analyze_job_path(@pr… | |
| + <% end %> | |
| + | |
| + <a class="btn btn-mini" href="<%= job_path(job) %>" data-confirm="… | |
| + </td> | |
| + </tr> | |
| + | |
| +<% end %> | |
| +</tbody> | |
| +</table> | |
| + | |
| +<%= will_paginate @jobs, :renderer => BootstrapPagination::Rails %> | |
| + | |
| +<% else %> | |
| + | |
| +<h1 class='title'>No Completed Jobs</h1> | |
| +<br/> | |
| + | |
| +<% end %> | |
| + | |
| +<a class="btn" href="<%= new_dialer_job_path %>"><i class="icon-plus"></i> Sta… | |
| diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/applica… | |
| @@ -23,6 +23,7 @@ | |
| <%= javascript_tag do %> | |
| $(document).ready(function() { | |
| $("a").tooltip(); | |
| + $("span").tooltip(); | |
| }); | |
| <% end %> | |
| </head> | |
| @@ -39,7 +40,7 @@ | |
| h(truncate(@project.na… | |
| ' <i class="icon-chevr… | |
| </li> | |
| - <%= menu_item "Results", calls_path(@project) … | |
| + <%= menu_item "Results", results_path(@project… | |
| <%= menu_item "Analysis", analyze_path(@projec… | |
| <% end %> | |
| diff --git a/app/views/shared/_lightbox_freq.html.erb b/app/views/shared/_light… | |
| @@ -0,0 +1,13 @@ | |
| +<% | |
| + lid = "call_#{call.id}_freq" | |
| +%> | |
| +<div id="<%= lid %>" class="lightbox hide fade" tabindex="-1" role="dialog" ar… | |
| + <div class='lightbox-header'> | |
| + <button type="button" class="close" data-dismiss="lightbox" ar… | |
| + </div> | |
| + <div class='lightbox-content'> | |
| + <img src="<%=resource_analyze_path(call.id, "big_freq")%>"> | |
| + </div> | |
| +</div> | |
| + | |
| +<a data-toggle="lightbox" href="#<%= lid %>"><img src="<%=resource_analyze_pat… | |
| diff --git a/app/views/shared/_lightbox_sig.html.erb b/app/views/shared/_lightb… | |
| @@ -0,0 +1,13 @@ | |
| +<% | |
| + lid = "call_#{call.id}_sig" | |
| +%> | |
| +<div id="<%= lid %>" class="lightbox hide fade" tabindex="-1" role="dialog" ar… | |
| + <div class='lightbox-header'> | |
| + <button type="button" class="close" data-dismiss="lightbox" ar… | |
| + </div> | |
| + <div class='lightbox-content'> | |
| + <img src="<%=resource_analyze_path(call.id, "big_sig_dots")%>"> | |
| + </div> | |
| +</div> | |
| + | |
| +<a data-toggle="lightbox" href="#<%= lid %>"><img src="<%=resource_analyze_pat… | |
| diff --git a/app/views/shared/lightbox_sig.html.erb b/app/views/shared/lightbox… | |
| @@ -0,0 +1,17 @@ | |
| +<% | |
| + lid = "call_#{call.id}_sig" | |
| +%> | |
| +<a href="<%=resource_analyze_path(call.id, "big_sig_dots")%>" class="lightbox"> | |
| +<img src="<%=resource_analyze_path(call.id, "small_sig")%>" /> | |
| +</a> | |
| + | |
| +<div id="<%= lid %>" class="lightbox hide fade" tabindex="-1" role="dialog" ar… | |
| + <div class='lightbox-header'> | |
| + <button type="button" class="close" data-dismiss="lightbox" ar… | |
| + </div> | |
| + <div class='lightbox-content'> | |
| + <img src="<%=resource_analyze_path(call.id, "big_sig_dots")%>"> | |
| + </div> | |
| +</div> | |
| + | |
| +<a data-toggle="lightbox" href="#<%= lid %>"><img src="<%=resource_analyze_pat… | |
| diff --git a/bin/analyze_result.rb b/bin/analyze_result.rb | |
| @@ -20,12 +20,16 @@ num = ARGV.shift || exit(0) | |
| $0 = "warvox(analyzer): #{inp} #{num}" | |
| +begin | |
| + | |
| $stdout.write( | |
| Marshal.dump( | |
| - WarVOX::Jobs::CallAnalysis.new( | |
| - 0 | |
| - ).analyze_call( | |
| + WarVOX::Jobs::Analysis.analyze_call( | |
| inp, num | |
| ) | |
| ) | |
| ) | |
| + | |
| +rescue ::Errno::EPIPE | |
| + # Hide pipe errors (parent is killed when task was cancelled) | |
| +end | |
| diff --git a/config/routes.rb b/config/routes.rb | |
| @@ -18,26 +18,26 @@ Web::Application.routes.draw do | |
| match '/jobs/analyzer' => 'jobs#analyzer', :as => :analyzer_job | |
| match '/jobs/:id/stop' => 'jobs#stop', :as => :stop_job | |
| + match '/projects/:project_id/results' => 'jobs#results', :as => :r… | |
| + match '/projects/:project_id/results/:id' => 'jobs#view_results', :as … | |
| + match '/projects/:project_id/results/:id/analyze' => 'jobs#analyze_job', :… | |
| + match '/projects/:project_id/results/:id/reanalyze' => 'jobs#reanalyze_job… | |
| + | |
| + | |
| - match '/projects/:project_id/calls/' => 'calls#index', :as =>… | |
| - match '/projects/:project_id/calls/:id/view' => 'calls#view', :as => … | |
| - match '/projects/:project_id/calls/:id/analyze' => 'calls#analyze', :as … | |
| - match '/projects/:project_id/calls/:id/reanalyze' => 'calls#reanalyze', :a… | |
| - match '/projects/:project_id/calls/:id/purge' => 'calls#purge', :as =>… | |
| - delete '/projects/:project_id/calls/:id' => 'calls#destroy' | |
| match '/projects/:project_id/analyze' => 'analyze#index', :as =>… | |
| - match '/projects/:project_id/analyze/:id/resource/:result_id/:type' => 'anal… | |
| + match '/calls/:result_id/:type' => 'analyze#resource', :as… | |
| match '/projects/:project_id/analyze/:id/view' => 'analyze#view', :as => … | |
| - match '/projects/:project_id/analyze/:call_id/matches' => 'analyze#view_m… | |
| - match '/projects/:project_id/analyze/:id/show' => 'analyze#show', :as => … | |
| + match '/projects/:project_id/analyze/:call_id/matches' => 'analyze#view_m… | |
| resources :settings | |
| resources :providers | |
| resources :users | |
| resources :projects | |
| resources :jobs | |
| + resources :calls | |
| match '/about' => 'home#about', :as => :about | |
| match '/help' => 'home#help', :as => :help | |
| diff --git a/lib/warvox/jobs/analysis.rb b/lib/warvox/jobs/analysis.rb | |
| @@ -65,15 +65,15 @@ class Analysis < Base | |
| case @conf[:scope] | |
| when 'job' | |
| if @conf[:force] | |
| - query = {:job_id => job.id, :answered => true,… | |
| + query = {:job_id => @conf[:target_id], :answer… | |
| else | |
| - query = {:job_id => job.id, :answered => true,… | |
| + query = {:job_id => @conf[:target_id], :answer… | |
| end | |
| when 'project' | |
| if @conf[:force] | |
| - query = {:project_id => job.project_id, :answe… | |
| + query = {:project_id => @conf[:target_id], :an… | |
| else | |
| - query = {:project_id => job.project_id, :answe… | |
| + query = {:project_id => @conf[:target_id], :an… | |
| end | |
| when 'global' | |
| if @conf[:force] | |
| @@ -89,23 +89,29 @@ class Analysis < Base | |
| @total_calls = Call.count(:conditions => query) | |
| @completed_calls = 0 | |
| + WarVOX::Log.debug("Conditions are #{query.inspect}") | |
| + | |
| Call.find_each(:conditions => query) do |call| | |
| - while @tasks.length < max_threads | |
| - call.analysis_started_at = Time.now.utc | |
| - call.analysis_job_id = job.id | |
| - @tasks << Thread.new(call) { |c| ::ActiveRecor… | |
| - end | |
| - clear_stale_tasks | |
| + if @tasks.length < max_threads | |
| + WarVOX::Log.debug("Spawning job for Call #{cal… | |
| + @tasks << Thread.new(call.id, job.id) { |c,j| … | |
| + else | |
| + clear_stale_tasks | |
| - # Update progress every 10 seconds or so | |
| - if Time.now.to_f - last_update.to_f > 10 | |
| - update_progress((@completed_calls / @total_cal… | |
| - last_update = Time.now | |
| - end | |
| + # Update progress every 10 seconds or so | |
| + if Time.now.to_f - last_update.to_f > 10 | |
| + update_progress((@completed_calls / @t… | |
| + last_update = Time.now | |
| + end | |
| - clear_zombies() | |
| + clear_zombies() | |
| + end | |
| end | |
| + @tasks.map {|t| t.join } | |
| + clear_stale_tasks | |
| + clear_zombies | |
| + | |
| } | |
| end | |
| @@ -121,8 +127,14 @@ class Analysis < Base | |
| } | |
| end | |
| - def run_analyze_call(dr) | |
| - $stderr.puts "DEBUG: Processing audio for #{dr.number}..." | |
| + def run_analyze_call(cid, jid) | |
| + | |
| + dr = Call.find(cid) | |
| + dr.analysis_started_at = Time.now.utc | |
| + dr.analysis_job_id = jid | |
| + dr.save | |
| + | |
| + WarVOX::Log.debug("Worker processing audio for #{dr.number}...… | |
| bin = File.join(WarVOX::Base, 'bin', 'analyze_result.rb') | |
| tmp = Tempfile.new("Analysis") | |
| @@ -163,7 +175,7 @@ class Analysis < Base | |
| end | |
| # Takes the raw file path as an argument, returns a hash | |
| - def analyze_call(input, num=nil) | |
| + def self.analyze_call(input, num=nil) | |
| return if not input | |
| return if not File.exist?(input) | |
| diff --git a/lib/warvox/jobs/base.rb b/lib/warvox/jobs/base.rb | |
| @@ -22,6 +22,10 @@ class Base | |
| end | |
| def clear_zombies | |
| + self.class.clear_zombies | |
| + end | |
| + | |
| + def self.clear_zombies | |
| begin | |
| # Clear zombies just in case... | |
| while(Process.waitpid(-1, Process::WNOHANG)) |