resque 3.0.3 → 3.1.0
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.
- checksums.yaml +4 -4
- data/HISTORY.md +24 -0
- data/README.markdown +13 -3
- data/Rakefile +0 -5
- data/lib/resque/data_store.rb +7 -7
- data/lib/resque/failure/base.rb +5 -0
- data/lib/resque/failure/multiple.rb +23 -1
- data/lib/resque/failure/redis.rb +1 -0
- data/lib/resque/failure/redis_multi_queue.rb +1 -0
- data/lib/resque/failure.rb +26 -5
- data/lib/resque/job.rb +18 -0
- data/lib/resque/plugin.rb +5 -0
- data/lib/resque/server/public/ranger.js +38 -0
- data/lib/resque/server/public/style.css +154 -44
- data/lib/resque/server/views/layout.erb +10 -6
- data/lib/resque/server/views/queues.erb +8 -1
- data/lib/resque/server.rb +15 -0
- data/lib/resque/server_helper.rb +8 -0
- data/lib/resque/tasks.rb +2 -9
- data/lib/resque/version.rb +1 -1
- data/lib/resque/worker.rb +9 -1
- data/lib/resque.rb +1 -0
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 47f174d6353bf3747da1ec6189f5b405717399418b421c8b071b3fa806869355
|
|
4
|
+
data.tar.gz: 573d98cd9d90a504bf1eb79e8d93b0fcd59d67bc36adc5126cbb144304e03bc3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 265e97433c7e8eb1d5fcaafad19f4505fabca6580e9e727a07f1e281a7af310ddae15b6721452cd7064249f55dcdf1bfa3967ea3823b8f1c436eee91c15bb917
|
|
7
|
+
data.tar.gz: 9bce6a28a4ee107fc1f347ac92b7b885823d67a2b725fd0152ea2ad7e9b34a6003884e35330f4876a9bddf854aaa46bbec096db1766e5d653b6572b4b0f99ec9
|
data/HISTORY.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
## 3.1.0
|
|
2
|
+
|
|
3
|
+
### Breaking Changes
|
|
4
|
+
|
|
5
|
+
* **Minimum Ruby version is now 3.2.0** - Ruby 3.0 and 3.1 are no longer supported (#1958)
|
|
6
|
+
* **Minimum Redis server version is now 4.0** - key removal now uses `UNLINK` instead of `DEL` (#1962)
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
* Added `always` hooks - run after a job finishes, whether it succeeded or failed (#1966)
|
|
11
|
+
* Each failure now has a unique `failure_id` (#1943)
|
|
12
|
+
* resque-web: Dark mode (#1963)
|
|
13
|
+
* resque-web: Delete button for queued jobs (#1969)
|
|
14
|
+
* resque-web: `link` and `script` tag helpers for plugins (#1914)
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
* Keep the heartbeat thread alive when a heartbeat fails (#1967)
|
|
19
|
+
* `Resque::Failure::Multiple` saves to every backend even when one of them raises (#1964)
|
|
20
|
+
* Let Rails eager load the application rather than doing it ourselves (#1867)
|
|
21
|
+
* Test the Rails and Redis versions the CI matrix claims to cover (#1978)
|
|
22
|
+
* Require `minitest/mock` from its extracted gem (#1976)
|
|
23
|
+
* Get on modern RuboCop (#1977)
|
|
24
|
+
|
|
1
25
|
## 3.0.3
|
|
2
26
|
|
|
3
27
|
### Fixed
|
data/README.markdown
CHANGED
|
@@ -39,13 +39,14 @@ The Resque frontend tells you what workers are doing, what workers are
|
|
|
39
39
|
not doing, what queues you're using, what's in those queues, provides
|
|
40
40
|
general usage stats, and helps you track failures.
|
|
41
41
|
|
|
42
|
-
Resque
|
|
42
|
+
Resque requires Ruby 3.2.0 or newer.
|
|
43
43
|
|
|
44
44
|
**Version Support:**
|
|
45
|
-
- Ruby: 3.
|
|
45
|
+
- Ruby: 3.2, 3.3, 3.4, 4.0
|
|
46
|
+
- Redis server: 4.0+
|
|
46
47
|
- Redis gem: 4.0+
|
|
47
48
|
- Rack: 2.x or 3.x
|
|
48
|
-
- Rails (for ActiveJob): 7.2+
|
|
49
|
+
- Rails (for ActiveJob): 7.2+
|
|
49
50
|
|
|
50
51
|
### Resque 3.0
|
|
51
52
|
|
|
@@ -431,6 +432,15 @@ Resque::Failure::Multiple.classes = [Resque::Failure::Redis, Resque::Failure::Ai
|
|
|
431
432
|
Resque::Failure.backend = Resque::Failure::Multiple
|
|
432
433
|
```
|
|
433
434
|
|
|
435
|
+
Each failure is recorded with a unique `failure_id` shared by every backend that
|
|
436
|
+
accepts one, so the same failure can be correlated across them. Failures are
|
|
437
|
+
still addressed by index; the id is additive metadata. See
|
|
438
|
+
[docs/FAILURE_IDS.md](https://github.com/resque/resque/blob/master/docs/FAILURE_IDS.md).
|
|
439
|
+
|
|
440
|
+
A backend that raises while saving no longer stops the ones after it. Once every
|
|
441
|
+
backend has run, `Resque::Failure::Multiple::BackendError` is raised; its
|
|
442
|
+
`original_errors` maps each failed backend class to the exception it raised.
|
|
443
|
+
|
|
434
444
|
Keep this in mind when writing your jobs: you may want to throw
|
|
435
445
|
exceptions you would not normally throw in order to assist debugging.
|
|
436
446
|
|
data/Rakefile
CHANGED
|
@@ -44,11 +44,6 @@ task "env:aj_integration" do
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
Rake::TestTask.new('test:activejob:integration' => 'env:aj_integration') do |t|
|
|
47
|
-
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.1")
|
|
48
|
-
puts "Integration tests require Ruby 3.1 or later"
|
|
49
|
-
exit 0
|
|
50
|
-
end
|
|
51
|
-
|
|
52
47
|
t.description = "Run integration tests for Resque::ActiveJob::Adapter"
|
|
53
48
|
t.libs << "test"
|
|
54
49
|
t.libs << "test/active_job"
|
data/lib/resque/data_store.rb
CHANGED
|
@@ -133,7 +133,7 @@ module Resque
|
|
|
133
133
|
def remove_queue(queue)
|
|
134
134
|
@redis.pipelined do |piped|
|
|
135
135
|
piped.srem(:queues, [queue.to_s])
|
|
136
|
-
piped.
|
|
136
|
+
piped.unlink(redis_key_for_queue(queue))
|
|
137
137
|
end
|
|
138
138
|
end
|
|
139
139
|
|
|
@@ -178,7 +178,7 @@ module Resque
|
|
|
178
178
|
end
|
|
179
179
|
|
|
180
180
|
def remove_failed_queue(failed_queue_name=:failed)
|
|
181
|
-
@redis.
|
|
181
|
+
@redis.unlink(failed_queue_name)
|
|
182
182
|
end
|
|
183
183
|
|
|
184
184
|
def num_failed(failed_queue_name=:failed)
|
|
@@ -198,7 +198,7 @@ module Resque
|
|
|
198
198
|
end
|
|
199
199
|
|
|
200
200
|
def clear_failed_queue(failed_queue_name=:failed)
|
|
201
|
-
@redis.
|
|
201
|
+
@redis.unlink(failed_queue_name)
|
|
202
202
|
end
|
|
203
203
|
|
|
204
204
|
def update_item_in_failed_queue(index_in_failed_queue,new_item_data,failed_queue_name=:failed)
|
|
@@ -252,8 +252,8 @@ module Resque
|
|
|
252
252
|
def unregister_worker(worker, &block)
|
|
253
253
|
@redis.pipelined do |piped|
|
|
254
254
|
piped.srem(:workers, [worker.id])
|
|
255
|
-
piped.
|
|
256
|
-
piped.
|
|
255
|
+
piped.unlink(redis_key_for_worker(worker))
|
|
256
|
+
piped.unlink(redis_key_for_worker_start_time(worker))
|
|
257
257
|
piped.hdel(HEARTBEAT_KEY, worker.to_s)
|
|
258
258
|
|
|
259
259
|
block.call redis: piped
|
|
@@ -291,7 +291,7 @@ module Resque
|
|
|
291
291
|
|
|
292
292
|
def worker_done_working(worker, &block)
|
|
293
293
|
@redis.pipelined do |piped|
|
|
294
|
-
piped.
|
|
294
|
+
piped.unlink(redis_key_for_worker(worker))
|
|
295
295
|
block.call redis: piped
|
|
296
296
|
end
|
|
297
297
|
end
|
|
@@ -328,7 +328,7 @@ module Resque
|
|
|
328
328
|
end
|
|
329
329
|
|
|
330
330
|
def clear_stat(stat, redis: @redis)
|
|
331
|
-
redis.
|
|
331
|
+
redis.unlink("stat:#{stat}")
|
|
332
332
|
end
|
|
333
333
|
end
|
|
334
334
|
end
|
data/lib/resque/failure/base.rb
CHANGED
|
@@ -17,11 +17,16 @@ module Resque
|
|
|
17
17
|
# The payload object associated with the failed job
|
|
18
18
|
attr_accessor :payload
|
|
19
19
|
|
|
20
|
+
# A unique identifier for this failure, shared by every backend that
|
|
21
|
+
# accepts one. Nil when generation is turned off.
|
|
22
|
+
attr_accessor :failure_id
|
|
23
|
+
|
|
20
24
|
def initialize(exception, worker, queue, payload)
|
|
21
25
|
@exception = exception
|
|
22
26
|
@worker = worker
|
|
23
27
|
@queue = queue
|
|
24
28
|
@payload = payload
|
|
29
|
+
@failure_id = SecureRandom.uuid if Resque::Failure.generate_failure_ids?
|
|
25
30
|
end
|
|
26
31
|
|
|
27
32
|
# When a job fails, a new instance of your Failure backend is created
|
|
@@ -3,6 +3,17 @@ module Resque
|
|
|
3
3
|
# A Failure backend that uses multiple backends
|
|
4
4
|
# delegates all queries to the first backend
|
|
5
5
|
class Multiple < Base
|
|
6
|
+
class BackendError < StandardError
|
|
7
|
+
attr_reader :original_errors
|
|
8
|
+
|
|
9
|
+
def initialize(original_errors = {})
|
|
10
|
+
@original_errors = original_errors
|
|
11
|
+
message = "Some backends raise error: " + @original_errors.map{|backend, e|
|
|
12
|
+
"#{backend}:#{e.class}:#{e.message.inspect}"
|
|
13
|
+
}.join("; ")
|
|
14
|
+
super(message)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
6
17
|
|
|
7
18
|
class << self
|
|
8
19
|
attr_accessor :classes
|
|
@@ -19,7 +30,18 @@ module Resque
|
|
|
19
30
|
end
|
|
20
31
|
|
|
21
32
|
def save
|
|
22
|
-
|
|
33
|
+
errors = {}
|
|
34
|
+
|
|
35
|
+
@backends.each do |backend|
|
|
36
|
+
backend.failure_id = failure_id if backend.respond_to?(:failure_id=)
|
|
37
|
+
backend.save
|
|
38
|
+
rescue
|
|
39
|
+
errors[backend.class] = $!
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
unless errors.empty?
|
|
43
|
+
raise BackendError.new(errors)
|
|
44
|
+
end
|
|
23
45
|
end
|
|
24
46
|
|
|
25
47
|
# The number of failures.
|
data/lib/resque/failure/redis.rb
CHANGED
data/lib/resque/failure.rb
CHANGED
|
@@ -9,12 +9,33 @@ module Resque
|
|
|
9
9
|
# Creates a new failure, which is delegated to the appropriate backend.
|
|
10
10
|
#
|
|
11
11
|
# Expects a hash with the following keys:
|
|
12
|
-
# :exception
|
|
13
|
-
# :worker
|
|
14
|
-
# :queue
|
|
15
|
-
# :payload
|
|
12
|
+
# :exception - The Exception object
|
|
13
|
+
# :worker - The Worker object who is reporting the failure
|
|
14
|
+
# :queue - The string name of the queue from which the job was pulled
|
|
15
|
+
# :payload - The job's payload
|
|
16
|
+
# :failure_id - Optional id to record this failure under, in place of the
|
|
17
|
+
# one the backend generates for itself.
|
|
16
18
|
def self.create(options = {})
|
|
17
|
-
backend.new(*options.values_at(:exception, :worker, :queue, :payload))
|
|
19
|
+
failure = backend.new(*options.values_at(:exception, :worker, :queue, :payload))
|
|
20
|
+
|
|
21
|
+
# Backends are not required to accept an id.
|
|
22
|
+
if options[:failure_id] && failure.respond_to?(:failure_id=)
|
|
23
|
+
failure.failure_id = options[:failure_id]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
failure.save
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
attr_writer :generate_failure_ids
|
|
31
|
+
|
|
32
|
+
# Whether failures are given a `failure_id` when they are built.
|
|
33
|
+
# Defaults to true. An explicit `:failure_id` passed to `create` sets the
|
|
34
|
+
# `failure_id` even if automatic generation is disabled.
|
|
35
|
+
def generate_failure_ids?
|
|
36
|
+
return true if @generate_failure_ids.nil?
|
|
37
|
+
!!@generate_failure_ids
|
|
38
|
+
end
|
|
18
39
|
end
|
|
19
40
|
|
|
20
41
|
#
|
data/lib/resque/job.rb
CHANGED
|
@@ -201,6 +201,8 @@ module Resque
|
|
|
201
201
|
rescue Object => e
|
|
202
202
|
run_failure_hooks(e)
|
|
203
203
|
raise e
|
|
204
|
+
ensure
|
|
205
|
+
run_always_hooks($!)
|
|
204
206
|
end
|
|
205
207
|
end
|
|
206
208
|
|
|
@@ -278,6 +280,10 @@ module Resque
|
|
|
278
280
|
@failure_hooks ||= Plugin.failure_hooks(payload_class)
|
|
279
281
|
end
|
|
280
282
|
|
|
283
|
+
def always_hooks
|
|
284
|
+
@always_hooks ||= Plugin.always_hooks(payload_class)
|
|
285
|
+
end
|
|
286
|
+
|
|
281
287
|
def run_failure_hooks(exception)
|
|
282
288
|
begin
|
|
283
289
|
job_args = args || []
|
|
@@ -292,5 +298,17 @@ module Resque
|
|
|
292
298
|
@failure_hooks_ran = true
|
|
293
299
|
end
|
|
294
300
|
end
|
|
301
|
+
|
|
302
|
+
def run_always_hooks(job_failure = nil)
|
|
303
|
+
job_args = args || []
|
|
304
|
+
always_hooks.each { |hook| payload_class.send(hook, *job_args) } if has_payload_class?
|
|
305
|
+
rescue Exception => e
|
|
306
|
+
if job_failure
|
|
307
|
+
error_message = "Additional error (#{e.class}: #{e}) occurred in running always hooks for job #{inspect}\n" \
|
|
308
|
+
"Original error that caused job failure was #{e.class}: #{job_failure.class}: #{job_failure.message}"
|
|
309
|
+
raise RuntimeError.new(error_message)
|
|
310
|
+
end
|
|
311
|
+
raise e
|
|
312
|
+
end
|
|
295
313
|
end
|
|
296
314
|
end
|
data/lib/resque/plugin.rb
CHANGED
|
@@ -55,6 +55,11 @@ module Resque
|
|
|
55
55
|
get_hook_names(job, 'on_failure')
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
# Given an object, returns a list of `always` hook names
|
|
59
|
+
def always_hooks(job)
|
|
60
|
+
get_hook_names(job, 'always')
|
|
61
|
+
end
|
|
62
|
+
|
|
58
63
|
# Given an object, returns a list `after_enqueue` hook names.
|
|
59
64
|
def after_enqueue_hooks(job)
|
|
60
65
|
get_hook_names(job, 'after_enqueue')
|
|
@@ -1,6 +1,44 @@
|
|
|
1
|
+
// Theme toggle functionality
|
|
2
|
+
(function() {
|
|
3
|
+
// Reading localStorage throws outright when the browser blocks site data,
|
|
4
|
+
// and this runs before the rest of the file.
|
|
5
|
+
var storedTheme;
|
|
6
|
+
try {
|
|
7
|
+
storedTheme = localStorage.getItem('resque-theme');
|
|
8
|
+
} catch (e) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (storedTheme) {
|
|
12
|
+
document.documentElement.setAttribute('data-theme', storedTheme);
|
|
13
|
+
}
|
|
14
|
+
})();
|
|
15
|
+
|
|
1
16
|
$(function() {
|
|
2
17
|
var poll_interval = 2
|
|
3
18
|
|
|
19
|
+
// Theme toggle handler
|
|
20
|
+
$('.theme-toggle').click(function() {
|
|
21
|
+
var html = document.documentElement;
|
|
22
|
+
var currentTheme = html.getAttribute('data-theme');
|
|
23
|
+
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
24
|
+
var newTheme;
|
|
25
|
+
|
|
26
|
+
if (currentTheme === 'dark') {
|
|
27
|
+
newTheme = 'light';
|
|
28
|
+
} else if (currentTheme === 'light') {
|
|
29
|
+
newTheme = 'dark';
|
|
30
|
+
} else {
|
|
31
|
+
// No explicit theme set, toggle from system preference
|
|
32
|
+
newTheme = prefersDark ? 'light' : 'dark';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
html.setAttribute('data-theme', newTheme);
|
|
36
|
+
try {
|
|
37
|
+
localStorage.setItem('resque-theme', newTheme);
|
|
38
|
+
} catch (e) {}
|
|
39
|
+
return false;
|
|
40
|
+
});
|
|
41
|
+
|
|
4
42
|
var relatizer = function(){
|
|
5
43
|
var dt = $(this).text(), relatized = $.relatizeDate(this)
|
|
6
44
|
if ($(this).parents("a").length > 0 || $(this).is("a")) {
|
|
@@ -1,91 +1,201 @@
|
|
|
1
|
-
|
|
1
|
+
:root {
|
|
2
|
+
--bg-primary: #efefef;
|
|
3
|
+
--bg-secondary: #fff;
|
|
4
|
+
--bg-header: #000;
|
|
5
|
+
--bg-header-hover: #333;
|
|
6
|
+
--bg-table-header: #efefef;
|
|
7
|
+
--bg-highlight: #efefef;
|
|
8
|
+
--bg-working: #f4ffe4;
|
|
9
|
+
--bg-failure: #ffecec;
|
|
10
|
+
--bg-queue-tag: #b1d2e9;
|
|
11
|
+
--bg-pagination: #efefef;
|
|
12
|
+
--text-pagination: #555;
|
|
13
|
+
--bg-failed-item: linear-gradient(to bottom, #efefef, #fff);
|
|
14
|
+
--bg-subnav-link: #dd5b5b;
|
|
15
|
+
--accent: #ce1212;
|
|
16
|
+
--accent-dark: #b00909;
|
|
17
|
+
--text-primary: #111;
|
|
18
|
+
--text-secondary: #888;
|
|
19
|
+
--text-muted: #999;
|
|
20
|
+
--text-header: #333;
|
|
21
|
+
--text-white: #fff;
|
|
22
|
+
--text-working: #7ac312;
|
|
23
|
+
--text-failure: #d37474;
|
|
24
|
+
--text-queue-tag: #3274a2;
|
|
25
|
+
--border-color: #ccc;
|
|
26
|
+
--border-footer: #ccc;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@media (prefers-color-scheme: dark) {
|
|
30
|
+
:root:not([data-theme="light"]) {
|
|
31
|
+
--bg-primary: #1a1a1a;
|
|
32
|
+
--bg-secondary: #2d2d2d;
|
|
33
|
+
--bg-header: #0d0d0d;
|
|
34
|
+
--bg-header-hover: #444;
|
|
35
|
+
--bg-table-header: #333;
|
|
36
|
+
--bg-highlight: #3a3a3a;
|
|
37
|
+
--bg-working: #1a2e0a;
|
|
38
|
+
--bg-failure: #3d1a1a;
|
|
39
|
+
--bg-queue-tag: #1a3a4d;
|
|
40
|
+
--bg-pagination: #333;
|
|
41
|
+
--text-pagination: #bbb;
|
|
42
|
+
--bg-failed-item: linear-gradient(to bottom, #333, #2d2d2d);
|
|
43
|
+
--bg-subnav-link: #8b3a3a;
|
|
44
|
+
--text-primary: #e0e0e0;
|
|
45
|
+
--text-secondary: #aaa;
|
|
46
|
+
--text-muted: #888;
|
|
47
|
+
--text-header: #ccc;
|
|
48
|
+
--text-working: #8fd14f;
|
|
49
|
+
--text-failure: #e88;
|
|
50
|
+
--text-queue-tag: #6ab0e0;
|
|
51
|
+
--border-color: #555;
|
|
52
|
+
--border-footer: #444;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
[data-theme="dark"] {
|
|
57
|
+
--bg-primary: #1a1a1a;
|
|
58
|
+
--bg-secondary: #2d2d2d;
|
|
59
|
+
--bg-header: #0d0d0d;
|
|
60
|
+
--bg-header-hover: #444;
|
|
61
|
+
--bg-table-header: #333;
|
|
62
|
+
--bg-highlight: #3a3a3a;
|
|
63
|
+
--bg-working: #1a2e0a;
|
|
64
|
+
--bg-failure: #3d1a1a;
|
|
65
|
+
--bg-queue-tag: #1a3a4d;
|
|
66
|
+
--bg-pagination: #333;
|
|
67
|
+
--text-pagination: #bbb;
|
|
68
|
+
--bg-failed-item: linear-gradient(to bottom, #333, #2d2d2d);
|
|
69
|
+
--bg-subnav-link: #8b3a3a;
|
|
70
|
+
--text-primary: #e0e0e0;
|
|
71
|
+
--text-secondary: #aaa;
|
|
72
|
+
--text-muted: #888;
|
|
73
|
+
--text-header: #ccc;
|
|
74
|
+
--text-working: #8fd14f;
|
|
75
|
+
--text-failure: #e88;
|
|
76
|
+
--text-queue-tag: #6ab0e0;
|
|
77
|
+
--border-color: #555;
|
|
78
|
+
--border-footer: #444;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
html { background: var(--bg-primary); font-family:Arial, Verdana, sans-serif; font-size:13px; }
|
|
2
82
|
body { padding:0; margin:0; }
|
|
3
83
|
|
|
4
|
-
.header { background
|
|
5
|
-
.header h1 { color
|
|
84
|
+
.header { background: var(--bg-header); padding:8px 5% 0 5%; border-bottom:5px solid var(--accent); position: relative;}
|
|
85
|
+
.header h1 { color: var(--text-header); font-size:90%; font-weight:bold; margin-bottom:6px;}
|
|
6
86
|
.header ul li { display:inline;}
|
|
7
|
-
.header ul li a { color
|
|
8
|
-
.header ul li a:hover { background
|
|
9
|
-
.header ul li.current a { background
|
|
87
|
+
.header ul li a { color: var(--text-white); text-decoration:none; margin-right:10px; display:inline-block; padding:8px; -webkit-border-top-right-radius:6px; -webkit-border-top-left-radius:6px; -moz-border-radius-topleft:6px; -moz-border-radius-topright:6px; }
|
|
88
|
+
.header ul li a:hover { background: var(--bg-header-hover);}
|
|
89
|
+
.header ul li.current a { background: var(--accent); font-weight:bold; color: var(--text-white);}
|
|
10
90
|
|
|
11
|
-
.header .namespace { position: absolute; right:
|
|
91
|
+
.header .namespace { position: absolute; right: 50px; top: 12px; color: var(--text-white); font-weight: bold; }
|
|
12
92
|
|
|
13
|
-
.subnav { padding:2px 5% 7px 5%; background
|
|
93
|
+
.subnav { padding:2px 5% 7px 5%; background: var(--accent); font-size:90%;}
|
|
14
94
|
.subnav li { display:inline;}
|
|
15
|
-
.subnav li a { color
|
|
16
|
-
.subnav li.current a { background
|
|
17
|
-
.subnav li a:active { background
|
|
95
|
+
.subnav li a { color: var(--text-white); text-decoration:none; margin-right:10px; display:inline-block; background: var(--bg-subnav-link); padding:5px; -webkit-border-radius:3px; -moz-border-radius:3px;}
|
|
96
|
+
.subnav li.current a { background: var(--bg-secondary); font-weight:bold; color: var(--accent);}
|
|
97
|
+
.subnav li a:active { background: var(--accent-dark);}
|
|
18
98
|
|
|
19
|
-
#main { padding:10px 5%; background
|
|
99
|
+
#main { padding:10px 5%; background: var(--bg-secondary); overflow:hidden; }
|
|
20
100
|
#main .logo { float:right; margin:10px;}
|
|
21
|
-
#main span.hl { background
|
|
22
|
-
#main h1 { margin:10px 0; font-size:190%; font-weight:bold; color
|
|
23
|
-
#main h2 { margin:10px 0; font-size:130%;}
|
|
101
|
+
#main span.hl { background: var(--bg-highlight); padding:2px;}
|
|
102
|
+
#main h1 { margin:10px 0; font-size:190%; font-weight:bold; color: var(--accent);}
|
|
103
|
+
#main h2 { margin:10px 0; font-size:130%; color: var(--text-primary);}
|
|
24
104
|
#main table { width:100%; margin:10px 0;}
|
|
25
|
-
#main table tr td, #main table tr th { border:1px solid
|
|
26
|
-
#main table tr th { background
|
|
27
|
-
#main table tr td
|
|
28
|
-
#main
|
|
29
|
-
#main
|
|
30
|
-
#main p
|
|
105
|
+
#main table tr td, #main table tr th { border:1px solid var(--border-color); padding:6px;}
|
|
106
|
+
#main table tr th { background: var(--bg-table-header); color: var(--text-secondary); font-size:15px; font-weight:bold;}
|
|
107
|
+
#main table tr td { background: var(--bg-secondary); color: var(--text-primary);}
|
|
108
|
+
#main table tr td.no-data { text-align:center; padding:40px 0; color: var(--text-muted); font-style:italic; font-size:130%;}
|
|
109
|
+
#main a { color: var(--text-primary);}
|
|
110
|
+
#main p { margin:5px 0; color: var(--text-primary);}
|
|
111
|
+
#main p.intro { margin-bottom:15px; font-size:85%; color: var(--text-muted); margin-top:0; line-height:1.3;}
|
|
31
112
|
#main h1.wi { margin-bottom:5px;}
|
|
32
|
-
#main p.sub { font-size:95%; color
|
|
113
|
+
#main p.sub { font-size:95%; color: var(--text-muted);}
|
|
33
114
|
|
|
34
115
|
#main table.queues { width:40%;}
|
|
35
116
|
#main table.queues td.queue { font-weight:bold; width:50%;}
|
|
36
|
-
#main table.queues tr.failure td { background
|
|
37
|
-
#main table.queues tr.failure td a{ color
|
|
38
|
-
#main table.queues tr.first_failure td { border-top:2px solid
|
|
117
|
+
#main table.queues tr.failure td { background: var(--bg-failure); font-size:90%; color: var(--text-failure);}
|
|
118
|
+
#main table.queues tr.failure td a{ color: var(--text-failure);}
|
|
119
|
+
#main table.queues tr.first_failure td { border-top:2px solid var(--text-failure); }
|
|
39
120
|
|
|
40
121
|
#main table.jobs td.class { font-family:Monaco, "Courier New", monospace; font-size:90%; width:50%;}
|
|
41
122
|
#main table.jobs td.args{ width:50%;}
|
|
123
|
+
#main table.jobs td.remove-job { width:1%; text-align:right;}
|
|
124
|
+
#main table.jobs td.remove-job form { float:none; margin:0;}
|
|
42
125
|
|
|
43
|
-
#main table.workers td.icon {width:1%; background
|
|
126
|
+
#main table.workers td.icon {width:1%; background: var(--bg-highlight);text-align:center;}
|
|
44
127
|
#main table.workers td.icon img { height: 16px; width: 16px; }
|
|
45
128
|
#main table.workers td.where { width:25%;}
|
|
46
129
|
#main table.workers td.queues { width:35%;}
|
|
47
|
-
#main .queue-tag { background
|
|
48
|
-
#main .queue-tag a { color:
|
|
130
|
+
#main .queue-tag { background: var(--bg-queue-tag); padding:2px; margin:0 3px; font-size:80%; text-decoration:none; text-transform:uppercase; font-weight:bold; color: var(--text-queue-tag); -webkit-border-radius:4px; -moz-border-radius:4px;}
|
|
131
|
+
#main .queue-tag a { color: var(--text-queue-tag); text-decoration: none; }
|
|
49
132
|
#main table.workers td.queues.queue { width:10%;}
|
|
50
133
|
#main table.workers td.process { width:35%;}
|
|
51
|
-
#main table.workers td.process span.waiting { color
|
|
134
|
+
#main table.workers td.process span.waiting { color: var(--text-muted); font-size:90%;}
|
|
52
135
|
#main table.workers td.process small { font-size:80%; margin-left:5px;}
|
|
53
136
|
#main table.workers td.process code { font-family:Monaco, "Courier New", monospace; font-size:90%;}
|
|
54
|
-
#main table.workers td.process small a { color
|
|
55
|
-
#main.polling table.workers tr.working td { background
|
|
56
|
-
#main.polling table.workers tr.working td.where a { color
|
|
137
|
+
#main table.workers td.process small a { color: var(--text-muted);}
|
|
138
|
+
#main.polling table.workers tr.working td { background: var(--bg-working); color: var(--text-working);}
|
|
139
|
+
#main.polling table.workers tr.working td.where a { color: var(--text-working);}
|
|
57
140
|
#main.polling table.workers tr.working td.process code { font-weight:bold;}
|
|
58
141
|
|
|
59
142
|
|
|
60
|
-
#main table.stats th { font-size:100%; width:40%; color
|
|
61
|
-
#main hr { border:0; border-top:5px solid
|
|
143
|
+
#main table.stats th { font-size:100%; width:40%; color: var(--text-primary);}
|
|
144
|
+
#main hr { border:0; border-top:5px solid var(--bg-highlight); margin:15px 0;}
|
|
62
145
|
|
|
63
|
-
#footer { padding:10px 5%; background
|
|
64
|
-
#footer p a { color
|
|
146
|
+
#footer { padding:10px 5%; background: var(--bg-primary); color: var(--text-muted); font-size:85%; line-height:1.5; border-top:5px solid var(--border-footer); padding-top:10px;}
|
|
147
|
+
#footer p a { color: var(--text-muted);}
|
|
65
148
|
|
|
66
149
|
#main p.poll { background:url(poll.png) no-repeat 0 2px; padding:3px 0; padding-left:23px; float:right; font-size:85%; }
|
|
67
150
|
|
|
68
151
|
#main ul.failed {}
|
|
69
|
-
#main ul.failed li {background
|
|
70
|
-
#main ul.failed li dl dt {font-size:80%; color
|
|
71
|
-
#main ul.failed li dl dd {margin-bottom:10px; margin-left:70px;}
|
|
152
|
+
#main ul.failed li {background: var(--bg-failed-item); background-color: var(--bg-highlight); margin-top:10px; padding:10px; overflow:hidden; -webkit-border-radius:5px; border:1px solid var(--border-color); }
|
|
153
|
+
#main ul.failed li dl dt {font-size:80%; color: var(--text-muted); width:60px; float:left; padding-top:1px; text-align:right;}
|
|
154
|
+
#main ul.failed li dl dd {margin-bottom:10px; margin-left:70px; color: var(--text-primary);}
|
|
72
155
|
#main ul.failed li dl dd .retried { float:right; text-align: right; }
|
|
73
156
|
#main ul.failed li dl dd .retried .remove { display:none; margin-top: 8px; }
|
|
74
157
|
#main ul.failed li.hover dl dd .retried .remove { display:block; }
|
|
75
158
|
#main ul.failed li dl dd .controls { display:none; float:right; }
|
|
76
159
|
#main ul.failed li.hover dl dd .controls { display:block; }
|
|
77
|
-
#main ul.failed li dl dd code, #main ul.failed li dl dd pre { font-family:Monaco, "Courier New", monospace; font-size:90%; white-space: pre-wrap;}
|
|
160
|
+
#main ul.failed li dl dd code, #main ul.failed li dl dd pre { font-family:Monaco, "Courier New", monospace; font-size:90%; white-space: pre-wrap; color: var(--text-primary);}
|
|
78
161
|
#main ul.failed li dl dd.error a {font-family:Monaco, "Courier New", monospace; font-size:90%; }
|
|
79
162
|
#main ul.failed li dl dd.error pre { margin-top:3px; line-height:1.3;}
|
|
80
163
|
|
|
81
|
-
#main div.pagination { background
|
|
82
|
-
#main div.pagination a, #main div.pagination span { display: inline-block; padding: 4px 8px; color:
|
|
83
|
-
#main div.pagination span { background:
|
|
164
|
+
#main div.pagination { background: var(--bg-pagination); padding:10px; text-align:center}
|
|
165
|
+
#main div.pagination a, #main div.pagination span { display: inline-block; padding: 4px 8px; color: var(--text-pagination); border: solid 1px var(--text-pagination); margin-top: 6px; }
|
|
166
|
+
#main div.pagination span { background: var(--text-pagination); color: var(--bg-secondary); }
|
|
84
167
|
|
|
85
168
|
#main form {float:right; margin-top:-10px;margin-left:10px;}
|
|
86
169
|
|
|
87
170
|
#main .time a.toggle_format {text-decoration:none;}
|
|
88
171
|
|
|
89
|
-
#failed tr.total td {background-color:
|
|
172
|
+
#failed tr.total td {background-color: var(--bg-failure); color: var(--text-failure); font-size: 15px; font-weight: bold;}
|
|
90
173
|
#failed .center {text-align: center;}
|
|
91
174
|
#failed .failed_class { padding-left: 20px; font-size:12px; }
|
|
175
|
+
|
|
176
|
+
/* Theme toggle button */
|
|
177
|
+
.theme-toggle {
|
|
178
|
+
position: absolute;
|
|
179
|
+
right: 15px;
|
|
180
|
+
top: 10px;
|
|
181
|
+
background: transparent;
|
|
182
|
+
border: none;
|
|
183
|
+
cursor: pointer;
|
|
184
|
+
padding: 5px;
|
|
185
|
+
font-size: 18px;
|
|
186
|
+
line-height: 1;
|
|
187
|
+
color: var(--text-white);
|
|
188
|
+
opacity: 0.8;
|
|
189
|
+
transition: opacity 0.2s;
|
|
190
|
+
}
|
|
191
|
+
.theme-toggle:hover {
|
|
192
|
+
opacity: 1;
|
|
193
|
+
}
|
|
194
|
+
.theme-toggle .sun { display: none; }
|
|
195
|
+
.theme-toggle .moon { display: inline; }
|
|
196
|
+
[data-theme="dark"] .theme-toggle .sun { display: inline; }
|
|
197
|
+
[data-theme="dark"] .theme-toggle .moon { display: none; }
|
|
198
|
+
@media (prefers-color-scheme: dark) {
|
|
199
|
+
:root:not([data-theme="light"]) .theme-toggle .sun { display: inline; }
|
|
200
|
+
:root:not([data-theme="light"]) .theme-toggle .moon { display: none; }
|
|
201
|
+
}
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<title>Resque</title>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
<%= link_tag 'reset.css' %>
|
|
7
|
+
<%= link_tag 'style.css' %>
|
|
8
|
+
<%= script_tag 'jquery-3.6.0.min.js' %>
|
|
9
|
+
<%= script_tag 'jquery.relatize_date.js' %>
|
|
10
|
+
<%= script_tag 'ranger.js' %>
|
|
11
|
+
<%= script_tag 'main.js' %>
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div class="header">
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
<%= tab tab_name %>
|
|
18
18
|
<% end %>
|
|
19
19
|
</ul>
|
|
20
|
+
<button class="theme-toggle" title="Toggle dark/light mode" aria-label="Toggle dark/light mode">
|
|
21
|
+
<span class="sun">☀</span>
|
|
22
|
+
<span class="moon">☾</span>
|
|
23
|
+
</button>
|
|
20
24
|
<% if Resque.redis.namespace != :resque %>
|
|
21
25
|
<abbr class="namespace" title="Resque's Redis Namespace">
|
|
22
26
|
<%= h Resque.redis.namespace %>
|
|
@@ -11,16 +11,23 @@
|
|
|
11
11
|
<tr>
|
|
12
12
|
<th>Class</th>
|
|
13
13
|
<th>Args</th>
|
|
14
|
+
<th> </th>
|
|
14
15
|
</tr>
|
|
15
16
|
<% for job in (jobs = resque.peek(current_queue, start, 20)) %>
|
|
16
17
|
<tr>
|
|
17
18
|
<td class='class'><%= partial :job_class, :job => job %></td>
|
|
18
19
|
<td class='args'><%=h job['args'].inspect %></td>
|
|
20
|
+
<td class='remove-job'>
|
|
21
|
+
<form method="POST" action="<%= h u("/queues/#{current_queue}/remove-job") %>">
|
|
22
|
+
<input type="hidden" name="payload" value="<%= h Resque.encode(job) %>" />
|
|
23
|
+
<input type="submit" name="" value="Delete" class="confirmSubmission" />
|
|
24
|
+
</form>
|
|
25
|
+
</td>
|
|
19
26
|
</tr>
|
|
20
27
|
<% end %>
|
|
21
28
|
<% if jobs.empty? %>
|
|
22
29
|
<tr>
|
|
23
|
-
<td class='no-data' colspan='
|
|
30
|
+
<td class='no-data' colspan='3'>There are no pending jobs in this queue</td>
|
|
24
31
|
</tr>
|
|
25
32
|
<% end %>
|
|
26
33
|
</table>
|
data/lib/resque/server.rb
CHANGED
|
@@ -73,6 +73,21 @@ module Resque
|
|
|
73
73
|
redirect u('queues')
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
+
post "/queues/:id/remove-job" do
|
|
77
|
+
halt 400, 'Missing job payload' if params[:payload].to_s.empty?
|
|
78
|
+
|
|
79
|
+
begin
|
|
80
|
+
# Round-tripped so the value removed is encoded exactly the way the
|
|
81
|
+
# queue holds it, whatever the form sent.
|
|
82
|
+
payload = Resque.encode(Resque.decode(params[:payload]))
|
|
83
|
+
rescue Resque::Helpers::DecodeException
|
|
84
|
+
halt 400, 'Invalid job payload'
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
Resque.data_store.remove_from_queue(params[:id], payload)
|
|
88
|
+
redirect u("queues/#{params[:id]}")
|
|
89
|
+
end
|
|
90
|
+
|
|
76
91
|
get "/failed/?" do
|
|
77
92
|
if Resque::Failure.url
|
|
78
93
|
redirect Resque::Failure.url
|
data/lib/resque/server_helper.rb
CHANGED
|
@@ -18,6 +18,14 @@ module Resque
|
|
|
18
18
|
end
|
|
19
19
|
alias_method :u, :url_path
|
|
20
20
|
|
|
21
|
+
def script_tag(src)
|
|
22
|
+
%Q(<script src="#{url_path(src)}" type="text/javascript"></script>)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def link_tag(src)
|
|
26
|
+
%Q(<link href="#{url_path(src)}" media="screen" rel="stylesheet" type="text/css">)
|
|
27
|
+
end
|
|
28
|
+
|
|
21
29
|
def path_prefix
|
|
22
30
|
request.env['SCRIPT_NAME']
|
|
23
31
|
end
|
data/lib/resque/tasks.rb
CHANGED
|
@@ -41,15 +41,8 @@ namespace :resque do
|
|
|
41
41
|
threads.each { |thread| thread.join }
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
#
|
|
45
|
-
task :preload => :setup
|
|
46
|
-
if defined?(Rails) && Rails.respond_to?(:application)
|
|
47
|
-
if Rails.application.config.eager_load
|
|
48
|
-
ActiveSupport.run_load_hooks(:before_eager_load, Rails.application)
|
|
49
|
-
Rails.application.config.eager_load_namespaces.each(&:eager_load!)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
44
|
+
# Defined for backwards compatibility.
|
|
45
|
+
task :preload => :setup
|
|
53
46
|
|
|
54
47
|
namespace :failures do
|
|
55
48
|
desc "Sort the 'failed' queue for the redis_multi_queue failure backend"
|
data/lib/resque/version.rb
CHANGED
data/lib/resque/worker.rb
CHANGED
|
@@ -553,7 +553,15 @@ module Resque
|
|
|
553
553
|
|
|
554
554
|
@heartbeat_thread = Thread.new do
|
|
555
555
|
loop do
|
|
556
|
-
|
|
556
|
+
begin
|
|
557
|
+
heartbeat!
|
|
558
|
+
rescue => e
|
|
559
|
+
# A raise here would kill the thread and stop the worker
|
|
560
|
+
# heartbeating, but the worker itself keeps running and is then
|
|
561
|
+
# pruned out from under itself.
|
|
562
|
+
log_with_severity :error, "Failed to heartbeat: #{e.class}: #{e.message}"
|
|
563
|
+
end
|
|
564
|
+
|
|
557
565
|
signaled = @heartbeat_thread_signal.wait_for_signal(Resque.heartbeat_interval)
|
|
558
566
|
break if signaled
|
|
559
567
|
end
|
data/lib/resque.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: resque
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Wanstrath
|
|
8
8
|
- Steve Klabnik
|
|
9
9
|
- Terence Lee
|
|
10
10
|
- Michael Bianco
|
|
11
|
+
- Patrick Tulskie
|
|
11
12
|
bindir: bin
|
|
12
13
|
cert_chain: []
|
|
13
14
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
@@ -153,7 +154,9 @@ description: |2
|
|
|
153
154
|
* A Ruby library for creating, querying, and processing jobs
|
|
154
155
|
* A Rake task for starting a worker which processes jobs
|
|
155
156
|
* A Sinatra app for monitoring queues, jobs, and workers.
|
|
156
|
-
email:
|
|
157
|
+
email:
|
|
158
|
+
- steve@steveklabnik.com
|
|
159
|
+
- PatrickTulskie@gmail.com
|
|
157
160
|
executables:
|
|
158
161
|
- resque
|
|
159
162
|
- resque-web
|
|
@@ -236,7 +239,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
236
239
|
requirements:
|
|
237
240
|
- - ">="
|
|
238
241
|
- !ruby/object:Gem::Version
|
|
239
|
-
version: 3.
|
|
242
|
+
version: 3.2.0
|
|
240
243
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
241
244
|
requirements:
|
|
242
245
|
- - ">="
|