Add new files - warvox - Unnamed repository; edit this file 'description' to na… | |
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 |