Introduction
Introduction Statistics Contact Development Disclaimer Help
Add new files - warvox - VoIP based wardialing tool, forked from rapid7/warvox.
Log
Files
Refs
README
---
commit aee6346ab4227e65a5cc0cf26636bd465e72a5cd
parent ef6496ad1da956df421e15ce6c2eadc06044f3d7
Author: HD Moore <[email protected]>
Date: Tue, 1 Jan 2013 21:07:48 -0600
Add new files
Diffstat:
A app/assets/stylesheets/formtastic-… | 46 +++++++++++++++++++++++++++…
A app/controllers/calls_controller.rb | 179 +++++++++++++++++++++++++++++…
A app/controllers/jobs_controller.rb | 115 +++++++++++++++++++++++++++++…
A app/helpers/calls_helper.rb | 2 ++
A app/helpers/jobs_helper.rb | 2 ++
A app/views/calls/analyze.html.erb | 27 +++++++++++++++++++++++++++
A app/views/calls/edit.html.erb | 48 +++++++++++++++++++++++++++++…
A app/views/calls/index.html.erb | 57 +++++++++++++++++++++++++++++…
A app/views/calls/new.html.erb | 43 ++++++++++++++++++++++++++++++
A app/views/calls/show.html.erb | 48 +++++++++++++++++++++++++++++…
A app/views/calls/view.html.erb | 58 ++++++++++++++++++++++++++++++
A app/views/jobs/edit.html.erb | 24 ++++++++++++++++++++++++
A app/views/jobs/index.html.erb | 104 +++++++++++++++++++++++++++++…
A app/views/jobs/new.html.erb | 35 +++++++++++++++++++++++++++++…
A app/views/jobs/new_dialer.html.erb | 21 +++++++++++++++++++++
A app/views/jobs/run.html.erb | 5 +++++
A app/views/jobs/show.html.erb | 39 +++++++++++++++++++++++++++++…
A bin/worker.rb | 91 +++++++++++++++++++++++++++++…
A bin/worker_manager.rb | 194 ++++++++++++++++++++++++++++++
19 files changed, 1138 insertions(+), 0 deletions(-)
---
diff --git a/app/assets/stylesheets/formtastic-overrides.css b/app/assets/style…
@@ -0,0 +1,46 @@
+.help-block {
+ font-size: 13px;
+ font-style: italic;
+}
+
+.control-label {
+ font-weight: bold;
+ font-size: 15px;
+}
+
+.formtastic .stringish input
+{
+ width: auto;
+}
+
+.formtastic .numeric input {
+ width: 5em;
+}
+
+.formtastic .stringish input#project_name
+{
+ width: 500px;
+}
+
+.formtastic .text textarea.project_description {
+ width: 500px;
+}
+
+.formtastic .text textarea.project_includes {
+ width: 200px;
+}
+
+.formtastic input {
+ padding: 4px;
+}
+
+.formtastic textarea {
+ padding: 4px;
+}
+
+.formtastic input.btn {
+ padding-left: 10px;
+ padding-right: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
diff --git a/app/controllers/calls_controller.rb b/app/controllers/calls_contro…
@@ -0,0 +1,179 @@
+class CallsController < ApplicationController
+
+ # GET /calls
+ # GET /calls.xml
+ def index
+ @jobs = Job.where(:status => 'answered').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
+
+ # GET /calls/1/reanalyze
+ def reanalyze
+ Call.update_all(['processed = ?', false], ['job_id = ?', params[:id]…
+ j = Job.find(params[:id])
+ j.processed = false
+ j.save
+
+ redirect_to :action => 'analyze'
+ end
+
+ # GET /calls/1/process
+ # GET /calls/1/process.xml
+ def analyze
+ @job_id = params[:id]
+ @job = Job.find(@job_id)
+
+ if(@job.processed)
+ redirect_to :controller => 'analyze', :action => 'view', :id =…
+ return
+ end
+
+ @dial_data_total = Call.count(
+ :conditions => [ 'job_id = ? and answered = ?', @job_id, true ]
+ )
+
+ @dial_data_done = Call.count(
+ :conditions => [ 'job_id = ? and processed = ?', @job_id, true…
+ )
+
+ ltypes = Call.find( :all, :select => 'DISTINCT line_type', :conditions…
+ res_types = {}
+
+ ltypes.each do |k|
+ next if not k
+ res_types[k.capitalize.to_sym] = Call.count(
+ :conditions => ['job_id = ? and line_type = ?', @job_i…
+ )
+ end
+
+ @lines_by_type = res_types
+
+ @dial_data_todo = Call.where(:job_id => @job_id).paginate(
+ :page => params[:page],
+ :order => 'number ASC',
+ :per_page => 50,
+ :conditions => [ 'answered = ? and processed = ? and busy = ?'…
+ )
+
+ if @dial_data_todo.length > 0
+ res = @job.schedule(:analysis)
+ unless res
+ flash[:error] = "Unable to launch analysis job"
+ end
+ end
+ end
+
+ # GET /calls/1/view
+ # GET /calls/1/view.xml
+ def view
+ @calls = Call.where(:job_id => params[:id]).paginate(
+ :page => params[:page],
+ :order => 'number ASC',
+ :per_page => 30
+ )
+
+ unless @calls and @calls.length > 0
+ redirect_to :action => :index
+ return
+ end
+ @call_results = {
+ :Timeout => Call.count(:conditions =>['job_id = ? and answere…
+ :Busy => Call.count(:conditions =>['job_id = ? and busy = …
+ :Answered => Call.count(:conditions =>['job_id = ? and answere…
+ }
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml { render :xml => @calls }
+ end
+ end
+
+ # GET /calls/1
+ # GET /calls/1.xml
+ def show
+ @call = Call.find(params[:id])
+
+ unless @call
+ redirect_to :action => :index
+ return
+ end
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.xml { render :xml => @call }
+ end
+ end
+
+ # GET /calls/new
+ # GET /calls/new.xml
+ def new
+ @call = Call.new
+
+ respond_to do |format|
+ format.html # new.html.erb
+ format.xml { render :xml => @call }
+ end
+ end
+
+ # GET /calls/1/edit
+ def edit
+ @call = Call.find(params[:id])
+ end
+
+ # POST /calls
+ # POST /calls.xml
+ def create
+ @call = Call.new(params[:call])
+
+ respond_to do |format|
+ if @call.save
+ flash[:notice] = 'Call was successfully created.'
+ format.html { redirect_to(@call) }
+ format.xml { render :xml => @call, :status => :created, :location => …
+ else
+ format.html { render :action => "new" }
+ format.xml { render :xml => @call.errors, :status => :unprocessable_e…
+ end
+ end
+ end
+
+ # PUT /calls/1
+ # PUT /calls/1.xml
+ def update
+ @call = Call.find(params[:id])
+
+ respond_to do |format|
+ if @call.update_attributes(params[:call])
+ flash[:notice] = 'Call was successfully updated.'
+ format.html { redirect_to(@call) }
+ format.xml { head :ok }
+ else
+ format.html { render :action => "edit" }
+ format.xml { render :xml => @call.errors, :status => :unprocessable_e…
+ end
+ end
+ end
+
+ # DELETE /calls/1
+ # DELETE /calls/1.xml
+ def destroy
+
+ @job = Job.find(params[:id])
+ @job.destroy
+
+ respond_to do |format|
+ format.html { redirect_to :action => 'index' }
+ format.xml { head :ok }
+ end
+ end
+
+end
diff --git a/app/controllers/jobs_controller.rb b/app/controllers/jobs_controll…
@@ -0,0 +1,115 @@
+class JobsController < ApplicationController
+
+ def index
+ @submitted_jobs = Job.where(:status => ['submitted', 'scheduled'], :…
+ @active_jobs = Job.where(:status => 'running', :completed_at => nil)
+ @inactive_jobs = Job.where('status NOT IN (?) OR completed_at IS NULL'…
+ :page => params[:page],
+ :order => 'id DESC',
+ :per_page => 30
+ )
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml { render :xml => @active_jobs + @submitted_jobs }
+ end
+ end
+
+
+ def new_dialer
+ @job = Job.new
+ if @project
+ @job.project = @project
+ else
+ @job.project = Project.last
+ end
+
+ respond_to do |format|
+ format.html # new.html.erb
+ format.xml { render :xml => @job }
+ end
+ end
+
+ def dialer
+ @job = Job.new(params[:job])
+ @job.created_by = current_user.login
+ @job.task = 'dialer'
+ @job.range.gsub!(/[^0-9X:,\n]/, '')
+ @job.cid_mask.gsub!(/[^0-9X]/, '') if @job.cid_mask != "SELF"
+
+ if @job.range_file.to_s != ""
+ @job.range = @job.range_file.read.gsub(/[^0-9X:,\n]/, '')
+ end
+
+ respond_to do |format|
+ if @job.schedule
+ flash[:notice] = 'Job was successfully created.'
+ format.html { redirect_to :action => :index }
+ format.xml { render :xml => @job, :status => :created }
+ else
+ format.html { render :action => "new_dialer" }
+ format.xml { render :xml => @job.errors, :status => :unprocessable_en…
+ end
+ end
+ end
+
+ def stop
+ @job = Job.find(params[:id])
+ @job.stop
+ flash[:notice] = "Job has been cancelled"
+ redirect_to :action => 'index'
+ end
+
+ def create
+
+ @job = Job.new(params[:job])
+
+ if(Provider.find_all_by_enabled(true).length == 0)
+ @job.errors.add(:base, "No providers have been configured or e…
+ respond_to do |format|
+ format.html { render :action => "new" }
+ format.xml { render :xml => @job.errors, :status => :…
+ end
+ return
+ end
+
+ @job.status = 'submitted'
+ @job.progress = 0
+ @job.started_at = nil
+ @job.completed_at = nil
+ @job.range = @job_range.gsub(/[^0-9X:,\n]/m, '')
+ @job.cid_mask = @cid_mask.gsub(/[^0-9X]/m, '') if @job.cid_mask !=…
+
+ if(@job.range_file.to_s != "")
+ @job.range = @job.range_file.read.gsub(/[^0-9X:,\n]/m, '')
+ end
+
+ 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 => @…
+ else
+ format.html { render :action => "new" }
+ format.xml { render :xml => @job.errors, :status => :unprocessable_en…
+ end
+ end
+ end
+
+ def destroy
+ @job = Job.find(params[:id])
+ @job.destroy
+
+ respond_to do |format|
+ format.html { redirect_to(jobs_url) }
+ format.xml { head :ok }
+ end
+ end
+
+end
diff --git a/app/helpers/calls_helper.rb b/app/helpers/calls_helper.rb
@@ -0,0 +1,2 @@
+module CallsHelper
+end
diff --git a/app/helpers/jobs_helper.rb b/app/helpers/jobs_helper.rb
@@ -0,0 +1,2 @@
+module JobsHelper
+end
diff --git a/app/views/calls/analyze.html.erb b/app/views/calls/analyze.html.erb
@@ -0,0 +1,27 @@
+<% if @dial_data_todo.length > 0 %>
+
+<h1 class='title'>
+ Analyzing Audio for <%= @dial_data_total-@dial_data_done %> of <%= @di…
+</h1>
+
+<table width='100%' align='center' border=0 cellspacing=0 cellpadding=6>
+<tr>
+<% if @dial_data_done > 0 %>
+ <td align='center'>
+ <%= render :partial => 'shared/graphs/lines_by_type' %>
+ </td>
+<% end %>
+</tr>
+</table>
+
+<script language="javascript">
+ setTimeout("location.reload(true);", 10000);
+</script>
+
+<% else %>
+
+<h1 class='title'>No Completed Calls Found</h1>
+
+<% end %>
+
+<br />
diff --git a/app/views/calls/edit.html.erb b/app/views/calls/edit.html.erb
@@ -0,0 +1,48 @@
+<h1>Editing call</h1>
+
+<%= form_for(@call) do |f| %>
+ <%= f.error_messages %>
+
+ <p>
+ <%= f.label :number %><br />
+ <%= f.text_field :number %>
+ </p>
+ <p>
+ <%= f.label :cid %><br />
+ <%= f.text_field :cid %>
+ </p>
+ <p>
+ <%= f.label :job_id %><br />
+ <%= f.text_field :job_id %>
+ </p>
+ <p>
+ <%= f.label :provider %><br />
+ <%= f.text_field :provider %>
+ </p>
+ <p>
+ <%= f.label :completed %><br />
+ <%= f.check_box :completed %>
+ </p>
+ <p>
+ <%= f.label :busy %><br />
+ <%= f.check_box :busy %>
+ </p>
+ <p>
+ <%= f.label :seconds %><br />
+ <%= f.text_field :seconds %>
+ </p>
+ <p>
+ <%= f.label :ringtime %><br />
+ <%= f.text_field :ringtime %>
+ </p>
+ <p>
+ <%= f.label :rawfile %><br />
+ <%= f.text_field :rawfile %>
+ </p>
+ <p>
+ <%= f.submit "Update" %>
+ </p>
+<% end %>
+
+<%= link_to 'Show', @call %> |
+<%= link_to 'Back', calls_path(@project) %>
diff --git a/app/views/calls/index.html.erb b/app/views/calls/index.html.erb
@@ -0,0 +1,57 @@
+<% if @jobs.length > 0 %>
+<h1 class='title'>Completed Jobs</h1>
+
+<%= raw(will_paginate @jobs) %>
+<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>Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+
+<% @jobs.sort{|a,b| b.id <=> a.id}.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…
+ "/" +
+ Call.count(:conditions => ['job_id = ?', job.id]).to_s
+ )%></td>
+ <td><%=h job.started_at.localtime.strftime("%Y-%m-%d %H:%M:%S") %></td>
+
+ <td>
+ <a class="btn btn-mini" href="<%= view_call_path(@project,job) %>" rel…
+
+ <% if(job.analysis_completed_at) %>
+ <a class="btn btn-mini" href="<%= analyze_call_path(@p…
+ <a class="btn btn-mini" href="<%= reanalyze_call_path(…
+ <% else %>
+ <a class="btn btn-mini" href="<%= analyze_call_path(@p…
+ <% end %>
+
+ <a class="btn btn-mini" href="<%= call_path(@project,job) %>" data…
+ </td>
+ </tr>
+
+<% end %>
+</tbody>
+</table>
+
+<%= raw(will_paginate @jobs) %>
+
+<% else %>
+
+<h1 class='title'>No Completed Jobs</h1>
+<br/>
+
+<% end %>
+
+<a class="btn" href="<%= new_dialer_job_path %>"><i class="icon-plus"></i> Sta…
diff --git a/app/views/calls/new.html.erb b/app/views/calls/new.html.erb
@@ -0,0 +1,43 @@
+<h1>New call</h1>
+
+<%= form_for(@call) do |f| %>
+ <%= f.error_messages %>
+
+ <p>
+ <%= f.label :number %><br />
+ <%= f.text_field :number %>
+ </p>
+ <p>
+ <%= f.label :job_id %><br />
+ <%= f.text_field :job_id %>
+ </p>
+ <p>
+ <%= f.label :provider %><br />
+ <%= f.text_field :provider %>
+ </p>
+ <p>
+ <%= f.label :completed %><br />
+ <%= f.check_box :completed %>
+ </p>
+ <p>
+ <%= f.label :busy %><br />
+ <%= f.check_box :busy %>
+ </p>
+ <p>
+ <%= f.label :seconds %><br />
+ <%= f.text_field :seconds %>
+ </p>
+ <p>
+ <%= f.label :ringtime %><br />
+ <%= f.text_field :ringtime %>
+ </p>
+ <p>
+ <%= f.label :rawfile %><br />
+ <%= f.text_field :rawfile %>
+ </p>
+ <p>
+ <%= f.submit "Create" %>
+ </p>
+<% end %>
+
+<%= link_to 'Back', calls_path(@project) %>
diff --git a/app/views/calls/show.html.erb b/app/views/calls/show.html.erb
@@ -0,0 +1,48 @@
+<p>
+ <b>Number:</b>
+ <%=h @call.number %>
+</p>
+
+<p>
+ <b>CallerID:</b>
+ <%=h @call.cid %>
+</p>
+
+<p>
+ <b>Dial job:</b>
+ <%=h @call.job_id %>
+</p>
+
+<p>
+ <b>Provider:</b>
+ <%=h @call.provider %>
+</p>
+
+<p>
+ <b>Completed:</b>
+ <%=h @call.completed %>
+</p>
+
+<p>
+ <b>Busy:</b>
+ <%=h @call.busy %>
+</p>
+
+<p>
+ <b>Seconds:</b>
+ <%=h @call.seconds %>
+</p>
+
+<p>
+ <b>Ringtime:</b>
+ <%=h @call.ringtime %>
+</p>
+
+<p>
+ <b>Rawfile:</b>
+ <%=h @call.rawfile %>
+</p>
+
+
+<%= link_to 'Edit', edit_call_path(@project, @call) %> |
+<%= link_to 'Back', calls_path(@project) %>
diff --git a/app/views/calls/view.html.erb b/app/views/calls/view.html.erb
@@ -0,0 +1,58 @@
+<% if @calls %>
+
+
+<h1 class='title'>Dial Results for Job <%=@calls[0].job_id%></h1>
+
+<%= raw(will_paginate @calls) %>
+<table width='100%' align='center' border=0 cellspacing=0 cellpadding=6>
+<tr>
+ <td align='center'>
+ <%= render :partial => 'shared/graphs/call_results' %>
+ </td>
+</tr>
+</table>
+
+<br/>
+
+<table class='table table-striped table-bordered' width='90%' id='results'>
+ <thead>
+ <tr>
+ <th>Number</th>
+ <th>CallerID</th>
+ <th>Provider</th>
+ <th>Completed</th>
+ <th>Busy</th>
+ <th>Seconds</th>
+ <th>Ring Time</th>
+ </tr>
+ </thead>
+ <tbody>
+<% for call in @calls.sort{|a,b| a.number <=> b.number } %>
+ <tr>
+ <td><%= call.number %></td>
+ <td><%= call.cid %></td>
+ <td><%= call.provider.name %></td>
+ <td><%= call.completed %></td>
+ <td><%= call.busy %></td>
+ <td><%= call.seconds %></td>
+ <td><%= call.ringtime.to_i %></td>
+ </tr>
+<% end %>
+ </tbody>
+</table>
+<%= raw(will_paginate @calls) %>
+
+<% else %>
+
+<h1 class='title'>No Dial Results</h1>
+
+<% end %>
+<br />
+
+<%= javascript_tag do %>
+// For fixed width containers
+$('#results').dataTable({
+ "sDom": "<'row'<'span6'l><'span6'f>r>t<'row'<'span6'i><'span6'p>>",
+ "sPaginationType": "bootstrap"
+});
+<% end %>
diff --git a/app/views/jobs/edit.html.erb b/app/views/jobs/edit.html.erb
@@ -0,0 +1,24 @@
+<h1 class='title'>Modify Job</h1>
+
+<%= form_for(@job) do |f| %>
+ <%= f.error_messages %>
+
+ <p>
+ <%= f.label :range %><br />
+ <%= f.text_area :range, :size => "35x5" %>
+ </p>
+ <p>
+ <%= f.label :seconds %><br />
+ <%= f.text_field :seconds %>
+ </p>
+ <p>
+ <%= f.label :lines %><br />
+ <%= f.text_field :lines %>
+ </p>
+ <p>
+ <%= f.submit "Update" %>
+ </p>
+<% end %>
+
+<%= link_to 'Show', @job %> |
+<%= link_to 'Back', jobs_path %>
diff --git a/app/views/jobs/index.html.erb b/app/views/jobs/index.html.erb
@@ -0,0 +1,104 @@
+<% if(@submitted_jobs.length > 0) %>
+
+<h1 class='title'>Submitted Jobs</h1>
+
+<table class='table table-striped table-bordered' width='90%'>
+ <tr>
+ <th>ID</th>
+ <th>Task</th>
+ <th>Status</th>
+ <th>Submitted Time</th>
+ <th>Actions</th>
+ </tr>
+
+<% @submitted_jobs.each do |job| %>
+ <tr>
+ <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>
+ <a class="btn btn-mini" href="<%= job_path(job) %>" data-confi…
+ </td>
+ </tr>
+<% end %>
+</table>
+<br />
+<% end %>
+
+<% if(@active_jobs.length > 0) %>
+
+<h1 class='title'>Active Jobs</h1>
+
+<table class='table table-striped table-bordered' width='90%'>
+ <tr>
+ <th>ID</th>
+ <th>Task</th>
+ <th>Progress</th>
+ <th>Submitted Time</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 valign='center'>
+ <div class="progress progress-success progress-striped">
+ <div class="bar" style="width: <%= job.progress %>%">
+ <span class='progress_pct'><%= job.progress %>…
+ </div>
+ </div>
+ </td>
+ <td><%= job.created_at.localtime.strftime("%Y-%m-%d %H:%M:%S %Z") %></td>
+ <td>
+ <a class="btn btn-mini" href="<%= stop_job_path(job) %>" data-…
+ </td>
+ </tr>
+<% end %>
+</table>
+<br />
+
+<% end %>
+
+<% if (@active_jobs.length + @submitted_jobs.length == 0) %>
+<h1 class='title'>No Active Jobs</h1>
+<% end %>
+
+<a class="btn" href="<%= new_dialer_job_path %>"><i class="icon-plus"></i> Sta…
+
+<% if(@inactive_jobs.length > 0) %>
+<br/><br/>
+<h1 class='title'>Inactive Jobs</h1>
+
+<%= raw(will_paginate @inactive_jobs) %>
+<table class='table table-striped table-bordered' width='90%'>
+ <tr>
+ <th>ID</th>
+ <th>Task</th>
+ <th>Status</th>
+ <th>Created</th>
+ <th>Completed</th>
+ <th>Project</th>
+ </tr>
+
+<% @inactive_jobs.each do |job| %>
+ <tr class='active_job_row'>
+ <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 %…
+ <td><%= link_to( truncate(job.project.name, :length => 25).html_safe, proj…
+ </tr>
+<% end %>
+</table>
+<%= raw(will_paginate @inactive_jobs) %>
+<br />
+
+<script language="javascript">
+ setTimeout("location.reload(true);", 20000);
+</script>
+
+<% end %>
diff --git a/app/views/jobs/new.html.erb b/app/views/jobs/new.html.erb
@@ -0,0 +1,35 @@
+<h1 class='title'>Submit a New Job</h1>
+
+<%= form_for(@job, :html => { :multipart => true }) do |f| %>
+ <%= f.error_messages %>
+ <p>
+ <%= f.label :range, 'Specify target telephone range(s) (1-123-456-7890 or …
+ <%= f.text_area :range, :size => "35x5" %>
+ </p>
+
+ <p>
+ <%= f.label :range_file, 'Or upload a file containing the target ranges' %…
+ <%= f.file_field :range_file %>
+ </p>
+
+ <p>
+ <%= f.label :seconds, 'Seconds of audio to capture' %><br />
+ <%= f.text_field :seconds, :value => 53 %>
+ </p>
+
+ <p>
+ <%= f.label :lines, 'Maximum number of outgoing lines' %><br />
+ <%= f.text_field :lines, :value => 10 %>
+ </p>
+
+ <p>
+ <%= f.label :lines, 'The source Caller ID range (1-555-555-55XX or SELF)' …
+ <%= f.text_field :cid_mask, :value => '1-123-456-XXXX' %>
+ </p>
+
+ <p>
+ <%= f.submit "Create" %>
+ </p>
+<% end %>
+
+<%= link_to 'Back', jobs_path %>
diff --git a/app/views/jobs/new_dialer.html.erb b/app/views/jobs/new_dialer.htm…
@@ -0,0 +1,21 @@
+<h1 class='title'>Dialer Job</h1>
+
+<%= semantic_form_for(@job, :url => dialer_job_path, :html => { :multipart => …
+ <%= f.input :project, :as => :select %>
+
+ <%= f.input :range, :as => :text,
+ :label => 'Target telephone range(s)',
+ :hint => 'Examples: 1-123-456-7890 / 1-123-456-XXXX / 1-123-30…
+ :input_html => { :rows => 3, :cols => 80, :autofocus => true }
+ %>
+ <%= f.input :range_file, :as => :file, :label => 'Or upload a file con…
+ <%= f.input :seconds, :as => :number, :label => 'Seconds of audio to …
+ <%= f.input :lines, :as => :number, :label => 'Maximum number of outg…
+ <%= f.input :cid_mask, :as => :string, :label => 'The source Caller I…
+
+ <%= f.action :submit, :label => 'Create', :button_html => { :class => …
+
+ <a class="btn btn-link" href="<%= jobs_path %>" rel="tooltip" title="R…
+<% end %>
+
+<%= set_focus('job_range') %>
diff --git a/app/views/jobs/run.html.erb b/app/views/jobs/run.html.erb
@@ -0,0 +1,5 @@
+<h1 class='title'>Run Job</h1>
+
+Running this job...<br/>
+
+<%= link_to 'Back', jobs_path %>
diff --git a/app/views/jobs/show.html.erb b/app/views/jobs/show.html.erb
@@ -0,0 +1,39 @@
+<h1 class='title'>Show Job</h1>
+<p>
+ <b>Range:</b>
+ <%=h @job.range %>
+</p>
+
+<p>
+ <b>Seconds:</b>
+ <%=h @job.seconds %>
+</p>
+
+<p>
+ <b>Lines:</b>
+ <%=h @job.lines %>
+</p>
+
+<p>
+ <b>Status:</b>
+ <%=h @job.status %>
+</p>
+
+<p>
+ <b>Progress:</b>
+ <%=h @job.progress %>
+</p>
+
+<p>
+ <b>Started at:</b>
+ <%=h @job.started_at %>
+</p>
+
+<p>
+ <b>Completed at:</b>
+ <%=h @job.completed_at %>
+</p>
+
+
+<%= link_to 'Edit', edit_job_path(@job) %> |
+<%= link_to 'Back', jobs_path %>
diff --git a/bin/worker.rb b/bin/worker.rb
@@ -0,0 +1,91 @@
+#!/usr/bin/env ruby
+###################
+
+#
+# Load the library path
+#
+base = __FILE__
+while File.symlink?(base)
+ base = File.expand_path(File.readlink(base), File.dirname(base))
+end
+$:.unshift(File.join(File.expand_path(File.dirname(base)), '..', 'lib'))
+
+require 'warvox'
+require 'fileutils'
+
+
+ENV['RAILS_ENV'] ||= 'production'
+$:.unshift(File.join(File.expand_path(File.dirname(base)), '..'))
+
+@task = nil
+@job = nil
+
+def usage
+ $stderr.puts "Usage: #{$0} [JID]"
+ exit(1)
+end
+
+def stop
+ if @task
+ @task.stop() rescue nil
+ end
+ if @job
+ Job.update_all({ :status => 'stopped', :completed_at => Time.n…
+ end
+ exit(0)
+end
+
+#
+# Script
+#
+
+jid = ARGV.shift() || usage()
+if (jid and jid =="-h") or (! jid)
+ usage()
+end
+
+require 'config/boot'
+require 'config/environment'
+
+trap("SIGTERM") { stop() }
+
+jid = jid.to_i
+
+@job = Job.where(:id => jid).first
+
+unless @job
+ $stderr.puts "Error: Specified job not found"
+ WarVOX::Log.warn("Worker rejected invalid Job #{jid}")
+ exit(1)
+end
+
+$0 = "warvox worker: #{jid} "
+
+Job.update_all({ :started_at => Time.now.utc, :status => 'running'}, { :id => …
+
+args = Marshal.load(@job.args) rescue {}
+
+
+WarVOX::Log.debug("Worker #{@job.id} #{@job.task} is running #{@job.task} with…
+
+begin
+
+case @job.task
+when 'dialer'
+ @task = WarVOX::Jobs::Dialer.new(@job.id, args)
+ @task.start
+when 'analysis'
+ @task = WarVOX::Jobs::Analysis.new(@job.id, args)
+ @task.start
+else
+ Job.update_all({ :error => 'unsupported', :status => 'error' }, { :id …
+end
+
[email protected]_progress(100)
+
+rescue ::SignalException, ::SystemExit
+ raise $!
+rescue ::Exception => e
+ WarVOX::Log.warn("Worker #{@job.id} #{@job.task} threw an exception: #…
+ Job.update_all({ :error => "Exception: #{e.class} #{e}", :status => 'e…
+end
diff --git a/bin/worker_manager.rb b/bin/worker_manager.rb
@@ -0,0 +1,194 @@
+#!/usr/bin/env ruby
+###################
+
+#
+# Load the library path
+#
+base = __FILE__
+while File.symlink?(base)
+ base = File.expand_path(File.readlink(base), File.dirname(base))
+end
+$:.unshift(File.join(File.expand_path(File.dirname(base)), '..', 'lib'))
+
+@worker_path = File.expand_path(File.join(File.dirname(base), "worker.rb"))
+
+require 'warvox'
+require 'socket'
+
+ENV['RAILS_ENV'] ||= 'production'
+
+$:.unshift(File.join(File.expand_path(File.dirname(base)), '..'))
+require 'config/boot'
+require 'config/environment'
+
+
+@jobs = []
+
+def stop
+ WarVOX::Log.info("Worker Manager is terminating due to signal")
+
+ unless @jobs.length > 0
+ exit(0)
+ end
+
+ # Update the database
+ Job.update_all({ :status => "stopped", :completed_at => Time.now.utc},…
+
+ # Signal running jobs to shut down
+ @jobs.map{|j| Process.kill("TERM", j[:pid]) rescue nil }
+
+ # Sleep for five seconds
+ sleep(5)
+
+ # Forcibly kill any remaining job processes
+ @jobs.map{|j| Process.kill("KILL", j[:pid]) rescue nil }
+
+ exit(0)
+end
+
+
+def clear_zombies
+ while ( r = Process.waitpid(-1, Process::WNOHANG) rescue nil ) do
+ end
+end
+
+def schedule_job(j)
+ WarVOX::Log.debug("Worker Manager is launching job #{j.id}")
+ @jobs << {
+ :id => j.id,
+ :pid => Process.fork { exec("#{@worker_path} #{j.id}") }
+ }
+end
+
+def stop_cancelled_jobs
+ jids = []
+ @jobs.each do |x|
+ jids << x[:id]
+ end
+
+ return if jids.length == 0
+ Job.where(:status => 'cancelled', :id => jids).find_each do |j|
+ job = @jobs.select{ |o| o[:id] == j.id }.first
+ next unless job and job[:pid]
+ pid = job[:pid]
+
+ WarVOX::Log.debug("Worker Manager is killing job #{j.id} with …
+ Process.kill('TERM', pid)
+ end
+end
+
+def clear_completed_jobs
+ dead_pids = []
+ dead_jids = []
+
+ @jobs.each do |j|
+ alive = Process.kill(0, j[:pid]) rescue nil
+ next if alive
+ dead_pids << j[:pid]
+ dead_jids << j[:id]
+ end
+
+ return unless dead_jids.length > 0
+
+ WarVOX::Log.debug("Worker Manager is clearing #{dead_pids.length} comp…
+
+ @jobs = @jobs.reject{|x| dead_pids.include?( x[:pid] ) }
+
+ # Mark failed/crashed jobs as completed
+ Job.update_all({ :completed_at => Time.now.utc }, { :id => dead_jids, …
+end
+
+def clear_stale_jobs
+ jids = @jobs.map{|x| x[:id] }
+ stale = nil
+
+ if jids.length > 0
+ stale = Job.where("completed_at IS NULL AND locked_by LIKE ? A…
+ else
+ stale = Job.where("completed_at IS NULL AND locked_by LIKE ?",…
+ end
+
+ dead = []
+ pids = {}
+
+ # Extract the PID from the locked_by cookie for each job
+ stale.each do |j|
+ host, pid, uniq = j.locked_by.to_s.split("^", 3)
+ next unless (pid and uniq)
+ pids[pid] ||= []
+ pids[pid] << j
+ end
+
+ # Identify dead processes (must be same user or root)
+ pids.keys.each do |pid|
+ alive = Process.kill(0, pid.to_i) rescue nil
+ next if alive
+ pids[pid].each do |j|
+ dead << j.id
+ end
+ end
+
+ # Mark these jobs as abandoned
+ if dead.length > 0
+ WarVOX::Log.debug("Worker Manager is marking #{dead.length} jo…
+ Job.update_all({ :locked_by => nil, :status => 'abandoned' }, …
+ end
+end
+
+def schedule_submitted_jobs
+ loop do
+ # Look for a candidate job with no current owner
+ j = Job.where(:status => 'submitted', :locked_by => nil).limi…
+ return unless j
+
+ # Try to get a lock on this job
+ Job.update_all({:locked_by => @cookie, :locked_at => Time.now.…
+
+ # See if we actually got the lock
+ j = Job.where(:id => j.id, :status => 'scheduled', :locked_by…
+
+ # Try again if we lost the race,
+ next unless j
+
+ # Hurray, we got a job, run it
+ schedule_job(j)
+
+ return true
+ end
+end
+
+#
+# Main
+#
+
+trap("SIGINT") { stop() }
+trap("SIGTERM") { stop() }
+
+@cookie = Socket.gethostname + "^" + $$.to_s + "^" + sprintf("%.8x", rand(0x…
+@max_jobs = 3
+
+
+WarVOX::Log.info("Worker Manager initialized with cookie #{@cookie}")
+
+loop do
+ $0 = "warvox manager: #{@jobs.length} active jobs (cookie : #{@cookie}…
+
+ # Clear any zombie processes
+ clear_zombies()
+
+ # Clear any completed jobs
+ clear_completed_jobs()
+
+ # Stop any jobs cancelled by the user
+ stop_cancelled_jobs()
+
+ # Clear locks on any stale jobs from this host
+ clear_stale_jobs()
+
+ while @jobs.length < @max_jobs
+ break unless schedule_submitted_jobs
+ end
+
+ # Sleep between 3-8 seconds before re-entering the loop
+ sleep(rand(5) + 3)
+end
You are viewing proxied material from jay.scot. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.