Swap lightbox, working results pages - warvox - VoIP based wardialing tool, forked from rapid7/warvox.
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
---
(DIR) commit 9974997874fab6f83540def599d2031acacbf5f1
(DIR) parent 70379df629033bdd61080eba61894c2e9b745c62
(HTM) Author: HD Moore <hd_moore@rapid7.com>
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(-)
---
(DIR) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
@@ -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
(DIR) diff --git a/app/assets/javascripts/bootstrap-lightbox.js b/app/assets/javascripts/bootstrap-lightbox.js
@@ -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.lightbox', $.proxy(this.hide, this));
+
+ this.options.remote && this.$element.find('.lightbox-body').load(this.options.remote);
+
+ 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.$element.hasClass('fade');
+
+ if (!that.$element.parent().length)
+ {
+ that.$element.appendTo(document.body); //don't move modals dom position
+ }
+
+ that.$element
+ .show();
+
+ if (transition)
+ {
+ that.$element[0].offsetWidth; // force reflow
+ }
+
+ that.$element
+ .addClass('in')
+ .attr('aria-hidden', false);
+
+ that.enforceFocus();
+
+ transition ?
+ that.$element.one($.support.transition.end, function () { that.centerImage(); that.$element.focus().trigger('shown'); }) :
+ (function(){ that.centerImage(); that.$element.focus().trigger('shown'); })()
+
+ });
+ },
+ 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.$element.has(e.target).length)
+ {
+ that.$element.focus();
+ }
+ });
+ },
+ escape: function ()
+ {
+ var that = this;
+ if (this.isShown && this.options.keyboard)
+ {
+ this.$element.on('keyup.dismiss.lightbox', function ( e )
+ {
+ 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 && animate;
+
+ this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
+ .appendTo(document.body);
+
+ this.$backdrop.click(
+ this.options.backdrop == 'static' ?
+ $.proxy(this.$element[0].focus, this.$element[0]) :
+ $.proxy(this.hide, this)
+ );
+
+ if (doAnimate) this.$backdrop[0].offsetWidth; // force reflow
+
+ this.$backdrop.addClass('in');
+
+ doAnimate ?
+ this.$backdrop.one($.support.transition.end, callback) :
+ callback();
+
+ }
+ else if (!this.isShown && this.$backdrop)
+ {
+ this.$backdrop.removeClass('in');
+
+ $.support.transition && this.$element.hasClass('fade')?
+ this.$backdrop.one($.support.transition.end, $.proxy(this.removeBackdrop, this)) :
+ 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').find('img:first');
+ // Save original filesize
+ if(!$img.data('osizew')) $img.data('osizew', $img.width());
+ if(!$img.data('osizeh')) $img.data('osizeh', $img.height());
+
+ 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').length > 0) sOffs += 10;
+ $img.css('max-width', $(window).width() - sOffs);
+ $img.css('max-height', $(window).height() - sOffs);
+
+ that.w = $img.width();
+ that.h = $img.height();
+ }
+
+ that.$element.css({
+ "position": "fixed",
+ "left": ( $(window).width() / 2 ) - ( that.w / 2 ),
+ "top": ( $(window).height() / 2 ) - ( that.h / 2 ) - resizedOffs
+ });
+ that.enforceFocus();
+ },
+ cloneSize: function() // The cloneSize function is only run once, but it helps keep image jumping down
+ {
+ var that = this;
+ // Clone the element and append it to the body
+ // this allows us to get an idea for the size of the lightbox
+ 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 / 2 ),
+ "top": ( $(window).height() / 2 ) - ( that.h / 2 )
+ });
+ }
+ }
+
+
+/* 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, $this.data(), typeof option == 'object' && option);
+ if (!data) $this.data('lightbox', (data = new Lightbox(this, options)));
+
+ 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"]', function (e)
+ {
+ var $this = $(this);
+ var href = $this.attr('href');
+ var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))); //strip for ie7
+ var option = $target.data('lightbox') ? 'toggle' : $.extend({ remote:!/#/.test(href) && href }, $target.data(), $this.data());
+ var img = $this.attr('data-image') || false;
+ var $imgElem;
+
+ e.preventDefault();
+
+ if(img)
+ {
+ $target.data('original-content', $target.find('.lightbox-content').html());
+ $target.find('.lightbox-content').html('<img border="0" src="'+img+'" />');
+ }
+
+ $target
+ .lightbox(option)
+ .one('hide', function ()
+ {
+ $this.focus()
+ })
+ .one('hidden',function ()
+ {
+ if( img )
+ {
+ $target.find('.lightbox-content').html( $target.data('original-content') );
+ img = undefined;
+ }
+ });
+ })
+
+}(window.jQuery);
+\ No newline at end of file
(DIR) diff --git a/app/assets/javascripts/jquery.lightbox-0.5.js b/app/assets/javascripts/jquery.lightbox-0.5.js
@@ -1,472 +0,0 @@
-/**
- * jQuery lightBox plugin
- * This jQuery plugin was inspired and based on Lightbox 2 by Lokesh Dhakar (http://www.huddletogether.com/projects/lightbox2/)
- * 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/licenses/by-sa/2.5/br/deed.en_US
- * @example Visit http://leandrovieira.com/projects/jquery/lightbox/ for more informations about this jQuery plugin
- */
-
-// Offering a Custom Alias suport - More info: http://docs.jquery.com/Plugins/Authoring#Custom_Alias
-(function($) {
- /**
- * $ is an alias to jQuery object
- *
- */
- $.fn.lightBox = function(settings) {
- // Settings to configure the jQuery lightBox plugin how you like
- settings = jQuery.extend({
- // Configuration related to overlay
- overlayBgColor: '#000', // (string) Background color to overlay; inform a hexadecimal value like: #RRGGBB. Where RR, GG, and BB are the hexadecimal values for the red, green, and blue values of the color.
- overlayOpacity: 0.8, // (integer) Opacity value to overlay; inform: 0.X. Where X are number from 0 to 9
- // Configuration related to navigation
- fixedNavigation: false, // (boolean) Boolean that informs if the navigation (next and prev button) will be fixed or not in the interface.
- // Configuration related to images
- imageLoading: '/assets/lightbox-ico-loading.gif', // (string) Path and the name of the loading icon
- imageBtnPrev: '/assets/lightbox-btn-prev.gif', // (string) Path and the name of the prev button image
- imageBtnNext: '/assets/lightbox-btn-next.gif', // (string) Path and the name of the next button image
- imageBtnClose: '/assets/lightbox-btn-close.gif', // (string) Path and the name of the close btn
- imageBlank: '/assets/lightbox-blank.gif', // (string) Path and the name of a blank image (one pixel)
- // Configuration related to container image box
- containerBorderSize: 10, // (integer) If you adjust the padding in the CSS for the container, #lightbox-container-image-box, you will need to update this value
- containerResizeSpeed: 400, // (integer) Specify the resize duration of container image. These number are miliseconds. 400 is default.
- // Configuration related to texts in caption. For example: Image 2 of 8. You can alter either "Image" and "of" texts.
- txtImage: 'Image', // (string) Specify text "Image"
- txtOf: 'of', // (string) Specify text "of"
- // Configuration related to keyboard navigation
- keyToClose: 'c', // (string) (c = close) Letter to close the jQuery lightBox interface. Beyond this letter, the letter X and the SCAPE key is used to.
- keyToPrev: 'p', // (string) (p = previous) Letter to show the previous image
- keyToNext: 'n', // (string) (n = next) Letter to show the next image.
- // 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 to jQuery object
- /**
- * Initializing the plugin calling the start function
- *
- * @return boolean false
- */
- function _initialize() {
- _start(this,jQueryMatchedObj); // This, in this context, refer to object (link) which the user have clicked
- return false; // Avoid the browser following the link
- }
- /**
- * Start the jQuery lightBox plugin
- *
- * @param object objClicked The object (link) whick the user have clicked
- * @param object jQueryMatchedObj The jQuery object with all elements matched
- */
- function _start(objClicked,jQueryMatchedObj) {
- // Hime some elements to avoid conflict with overlay in IE. These elements appear above the overlay.
- $('embed, object, select').css({ 'visibility' : 'hidden' });
- // Call the function to create the markup structure; style some elements; assign events in some elements.
- _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 it.
- if ( jQueryMatchedObj.length == 1 ) {
- settings.imageArray.push(new Array(objClicked.getAttribute('href'),objClicked.getAttribute('title')));
- } else {
- // Add an Array (as many as we have), with href and title atributes, inside the Array that storage the images references
- for ( var i = 0; i < jQueryMatchedObj.length; i++ ) {
- settings.imageArray.push(new Array(jQueryMatchedObj[i].getAttribute('href'),jQueryMatchedObj[i].getAttribute('title')));
- }
- }
- while ( settings.imageArray[settings.activeImage][0] != objClicked.getAttribute('href') ) {
- 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="lightbox-image">
- <div id="lightbox-nav">
- <a href="#" id="lightbox-nav-btnPrev"></a>
- <a href="#" id="lightbox-nav-btnNext"></a>
- </div>
- <div id="lightbox-loading">
- <a href="#" id="lightbox-loading-link">
- <img src="..//assets/lightbox-ico-loading.gif">
- </a>
- </div>
- </div>
- </div>
- <div id="lightbox-container-image-data-box">
- <div id="lightbox-container-image-data">
- <div id="lightbox-image-details">
- <span id="lightbox-image-details-caption"></span>
- <span id="lightbox-image-details-currentNumber"></span>
- </div>
- <div id="lightbox-secNav">
- <a href="#" id="lightbox-secNav-btnClose">
- <img src="..//assets/lightbox-btn-close.gif">
- </a>
- </div>
- </div>
- </div>
- </div>
- *
- */
- function _set_interface() {
- // Apply the HTML markup into body tag
- $('body').append('<div id="jquery-overlay"></div><div id="jquery-lightbox"><div id="lightbox-container-image-box"><div id="lightbox-container-image"><img id="lightbox-image"><div style="" id="lightbox-nav"><a href="#" id="lightbox-nav-btnPrev"></a><a href="#" id="lightbox-nav-btnNext"></a></div><div id="lightbox-loading"><a href="#" id="lightbox-loading-link"><img src="' + settings.imageLoading + '"></a></div></div></div><div id="lightbox-container-image-data-box"><div id="lightbox-container-image-data"><div id="lightbox-image-details"><span id="lightbox-image-details-caption"></span><span id="lightbox-image-details-currentNumber"></span></div><div id="lightbox-secNav"><a href="#" id="lightbox-secNav-btnClose"><img src="' + settings.imageBtnClose + '"></a></div></div></div></div>');
- // Get page sizes
- var arrPageSizes = ___getPageSize();
- // Style overlay and show it
- $('#jquery-overlay').css({
- backgroundColor: settings.overlayBgColor,
- opacity: settings.overlayOpacity,
- width: arrPageSizes[0],
- height: arrPageSizes[1]
- }).fadeIn();
- // Get page scroll
- var arrPageScroll = ___getPageScroll();
- // Calculate top and left offset for the jquery-lightbox div object and show it
- $('#jquery-lightbox').css({
- top: arrPageScroll[1] + (arrPageSizes[3] / 10),
- 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-link and lightbox-secNav-btnClose objects
- $('#lightbox-loading-link,#lightbox-secNav-btnClose').click(function() {
- _finish();
- return false;
- });
- // If window was resized, calculate the new overlay dimensions
- $(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 jquery-lightbox div object and show it
- $('#jquery-lightbox').css({
- top: arrPageScroll[1] + (arrPageSizes[3] / 10),
- left: arrPageScroll[0]
- });
- });
- }
- /**
- * Prepares image exibition; doing a image???s preloader to calculate it???s size
- *
- */
- function _set_image_to_view() { // show the loading
- // Show the loading
- $('#lightbox-loading').show();
- if ( settings.fixedNavigation ) {
- $('#lightbox-image,#lightbox-container-image-data-box,#lightbox-image-details-currentNumber').hide();
- } else {
- // Hide some elements
- $('#lightbox-image,#lightbox-nav,#lightbox-nav-btnPrev,#lightbox-nav-btnNext,#lightbox-container-image-data-box,#lightbox-image-details-currentNumber').hide();
- }
- // Image preload process
- var objImagePreloader = new Image();
- objImagePreloader.onload = function() {
- $('#lightbox-image').attr('src',settings.imageArray[settings.activeImage][0]);
- // Perfomance an effect in the image container resizing it
- _resize_container_image_box(objImagePreloader.width,objImagePreloader.height);
- // clear onLoad, IE behaves irratically with animated gifs otherwise
- objImagePreloader.onload=function(){};
- };
- objImagePreloader.src = settings.imageArray[settings.activeImage][0];
- };
- /**
- * Perfomance an effect in the image container resizing it
- *
- * @param integer intImageWidth The image???s width that will be showed
- * @param integer intImageHeight The image???s height that will be showed
- */
- function _resize_container_image_box(intImageWidth,intImageHeight) {
- // Get current width and height
- var intCurrentWidth = $('#lightbox-container-image-box').width();
- var intCurrentHeight = $('#lightbox-container-image-box').height();
- // Get the width and height of the selected image plus the padding
- var intWidth = (intImageWidth + (settings.containerBorderSize * 2)); // Plus the image???s width and the left and right padding value
- var intHeight = (intImageHeight + (settings.containerBorderSize * 2)); // Plus the image???s height and the left and right padding value
- // Diferences
- var intDiffW = intCurrentWidth - intWidth;
- var intDiffH = intCurrentHeight - intHeight;
- // Perfomance the effect
- $('#lightbox-container-image-box').animate({ width: intWidth, height: intHeight },settings.containerResizeSpeed,function() { _show_image(); });
- if ( ( intDiffW == 0 ) && ( intDiffH == 0 ) ) {
- if ( $.browser.msie ) {
- ___pause(250);
- } else {
- ___pause(100);
- }
- }
- $('#lightbox-container-image-data-box').css({ width: intImageWidth });
- $('#lightbox-nav-btnPrev,#lightbox-nav-btnNext').css({ height: intImageHeight + (settings.containerBorderSize * 2) });
- };
- /**
- * 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('fast');
- $('#lightbox-image-details-caption').hide();
- if ( settings.imageArray[settings.activeImage][1] ) {
- $('#lightbox-image-details-caption').html(settings.imageArray[settings.activeImage][1]).show();
- }
- // If we have a image set, display 'Image X of X'
- if ( settings.imageArray.length > 1 ) {
- $('#lightbox-image-details-currentNumber').html(settings.txtImage + ' ' + ( settings.activeImage + 1 ) + ' ' + settings.txtOf + ' ' + settings.imageArray.length).show();
- }
- }
- /**
- * Display the button navigations
- *
- */
- function _set_navigation() {
- $('#lightbox-nav').show();
-
- // Instead to define this configuration in CSS file, we define here. And it???s need to IE. Just.
- $('#lightbox-nav-btnPrev,#lightbox-nav-btnNext').css({ 'background' : 'transparent url(' + settings.imageBlank + ') no-repeat' });
-
- // Show the prev button, if not the first image in set
- if ( settings.activeImage != 0 ) {
- if ( settings.fixedNavigation ) {
- $('#lightbox-nav-btnPrev').css({ 'background' : 'url(' + settings.imageBtnPrev + ') left 15% no-repeat' })
- .unbind()
- .bind('click',function() {
- settings.activeImage = settings.activeImage - 1;
- _set_image_to_view();
- return false;
- });
- } else {
- // Show the images button for Next buttons
- $('#lightbox-nav-btnPrev').unbind().hover(function() {
- $(this).css({ 'background' : 'url(' + settings.imageBtnPrev + ') left 15% no-repeat' });
- },function() {
- $(this).css({ 'background' : 'transparent url(' + settings.imageBlank + ') no-repeat' });
- }).show().bind('click',function() {
- settings.activeImage = settings.activeImage - 1;
- _set_image_to_view();
- return false;
- });
- }
- }
-
- // Show the next button, if not the last image in set
- if ( settings.activeImage != ( settings.imageArray.length -1 ) ) {
- if ( settings.fixedNavigation ) {
- $('#lightbox-nav-btnNext').css({ 'background' : 'url(' + settings.imageBtnNext + ') right 15% no-repeat' })
- .unbind()
- .bind('click',function() {
- settings.activeImage = settings.activeImage + 1;
- _set_image_to_view();
- return false;
- });
- } else {
- // Show the images button for Next buttons
- $('#lightbox-nav-btnNext').unbind().hover(function() {
- $(this).css({ 'background' : 'url(' + settings.imageBtnNext + ') right 15% no-repeat' });
- },function() {
- $(this).css({ 'background' : 'transparent url(' + settings.imageBlank + ') no-repeat' });
- }).show().bind('click',function() {
- settings.activeImage = settings.activeImage + 1;
- _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' ) || ( keycode == escapeKey ) ) {
- _finish();
- }
- // Verify the key to show the previous image
- if ( ( key == settings.keyToPrev ) || ( keycode == 37 ) ) {
- // If we???re not showing the first image, call the previous
- if ( settings.activeImage != 0 ) {
- settings.activeImage = settings.activeImage - 1;
- _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 the next
- if ( settings.activeImage != ( settings.imageArray.length - 1 ) ) {
- settings.activeImage = settings.activeImage + 1;
- _set_image_to_view();
- _disable_keyboard_navigation();
- }
- }
- }
- /**
- * Preload prev and next images being showed
- *
- */
- function _preload_neighbor_images() {
- if ( (settings.imageArray.length -1) > settings.activeImage ) {
- objNext = new Image();
- objNext.src = settings.imageArray[settings.activeImage + 1][0];
- }
- if ( settings.activeImage > 0 ) {
- objPrev = new Image();
- objPrev.src = settings.imageArray[settings.activeImage -1][0];
- }
- }
- /**
- * Remove jQuery lightBox plugin HTML markup
- *
- */
- function _finish() {
- $('#jquery-lightbox').remove();
- $('#jquery-overlay').fadeOut(function() { $('#jquery-overlay').remove(); });
- // Show some elements to avoid conflict with overlay in IE. These elements appear above the overlay.
- $('embed, object, select').css({ 'visibility' : 'visible' });
- }
- /**
- / THIRD FUNCTION
- * getPageSize() by quirksmode.com
- *
- * @return Array Return an array with page width, height and window width, height
- */
- function ___getPageSize() {
- var xScroll, yScroll;
- if (window.innerHeight && window.scrollMaxY) {
- xScroll = window.innerWidth + window.scrollMaxX;
- yScroll = window.innerHeight + window.scrollMaxY;
- } else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
- xScroll = document.body.scrollWidth;
- yScroll = document.body.scrollHeight;
- } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
- xScroll = document.body.offsetWidth;
- yScroll = document.body.offsetHeight;
- }
- var windowWidth, windowHeight;
- if (self.innerHeight) { // all except Explorer
- if(document.documentElement.clientWidth){
- windowWidth = document.documentElement.clientWidth;
- } else {
- windowWidth = self.innerWidth;
- }
- windowHeight = self.innerHeight;
- } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
- windowWidth = document.documentElement.clientWidth;
- windowHeight = document.documentElement.clientHeight;
- } else if (document.body) { // other Explorers
- windowWidth = document.body.clientWidth;
- windowHeight = document.body.clientHeight;
- }
- // for small pages with total height less then height of the viewport
- if(yScroll < windowHeight){
- pageHeight = windowHeight;
- } else {
- pageHeight = yScroll;
- }
- // for small pages with total width less then width of the viewport
- if(xScroll < windowWidth){
- pageWidth = xScroll;
- } else {
- pageWidth = windowWidth;
- }
- arrayPageSize = new Array(pageWidth,pageHeight,windowWidth,windowHeight);
- 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.documentElement.scrollTop) { // Explorer 6 Strict
- 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 used to avoid click conflict when the plugin is called more than once
- return this.unbind('click').click(_initialize);
- };
-})(jQuery); // Call and execute the function immediately passing the jQuery object
(DIR) diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss
@@ -7,5 +7,6 @@
*= require formtastic
*= require formtastic-bootstrap
*= require formtastic-overrides
+ *= require bootstrap-lightbox
*= require dataTables/jquery.dataTables.bootstrap
*/
(DIR) diff --git a/app/assets/stylesheets/bootstrap-lightbox.css b/app/assets/stylesheets/bootstrap-lightbox.css
@@ -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);
+}
(DIR) diff --git a/app/controllers/analyze_controller.rb b/app/controllers/analyze_controller.rb
@@ -30,14 +30,14 @@ class AnalyzeController < ApplicationController
:page => params[:page],
:order => 'number ASC',
:per_page => 10,
- :conditions => [ 'completed = ? and processed = ? and busy = ? and line_type = ?', true, true, false, @shown ]
+ :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 => [ 'completed = ? and processed = ? and busy = ?', true, true, false ]
+ :conditions => [ 'answered = ? and analysis_completed_at IS NOT NULL and busy = ?', true, false ]
)
end
(DIR) diff --git a/app/controllers/calls_controller.rb b/app/controllers/calls_controller.rb
@@ -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', 'dialer').paginate(
: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 => @job_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 => ["job_id = ?", @job_id] ).map{|r| r.line_type}
- 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_id, k]
- )
- 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 = ?', true, false, false ]
- )
-
- 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
(DIR) diff --git a/app/controllers/jobs_controller.rb b/app/controllers/jobs_controller.rb
@@ -15,6 +15,28 @@ class JobsController < ApplicationController
end
end
+ def results
+
+ @jobs = @project.jobs.where('task = ? AND completed_at IS NOT NULL', 'dialer').paginate(
+ :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, :force => true,
+ :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.inspect
+ format.html { redirect_to results_path(@project) }
+ format.xml { render :xml => @job.errors, :status => :unprocessable_entity }
+ 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 enabled, this job cannot be run")
- respond_to do |format|
- format.html { render :action => "new" }
- format.xml { render :xml => @job.errors, :status => :unprocessable_entity }
- 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 != "SELF"
-
- 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 => @job }
+ 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.inspect
+ format.html { redirect_to results_path(@project) }
format.xml { render :xml => @job.errors, :status => :unprocessable_entity }
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
(DIR) 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 be between 1 and 10,000"
end
when 'analysis'
+ unless ['job', 'project', 'global'].include?(record.scope)
+ record.errors[:scope] << "Scope must be job, project, or global"
+ end
+ if record.scope == "job" and Job.where(:id => record.target_id.to_i, :task => 'dialer').count == 0
+ record.errors[:job_id] << "The job_id is not valid"
+ end
+ if record.scope == "project" and Job.where(:id => record.target_id.to_i, :task => 'dialer').count == 0
+ record.errors[:project_id] << "The project_id is not valid"
+ end
when 'import'
else
record.errors[:base] << "Invalid task specified"
@@ -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.id })
- end
+ # XXX: Purging a single job will be slow, but deleting the project is fast
+ has_many :calls, :dependent => :destroy
- def update_progress(pct)
- if pct >= 100
- self.class.update_all({ :progress => pct, :completed_at => Time.now.utc, :status => 'completed' }, { :id => self.id })
- else
- self.class.update_all({ :progress => pct }, { :id => self.id })
- 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.id })
+ end
+
+ def update_progress(pct)
+ if pct >= 100
+ self.class.update_all({ :progress => pct, :completed_at => Time.now.utc, :status => 'completed' }, { :id => self.id })
+ else
+ self.class.update_all({ :progress => pct }, { :id => self.id })
+ 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 / project/ global
+ :force => !!(self.force), # true / false
+ :target_id => self.target_id.to_i # job_id or project_id or nil
+ })
+ return self.save
else
raise ::RuntimeError, "Unsupported Job type"
end
(DIR) 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_analyze_path(@job_id, call.id, "mp3")%>"
+ data="/assets/musicplayer.swf?song_url=<%=resource_analyze_path(call.id, "mp3")%>"
width="20"
height="17"
style="margin-bottom: -5px;"
>
- <param name="movie" value="/assets/musicplayer.swf?song_url=<%=resource_analyze_path(@job_id, call.id, "mp3")%>"></param>
+ <param name="movie" value="/assets/musicplayer.swf?song_url=<%=resource_analyze_path(call.id, "mp3")%>"></param>
<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_dots")%>" class="lightbox"><img src="<%=resource_analyze_path(@job_id, call.id, "small_sig")%>" /></a>
- <a href="<%=resource_analyze_path(@job_id, call.id, "big_freq")%>" class="lightbox"><img src="<%=resource_analyze_path(@job_id, call.id, "small_freq")%>" /></a><br/>
- <% (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 %>@<%=h mat %>)</div>
- <% end %>
+ <%= render :partial => 'shared/lightbox_sig', :locals => { :call => call } %>
+ <%= render :partial => 'shared/lightbox_freq', :locals => { :call => call } %>
<% if call.fprint and call.fprint.length > 0 %>
- <a href="<%=view_matches_path(call.id)%>">View Matches</a>
+ <a href="<%=view_matches_path(@project, call.id)%>">View Matches</a>
<% 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 %>
(DIR) 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.id, true]).to_s +
+ <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_s +
"/" +
- 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="tooltip" title="View Call Connections" ><i class="icon-bar-chart"></i></a>
- <% if(job.analysis_completed_at) %>
+ <% if job.calls.where("analysis_completed_at IS NOT NULL").count > 0 %>
<a class="btn btn-mini" href="<%= analyze_call_path(@project,job) %>" rel="tooltip" title="View Call Analysis"><i class="icon-eye-open"></i></a>
<a class="btn btn-mini" href="<%= reanalyze_call_path(@project,job) %>" data-confirm="Reprocess this job?" rel="nofollow tooltip" title="Rerun Call Analysis"><i class="icon-refresh"></i></a>
<% else %>
@@ -45,7 +45,7 @@
</tbody>
</table>
-<%= raw(will_paginate @jobs) %>
+<%= will_paginate @jobs, :renderer => BootstrapPagination::Rails %>
<% else %>
(DIR) 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>
(DIR) 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, false).count.to_i
+ pct_answered = 0
+ unless cnt_dialed == 0
+ pct_answered = ((cnt_answered.to_f / cnt_dialed.to_f) * 100).to_i
+ 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_answered %> / <%= cnt_dialed %></span></td>
+
+
+ <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) %>" rel="tooltip" title="View Call Connections" ><i class="icon-zoom-in"></i></a>
+
+ <% if job.calls.where("analysis_completed_at IS NOT NULL").count > 0 %>
+ <a class="btn btn-mini" href="<%= view_analyze_path(@project,job) %>" rel="tooltip" title="View Call Analysis"><i class="icon-eye-open"></i></a>
+ <a class="btn btn-mini" href="<%= reanalyze_job_path(@project,job) %>" data-confirm="Reprocess this job?" rel="nofollow tooltip" title="Rerun Call Analysis"><i class="icon-refresh"></i></a>
+ <% else %>
+ <a class="btn btn-mini" href="<%= analyze_job_path(@project,job) %>" data-confirm="Analyze this job?" rel="nofollow tooltip" title="Run Call Analysis"><i class="icon-cog"></i></a>
+ <% end %>
+
+ <a class="btn btn-mini" href="<%= job_path(job) %>" data-confirm="Delete all data for this job?" data-method="delete" rel="nofollow tooltip" title="Delete Call Data"><i class="icon-trash"></i></a>
+ </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> Start Job </a>
(DIR) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
@@ -23,6 +23,7 @@
<%= javascript_tag do %>
$(document).ready(function() {
$("a").tooltip();
+ $("span").tooltip();
});
<% end %>
</head>
@@ -39,7 +40,7 @@
h(truncate(@project.name, :length => 20)) +
' <i class="icon-chevron-right icon-white"></i>'), project_path(@project), :class => 'project-title') %>
</li>
- <%= menu_item "Results", calls_path(@project) %>
+ <%= menu_item "Results", results_path(@project) %>
<%= menu_item "Analysis", analyze_path(@project)%>
<% end %>
(DIR) diff --git a/app/views/shared/_lightbox_freq.html.erb b/app/views/shared/_lightbox_freq.html.erb
@@ -0,0 +1,13 @@
+<%
+ lid = "call_#{call.id}_freq"
+%>
+<div id="<%= lid %>" class="lightbox hide fade" tabindex="-1" role="dialog" aria-hidden="true">
+ <div class='lightbox-header'>
+ <button type="button" class="close" data-dismiss="lightbox" aria-hidden="true">×</button>
+ </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_path(call.id, "small_freq")%>" alt="<%= call.number %> frequency graph"/></a>
(DIR) diff --git a/app/views/shared/_lightbox_sig.html.erb b/app/views/shared/_lightbox_sig.html.erb
@@ -0,0 +1,13 @@
+<%
+ lid = "call_#{call.id}_sig"
+%>
+<div id="<%= lid %>" class="lightbox hide fade" tabindex="-1" role="dialog" aria-hidden="true">
+ <div class='lightbox-header'>
+ <button type="button" class="close" data-dismiss="lightbox" aria-hidden="true">×</button>
+ </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_path(call.id, "small_sig")%>" alt="<%= call.number %> signal graph" /></a>
(DIR) diff --git a/app/views/shared/lightbox_sig.html.erb b/app/views/shared/lightbox_sig.html.erb
@@ -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" aria-hidden="true">
+ <div class='lightbox-header'>
+ <button type="button" class="close" data-dismiss="lightbox" aria-hidden="true">×</button>
+ </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_path(call.id, "small_sig")%>" /></a>
(DIR) 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
(DIR) 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 => :results
+ match '/projects/:project_id/results/:id' => 'jobs#view_results', :as => :view_results
+ match '/projects/:project_id/results/:id/analyze' => 'jobs#analyze_job', :as => :analyze_job
+ match '/projects/:project_id/results/:id/reanalyze' => 'jobs#reanalyze_job', :as => :reanalyze_job
+
+
- match '/projects/:project_id/calls/' => 'calls#index', :as => :calls
- match '/projects/:project_id/calls/:id/view' => 'calls#view', :as => :view_call
- match '/projects/:project_id/calls/:id/analyze' => 'calls#analyze', :as => :analyze_call
- match '/projects/:project_id/calls/:id/reanalyze' => 'calls#reanalyze', :as => :reanalyze_call
- match '/projects/:project_id/calls/:id/purge' => 'calls#purge', :as => :purge_call
- delete '/projects/:project_id/calls/:id' => 'calls#destroy'
match '/projects/:project_id/analyze' => 'analyze#index', :as => :analyze
- match '/projects/:project_id/analyze/:id/resource/:result_id/:type' => 'analyze#resource', :as => :resource_analyze
+ match '/calls/:result_id/:type' => 'analyze#resource', :as => :resource_analyze
match '/projects/:project_id/analyze/:id/view' => 'analyze#view', :as => :view_analyze
- match '/projects/:project_id/analyze/:call_id/matches' => 'analyze#view_matches', :as => :view_matches
- match '/projects/:project_id/analyze/:id/show' => 'analyze#show', :as => :show_analyze
+ match '/projects/:project_id/analyze/:call_id/matches' => 'analyze#view_matches', :as => :view_matches
resources :settings
resources :providers
resources :users
resources :projects
resources :jobs
+ resources :calls
match '/about' => 'home#about', :as => :about
match '/help' => 'home#help', :as => :help
(DIR) 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, :busy => false}
+ query = {:job_id => @conf[:target_id], :answered => true, :busy => false}
else
- query = {:job_id => job.id, :answered => true, :busy => false, :analysis_started_at => nil}
+ query = {:job_id => @conf[:target_id], :answered => true, :busy => false, :analysis_started_at => nil}
end
when 'project'
if @conf[:force]
- query = {:project_id => job.project_id, :answered => true, :busy => false}
+ query = {:project_id => @conf[:target_id], :answered => true, :busy => false}
else
- query = {:project_id => job.project_id, :answered => true, :busy => false, :analysis_started_at => nil}
+ query = {:project_id => @conf[:target_id], :answered => true, :busy => false, :analysis_started_at => nil}
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| ::ActiveRecord::Base.connection_pool.with_connection { run_analyze_call(c) }}
- end
- clear_stale_tasks
+ if @tasks.length < max_threads
+ WarVOX::Log.debug("Spawning job for Call #{call.inspect}")
+ @tasks << Thread.new(call.id, job.id) { |c,j| ::ActiveRecord::Base.connection_pool.with_connection { run_analyze_call(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_calls.to_f) * 100)
- 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 / @total_calls.to_f) * 100)
+ 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)
(DIR) 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))