Sync research changes - warvox - VoIP based wardialing tool, forked from rapid7/warvox.
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
       ---
 (DIR) commit 3e3fbfb5bd9f1295c66868ae3a94b3d41040a98d
 (DIR) parent 93b2de6b613e819c4123fa15eccf878a5adb4949
 (HTM) Author: HD Moore <hd_moore@rapid7.com>
       Date:   Sun, 13 Jan 2013 11:12:14 -0600
       
       Sync research changes
       
       Diffstat:
         M app/assets/stylesheets/bootstrap_a… |      93 ++++++++++++++++++++++++++++---
         M app/controllers/application_contro… |       2 +-
         M app/controllers/jobs_controller.rb  |       2 +-
         M app/controllers/projects_controlle… |     196 +++++++++++++++++++------------
         M app/helpers/application_helper.rb   |       4 ++++
         M app/models/call.rb                  |      31 +++++++++++++++++++++++--------
         M app/models/job.rb                   |      20 +++++++++++++++++---
         M app/views/jobs/index.html.erb       |      14 +++++++++-----
         M app/views/jobs/results.html.erb     |       6 +++---
         M app/views/layouts/application.html… |       2 +-
         M app/views/projects/index.html.erb   |       4 ++--
         M app/views/projects/show.html.erb    |     240 +++++++++++++++++++++++++------
         M app/views/shared/graphs/_sparkline… |       4 ++--
         M db/migrate/20121228171549_initial_… |       2 +-
         M db/schema.rb                        |       2 +-
       
       15 files changed, 467 insertions(+), 155 deletions(-)
       ---
 (DIR) diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/bootstrap_and_overrides.css.less
       @@ -86,8 +86,13 @@
        
        
        .sparkline {
       -        width: 100px;
       -        height: 20px;
       +        width: 100%;
       +        height: 30px;
       +}
       +
       +.sparkline-title {
       +        text-align: center;
       +        font-size: 11px;
        }
        
        .call-detail {
       @@ -99,17 +104,89 @@
                border-bottom: 1px solid #eeeeee;
        }
        
       +.zoom {
       +        background-color: @orange;
       +        height: 40px;
       +}
       +
       +
        .stat-box {
                padding: 10px;
       -
       -        background-color: #eeeeee;
       -        border: 1px solid #bbbbbb;
       -        font-size: 18px;
       +        background-color: white;
       +        border: 2px solid @darkGray;
       +        font-size: 16px;
                font-weight: bold;
                color: @darkGray;
       -        width: 65px;
       +        width: 55px;
                margin: auto auto;
                text-align: center;
       +
       +        border-top-left-radius: 20px;
       +        border-top-right-radius: 20px;
       +        border-bottom-right-radius: 20px;
       +        border-bottom-left-radius: 20px;
       +}
       +
       +.stat-modem {
       +        background-color: white;
       +        border: 2px solid @red;
       +        color: @red;
       +}
       +
       +
       +.stat-nodata {
       +        background-color: #f4f4f4;
       +        border: 2px solid @darkGray;
       +        color: @darkGray;
       +        font-size: 24px;
       +}
       +
       +.stat-completed {
       +        background-color: white;
       +        border: 2px solid @orange;
       +        color: @darkGray;
       +}
       +
       +.stat-voice {
       +        background-color: white;
       +        border: 2px solid @green;
       +        color: @darkGray;
       +}
       +
       +.stat-voicemail {
       +        background-color: white;
       +        border: 2px solid @blue;
       +        color: @darkGray;
       +}
       +
       +.stat-fax {
       +        background-color: white;
       +        border: 2px solid @darkGray;
       +        color: @darkGray;
       +}
       +
       +.stat-modem {
       +        background-color: white;
       +        border: 2px solid @red;
       +        color: @darkGray;
       +}
       +
       +
       +.arrow-down {
       +        font-size: 18px;
       +        text-align: center;
       +        margin: auto auto;
       +        padding-top: 10px;
       +        padding-bottom: 10px;
       +        color: @darkOrange;
       +}
       +
       +.arrow-right {
       +        font-size: 22px;
       +        text-align: center;
       +        margin: auto auto;
       +        padding-top: 10px;
       +        padding-bottom: 10px;
        }
        
        .stat-subtitle {
       @@ -170,7 +247,7 @@
        }
        
        .progress_pct {
       -        color: @red;
       +        color: @darkGray;
                margin-left: 10px;
                font-weight: bold;
        }
 (DIR) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
       @@ -6,7 +6,7 @@ class ApplicationController < ActionController::Base
                before_filter :require_user, :load_project
                add_breadcrumb :projects, :root_path
        
       -
       +        include ActionView::Helpers::NumberHelper
        
        private
        
 (DIR) diff --git a/app/controllers/jobs_controller.rb b/app/controllers/jobs_controller.rb
       @@ -7,7 +7,7 @@ class JobsController < ApplicationController
        
                        @submitted_jobs = Job.where(:status => ['submitted', 'scheduled'], :completed_at => nil)
                        @active_jobs    = Job.where(:status => 'running', :completed_at => nil)
       -                @inactive_jobs  = Job.where('status NOT IN (?) OR completed_at IS NULL', ['submitted', 'scheduled', 'running']).paginate(
       +                @inactive_jobs  = Job.where('status NOT IN (?)', ['submitted', 'scheduled', 'running']).paginate(
                                :page => params[:page],
                                :order => 'id DESC',
                                :per_page => 30
 (DIR) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
       @@ -1,7 +1,7 @@
        class ProjectsController < ApplicationController
        
                def index
       -                   @projects = Project.paginate(
       +                 @projects = Project.paginate(
                                :page => params[:page],
                                :order => 'id DESC',
                                :per_page => 10
       @@ -10,83 +10,129 @@ class ProjectsController < ApplicationController
                        @new_project = Project.new
        
                        respond_to do |format|
       -                        format.html # index.html.erb
       -                        format.xml  { render :xml => @projects }
       +                        format.html
       +                        format.xml        { render :xml => @projects }
                        end
                end
        
       -  # GET /projects/1
       -  # GET /projects/1.xml
       -  def show
       -    @project = Project.find(params[:id])
       -    respond_to do |format|
       -      format.html # show.html.erb
       -      format.xml  { render :xml => @project }
       -    end
       -
       -  end
       -
       -  # GET /projects/new
       -  # GET /projects/new.xml
       -  def new
       -    @new_project = Project.new
       -    respond_to do |format|
       -      format.html # new.html.erb
       -      format.xml  { render :xml => @new_project }
       -    end
       -  end
       -
       -  # GET /projects/1/edit
       -  def edit
       -    @project = Project.find(params[:id])
       -  end
       -
       -  # POST /projects
       -  # POST /projects.xml
       -  def create
       -    @new_project = Project.new(params[:project])
       -    @new_project.created_by = current_user.login
       -
       -    respond_to do |format|
       -      if @new_project.save
       -        flash[:notice] = 'Project was successfully created.'
       -        format.html { redirect_to(project_path(@new_project)) }
       -        format.xml  { render :xml => @project, :status => :created, :location => @new_project }
       -      else
       -        format.html { render :action => "new" }
       -        format.xml  { render :xml => @new_project.errors, :status => :unprocessable_entity }
       -      end
       -    end
       -  end
       -
       -  # PUT /projects/1
       -  # PUT /projects/1.xml
       -  def update
       -    @project = Project.find(params[:id])
       -
       -    respond_to do |format|
       -      if @project.update_attributes(params[:project])
       -        flash[:notice] = 'Project was successfully updated.'
       -        format.html { redirect_to projects_path }
       -        format.xml  { head :ok }
       -      else
       -        format.html { render :action => "edit" }
       -        format.xml  { render :xml => @project.errors, :status => :unprocessable_entity }
       -      end
       -    end
       -  end
       -
       -  # DELETE /projects/1
       -  # DELETE /projects/1.xml
       -  def destroy
       -    @project = Project.find(params[:id])
       -    @project.destroy
       -
       -    respond_to do |format|
       -      format.html { redirect_to(projects_url) }
       -      format.xml  { head :ok }
       -    end
       -  end
       +        def show
       +                @project = Project.find(params[:id])
       +                @active_jobs = @project.jobs.where(:status => 'running', :completed_at => nil)
       +                @inactive_jobs        = @project.jobs.where('status NOT IN (?)', ['submitted', 'scheduled', 'running']).paginate(
       +                        :page => params[:page],
       +                        :order => 'id DESC',
       +                        :per_page => 30
       +                )
       +
       +                @boxes = {
       +                        :called    => { :cnt => @project.calls.count },
       +                        :answered  => { :cnt => @project.calls.where(:answered => true).count },
       +                        :analyzed  => { :cnt => @project.calls.where('analysis_completed_at IS NOT NULL').count },
       +                        :voice     => { :cnt => @project.lines.where(:line_type => 'voice').count },
       +                        :voicemail => { :cnt => @project.lines.where(:line_type => 'voicemail').count },
       +                        :fax       => { :cnt => @project.lines.where(:line_type => 'fax').count },
       +                        :modem     => { :cnt => @project.lines.where(:line_type => 'modem').count }
       +                }
       +
       +                if @boxes[:called][:cnt] == 0
       +                        @boxes[:called][:txt] = '0'
       +                        @boxes[:called][:cls] = 'nodata'
       +
       +                        # No calls, so everything else is unknown
       +                        [ :answered, :analyzed, :voice, :voicemail, :fax, :modem ].each do |t|
       +                                @boxes[t][:txt] = '?'
       +                                @boxes[t][:cls] = 'nodata'
       +                        end
       +
       +                else
       +
       +                        [ :called, :answered, :analyzed].each do |t|
       +                                @boxes[t][:txt] = number_with_delimiter(@boxes[t][:cnt])
       +                                @boxes[t][:cls] = 'completed'
       +                        end
       +
       +                        if @boxes[:answered][:cnt] == 0
       +                                @boxes[:answered][:txt] = '0'
       +                                @boxes[:answered][:cls] = 'nodata'
       +                        end
       +
       +                        if @boxes[:analyzed][:cnt] == 0
       +                                [ :voice, :voicemail, :fax, :modem ].each do |t|
       +                                        @boxes[t][:txt] = '?'
       +                                        @boxes[t][:cls] = 'nodata'
       +                                end
       +                                @boxes[:analyzed][:cls] = 'nodata'
       +                        else
       +
       +                                @boxes[:voice][:txt] = number_with_delimiter(@boxes[:voice][:cnt])
       +                                @boxes[:voice][:cls] = 'voice'
       +
       +                                @boxes[:voicemail][:txt] = number_with_delimiter(@boxes[:voicemail][:cnt])
       +                                @boxes[:voicemail][:cls] = 'voicemail'
       +
       +                                @boxes[:fax][:txt] = number_with_delimiter(@boxes[:fax][:cnt])
       +                                @boxes[:fax][:cls] = 'fax'
       +
       +                                @boxes[:modem][:txt] = number_with_delimiter(@boxes[:modem][:cnt])
       +                                @boxes[:modem][:cls] = 'modem'
       +                        end
       +                end
       +
       +                respond_to do |format|
       +                        format.html
       +                        format.xml        { render :xml => @project }
       +                end
       +        end
       +
       +        def new
       +                @new_project = Project.new
       +                respond_to do |format|
       +                        format.html
       +                        format.xml        { render :xml => @new_project }
       +                end
       +        end
       +
       +
       +        def edit
       +                @project = Project.find(params[:id])
       +        end
       +
       +        def create
       +                @new_project = Project.new(params[:project])
       +                @new_project.created_by = current_user.login
       +
       +                respond_to do |format|
       +                        if @new_project.save
       +                                format.html { redirect_to(project_path(@new_project)) }
       +                                format.xml        { render :xml => @project, :status => :created, :location => @new_project }
       +                        else
       +                                format.html { render :action => "new" }
       +                                format.xml        { render :xml => @new_project.errors, :status => :unprocessable_entity }
       +                        end
       +                end
       +        end
       +
       +        def update
       +                @project = Project.find(params[:id])
       +
       +                respond_to do |format|
       +                        if @project.update_attributes(params[:project])
       +                                format.html { redirect_to projects_path }
       +                                format.xml        { head :ok }
       +                        else
       +                                format.html { render :action => "edit" }
       +                                format.xml        { render :xml => @project.errors, :status => :unprocessable_entity }
       +                        end
       +                end
       +        end
        
       +        def destroy
       +                @project = Project.find(params[:id])
       +                @project.destroy
        
       +                respond_to do |format|
       +                        format.html { redirect_to(projects_url) }
       +                        format.xml        { head :ok }
       +                end
       +        end
        end
 (DIR) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
       @@ -67,6 +67,10 @@ module ApplicationHelper
                        end
                end
        
       +        def format_job_rate(job)
       +                pluralize(job.rate.to_i, "call") + "/s"
       +        end
       +
                #
                # Includes any javascripts specific to this view. The hosts/show view
                # will automatically include any javascripts at public/javascripts/hosts/show.js.
 (DIR) diff --git a/app/models/call.rb b/app/models/call.rb
       @@ -1,14 +1,14 @@
        class Call < ActiveRecord::Base
        
       -        reportable :hourly, :aggregation => :count, :grouping => :hour, :live_data => true, :cacheable => false
       -        reportable :daily, :aggregation => :count, :grouping => :day, :live_data => true, :cacheable => false
       -        reportable :weekly, :aggregation => :count, :grouping => :week, :live_data => true, :cacheable => false
       -        reportable :monthly, :aggregation => :count, :grouping => :month, :live_data => true, :cacheable => false
       +        reportable :hourly, :aggregation => :count, :grouping => :hour, :live_data => true, :cacheable => false, :limit => 24
       +        reportable :daily, :aggregation => :count, :grouping => :day, :live_data => true, :cacheable => false, :limit => 7
       +        reportable :weekly, :aggregation => :count, :grouping => :week, :live_data => true, :cacheable => false, :limit => 52
       +        reportable :monthly, :aggregation => :count, :grouping => :month, :live_data => true, :cacheable => false, :limit => 12
        
       -        reportable :analyzed_hourly, :aggregation => :count, :grouping => :hour, :date_column => :analysis_completed_at, :live_data => true, :cacheable => false
       -        reportable :analyzed_daily, :aggregation => :count, :grouping => :day, :date_column => :analysis_completed_at, :live_data => true, :cacheable => false
       -        reportable :analyzed_weekly, :aggregation => :count, :grouping => :week, :date_column => :analysis_completed_at, :live_data => true, :cacheable => false
       -        reportable :analyzed_monthly, :aggregation => :count, :grouping => :month, :date_column => :analysis_completed_at, :live_data => true, :cacheable => false
       +        reportable :analyzed_hourly, :aggregation => :count, :grouping => :hour, :date_column => :analysis_completed_at, :live_data => true, :cacheable => false, :limit => 24
       +        reportable :analyzed_daily, :aggregation => :count, :grouping => :day, :date_column => :analysis_completed_at, :live_data => true, :cacheable => false, :limit => 7
       +        reportable :analyzed_weekly, :aggregation => :count, :grouping => :week, :date_column => :analysis_completed_at, :live_data => true, :cacheable => false, :limit => 52
       +        reportable :analyzed_monthly, :aggregation => :count, :grouping => :month, :date_column => :analysis_completed_at, :live_data => true, :cacheable => false, :limit => 12
        
                belongs_to :project
                belongs_to :provider
       @@ -37,6 +37,8 @@ class Call < ActiveRecord::Base
                }
        
        
       +        after_save :update_linked_line
       +
                def paginate_matches(scope, min_match, page, per_page)
        
                        scope_limit = ""
       @@ -68,4 +70,17 @@ class Call < ActiveRecord::Base
                        CallMedium.columns_hash.keys.reject{|x| x =~ /^id|_id$/}
                end
        
       +        def linked_line
       +                Line.find_or_create_by_number_and_project_id(self[:number], self[:project_id])
       +        end
       +
       +        def update_linked_line
       +                line = linked_line
       +
       +                if self[:line_type]
       +                        line.line_type = self[:line_type]
       +                        line.save
       +                end
       +        end
       +
        end
 (DIR) diff --git a/app/models/job.rb b/app/models/job.rb
       @@ -47,7 +47,6 @@ class Job < ActiveRecord::Base
                        end
                end
        
       -
                # XXX: Purging a single job will be slow, but deleting the project is fast
                has_many :calls, :dependent => :destroy
        
       @@ -105,9 +104,8 @@ class Job < ActiveRecord::Base
                                        :seconds  => self.seconds.to_i,
                                        :cid_mask => self.cid_mask
                                })
       -                        $stderr.puts self.inspect
       -
                                return self.save
       +
                        when 'analysis'
                                self.status = 'submitted'
                                self.args = Marshal.dump({
       @@ -122,4 +120,20 @@ class Job < ActiveRecord::Base
                        end
                end
        
       +        def rate
       +                tend = (self.completed_at || Time.now)
       +                tlen = tend.to_f - self.started_at.to_f
       +
       +                case self.task
       +                when 'dialer'
       +                        Call.where('job_id = ?', self.id).count() / tlen
       +                when 'analysis'
       +                        Call.where('job_id = ? AND analysis_completed_at > ? AND analysis_completed_at < ?', self.details[:target_id], self.created_at, tend).count() / tlen
       +                when 'import'
       +                        Call.where('job_id = ?', self.id).count() / tlen
       +                else
       +                        0
       +                end
       +        end
       +
        end
 (DIR) diff --git a/app/views/jobs/index.html.erb b/app/views/jobs/index.html.erb
       @@ -17,7 +17,7 @@
            <td><%= job.id %></td>
            <td><%= format_job_details(job) %></td>
                <td><%= format_job_status(job) %></td>
       -    <td><%= job.created_at.localtime.strftime("%Y-%m-%d %H:%M:%S %Z") %></td>
       +    <td><%= time_ago_in_words(job.created_at) %> ago</td>
        
            <td>
                        <a class="btn" href="<%= job_path(job) %>" data-confirm="Remove this job?" data-method="delete" rel="nofollow tooltip" title="Remove Job"><i class="icon-trash"></i></a>
       @@ -38,6 +38,7 @@
            <th>ID</th>
            <th>Task</th>
            <th>Progress</th>
       +    <th>Rate</th>
            <th>Launched</th>
            <th>Actions</th>
            <th>Project</th>
       @@ -48,13 +49,14 @@
            <td><%= job.id %></td>
            <td><%= format_job_details(job) %></td>
            <td>
       -                <div class="progress progress-success progress-striped progress-bar">
       +                <div class="progress progress-warning progress-striped progress-bar">
                                <div class="bar" style="width: <%= job.progress %>%">
                                        <span class='progress_pct'><%= job.progress %>%</span>
                                </div>
                        </div>
                </td>
       -    <td><%= job.created_at.localtime.strftime("%Y-%m-%d %H:%M:%S %Z") %></td>
       +        <td><%= format_job_rate(job) %></td>
       +    <td><%= time_ago_in_words(job.created_at) %> ago</td>
            <td>
                    <% if job.task == "dialer" %>
                            <a class="btn" href="<%= view_results_path(job.project,job) %>" rel="tooltip" title="View Current Stats" ><i class="icon-zoom-in"></i></a>
       @@ -88,6 +90,7 @@
            <th>ID</th>
            <th>Task</th>
            <th>Status</th>
       +    <th>Rate</th>
            <th>Created</th>
            <th>Completed</th>
            <th>Project</th>
       @@ -106,8 +109,9 @@
            <td><%= job.id %></td>
            <td><%= format_job_details(job) %></td>
                <td><%= format_job_status(job) %></td>
       -    <td><%= job.created_at.localtime.strftime("%Y-%m-%d %H:%M:%S %Z") %></td>
       -    <td><%= job.completed_at ? job.completed_at.localtime.strftime("%Y-%m-%d %H:%M:%S %Z") : "incomplete" %></td>
       +        <td><%= format_job_rate(job) %></td>
       +    <td><%= time_ago_in_words(job.created_at) %> ago</td>
       +    <td><%= job.completed_at ? "after " + time_ago_in_words(Time.at(Time.now.to_i - (job.completed_at.to_f - job.created_at.to_f))) : "incomplete" %></td>
            <td><%= link_to( h(truncate(job.project.name, :length => 25)), project_path(job.project)) %></td>
          </tr>
        <% end %>
 (DIR) diff --git a/app/views/jobs/results.html.erb b/app/views/jobs/results.html.erb
       @@ -49,7 +49,7 @@
            <td><span rel="tooltip" class="xtooltip" title="<%= pct_analyzed %>% analyzed"><%= number_with_delimiter(cnt_analyzed) %></span></td>
        
        
       -        <td><%= job.created_at.strftime("%Y-%m-%d %H:%M:%S") %></td>
       +        <td><%= time_ago_in_words(job.created_at) %> ago</td>
                <td><%= job.created_by %></td>
            <td>
                <a class="btn" href="<%= view_results_path(@project,job) %>" rel="tooltip" title="View Call Connections" ><i class="icon-zoom-in"></i></a>
       @@ -59,11 +59,11 @@
                                <% if pct_analyzed == 100 %>
                                        <a class="btn" 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" href="<%= analyze_job_path(@project,job) %>" data-confirm="Continue to process this job?" rel="nofollow tooltip" title="Finish Call Analysis"><i class="icon-cog"></i></a>
       +                                <a class="btn" href="<%= analyze_job_path(@project,job) %>" data-confirm="Continue to process this job?" rel="nofollow tooltip" title="Finish Call Analysis"><i class="icon-cogs"></i></a>
                                <% end %>
                        <% else %>
                                <% if cnt_answered > 0 %>
       -                        <a class="btn" href="<%= analyze_job_path(@project,job) %>" data-confirm="Analyze this job?" rel="nofollow tooltip" title="Run Call Analysis"><i class="icon-cog"></i></a>
       +                        <a class="btn" href="<%= analyze_job_path(@project,job) %>" data-confirm="Analyze this job?" rel="nofollow tooltip" title="Run Call Analysis"><i class="icon-cogs"></i></a>
                                <% end %>
                        <% end %>
        
 (DIR) diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
       @@ -77,7 +77,7 @@
                                                <%= menu_item raw('<i class="icon-info-sign"></i> About'), about_path %>
                                        <% end %>
        
       -                                <%= menu_item raw('<div class="help-icon"><i class="icon-off icon-white" rel="tooltip" title="Logout"></i></div>'), logout_path %>
       +                                <%= menu_item raw('<div class="help-icon"><i class="icon-signout icon-white" rel="tooltip" title="Logout"></i></div>'), logout_path %>
        
                                        <%= menu_item raw('<div class="help-icon"><i class="icon-question-sign icon-white" rel="tooltip" title="Help!"></i></div>'), check_path %>
                                <% end %>
 (DIR) diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb
       @@ -10,7 +10,7 @@
            <th>Jobs</th>
            <th>Calls</th>
            <th>Analyzed</th>
       -    <th>Date</th>
       +    <th>Created</th>
            <th>Actions</th>
          </tr>
          </thead>
       @@ -23,7 +23,7 @@
            <td><%= number_with_delimiter(project.jobs.count) %></td>
            <td><%= number_with_delimiter(project.calls.count) %></td>
            <td><%= number_with_delimiter(project.calls.where('analysis_completed_at IS NOT NULL').count) %></td>
       -    <td><%= project.updated_at.localtime.strftime("%Y-%m-%d %H:%M:%S") %></td>
       +    <td><%= time_ago_in_words(project.created_at) %> ago</td>
            <td>
                      <a class="btn" href="<%= edit_project_path(project) %>"rel="tooltip" title="Update Project Information"><i class="icon-pencil"></i></a>
                    <a class="btn" href="<%= project_path(project) %>" data-confirm="Delete this project?" data-method="delete" rel="nofollow tooltip" title="Delete Project"><i class="icon-trash"></i></a>
 (DIR) diff --git a/app/views/projects/show.html.erb b/app/views/projects/show.html.erb
       @@ -1,6 +1,3 @@
       -
       -
       -
        <div class="row-fluid">
                <div class="span12">
                        <a class="btn btn-small pull-right" href="<%= edit_project_path(@project) %>" rel="tooltip" title="Update project settings"><i class="icon-wrench"></i> Settings</a>
       @@ -9,72 +6,227 @@
                </div>
        </div>
        
       -
        <div class="row-fluid">
       -        <div class="span2">
       -                <div class="stat-box">
       -                        <div><%= number_with_delimiter( @project.calls.count ) %></div>
       +        <div class="span3">
       +                <div class="stat-box stat-<%= @boxes[:called][:cls]%>">
       +                        <div><%= @boxes[:called][:txt] %></div>
                                <span class="stat-subtitle">Calls</span>
                        </div>
                </div>
       -        <div class="span2">
       -                <div class="stat-box">
       -                        <div><%= number_with_delimiter( @project.calls.where(:answered => true).count ) %></div>
       -                        <span class="stat-subtitle">Answered</span>
       -                </div>
       -        </div>
       -        <div class="span2">
       -                <div class="stat-box">
       -                        <div><%= number_with_delimiter( @project.calls.where('analysis_completed_at IS NOT NULL').count ) %></div>
       -                        <span class="stat-subtitle">Analyzed</span>
       +        <div class="span9">
       +                <div class="row-fluid">
       +                        <div class="span2">&nbsp;</div>
       +                        <div class="span2"><a href="<%= new_dialer_job_path %>" class="btn" rel="tooltip" title="Gather data by dialing a range of numbers"><i class="icon-phone"></i> <strong>Wardial</strong></a></div>
       +                        <% if @boxes[:answered][:cnt] > 0 %>
       +                        <div class="span2"><a href="#" class="btn" rel="tooltip" title="Analyze call data to determine line types and frequencies"><i class="icon-cogs"></i> <strong>Analyze</strong></a></div>
       +                        <div class="span2"><a href="#" class="btn" rel="tooltip" title="Look up meta-information about specific lines"><i class="icon-user"></i> <strong>Identify</strong></a></div>
       +                        <div class="span2"><a href="#" class="btn" rel="tooltip" title="Signal analysis and other research tools"><i class="icon-star"></i> <strong>Research</strong></a></div>
       +                        <% else %>
       +                        <div class="span2"><a href="#" class="btn disabled" rel="tooltip" title="No call data is available to analyze"><i class="icon-cogs"></i> <strong>Analyze</strong></a></div>
       +                        <div class="span2"><a href="#" class="btn" rel="tooltip" title="Look up meta-information about specific lines"><i class="icon-user"></i> <strong>Identify</strong></a></div>
       +                        <div class="span2"><a href="#" class="btn disabled" rel="tooltip" title="No call data is available to research"><i class="icon-star"></i> <strong>Research</strong></a></div>
       +                        <% end %>
       +                        <div class="span2">&nbsp;</div>
                        </div>
                </div>
       +</div>
        
       -        <div class="span2">
       -                <div class="stat-box">
       -                        <div><%= number_with_delimiter( @project.calls.where(:line_type => 'voice').count ) %></div>
       -                        <span class="stat-subtitle">Voice</span>
       -                </div>
       -        </div>
       -        <div class="span2">
       -                <div class="stat-box">
       -                        <div><%= number_with_delimiter( @project.calls.where(:line_type => 'fax').count ) %></div>
       -                        <span class="stat-subtitle">Fax</span>
       +<div class="row-fluid">
       +        <div class="span3 arrow-down"><i class="icon-arrow-down"></i></div>
       +        <div class="span9">&nbsp;</div>
       +</div>
       +
       +<div class="row-fluid">
       +        <div class="span3">
       +                <div class="stat-box stat-<%= @boxes[:answered][:cls]%>">
       +                        <div><%= @boxes[:answered][:txt] %></div>
       +                        <span class="stat-subtitle">Answered</span>
                        </div>
                </div>
       -        <div class="span2">
       -                <div class="stat-box">
       -                        <div><%= number_with_delimiter( @project.calls.where(:line_type => 'modem').count ) %></div>
       -                        <span class="stat-subtitle">Modem</span>
       +        <div class="span9"></div>
       +</div>
       +
       +<div class="row-fluid">
       +        <div class="span3 arrow-down"><i class="icon-arrow-down"></i></div>
       +        <div class="span9">&nbsp;</div>
       +</div>
       +
       +<div class="row-fluid">
       +        <div class="span3">
       +                <div class="stat-box stat-<%= @boxes[:analyzed][:cls]%>">
       +                        <div><%= @boxes[:analyzed][:txt] %></div>
       +                        <span class="stat-subtitle">Analyzed</span>
                        </div>
                </div>
       +        <div class="span9">&nbsp;</div>
        </div>
        
       -
       +<div class="row-fluid">
       +        <div class="span3 arrow-down"><i class="icon-arrow-down"></i></div>
       +        <div class="span9">&nbsp;</div>
       +</div>
        
        <div class="row-fluid">
       -        <div class="span2">
       +        <div class="span3">
       +                <div class="row-fluid">
       +                        <div class="span6">
       +                                <div class="stat-box stat-<%= @boxes[:voice][:cls]%>">
       +                                        <div><%= @boxes[:voice][:txt] %></div>
       +                                        <span class="stat-subtitle">Voice</span>
       +                                </div>
       +                        </div>
       +                        <div class="span6">
       +                                <div class="stat-box stat-<%= @boxes[:voicemail][:cls]%>">
       +                                        <div><%= @boxes[:voicemail][:txt] %></div>
       +                                        <span class="stat-subtitle">VoiceMail</span>
       +                                </div>
       +                        </div>
       +                </div>
       +        </div>
       +        <div class="span3 sparkline-cell">
                        <%= render :partial => 'shared/graphs/sparkline', :locals => { :points => @project.calls.hourly_report.to_a.map{|x| x[1] } } %>
       -                Calls/Hour
       +                <span class='sparkline-title'>Phone Calls / Hour (Last Day)</a>
                </div>
       -        <div class="span2">
       +        <div class="span3 sparkline-cell">
                        <%= render :partial => 'shared/graphs/sparkline', :locals => { :points => @project.calls.daily_report.to_a.map{|x| x[1] } } %>
       -                Calls/Day
       +                <span class='sparkline-title'>Phone Calls / Day (Last Week)</a>
                </div>
       -        <div class="span2">
       +        <div class="span3 sparkline-cell">
                        <%= render :partial => 'shared/graphs/sparkline', :locals => { :points => @project.calls.weekly_report.to_a.map{|x| x[1] } } %>
       -                Calls/Week
       +                <span class='sparkline-title'>Phone Calls / Week (Last Month)</a>
       +        </div>
       +</div>
       +
       +<div class="row-fluid">
       +        <div class="span12">&nbsp;</div>
       +</div>
       +
       +<div class="row-fluid">
       +        <div class="span3">
       +                <div class="row-fluid">
       +                        <div class="span6">
       +                                <div class="stat-box stat-<%= @boxes[:fax][:cls]%>">
       +                                        <div><%= @boxes[:fax][:txt] %></div>
       +                                        <span class="stat-subtitle">Fax</span>
       +                                </div>
       +                        </div>
       +                        <div class="span6">
       +                                <div class="stat-box stat-<%= @boxes[:modem][:cls]%>">
       +                                        <div><%= @boxes[:modem][:txt] %></div>
       +                                        <span class="stat-subtitle">Modems</span>
       +                                </div>
       +                        </div>
       +                </div>
                </div>
       -        <div class="span2">
       +        <div class="span3 sparkline-cell">
                        <%= render :partial => 'shared/graphs/sparkline', :locals => { :points => @project.calls.analyzed_hourly_report.to_a.map{|x| x[1] } } %>
       -                Analysis/Hour
       +                <span class='sparkline-title'>Analyzed Calls / Hour (Last Day)</a>
                </div>
       -        <div class="span2">
       +        <div class="span3 sparkline-cell">
                        <%= render :partial => 'shared/graphs/sparkline', :locals => { :points => @project.calls.analyzed_daily_report.to_a.map{|x| x[1] } } %>
       -                Analysis/Day
       +                <span class='sparkline-title'>Analyzed Calls / Day (Last Week)</a>
                </div>
       -        <div class="span2">
       +        <div class="span3 sparkline-cell">
                        <%= render :partial => 'shared/graphs/sparkline', :locals => { :points => @project.calls.analyzed_weekly_report.to_a.map{|x| x[1] } } %>
       -                Analysis/Week
       +                <span class='sparkline-title'>Analyzed Calls / Week (Last Month)</a>
       +        </div>
       +        <div class="span6">&nbsp;</div>
       +</div>
       +
       +<div class="row-fluid">
       +        <div class="span12">&nbsp;</div>
       +</div>
       +
       +<div class="row-fluid">
       +        <div class="span12">
       +                <p class='project-header'><%= @project.description %></p>
       +        </div>
       +</div>
       +
       +<% if @active_jobs.count > 0 %>
       +
       +<div class="row-fluid">
       +        <div class="span12">
       +
       +        <h2 class='title'>Active Jobs</h1>
       +
       +        <table class='table table-striped table-condensed' width='90%'>
       +          <tr>
       +            <th>ID</th>
       +            <th>Task</th>
       +            <th>Progress</th>
       +            <th>Rate</th>
       +            <th>Launched</th>
       +            <th>Actions</th>
       +          </tr>
       +
       +<% @active_jobs.each do |job| %>
       +          <tr class='active_job_row'>
       +            <td><%= job.id %></td>
       +            <td><%= format_job_details(job) %></td>
       +            <td>
       +                        <div class="progress progress-warning progress-striped progress-bar">
       +                                <div class="bar" style="width: <%= job.progress %>%">
       +                                        <span class='progress_pct'><%= job.progress %>%</span>
       +                                </div>
       +                        </div>
       +                </td>
       +                <td><%= format_job_rate(job) %></td>
       +            <td><%= time_ago_in_words(job.created_at) %> ago</td>
       +            <td>
       +                    <% if job.task == "dialer" %>
       +                            <a class="btn" href="<%= view_results_path(job.project,job) %>" rel="tooltip" title="View Current Stats" ><i class="icon-zoom-in"></i></a>
       +                            <% end %>
       +                    <% if job.task == "analysis" and job.details[:scope].to_s != "calls" %>
       +                            <a class="btn" href="<%= view_analyze_path(job.project,job.details[:target_id]||job.id) %>" rel="tooltip" title="View Call Analysis"><i class="icon-eye-open"></i></a>
       +                    <% end %>
       +                        <a class="btn" href="<%= stop_job_path(job) %>" data-confirm="Terminate this job?" rel="nofollow tooltip" title="Terminate Job"><i class="icon-stop"></i></a>
       +                        </td>
       +          </tr>
       +<% end %>
       +        </table>
       +        </div>
       +</div>
       +<% end %>
       +
       +
       +<% if(@inactive_jobs.length > 0) %>
       +
       +<div class="row-fluid">
       +        <div class="span12">
       +        <h2 class='title'>Completed Jobs</h2>
       +
       +        <%= will_paginate @inactive_jobs, :renderer => BootstrapPagination::Rails %>
       +        <table class='table table-striped table-condensed' width='90%'>
       +          <tr>
       +            <th>ID</th>
       +            <th>Task</th>
       +            <th>Status</th>
       +            <th>Rate</th>
       +            <th>Started</th>
       +            <th>Completed</th>
       +          </tr>
       +
       +        <% @inactive_jobs.each do |job|
       +                special = ""
       +                case job.status
       +                when "error"
       +                        special = "error"
       +                when "stopped"
       +                        special = "warning"
       +                end
       +        %>
       +          <tr class='<%= special %>'>
       +            <td><%= job.id %></td>
       +            <td><%= format_job_details(job) %></td>
       +                <td><%= format_job_status(job) %></td>
       +                <td><%= format_job_rate(job) %></td>
       +            <td><%= time_ago_in_words(job.created_at) %> ago</td>
       +            <td><%= job.completed_at ? "after " + time_ago_in_words(Time.at(Time.now.to_i - (job.completed_at.to_f - job.created_at.to_f))) : "incomplete" %></td>
       +          </tr>
       +        <% end %>
       +        </table>
       +        <%= will_paginate @inactive_jobs, :renderer => BootstrapPagination::Rails %>
                </div>
        </div>
       +<% end %>
 (DIR) diff --git a/app/views/shared/graphs/_sparkline.html.erb b/app/views/shared/graphs/_sparkline.html.erb
       @@ -57,8 +57,8 @@
                                }
                        },
                        series: [{
       -                        color:'#666',
       -                        fillColor:'rgba(204,204,204,.25)',
       +                        color:'#BB4607',
       +                        fillColor:'#fcfcfc',
                                data: [ <%= raw(points.join(", ")) %> ]
                        }]
                });
 (DIR) diff --git a/db/migrate/20121228171549_initial_schema.rb b/db/migrate/20121228171549_initial_schema.rb
       @@ -65,7 +65,7 @@ class InitialSchema < ActiveRecord::Migration
                                t.timestamps
                                t.text                        "number", :null => false
                                t.integer                "project_id", :null => false
       -                        t.text                        "type"
       +                        t.text                        "line_type"
                                t.text                        "notes"
                        end
        
 (DIR) diff --git a/db/schema.rb b/db/schema.rb
       @@ -92,7 +92,7 @@ ActiveRecord::Schema.define(:version => 20130113004653) do
            t.datetime "updated_at", :null => false
            t.text     "number",     :null => false
            t.integer  "project_id", :null => false
       -    t.text     "type"
       +    t.text     "line_type"
            t.text     "notes"
          end