delayed_job_monitor 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.DS_Store +0 -0
- data/.gitignore +18 -0
- data/Gemfile +23 -0
- data/LICENSE.txt +22 -0
- data/README.md +57 -0
- data/Rakefile +1 -0
- data/config.yml +29 -0
- data/delayed_job_monitor.gemspec +18 -0
- data/delayed_job_monitor_app.rb +7 -0
- data/lib/.DS_Store +0 -0
- data/lib/delayed_job_monitor/.DS_Store +0 -0
- data/lib/delayed_job_monitor/application/.DS_Store +0 -0
- data/lib/delayed_job_monitor/application/app.rb +187 -0
- data/lib/delayed_job_monitor/application/public/images/poll.png +0 -0
- data/lib/delayed_job_monitor/application/public/javascripts/application.js +64 -0
- data/lib/delayed_job_monitor/application/public/javascripts/jquery-1.7.1.min.js +4 -0
- data/lib/delayed_job_monitor/application/public/javascripts/jquery.relatize_date.js +117 -0
- data/lib/delayed_job_monitor/application/public/stylesheets/reset.css +44 -0
- data/lib/delayed_job_monitor/application/public/stylesheets/style.css +150 -0
- data/lib/delayed_job_monitor/application/views/enqueued.haml +10 -0
- data/lib/delayed_job_monitor/application/views/error.haml +2 -0
- data/lib/delayed_job_monitor/application/views/failed.haml +14 -0
- data/lib/delayed_job_monitor/application/views/job.haml +43 -0
- data/lib/delayed_job_monitor/application/views/layout.haml +23 -0
- data/lib/delayed_job_monitor/application/views/next_more.haml +6 -0
- data/lib/delayed_job_monitor/application/views/overview.haml +110 -0
- data/lib/delayed_job_monitor/application/views/pending.haml +9 -0
- data/lib/delayed_job_monitor/application/views/queue.haml +13 -0
- data/lib/delayed_job_monitor/application/views/servers.haml +16 -0
- data/lib/delayed_job_monitor/application/views/workers.haml +15 -0
- data/lib/delayed_job_monitor/application/views/working.haml +9 -0
- data/lib/delayed_job_monitor/job.rb +31 -0
- data/lib/delayed_job_monitor/server.rb +77 -0
- data/lib/delayed_job_monitor/version.rb +3 -0
- data/lib/delayed_job_monitor/worker.rb +10 -0
- data/lib/delayed_job_monitor.rb +85 -0
- metadata +81 -0
data/.DS_Store
ADDED
Binary file
|
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
#CORE GEMS
|
3
|
+
gem "sinatra"
|
4
|
+
gem "haml"
|
5
|
+
gem 'activerecord'
|
6
|
+
gem 'delayed_job'
|
7
|
+
gem 'rdoc'
|
8
|
+
gem 'hashie'
|
9
|
+
#gem 'request-log-analyzer'
|
10
|
+
#DATABASE ADAPTERS
|
11
|
+
gem 'pg'
|
12
|
+
gem 'sqlite3'
|
13
|
+
#gem 'mongoid' #not yet supported
|
14
|
+
|
15
|
+
group :development do
|
16
|
+
gem "pry"
|
17
|
+
gem 'rspec'
|
18
|
+
gem "shoulda"
|
19
|
+
gem "bundler"
|
20
|
+
gem "jeweler"
|
21
|
+
gem "simplecov"
|
22
|
+
gem "rack-test"
|
23
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Dan Barrett
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
========
|
2
|
+
DelayedJobMonitor
|
3
|
+
========
|
4
|
+
|
5
|
+
**WARNING!!!!**
|
6
|
+
* This gem is extremely rough. There are no tests of any kind. Use at your own risk!
|
7
|
+
|
8
|
+
Delayed Jobs Administration for the Masses!
|
9
|
+
|
10
|
+
Built for/by [Contently](http://www.contently.com) by [thoughpunch](http://www.about.me/thoughtpunch)
|
11
|
+
|
12
|
+
## Description
|
13
|
+
|
14
|
+
DelayedJobMonitor is a Sinatra-based web front-end for managing [Delayed Jobs](https://github.com/collectiveidea/delayed_job) heavily inspired by the excellent [delayed_job_web](https://github.com/ejschmitt/delayed_job_web) gem.
|
15
|
+
|
16
|
+
What makes DelayedJobMonitor unique is that it can be run in one of two modes. 'Gem' mode runs the mounted Sinatra app inside your Rails app via a route. 'Stand Alone' mode can be run from the same server or even a different location, provided that you configure the database settings to connect to the instance that is storing the delayed jobs. It's a Sinatra app, it's a Gem, it's a Sinatra app inside a gem!
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
gem install 'delayed_job_monitor'
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
**Gem Mode**
|
25
|
+
* Mount the Sinatra app in your route.rb file like so:
|
26
|
+
* ```match "/jobs" => DelayedJobMonitor::App, :anchor => false```
|
27
|
+
|
28
|
+
**Stand Alone Mode**
|
29
|
+
* Cloning the entire repo into a directory and run the following command will start the stand-alone Sinatra app.
|
30
|
+
* ``ruby delayed_job_monitor_app.rb```
|
31
|
+
|
32
|
+
## RoadMap
|
33
|
+
|
34
|
+
The overall goal is to allow full management of Delayed Jobs workers and tasks from both inside and outside a Rails App.
|
35
|
+
To do so *might* require stubbing enough Rails functionality that Delayed::Worker and Delayed::Command can run without error.
|
36
|
+
|
37
|
+
1. Make fully functional as stand alone app
|
38
|
+
1. Ensure can use any ActiveRecord-supported database
|
39
|
+
2. Add PID/Daemon monitoring for Delayed Job Deamons
|
40
|
+
1. Ability to restart jobs if not running from outside Rails app
|
41
|
+
3. Delayed Job log parsing/tailing
|
42
|
+
1. Tail all delayed_job logs in the view via JS [like so](http://dojo4.com/blog/easy-cheasy-realtime-log-tailing-in-a-rails-admin-view)
|
43
|
+
4. Ability to edit jobs on the fly, individually or in bulk
|
44
|
+
1. Change PID, queue, run_at
|
45
|
+
5. Delayed Jobs statistics and processing numbers
|
46
|
+
1. Store jobs total and jobs per second while app is running, maybe store in YAML?
|
47
|
+
2. Use request-log-analyzer for past dj stats (Note: Current version can not parse logs from Delayed Job v3.0 and later)
|
48
|
+
3. Realtime Graphs and charts
|
49
|
+
6. Write Tests
|
50
|
+
1. Integration tests for Sinatra App, unit tests for all modules and classes.
|
51
|
+
2. Version lock all gems after testing
|
52
|
+
|
53
|
+
## Contributing
|
54
|
+
|
55
|
+
1. Fork it
|
56
|
+
2. Writer tests or extend functionality from the roadmap
|
57
|
+
3. Submit a pull request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/config.yml
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# DELAYED JOB ADMIN CONFIGURATION
|
2
|
+
|
3
|
+
#DELAYED JOB WORKER MACHINES
|
4
|
+
# - Configure SSH connection info for the seperates hosts that are working delayed jobs
|
5
|
+
# - To add additional host connections, simply add another nested entry below
|
6
|
+
# - The DelayedJobMonitor::Worker module will establish SSH connections to each
|
7
|
+
# one to monitor, start, and stop delayed_job workers.
|
8
|
+
#DELAYED JOB SERVER
|
9
|
+
# - Configure an ActiveRecord connection to the database that is hosting the delay_jobs database
|
10
|
+
# - Only 1 DB connection is supported at this time.
|
11
|
+
|
12
|
+
application:
|
13
|
+
name: #used to parse Rails path, log paths, etc
|
14
|
+
email: #email upon failures,changes,etc
|
15
|
+
hosts:
|
16
|
+
-
|
17
|
+
name: # 'nickname' of host
|
18
|
+
rails_path: # path of the Rails app on host machine, assumes '/var/www/RAILS_APP' by default
|
19
|
+
host: # domain name of host for SSH connection
|
20
|
+
username: # SSH Username
|
21
|
+
password: # SSH Password
|
22
|
+
database:
|
23
|
+
host:
|
24
|
+
port:
|
25
|
+
adapter:
|
26
|
+
database:
|
27
|
+
username:
|
28
|
+
password:
|
29
|
+
template:
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'delayed_job_monitor/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "delayed_job_monitor"
|
8
|
+
gem.version = DelayedJobMonitor::VERSION
|
9
|
+
gem.authors = ["Dan Barrett"]
|
10
|
+
gem.email = ["dbarrett83@gmail.com"]
|
11
|
+
gem.description = "An Adminstrative tool for Delayed Jobs"
|
12
|
+
gem.summary = "Manage all your delayed jobs"
|
13
|
+
gem.homepage = "http://www.contently.com"
|
14
|
+
gem.files = `git ls-files`.split($/)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
end
|
data/lib/.DS_Store
ADDED
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'sinatra/base'
|
3
|
+
require 'haml'
|
4
|
+
|
5
|
+
module DelayedJobMonitor
|
6
|
+
class App < Sinatra::Base
|
7
|
+
set :root, File.dirname(__FILE__)
|
8
|
+
set :static, true
|
9
|
+
set :public_folder, File.expand_path('../public', __FILE__)
|
10
|
+
set :views, File.expand_path('../views', __FILE__)
|
11
|
+
set :haml, { :format => :html5 }
|
12
|
+
set :port, 4567
|
13
|
+
|
14
|
+
def delayed_job
|
15
|
+
begin
|
16
|
+
DelayedJobMonitor::Job
|
17
|
+
rescue
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
############################## SINTRA APP ################################
|
23
|
+
def current_page
|
24
|
+
url_path request.path_info.sub('/','')
|
25
|
+
end
|
26
|
+
|
27
|
+
def start
|
28
|
+
params[:start].to_i
|
29
|
+
end
|
30
|
+
|
31
|
+
def per_page
|
32
|
+
25
|
33
|
+
end
|
34
|
+
|
35
|
+
def url_path(*path_parts)
|
36
|
+
[ path_prefix, path_parts ].join("/").squeeze('/')
|
37
|
+
end
|
38
|
+
alias_method :u, :url_path
|
39
|
+
|
40
|
+
def path_prefix
|
41
|
+
request.env['SCRIPT_NAME']
|
42
|
+
end
|
43
|
+
|
44
|
+
def delayed_jobs(type)
|
45
|
+
delayed_job.where(delayed_job_sql(type))
|
46
|
+
end
|
47
|
+
|
48
|
+
def delayed_job_sql(type)
|
49
|
+
case type
|
50
|
+
when :enqueued
|
51
|
+
''
|
52
|
+
when :working
|
53
|
+
'locked_at is not null'
|
54
|
+
when :failed
|
55
|
+
'last_error is not null'
|
56
|
+
when :pending
|
57
|
+
'attempts = 0'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def partial(template, local_vars = {})
|
62
|
+
@partial = true
|
63
|
+
haml(template.to_sym, {:layout => false}, local_vars)
|
64
|
+
ensure
|
65
|
+
@partial = false
|
66
|
+
end
|
67
|
+
|
68
|
+
def poll
|
69
|
+
if @polling
|
70
|
+
text = "Last Updated: #{Time.now.strftime("%H:%M:%S")}"
|
71
|
+
else
|
72
|
+
text = "<a href='#{u(request.path_info)}.poll' rel='poll'>Live Poll</a>"
|
73
|
+
end
|
74
|
+
"<p class='poll'>#{text}</p>"
|
75
|
+
end
|
76
|
+
|
77
|
+
def show_for_polling(page)
|
78
|
+
content_type "text/html"
|
79
|
+
@polling = true
|
80
|
+
# show(page.to_sym, false).gsub(/\s{1,}/, ' ')
|
81
|
+
@jobs = delayed_jobs(page.to_sym)
|
82
|
+
haml(page.to_sym, {:layout => false})
|
83
|
+
end
|
84
|
+
|
85
|
+
####################### SINATRA ROUTES/ACTIONS ##########################
|
86
|
+
def tabs
|
87
|
+
[
|
88
|
+
{:name => 'Overview', :path => '/overview'},
|
89
|
+
{:name => 'Enqueued', :path => '/enqueued'},
|
90
|
+
{:name => 'Working', :path => '/working'},
|
91
|
+
{:name => 'Pending', :path => '/pending'},
|
92
|
+
{:name => 'Failed', :path => '/failed'}
|
93
|
+
# {:name => 'Servers', :path => '/servers'},
|
94
|
+
# {:name => 'Workers', :path => '/workers'}
|
95
|
+
]
|
96
|
+
end
|
97
|
+
|
98
|
+
get "/?" do
|
99
|
+
redirect u(:overview)
|
100
|
+
end
|
101
|
+
|
102
|
+
#Static Page Rendering
|
103
|
+
%w(enqueued working pending failed).each do |page|
|
104
|
+
get "/#{page}" do
|
105
|
+
@jobs = delayed_jobs(page.to_sym).order('created_at desc, id desc').offset(start).limit(per_page)
|
106
|
+
@all_jobs = delayed_jobs(page.to_sym)
|
107
|
+
haml page.to_sym
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
#Polling Page Rendering
|
112
|
+
%w(overview enqueued working pending failed stats) .each do |page|
|
113
|
+
get "/#{page}.poll" do
|
114
|
+
show_for_polling(page)
|
115
|
+
end
|
116
|
+
|
117
|
+
get "/#{page}/:id.poll" do
|
118
|
+
show_for_polling(page)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
get '/overview' do
|
123
|
+
if delayed_job
|
124
|
+
haml :overview
|
125
|
+
else
|
126
|
+
@message = "Unable to connected to Delayed::Job database"
|
127
|
+
haml :error
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# get "/servers" do
|
132
|
+
# @servers = delayed_job_monitor::Server.servers
|
133
|
+
# p @servers.class
|
134
|
+
# p @servers.count
|
135
|
+
# p @servers.first
|
136
|
+
# end
|
137
|
+
|
138
|
+
# get "/workers" do
|
139
|
+
# @workers = delayed_job_monitor::Server.servers.map{|x| x.delayed_job_workers}
|
140
|
+
# end
|
141
|
+
|
142
|
+
get "/queue/:queue" do
|
143
|
+
@jobs = delayed_job.where(:queue=>params[:queue]).order('created_at desc, id desc').offset(start).limit(per_page)
|
144
|
+
@all_jobs = delayed_job.where(:queue=>params[:queue]).count
|
145
|
+
haml :queue
|
146
|
+
end
|
147
|
+
|
148
|
+
get "/remove/:id" do
|
149
|
+
delayed_job.find(params[:id]).delete
|
150
|
+
redirect back
|
151
|
+
end
|
152
|
+
|
153
|
+
get "/requeue/:id" do
|
154
|
+
job = delayed_job.find(params[:id])
|
155
|
+
job.update_attributes(:run_at =>Time.now,:failed_at=>nil,:locked_at=>nil,:attempts=>0)
|
156
|
+
redirect back
|
157
|
+
end
|
158
|
+
|
159
|
+
post "/failed/clear" do
|
160
|
+
delayed_job.where("last_error IS NOT NULL").destroy_all
|
161
|
+
redirect back
|
162
|
+
end
|
163
|
+
|
164
|
+
post "/requeue/all" do
|
165
|
+
delayed_job.where("last_error IS NOT NULL").update_all(
|
166
|
+
:run_at=>Time.now,
|
167
|
+
:failed_at => nil,
|
168
|
+
:attempts=>0,
|
169
|
+
:last_error=>nil,
|
170
|
+
:locked_at=>nil)
|
171
|
+
redirect back
|
172
|
+
end
|
173
|
+
|
174
|
+
get '/update/:id' do
|
175
|
+
"#{params[:id]}"
|
176
|
+
end
|
177
|
+
|
178
|
+
post '/update/all' do
|
179
|
+
"#{params}"
|
180
|
+
end
|
181
|
+
|
182
|
+
post '/kill/:pid' do
|
183
|
+
delayed_job_monitor::Worker.kill_worker(params[:pid])
|
184
|
+
redirect back
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
Binary file
|
@@ -0,0 +1,64 @@
|
|
1
|
+
$(function() {
|
2
|
+
var poll_interval = 3;
|
3
|
+
|
4
|
+
var relatizer = function(){
|
5
|
+
var dt = $(this).text(), relatized = $.relatizeDate(this)
|
6
|
+
if ($(this).parents("a").length > 0 || $(this).is("a")) {
|
7
|
+
$(this).relatizeDate()
|
8
|
+
if (!$(this).attr('title')) {
|
9
|
+
$(this).attr('title', dt)
|
10
|
+
}
|
11
|
+
} else {
|
12
|
+
$(this)
|
13
|
+
.text('')
|
14
|
+
.append( $('<a href="#" class="toggle_format" title="' + dt + '" />')
|
15
|
+
.append('<span class="date_time">' + dt +
|
16
|
+
'</span><span class="relatized_time">' +
|
17
|
+
relatized + '</span>') )
|
18
|
+
}
|
19
|
+
};
|
20
|
+
|
21
|
+
$('.time').each(relatizer);
|
22
|
+
|
23
|
+
$('.time a.toggle_format .date_time').hide();
|
24
|
+
|
25
|
+
var format_toggler = function(){
|
26
|
+
$('.time a.toggle_format span').toggle();
|
27
|
+
$(this).attr('title', $('span:hidden',this).text());
|
28
|
+
return false;
|
29
|
+
};
|
30
|
+
|
31
|
+
$('.time a.toggle_format').click(format_toggler);
|
32
|
+
|
33
|
+
$('ul li.job').hover(function() {
|
34
|
+
$(this).addClass('hover');
|
35
|
+
}, function() {
|
36
|
+
$(this).removeClass('hover');
|
37
|
+
})
|
38
|
+
|
39
|
+
$('a.backtrace').click(function (e) {
|
40
|
+
e.preventDefault();
|
41
|
+
if($(this).prev('div.backtrace:visible').length > 0) {
|
42
|
+
$(this).next('div.backtrace').show();
|
43
|
+
$(this).prev('div.backtrace').hide();
|
44
|
+
} else {
|
45
|
+
$(this).next('div.backtrace').hide();
|
46
|
+
$(this).prev('div.backtrace').show();
|
47
|
+
}
|
48
|
+
});
|
49
|
+
|
50
|
+
$('a[rel=poll]').click(function(e) {
|
51
|
+
e.preventDefault();
|
52
|
+
var href = $(this).attr('href')
|
53
|
+
$(this).parent().text('Starting...')
|
54
|
+
$("#main").addClass('polling')
|
55
|
+
|
56
|
+
setInterval(function() {
|
57
|
+
$.ajax({dataType: 'text', type: 'get', url: href, success: function(data) {
|
58
|
+
$('#main').html(data);
|
59
|
+
}})
|
60
|
+
}, poll_interval * 1000)
|
61
|
+
|
62
|
+
return false
|
63
|
+
})
|
64
|
+
})
|