sidekiq-cron 2.1.0 → 2.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d5f570d2d2d72cdb4fc1a2458dc6cae7abfa5a0da1f27915310b5d5dc835e89
4
- data.tar.gz: 514d74f88c65af3db1956190ca9092d4f1a95d6bce4b0a9191463e2bb0f9181a
3
+ metadata.gz: e256288ae9e2ffd550dfd47aa2d2776cb75edff89c2c7605bbd11685afba79b2
4
+ data.tar.gz: 84eded49d6d3e3da996a73902daf9f27010394b334c769fdebad789acdc006c9
5
5
  SHA512:
6
- metadata.gz: 61f4af041526789dcf51264baa88a5e1855fa124a0fe9f2c5ed04eec36bd4b617aae1152890ab281d5938914725ba9cce812f70bc258a6e4b8d82b642882212a
7
- data.tar.gz: 9e82488bd6459b3ce373e52c94fc3607631b5fc67fa58e578d190368a09f4edc3b4502318646d5285e9fb9d139a080d90d560b28ad31a56e9d01d81d1dc0a2f8
6
+ metadata.gz: a329899c823ee69c96a8849eddb5abcfac3d99d5ac0003e2066f7e919c3bc25fa071518d0fe98c308d75d9076925ca22ec77c1fbc66acd1d740f14086c4c6a97
7
+ data.tar.gz: 917fd615b9855cfa55a90624be4769febc0de1d7e0012f928471f1ba5fa1aa543a39a212b8cd725f6be8eeb7d7494df5a3fb74b532abb8a5d970563ceb3b8da6
data/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 2.3.0
6
+
7
+ - **WARNING** The default value for `available_namespaces` has changed from `nil` to `[default_namespace]`. Please refer to the [migration guide](https://github.com/sidekiq-cron/sidekiq-cron/blob/master/README.md#migrating-to-23) for further details (https://github.com/sidekiq-cron/sidekiq-cron/pull/552)
8
+ - Fix deprecation warning for using raw params in WEB extension (https://github.com/sidekiq-cron/sidekiq-cron/pull/547)
9
+ - Allow for sidekiq embedded configuration (https://github.com/sidekiq-cron/sidekiq-cron/pull/549)
10
+ - Load schedule file within sidekiq callback (https://github.com/sidekiq-cron/sidekiq-cron/pull/550)
11
+ - Fix missing default namespace if namespaces are explicitly provided (https://github.com/sidekiq-cron/sidekiq-cron/pull/551)
12
+
13
+ ## 2.2.0
14
+
15
+ - Add support for Sidekiq v8 (https://github.com/sidekiq-cron/sidekiq-cron/pull/531, https://github.com/sidekiq-cron/sidekiq-cron/pull/538)
16
+ - Always show parsed cron expression in web UI (https://github.com/sidekiq-cron/sidekiq-cron/pull/535)
17
+ - Add fallback to .yaml extension (https://github.com/sidekiq-cron/sidekiq-cron/pull/534)
18
+ - Refactor constantize helper to use Object.const_get (https://github.com/sidekiq-cron/sidekiq-cron/pull/541)
19
+ - Allow testing of schedule by library consumers (https://github.com/sidekiq-cron/sidekiq-cron/pull/543)
20
+
5
21
  ## 2.1.0
6
22
 
7
23
  - Add `available_namespaces` configuration option (https://github.com/sidekiq-cron/sidekiq-cron/pull/524)
data/Gemfile CHANGED
@@ -5,5 +5,5 @@ gemspec
5
5
  # To test different Sidekiq versions
6
6
  gem "sidekiq", ENV.fetch("SIDEKIQ_VERSION", ">= 6")
7
7
 
8
- # To test different Active Job versions
9
- gem "activejob", ENV.fetch("ACTIVE_JOB_VERSION", "~> 7")
8
+ # To test different Rails versions
9
+ gem "rails", ENV.fetch("RAILS_VERSION", "~> 7")
data/README.md CHANGED
@@ -56,7 +56,7 @@ gem "sidekiq-cron"
56
56
  'active_job' => true, # enqueue job through Active Job interface
57
57
  'queue_name_prefix' => 'prefix', # Active Job queue with prefix
58
58
  'queue_name_delimiter' => '.', # Active Job queue with custom delimiter (default: '_')
59
- 'description' => 'A sentence describing what work this job performs'
59
+ 'description' => 'A sentence describing what work this job performs',
60
60
  'status' => 'disabled' # default: enabled
61
61
  }
62
62
  ```
@@ -73,9 +73,9 @@ Sidekiq::Cron.configure do |config|
73
73
  config.cron_schedule_file = 'config/my_schedule.yml' # Default is 'config/schedule.yml'
74
74
  config.cron_history_size = 20 # Default is 10
75
75
  config.default_namespace = 'statistics' # Default is 'default'
76
+ config.available_namespaces = %w[statistics maintenance billing] # Default is `[config.default_namespace]`
76
77
  config.natural_cron_parsing_mode = :strict # Default is :single
77
78
  config.reschedule_grace_period = 300 # Default is 60
78
- config.available_namespaces = %w[maintenance billing] # Default is `nil`
79
79
  end
80
80
  ```
81
81
 
@@ -163,7 +163,7 @@ In the case you'd like to change this value, you can change it via the following
163
163
 
164
164
  ```ruby
165
165
  Sidekiq::Cron.configure do |config|
166
- config.default_namespace = 'statics'
166
+ config.default_namespace = 'statistics'
167
167
  end
168
168
  ```
169
169
 
@@ -179,11 +179,21 @@ Sidekiq::Cron::Job.all('YOUR_OLD_NAMESPACE_NAME').each { |job| job.destroy }
179
179
 
180
180
  #### Available namespaces
181
181
 
182
- By default, Sidekiq Cron retrieves all existing jobs from Redis to determine the namespaces your application uses. However, this approach may not be suitable for large Redis installations. To address this, you can explicitly specify the list of available namespaces using the `available_namespaces` configuration option.
182
+ By default, Sidekiq Cron uses the available_namespaces configuration option to determine which namespaces your application utilizes. The default namespace (`"default"`, by default) is always included in the list of available namespaces.
183
183
 
184
- If `available_namespaces` is set and a job is created with an unexpected namespace, a warning will be printed, and the job will be assigned to the default namespace.
184
+ If you want Sidekiq Cron to automatically detect existing namespaces from the Redis database, you can set `available_namespaces` to the special option `:auto`.
185
185
 
186
- For more details and discussion, see [this issue](https://github.com/sidekiq-cron/sidekiq-cron/issues/516).
186
+ If available_namespaces is explicitly set and a job is created with an unexpected namespace, a warning will be printed, and the job will be assigned to the default namespace.
187
+
188
+ #### Migrating to 2.3
189
+
190
+ As discussed in [this issue](https://github.com/sidekiq-cron/sidekiq-cron/issues/516), the approach introduced in Sidekiq Cron 2.0 for determining available namespaces using the `KEYS` command is not acceptable. Therefore, starting from version 2.3, namespacing has been reworked:
191
+
192
+ - If you were not using the namespacing feature, no action is required. You can even remove `available_namespaces = %w[default]`, as it is now the default.
193
+
194
+ - If you were using the namespacing feature and explicitly specified available namespaces as a list, no changes are needed.
195
+
196
+ - If you were using the namespacing feature and relied on automatic namespace inference, you should either specify all used namespaces explicitly or set `available_namespaces` to `:auto` to maintain automatic detection. However, note that this approach does not scale well (see the referenced issue for details).
187
197
 
188
198
  #### Usage
189
199
 
@@ -511,6 +521,45 @@ Sidekiq::Cron.configure do |config|
511
521
  end
512
522
  ```
513
523
 
524
+ ## Testing your configuration
525
+
526
+ You can test your application's configuration by loading the schedule in your test suite. Below is an example using RSpec in a Rails project:
527
+
528
+ ```ruby
529
+ # spec/cron_schedule_spec.rb
530
+ require "rails_helper"
531
+
532
+ RSpec.describe "Cron Schedule" do
533
+ let(:schedule_loader) { Sidekiq::Cron::ScheduleLoader.new }
534
+ let(:all_jobs) { Sidekiq::Cron::Job.all }
535
+
536
+ # Confirms that `config.cron_schedule_file` points to a real file.
537
+ it "has a schedule file" do
538
+ expect(schedule_loader).to have_schedule_file
539
+ end
540
+
541
+ # Confirms that no jobs in the schedule have an invalid cron string.
542
+ it "does not return any errors" do
543
+ expect(schedule_loader.load_schedule).to be_empty
544
+ end
545
+
546
+ # May be subject to churn, but adds confidence.
547
+ it "adds the expected number of jobs" do
548
+ schedule_loader.load_schedule
549
+ expect(all_jobs.size).to eq 5
550
+ end
551
+
552
+ # Confirms that all job classes exist.
553
+ it "has a valid class for each added job" do
554
+ schedule_loader.load_schedule
555
+ # Shows that all classes exist (as we can constantize the names without raising).
556
+ job_classes = all_jobs.map { |job| job.klass.constantize }
557
+ # Naive check that classes are sidekiq jobs (as they all have `.perfrom_async`).
558
+ expect(job_classes).to all(respond_to(:perform_async))
559
+ end
560
+ end
561
+ ```
562
+
514
563
  ## Contributing
515
564
 
516
565
  **Thanks to all [contributors](https://github.com/sidekiq-cron/sidekiq-cron/graphs/contributors), you’re awesome and this wouldn’t be possible without you!**
@@ -18,7 +18,7 @@ module Sidekiq
18
18
  GLOBALID_KEY = "_sc_globalid"
19
19
 
20
20
  attr_accessor :name, :namespace, :cron, :description, :klass, :args, :message
21
- attr_reader :last_enqueue_time, :fetch_missing_args, :source
21
+ attr_reader :cron_expression_string, :last_enqueue_time, :fetch_missing_args, :source
22
22
 
23
23
  def initialize input_args = {}
24
24
  args = Hash[input_args.map{ |k, v| [k.to_s, v] }]
@@ -32,7 +32,7 @@ module Sidekiq
32
32
 
33
33
  default_namespace = Sidekiq::Cron.configuration.default_namespace
34
34
  @namespace = args["namespace"] || default_namespace
35
- if Sidekiq::Cron::Namespace.available_namespaces_provided? && !Sidekiq::Cron::Namespace.all.include?(@namespace) && @namespace != default_namespace
35
+ if Sidekiq::Cron::Namespace.available_namespaces_provided? && !Sidekiq::Cron::Namespace.all.include?(@namespace)
36
36
  Sidekiq.logger.warn { "Cron Jobs - unexpected namespace #{@namespace} encountered. Assigning to default namespace." }
37
37
  @namespace = default_namespace
38
38
  end
@@ -132,12 +132,7 @@ module Sidekiq
132
132
  def enqueue! time = Time.now.utc
133
133
  @last_enqueue_time = time
134
134
 
135
- klass_const =
136
- begin
137
- Sidekiq::Cron::Support.constantize(@klass.to_s)
138
- rescue NameError
139
- nil
140
- end
135
+ klass_const = Sidekiq::Cron::Support.safe_constantize(@klass.to_s)
141
136
 
142
137
  jid =
143
138
  if klass_const
@@ -160,9 +155,10 @@ module Sidekiq
160
155
  end
161
156
 
162
157
  def is_active_job?(klass = nil)
163
- @active_job || defined?(::ActiveJob::Base) && (klass || Sidekiq::Cron::Support.constantize(@klass.to_s)) < ::ActiveJob::Base
164
- rescue NameError
165
- false
158
+ @active_job || defined?(::ActiveJob::Base) && begin
159
+ klass ||= Sidekiq::Cron::Support.safe_constantize(@klass.to_s)
160
+ klass ? klass < ::ActiveJob::Base : false
161
+ end
166
162
  end
167
163
 
168
164
  def date_as_argument?
@@ -600,6 +596,10 @@ module Sidekiq
600
596
  @args = parse_args(args)
601
597
  end
602
598
 
599
+ def cron_expression_string
600
+ parsed_cron.to_cron_s
601
+ end
602
+
603
603
  private
604
604
 
605
605
  def parsed_cron
@@ -693,7 +693,7 @@ module Sidekiq
693
693
  def self.job_keys_from_namespace(namespace = Sidekiq::Cron.configuration.default_namespace)
694
694
  Sidekiq.redis do |conn|
695
695
  if namespace == '*'
696
- namespaces = Sidekiq::Cron.configuration.available_namespaces&.map { jobs_key(_1) } || conn.keys(jobs_key(namespace))
696
+ namespaces = Sidekiq::Cron::Namespace.all.map { jobs_key(_1) }
697
697
  namespaces.flat_map { |name| conn.smembers(name) }
698
698
  else
699
699
  conn.smembers(jobs_key(namespace))
@@ -800,11 +800,7 @@ module Sidekiq
800
800
  end
801
801
 
802
802
  def get_job_options(klass, args)
803
- klass = klass.is_a?(Class) ? klass : begin
804
- Sidekiq::Cron::Support.constantize(klass)
805
- rescue NameError
806
- # noop
807
- end
803
+ klass = klass.is_a?(Class) ? klass : Sidekiq::Cron::Support.safe_constantize(klass)
808
804
 
809
805
  if klass.nil?
810
806
  # Unknown class
@@ -2,25 +2,20 @@ module Sidekiq
2
2
  module Cron
3
3
  class Namespace
4
4
  def self.all
5
- namespaces = Sidekiq::Cron.configuration.available_namespaces
6
- return namespaces if namespaces
7
-
8
- Sidekiq.redis do |conn|
9
- namespaces = conn.keys('cron_jobs:*').collect do |key|
10
- key.split(':').last
5
+ namespaces = case (available_namespaces = Sidekiq::Cron.configuration.available_namespaces)
6
+ when NilClass then []
7
+ when Array then available_namespaces
8
+ when :auto
9
+ Sidekiq.redis do |conn|
10
+ conn.keys('cron_jobs:*').collect do |key|
11
+ key.split(':').last
12
+ end
13
+ end
14
+ else
15
+ raise ArgumentError, "Unexpected value provided for `available_namespaces`: #{available_namespaces.inspect}"
11
16
  end
12
- end
13
-
14
- # Adds the default namespace if not present
15
- has_default = namespaces.detect do |name|
16
- name == Sidekiq::Cron.configuration.default_namespace
17
- end
18
-
19
- unless has_default
20
- namespaces << Sidekiq::Cron.configuration.default_namespace
21
- end
22
17
 
23
- namespaces
18
+ namespaces | [Sidekiq::Cron.configuration.default_namespace]
24
19
  end
25
20
 
26
21
  def self.all_with_count
@@ -33,15 +28,15 @@ module Sidekiq
33
28
  end
34
29
 
35
30
  def self.count(name = Sidekiq::Cron.configuration.default_namespace)
36
- out = 0
37
31
  Sidekiq.redis do |conn|
38
- out = conn.scard("cron_jobs:#{name}")
32
+ conn.scard("cron_jobs:#{name}")
39
33
  end
40
- out
41
34
  end
42
35
 
43
36
  def self.available_namespaces_provided?
44
- !!Sidekiq::Cron.configuration.available_namespaces
37
+ available_namespaces = Sidekiq::Cron.configuration.available_namespaces
38
+
39
+ available_namespaces != nil && available_namespaces != :auto
45
40
  end
46
41
  end
47
42
  end
@@ -1,16 +1,57 @@
1
- Sidekiq.configure_server do |config|
2
- schedule_file = Sidekiq::Cron.configuration.cron_schedule_file
3
-
4
- if File.exist?(schedule_file)
5
- config.on(:startup) do
6
- schedule = Sidekiq::Cron::Support.load_yaml(ERB.new(IO.read(schedule_file)).result)
7
- if schedule.kind_of?(Hash)
8
- Sidekiq::Cron::Job.load_from_hash!(schedule, source: "schedule")
9
- elsif schedule.kind_of?(Array)
10
- Sidekiq::Cron::Job.load_from_array!(schedule, source: "schedule")
11
- else
12
- raise "Not supported schedule format. Confirm your #{schedule_file}"
1
+ module Sidekiq
2
+ module Cron
3
+ class ScheduleLoader
4
+ def load_schedule
5
+ if schedule.is_a?(Hash)
6
+ Sidekiq::Cron::Job.load_from_hash!(schedule, source: "schedule")
7
+ elsif schedule.is_a?(Array)
8
+ Sidekiq::Cron::Job.load_from_array!(schedule, source: "schedule")
9
+ else
10
+ raise "Not supported schedule format. Confirm your #{schedule_file_name}"
11
+ end
12
+ end
13
+
14
+ def has_schedule_file?
15
+ File.exist?(schedule_file_name)
16
+ end
17
+
18
+ private
19
+
20
+ def schedule
21
+ @schedule ||= Sidekiq::Cron::Support.load_yaml(rendered_schedule_template)
22
+ end
23
+
24
+ def rendered_schedule_template
25
+ ERB.new(schedule_file_content).result
26
+ end
27
+
28
+ def schedule_file_content
29
+ IO.read(schedule_file_name)
30
+ end
31
+
32
+ def schedule_file_name
33
+ @schedule_file_name ||= yml_to_yaml_unless_file_exists(schedule_file_name_from_config)
34
+ end
35
+
36
+ def schedule_file_name_from_config
37
+ Sidekiq::Cron.configuration.cron_schedule_file
38
+ end
39
+
40
+ def yml_to_yaml_unless_file_exists(file_name)
41
+ if File.exist?(file_name)
42
+ file_name
43
+ else
44
+ file_name.sub(/\.yml$/, ".yaml")
45
+ end
13
46
  end
14
47
  end
15
48
  end
16
49
  end
50
+
51
+ Sidekiq.configure_server do |config|
52
+ config.on(:startup) do
53
+ schedule_loader = Sidekiq::Cron::ScheduleLoader.new
54
+ next unless schedule_loader.has_schedule_file?
55
+ schedule_loader.load_schedule
56
+ end
57
+ end
@@ -1,35 +1,10 @@
1
1
  module Sidekiq
2
2
  module Cron
3
3
  module Support
4
- # Inspired by Active Support Inflector
5
- def self.constantize(camel_cased_word)
6
- names = camel_cased_word.split("::".freeze)
7
-
8
- # Trigger a built-in NameError exception including the ill-formed constant in the message.
9
- Object.const_get(camel_cased_word) if names.empty?
10
-
11
- # Remove the first blank element in case of '::ClassName' notation.
12
- names.shift if names.size > 1 && names.first.empty?
13
-
14
- names.inject(Object) do |constant, name|
15
- if constant == Object
16
- constant.const_get(name)
17
- else
18
- candidate = constant.const_get(name)
19
- next candidate if constant.const_defined?(name, false)
20
- next candidate unless Object.const_defined?(name)
21
-
22
- # Go down the ancestors to check if it is owned directly. The check
23
- # stops when we reach Object or the end of ancestors tree.
24
- constant = constant.ancestors.inject(constant) do |const, ancestor|
25
- break const if ancestor == Object
26
- break ancestor if ancestor.const_defined?(name, false)
27
- const
28
- end
29
-
30
- constant.const_get(name, false)
31
- end
32
- end
4
+ def self.safe_constantize(klass_name)
5
+ Object.const_get(klass_name)
6
+ rescue NameError
7
+ nil
33
8
  end
34
9
 
35
10
  def self.load_yaml(src)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sidekiq
4
4
  module Cron
5
- VERSION = "2.1.0"
5
+ VERSION = "2.3.0"
6
6
  end
7
7
  end
@@ -1,113 +1,103 @@
1
- <header class='row'>
2
- <div class='col-sm-5 pull-left'>
3
- <h3>
1
+ <section>
2
+ <header>
3
+ <h2>
4
4
  <%= t('CronJobs') %>
5
- <small><%= @current_namespace %></small>
6
- </h3>
7
- </div>
8
- <div class='col-sm-7 pull-right h2'>
5
+ <small>(<%= @current_namespace %>)</small>
6
+ </h2>
9
7
  <% if @cron_jobs.size > 0 %>
10
- <form action="<%= root_path %>cron/namespaces/<%= @current_namespace %>/all/delete" method="post" class="pull-right">
11
- <%= csrf_tag %>
12
- <input class="btn btn-danger" type="submit" name="delete" value="<%= t('DeleteAll') %>" data-confirm="<%= t('AreYouSureDeleteCronJobs') %>" />
13
- </form>
14
- <form action="<%= root_path %>cron/namespaces/<%= @current_namespace %>/all/disable" method="post" class="pull-right">
15
- <%= csrf_tag %>
16
- <input class="btn btn-warn" type="submit" name="enqueue" value="<%= t('DisableAll') %>" />
17
- </form>
18
- <form action="<%= root_path %>cron/namespaces/<%= @current_namespace %>/all/enable" method="post" class="pull-right">
19
- <%= csrf_tag %>
20
- <input class="btn btn-warn" type="submit" name="enqueue" value="<%= t('EnableAll') %>" />
21
- </form>
22
- <form action="<%= root_path %>cron/namespaces/<%= @current_namespace %>/all/enqueue" method="post" class="pull-right">
23
- <%= csrf_tag %>
24
- <input class="btn btn-warn" type="submit" name="enqueue" value="<%= t('EnqueueAll') %>" data-confirm="<%= t('AreYouSureEnqueueCronJobs') %>" />
8
+ <form class="filter">
9
+ <form action="<%= root_path %>cron/namespaces/<%= @current_namespace %>/all/delete" method="post">
10
+ <%= csrf_tag %>
11
+ <input class="btn btn-danger" type="submit" name="delete" value="<%= t('DeleteAll') %>" data-confirm="<%= t('AreYouSureDeleteCronJobs') %>" />
12
+ </form>
13
+ <form action="<%= root_path %>cron/namespaces/<%= @current_namespace %>/all/disable" method="post">
14
+ <%= csrf_tag %>
15
+ <input class="btn btn-primary" type="submit" name="enqueue" value="<%= t('DisableAll') %>" />
16
+ </form>
17
+ <form action="<%= root_path %>cron/namespaces/<%= @current_namespace %>/all/enable" method="post">
18
+ <%= csrf_tag %>
19
+ <input class="btn btn-primary" type="submit" name="enqueue" value="<%= t('EnableAll') %>" />
20
+ </form>
21
+ <form action="<%= root_path %>cron/namespaces/<%= @current_namespace %>/all/enqueue" method="post">
22
+ <%= csrf_tag %>
23
+ <input class="btn btn-primary" type="submit" name="enqueue" value="<%= t('EnqueueAll') %>" data-confirm="<%= t('AreYouSureEnqueueCronJobs') %>" />
24
+ </form>
25
25
  </form>
26
26
  <% end %>
27
+ </header>
28
+ <!-- Namespaces -->
29
+ <div class="cards-container">
30
+ <% Sidekiq::Cron::Namespace.all_with_count.sort_by { |namespace| namespace[:name] }.each do |namespace| %>
31
+ <article>
32
+ <a href="<%= root_path %>cron/namespaces/<%= namespace[:name] %>">
33
+ <span class="count"><%= namespace[:count] %></span>
34
+ <span class="desc"><%= namespace[:name] %></span>
35
+ </a>
36
+ </article>
37
+ <% end %>
27
38
  </div>
28
- </header>
29
-
30
- <!-- Namespaces -->
31
- <div class='row'>
32
- <div class="col-sm-12 summary_bar">
33
- <ul class="list-unstyled summary row">
34
- <% Sidekiq::Cron::Namespace.all_with_count.sort_by { |namespace| namespace[:name] }.each do |namespace| %>
35
- <li class="col-sm-1">
36
- <a href="<%= root_path %>cron/namespaces/<%= namespace[:name] %>">
37
- <span class="count"><%= namespace[:count] %></span>
38
- <span class="desc"><%= namespace[:name] %></span>
39
- </a>
40
- </li>
41
- <% end %>
42
- </ul>
43
- </div>
44
- </div>
45
- <!-- Namespaces -->
46
-
47
- <% if @cron_jobs.size > 0 %>
48
- <table class="table table-hover table-bordered table-striped table-white">
49
- <thead>
50
- <tr>
51
- <th><%= t('Status') %></th>
52
- <th width="50%"><%= t('Name') %></th>
53
- <th><%= t('CronString') %></th>
54
- <th><%= t('LastEnqueued') %></th>
55
- <th width="180"><%= t('Actions') %></th>
56
- </tr>
57
- </thead>
58
-
59
- <tbody>
60
- <% @cron_jobs.sort{ |a,b| a.sort_name <=> b.sort_name }.each do |job| %>
61
- <% klass = (job.status == 'disabled') ? 'bg-danger text-muted' : '' %>
62
- <% escaped_job_name = CGI.escape(job.name).gsub('+', '%20') %>
39
+ <!-- Namespaces -->
40
+ <% if @cron_jobs.size > 0 %>
41
+ <table class="table table-hover table-bordered table-striped table-white">
42
+ <thead>
63
43
  <tr>
64
- <td class="<%= klass %>"><%= t job.status %></td>
65
- <td class="<%= klass %>">
66
- <a href="<%= root_path %>cron/namespaces/<%= job.namespace %>/jobs/<%= escaped_job_name %>" title="<%= job.description %>">
67
- <b class="<%= klass %>"><%= job.name %></b>
68
- </a>
69
- <br/>
70
- <% if job.message and job.message.to_s.size > 100 %>
71
- <details>
72
- <summary class="btn btn-warn btn-xs">Show message</summary>
73
- <p><small><%= job.message %></small></p>
74
- </details>
75
- <% else %>
76
- <small><%= job.message %></small>
77
- <% end %>
78
- </td>
79
- <td class="<%= klass %>"><b><%= job.human_cron %><br/><small><%= job.cron.gsub(" ", "&nbsp;") %></small></b></td>
80
- <td class="<%= klass %>"><%= job.last_enqueue_time ? relative_time(job.last_enqueue_time) : "-" %></td>
81
- <td class="<%= klass %>">
82
- <% if job.status == 'enabled' %>
83
- <form action="<%= root_path %>cron/namespaces/<%= job.namespace %>/jobs/<%= escaped_job_name %>/enqueue" method="post">
84
- <%= csrf_tag %>
85
- <input class='btn btn-warn btn-xs pull-left' type="submit" name="enqueue" value="<%= t('EnqueueNow') %>" data-confirm="<%= t('AreYouSureEnqueueCronJob', :job => job.name) %>"/>
86
- </form>
87
- <form action="<%= root_path %>cron/namespaces/<%= job.namespace %>/jobs/<%= escaped_job_name %>/disable" method="post">
88
- <%= csrf_tag %>
89
- <input class='btn btn-warn btn-xs pull-left' type="submit" name="disable" value="<%= t('Disable') %>"/>
90
- </form>
91
- <% else %>
92
- <form action="<%= root_path %>cron/namespaces/<%= job.namespace %>/jobs/<%= escaped_job_name %>/enqueue" method="post">
93
- <%= csrf_tag %>
94
- <input class='btn btn-warn btn-xs pull-left' type="submit" name="enqueue" value="<%= t('EnqueueNow') %>" data-confirm="<%= t('AreYouSureEnqueueCronJob', :job => job.name) %>"/>
95
- </form>
96
- <form action="<%= root_path %>cron/namespaces/<%= job.namespace %>/jobs/<%= escaped_job_name %>/enable" method="post">
97
- <%= csrf_tag %>
98
- <input class='btn btn-warn btn-xs pull-left' type="submit" name="enable" value="<%= t('Enable') %>"/>
99
- </form>
100
- <form action="<%= root_path %>cron/namespaces/<%= job.namespace %>/jobs/<%= escaped_job_name %>/delete" method="post">
101
- <%= csrf_tag %>
102
- <input class='btn btn-xs btn-danger pull-left help-block' type="submit" name="delete" value="<%= t('Delete') %>" data-confirm="<%= t('AreYouSureDeleteCronJob', :job => job.name) %>"/>
103
- </form>
104
- <% end %>
105
- </td>
44
+ <th><%= t('Status') %></th>
45
+ <th width="50%"><%= t('Name') %></th>
46
+ <th><%= t('CronString') %></th>
47
+ <th><%= t('LastEnqueued') %></th>
48
+ <th width="180"><%= t('Actions') %></th>
106
49
  </tr>
107
- <% end %>
108
- </tbody>
109
- </table>
110
- <% else %>
111
- <div class='alert alert-success'><%= t('NoCronJobsWereFound') %></div>
112
- <% end %>
113
-
50
+ </thead>
51
+ <tbody>
52
+ <% @cron_jobs.sort{ |a,b| a.sort_name <=> b.sort_name }.each do |job| %>
53
+ <% klass = (job.status == 'disabled') ? 'bg-danger text-muted' : '' %>
54
+ <% escaped_job_name = CGI.escape(job.name).gsub('+', '%20') %>
55
+ <tr>
56
+ <td class="<%= klass %>"><%= t job.status %></td>
57
+ <td class="<%= klass %>">
58
+ <a href="<%= root_path %>cron/namespaces/<%= job.namespace %>/jobs/<%= escaped_job_name %>" title="<%= job.description %>">
59
+ <b class="<%= klass %>"><%= job.name %></b>
60
+ </a>
61
+ <br/>
62
+ <% if job.message and job.message.to_s.size > 100 %>
63
+ <details>
64
+ <p><small><%= job.message %></small></p>
65
+ </details>
66
+ <% else %>
67
+ <small><%= job.message %></small>
68
+ <% end %>
69
+ </td>
70
+ <td class="<%= klass %>"><b><%= job.human_cron %><br/>
71
+ <small><%= job.cron.gsub(" ", "&nbsp;") %></small></b></td>
72
+ <td class="<%= klass %>"><%= job.last_enqueue_time ? relative_time(job.last_enqueue_time) : "-" %></td>
73
+ <td class="<%= klass %>">
74
+ <div class="pagination" style="padding: 0px">
75
+ <form action="<%= root_path %>cron/namespaces/<%= job.namespace %>/jobs/<%= escaped_job_name %>/enqueue" method="post">
76
+ <%= csrf_tag %>
77
+ <input class='btn btn-warn' type="submit" name="enqueue" value="<%= t('EnqueueNow') %>" data-confirm="<%= t('AreYouSureEnqueueCronJob', :job => job.name) %>"/>
78
+ </form>
79
+ <% if job.status == 'enabled' %>
80
+ <form action="<%= root_path %>cron/namespaces/<%= job.namespace %>/jobs/<%= escaped_job_name %>/disable" method="post">
81
+ <%= csrf_tag %>
82
+ <input class='btn btn-warn' type="submit" name="disable" value="<%= t('Disable') %>"/>
83
+ </form>
84
+ <% else %>
85
+ <form action="<%= root_path %>cron/namespaces/<%= job.namespace %>/jobs/<%= escaped_job_name %>/enable" method="post">
86
+ <%= csrf_tag %>
87
+ <input class='btn btn-warn' type="submit" name="enable" value="<%= t('Enable') %>"/>
88
+ </form>
89
+ <form action="<%= root_path %>cron/namespaces/<%= job.namespace %>/jobs/<%= escaped_job_name %>/delete" method="post">
90
+ <%= csrf_tag %>
91
+ <input class='btn btn-danger' type="submit" name="delete" value="<%= t('Delete') %>" data-confirm="<%= t('AreYouSureDeleteCronJob', :job => job.name) %>"/>
92
+ </form>
93
+ <% end %>
94
+ </div>
95
+ </td>
96
+ </tr>
97
+ <% end %>
98
+ </tbody>
99
+ </table>
100
+ <% else %>
101
+ <div class='alert alert-success'><%= t('NoCronJobsWereFound') %></div>
102
+ <% end %>
103
+ </section>