cosmonats 0.2.0 → 0.4.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/README.md +300 -187
- data/lib/cosmo/active_job/adapter.rb +46 -0
- data/lib/cosmo/active_job/executor.rb +16 -0
- data/lib/cosmo/active_job/options.rb +50 -0
- data/lib/cosmo/active_job.rb +29 -0
- data/lib/cosmo/api/busy.rb +2 -2
- data/lib/cosmo/api/counter.rb +2 -2
- data/lib/cosmo/api/cron/entry.rb +99 -0
- data/lib/cosmo/api/cron.rb +118 -0
- data/lib/cosmo/api/kv.rb +36 -14
- data/lib/cosmo/api/stream.rb +27 -9
- data/lib/cosmo/api.rb +1 -0
- data/lib/cosmo/cli.rb +27 -9
- data/lib/cosmo/client.rb +75 -5
- data/lib/cosmo/config.rb +14 -32
- data/lib/cosmo/engine.rb +1 -1
- data/lib/cosmo/job/data.rb +1 -1
- data/lib/cosmo/job/limit.rb +51 -0
- data/lib/cosmo/job/processor.rb +82 -63
- data/lib/cosmo/job.rb +51 -2
- data/lib/cosmo/logger.rb +4 -1
- data/lib/cosmo/processor.rb +108 -0
- data/lib/cosmo/railtie.rb +21 -0
- data/lib/cosmo/stream/processor.rb +24 -60
- data/lib/cosmo/stream.rb +4 -3
- data/lib/cosmo/utils/hash.rb +13 -24
- data/lib/cosmo/utils/overrides.rb +1 -1
- data/lib/cosmo/utils/ttl_cache.rb +44 -0
- data/lib/cosmo/utils.rb +1 -0
- data/lib/cosmo/version.rb +1 -1
- data/lib/cosmo/web/assets/app.css +88 -0
- data/lib/cosmo/web/controllers/crons.rb +41 -0
- data/lib/cosmo/web/controllers/jobs.rb +7 -3
- data/lib/cosmo/web/controllers/streams.rb +36 -10
- data/lib/cosmo/web/helpers/application.rb +17 -2
- data/lib/cosmo/web/views/actions/index.erb +1 -1
- data/lib/cosmo/web/views/crons/_table.erb +58 -0
- data/lib/cosmo/web/views/crons/index.erb +10 -0
- data/lib/cosmo/web/views/jobs/_busy.erb +54 -49
- data/lib/cosmo/web/views/jobs/_dead.erb +70 -65
- data/lib/cosmo/web/views/jobs/_enqueued.erb +82 -56
- data/lib/cosmo/web/views/jobs/_scheduled.erb +53 -48
- data/lib/cosmo/web/views/jobs/_tabs.erb +6 -0
- data/lib/cosmo/web/views/jobs/busy.erb +8 -6
- data/lib/cosmo/web/views/jobs/dead.erb +6 -5
- data/lib/cosmo/web/views/jobs/enqueued.erb +8 -6
- data/lib/cosmo/web/views/jobs/index.erb +1 -1
- data/lib/cosmo/web/views/jobs/scheduled.erb +6 -5
- data/lib/cosmo/web/views/layout.erb +1 -1
- data/lib/cosmo/web/views/streams/_info.erb +3 -0
- data/lib/cosmo/web/views/streams/_pause_banner.erb +17 -0
- data/lib/cosmo/web/views/streams/_stream_row.erb +42 -0
- data/lib/cosmo/web/views/streams/_table.erb +4 -21
- data/lib/cosmo/web.rb +7 -0
- data/lib/cosmo.rb +1 -0
- data/sig/cosmo/active_job/adapter.rbs +13 -0
- data/sig/cosmo/active_job/executor.rbs +9 -0
- data/sig/cosmo/active_job/options.rbs +14 -0
- data/sig/cosmo/api/cron/entry.rbs +30 -0
- data/sig/cosmo/api/cron.rbs +25 -0
- data/sig/cosmo/api/kv.rbs +4 -6
- data/sig/cosmo/api/stream.rbs +7 -1
- data/sig/cosmo/client.rbs +20 -4
- data/sig/cosmo/config.rbs +3 -15
- data/sig/cosmo/job/data.rbs +1 -1
- data/sig/cosmo/job/limit.rbs +18 -0
- data/sig/cosmo/job/processor.rbs +19 -9
- data/sig/cosmo/job.rbs +9 -4
- data/sig/cosmo/processor.rbs +26 -0
- data/sig/cosmo/railtie.rbs +4 -0
- data/sig/cosmo/stream/processor.rbs +4 -10
- data/sig/cosmo/utils/hash.rbs +4 -8
- data/sig/cosmo/utils/ttl_cache.rbs +20 -0
- metadata +25 -3
- data/lib/cosmo/defaults.yml +0 -70
|
@@ -8,6 +8,12 @@ module Cosmo
|
|
|
8
8
|
module Application
|
|
9
9
|
include Renderer
|
|
10
10
|
|
|
11
|
+
def render(template, locals = nil)
|
|
12
|
+
defaults = { request: @request }
|
|
13
|
+
locals = Hash(locals).merge(defaults)
|
|
14
|
+
erb(template, locals)
|
|
15
|
+
end
|
|
16
|
+
|
|
11
17
|
def format_bytes(bytes)
|
|
12
18
|
b = bytes.to_i
|
|
13
19
|
return "0 B" if b.zero?
|
|
@@ -64,11 +70,20 @@ module Cosmo
|
|
|
64
70
|
def current_page?(path)
|
|
65
71
|
request_path = @request.path_info
|
|
66
72
|
request_path = "/" if request_path.empty?
|
|
67
|
-
request_path ==
|
|
73
|
+
request_path == path
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def path_prefix?(*values)
|
|
77
|
+
values.any? { |v| @request.path_info.start_with?(v) }
|
|
68
78
|
end
|
|
69
79
|
|
|
70
80
|
def referrer?(path)
|
|
71
|
-
URI(@request.referrer)
|
|
81
|
+
referrer_uri = URI(@request.referrer)
|
|
82
|
+
referrer_path = referrer_uri.path
|
|
83
|
+
script_name = @request.script_name
|
|
84
|
+
referrer_path = referrer_path.delete_prefix(script_name) if script_name && !script_name.empty?
|
|
85
|
+
referrer_path = "/" if referrer_path.empty?
|
|
86
|
+
referrer_path == path
|
|
72
87
|
end
|
|
73
88
|
end
|
|
74
89
|
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<% if @schedules.empty? -%>
|
|
2
|
+
<div class="alert alert-success">
|
|
3
|
+
No cron schedules deployed in NATS.<br>
|
|
4
|
+
Add a <code>cron:</code> section to <code>cosmo.yml</code> and run
|
|
5
|
+
<code>cosmo --setup</code> to publish them.
|
|
6
|
+
</div>
|
|
7
|
+
<% else -%>
|
|
8
|
+
<div class="table-container">
|
|
9
|
+
<table>
|
|
10
|
+
<thead>
|
|
11
|
+
<tr>
|
|
12
|
+
<th>Job</th>
|
|
13
|
+
<th>Expression</th>
|
|
14
|
+
<th>Timezone</th>
|
|
15
|
+
<th>Args</th>
|
|
16
|
+
<th>Stream</th>
|
|
17
|
+
<th>Actions</th>
|
|
18
|
+
</tr>
|
|
19
|
+
</thead>
|
|
20
|
+
<tbody>
|
|
21
|
+
<% @schedules.each do |s| -%>
|
|
22
|
+
<tr id="cron-row-<%= h(s[:registry_key].to_s.gsub("/", "-")) %>">
|
|
23
|
+
<td>
|
|
24
|
+
<div class="job-class"><%= h(s[:class].to_s) %></div>
|
|
25
|
+
<% if s[:name] -%>
|
|
26
|
+
<code class="text-muted" style="font-size:0.8em;"><%= h(s[:name].to_s) %></code>
|
|
27
|
+
<% end -%>
|
|
28
|
+
<div style="font-size:0.75em; color:#888; margin-top:2px;">
|
|
29
|
+
<code><%= h(s[:schedule_subject].to_s) %></code>
|
|
30
|
+
</div>
|
|
31
|
+
</td>
|
|
32
|
+
<td><code class="subject-tag"><%= h(s[:schedule].to_s) %></code></td>
|
|
33
|
+
<td><%= h(s[:timezone] || "UTC") %></td>
|
|
34
|
+
<td>
|
|
35
|
+
<% if s[:args]&.any? -%>
|
|
36
|
+
<code><%= h(s[:args].inspect) %></code>
|
|
37
|
+
<% else -%>
|
|
38
|
+
<span class="text-muted">—</span>
|
|
39
|
+
<% end -%>
|
|
40
|
+
</td>
|
|
41
|
+
<td><code><%= h(s[:stream].to_s) %></code></td>
|
|
42
|
+
<td style="white-space:nowrap;">
|
|
43
|
+
<a hx-post="<%= url_for('/crons/run', { subject: u(s[:schedule_subject].to_s) }) %>"
|
|
44
|
+
class="btn btn-success btn-sm"
|
|
45
|
+
title="Dispatch this job now, bypassing the schedule timer">► Run Now</a>
|
|
46
|
+
<a hx-delete="<%= url_for('/crons/delete', { subject: u(s[:schedule_subject].to_s) }) %>"
|
|
47
|
+
hx-target="#crons-table"
|
|
48
|
+
hx-swap="innerHTML"
|
|
49
|
+
hx-confirm="Delete '<%= h(s[:schedule_subject].to_s) %>'? NATS will stop firing it."
|
|
50
|
+
class="btn btn-danger btn-sm"
|
|
51
|
+
title="Purge schedule from NATS">🗑 Delete</a>
|
|
52
|
+
</td>
|
|
53
|
+
</tr>
|
|
54
|
+
<% end -%>
|
|
55
|
+
</tbody>
|
|
56
|
+
</table>
|
|
57
|
+
</div>
|
|
58
|
+
<% end -%>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<section>
|
|
2
|
+
<header><%= render('jobs/_tabs') %></header>
|
|
3
|
+
|
|
4
|
+
<div id="crons-table"
|
|
5
|
+
hx-get="<%= url_for('/crons/_table') %>"
|
|
6
|
+
hx-trigger="load, every 10s"
|
|
7
|
+
hx-swap="innerHTML">
|
|
8
|
+
<div class="alert alert-info">Loading cron schedules…</div>
|
|
9
|
+
</div>
|
|
10
|
+
</section>
|
|
@@ -1,50 +1,55 @@
|
|
|
1
|
-
<div
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<% else -%>
|
|
9
|
-
<div class="table-container">
|
|
10
|
-
<table>
|
|
11
|
-
<thead>
|
|
12
|
-
<tr>
|
|
13
|
-
<th>Job</th>
|
|
14
|
-
<th>JID[Args]</th>
|
|
15
|
-
<th>Stream</th>
|
|
16
|
-
<th>Worker</th>
|
|
17
|
-
<th>Running for</th>
|
|
18
|
-
</tr>
|
|
19
|
-
</thead>
|
|
20
|
-
<tbody>
|
|
21
|
-
<% @jobs.each do |job| -%>
|
|
22
|
-
<tr>
|
|
23
|
-
<td>
|
|
24
|
-
<div class="job-class">
|
|
25
|
-
<%= h(job.dig(:data, :class).to_s) %>
|
|
26
|
-
</div>
|
|
27
|
-
</td>
|
|
28
|
-
<td>
|
|
29
|
-
<div class="job-id">
|
|
30
|
-
<details>
|
|
31
|
-
<summary>
|
|
32
|
-
<code><%= h(job.dig(:data, :jid)) %></code>
|
|
33
|
-
</summary>
|
|
34
|
-
<code><%= h(JSON.pretty_generate(job.dig(:data, :args))) %></code>
|
|
35
|
-
</details>
|
|
36
|
-
</div>
|
|
37
|
-
</td>
|
|
38
|
-
<td>
|
|
39
|
-
<code><%= h(job[:stream].to_s) %></code>
|
|
40
|
-
</td>
|
|
41
|
-
<td><code style="font-size: var(--font-size-small);"><%= h(job[:worker].to_s) %></code></td>
|
|
42
|
-
<td>
|
|
43
|
-
<%= elapsed(job[:started_at]) %>
|
|
44
|
-
</td>
|
|
45
|
-
</tr>
|
|
46
|
-
<% end -%>
|
|
47
|
-
</tbody>
|
|
48
|
-
</table>
|
|
1
|
+
<div id="busy-poller"
|
|
2
|
+
hx-get="<%= url_for('/jobs/_busy') %>"
|
|
3
|
+
hx-trigger="every 5s"
|
|
4
|
+
hx-swap="outerHTML">
|
|
5
|
+
<div class="pending">
|
|
6
|
+
<span></span>
|
|
7
|
+
<span class="text-muted"><%= @total %> job(s) running</span>
|
|
49
8
|
</div>
|
|
50
|
-
|
|
9
|
+
|
|
10
|
+
<% if @jobs.empty? -%>
|
|
11
|
+
<div class="alert alert-success">No jobs are currently running</div>
|
|
12
|
+
<% else -%>
|
|
13
|
+
<div class="table-container">
|
|
14
|
+
<table>
|
|
15
|
+
<thead>
|
|
16
|
+
<tr>
|
|
17
|
+
<th>Job</th>
|
|
18
|
+
<th>JID[Args]</th>
|
|
19
|
+
<th>Stream</th>
|
|
20
|
+
<th>Worker</th>
|
|
21
|
+
<th>Running for</th>
|
|
22
|
+
</tr>
|
|
23
|
+
</thead>
|
|
24
|
+
<tbody>
|
|
25
|
+
<% @jobs.each do |job| -%>
|
|
26
|
+
<tr>
|
|
27
|
+
<td>
|
|
28
|
+
<div class="job-class">
|
|
29
|
+
<%= h(job.dig(:data, :class).to_s) %>
|
|
30
|
+
</div>
|
|
31
|
+
</td>
|
|
32
|
+
<td>
|
|
33
|
+
<div class="job-id">
|
|
34
|
+
<details>
|
|
35
|
+
<summary>
|
|
36
|
+
<code><%= h(job.dig(:data, :jid)) %></code>
|
|
37
|
+
</summary>
|
|
38
|
+
<code><%= h(JSON.pretty_generate(job.dig(:data, :args))) %></code>
|
|
39
|
+
</details>
|
|
40
|
+
</div>
|
|
41
|
+
</td>
|
|
42
|
+
<td>
|
|
43
|
+
<code><%= h(job[:stream].to_s) %></code>
|
|
44
|
+
</td>
|
|
45
|
+
<td><code style="font-size: var(--font-size-small);"><%= h(job[:worker].to_s) %></code></td>
|
|
46
|
+
<td>
|
|
47
|
+
<%= elapsed(job[:started_at]) %>
|
|
48
|
+
</td>
|
|
49
|
+
</tr>
|
|
50
|
+
<% end -%>
|
|
51
|
+
</tbody>
|
|
52
|
+
</table>
|
|
53
|
+
</div>
|
|
54
|
+
<% end -%>
|
|
55
|
+
</div>
|
|
@@ -1,65 +1,70 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
<% end -%>
|
|
1
|
+
<div id="dead-poller"
|
|
2
|
+
hx-get="<%= url_for('/jobs/_dead') %>"
|
|
3
|
+
hx-trigger="every 5s"
|
|
4
|
+
hx-swap="outerHTML">
|
|
5
|
+
<% if @jobs.empty? -%>
|
|
6
|
+
<div class="alert alert-success">No dead jobs found</div>
|
|
7
|
+
<% else -%>
|
|
8
|
+
<div class="table-container">
|
|
9
|
+
<table>
|
|
10
|
+
<thead>
|
|
11
|
+
<tr>
|
|
12
|
+
<th>Job</th>
|
|
13
|
+
<th>JID[Args]</th>
|
|
14
|
+
<th>Stream</th>
|
|
15
|
+
<th>Error</th>
|
|
16
|
+
<th>Failed</th>
|
|
17
|
+
<th>Actions</th>
|
|
18
|
+
</tr>
|
|
19
|
+
</thead>
|
|
20
|
+
<tbody>
|
|
21
|
+
<% @jobs.each do |job| -%>
|
|
22
|
+
<tr class="dead-row" id="dead-row-<%= job.seq %>">
|
|
23
|
+
<td>
|
|
24
|
+
<div class="job-class">
|
|
25
|
+
<%= h(job.data[:class].to_s) %>
|
|
26
|
+
</div>
|
|
27
|
+
</td>
|
|
28
|
+
<td>
|
|
29
|
+
<div class="job-id">
|
|
30
|
+
<details>
|
|
31
|
+
<summary>
|
|
32
|
+
<code><%= h(job.data[:jid].to_s) %></code>
|
|
33
|
+
</summary>
|
|
34
|
+
<code><%= h((JSON.pretty_generate(job.data[:args]) rescue job.data[:args].to_s)) %></code>
|
|
35
|
+
</details>
|
|
36
|
+
</div>
|
|
37
|
+
</td>
|
|
38
|
+
<td>
|
|
39
|
+
<code><%= h(job.stream) %></code>
|
|
40
|
+
</td>
|
|
41
|
+
<td style="max-width: 400px;">
|
|
42
|
+
<% if job.data[:error] -%>
|
|
43
|
+
<div class="error-message">
|
|
44
|
+
<strong><%= h(job.data[:error].to_s.split(":").first || "Error") %></strong>
|
|
45
|
+
<p><%= h(job.data[:error]) %></p>
|
|
46
|
+
</div>
|
|
47
|
+
<% end -%>
|
|
48
|
+
</td>
|
|
49
|
+
<td style="white-space: nowrap;">
|
|
50
|
+
<time><%= h(job.timestamp) %></time>
|
|
51
|
+
</td>
|
|
52
|
+
<td>
|
|
53
|
+
<a hx-patch="<%= url_for("/jobs/retry/#{job.seq}") %>"
|
|
54
|
+
hx-target="#dead-row-<%= job.seq %>"
|
|
55
|
+
hx-swap="innerHTML"
|
|
56
|
+
class="btn btn-primary"
|
|
57
|
+
style="width: 100%; text-align: center; margin-bottom: 3px">↻ Retry</a>
|
|
58
|
+
<a hx-delete="<%= url_for("/jobs/delete/#{job.seq}") %>"
|
|
59
|
+
hx-target="#dead-row-<%= job.seq %>"
|
|
60
|
+
hx-swap="innerHTML"
|
|
61
|
+
class="btn btn-danger"
|
|
62
|
+
style="width: 100%; text-align: center; margin-bottom: 3px">✕ Delete</a>
|
|
63
|
+
</td>
|
|
64
|
+
</tr>
|
|
65
|
+
<% end -%>
|
|
66
|
+
</tbody>
|
|
67
|
+
</table>
|
|
68
|
+
</div>
|
|
69
|
+
<% end -%>
|
|
70
|
+
</div>
|
|
@@ -1,60 +1,86 @@
|
|
|
1
|
-
<div
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
<div id="enqueued-poller">
|
|
2
|
+
<div class="nav">
|
|
3
|
+
<% @stream_names.each do |stream_name| -%>
|
|
4
|
+
<button hx-get="<%= url_for('/jobs/enqueued', { stream_name: }) %>"
|
|
5
|
+
hx-target="#content"
|
|
6
|
+
hx-swap="innerHTML"
|
|
7
|
+
hx-push-url="true"
|
|
8
|
+
class="btn <%= "btn-primary" if @stream_name.to_s == stream_name.to_s %>">
|
|
9
|
+
<%= h(stream_name) %>
|
|
10
|
+
</button>
|
|
11
|
+
<% end -%>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="pending">
|
|
15
|
+
<span></span>
|
|
16
|
+
<span class="text-muted"><%= @total %> message(s) pending</span>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<% if @jobs.empty? -%>
|
|
20
|
+
<div class="alert alert-success">Stream <code><%= h(@stream_name) %></code> is empty</div>
|
|
21
|
+
<% else -%>
|
|
22
|
+
<div class="table-container">
|
|
23
|
+
<table>
|
|
24
|
+
<thead>
|
|
25
|
+
<tr>
|
|
26
|
+
<th>Job</th>
|
|
27
|
+
<th>JID[Args]</th>
|
|
28
|
+
<th>Stream</th>
|
|
29
|
+
<th>Seq</th>
|
|
30
|
+
</tr>
|
|
31
|
+
</thead>
|
|
32
|
+
<tbody>
|
|
33
|
+
<% @jobs.each do |job| -%>
|
|
34
|
+
<tr>
|
|
35
|
+
<td>
|
|
36
|
+
<div class="job-class">
|
|
37
|
+
<%= h(job.data[:class].to_s) %>
|
|
38
|
+
</div>
|
|
39
|
+
</td>
|
|
40
|
+
<td>
|
|
41
|
+
<div class="job-id">
|
|
42
|
+
<details>
|
|
43
|
+
<summary>
|
|
44
|
+
<code><%= h(job.data[:jid].to_s) %></code>
|
|
45
|
+
</summary>
|
|
46
|
+
<code><%= h((JSON.pretty_generate(job.data[:args]) rescue job.data[:args].to_s)) %></code>
|
|
47
|
+
</details>
|
|
48
|
+
</div>
|
|
49
|
+
</td>
|
|
50
|
+
<td>
|
|
51
|
+
<code><%= h(job.stream) %></code>
|
|
52
|
+
</td>
|
|
53
|
+
<td>
|
|
54
|
+
<code><%= job.seq %></code>
|
|
55
|
+
</td>
|
|
56
|
+
</tr>
|
|
57
|
+
<% end -%>
|
|
58
|
+
</tbody>
|
|
59
|
+
</table>
|
|
60
|
+
</div>
|
|
10
61
|
<% end -%>
|
|
11
|
-
</div>
|
|
12
62
|
|
|
13
|
-
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
63
|
+
<% if @total_pages > 1 -%>
|
|
64
|
+
<div class="pagination">
|
|
65
|
+
<% if @page > 1 -%>
|
|
66
|
+
<a hx-get="<%= url_for('/jobs/_enqueued', stream_name: @stream_name, page: @page - 1, limit: @limit) %>"
|
|
67
|
+
hx-target="#enqueued-poller"
|
|
68
|
+
hx-swap="outerHTML"
|
|
69
|
+
class="btn">← Prev</a>
|
|
70
|
+
<% else -%>
|
|
71
|
+
<span class="btn btn-disabled">← Prev</span>
|
|
72
|
+
<% end -%>
|
|
73
|
+
|
|
74
|
+
<span class="text-muted">Page <%= @page %> of <%= @total_pages %></span>
|
|
17
75
|
|
|
18
|
-
<% if @
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<th>JID[Args]</th>
|
|
27
|
-
<th>Stream</th>
|
|
28
|
-
<th>Seq</th>
|
|
29
|
-
</tr>
|
|
30
|
-
</thead>
|
|
31
|
-
<tbody>
|
|
32
|
-
<% @jobs.each do |job| -%>
|
|
33
|
-
<tr>
|
|
34
|
-
<td>
|
|
35
|
-
<div class="job-class">
|
|
36
|
-
<%= h(job.data[:class].to_s) %>
|
|
37
|
-
</div>
|
|
38
|
-
</td>
|
|
39
|
-
<td>
|
|
40
|
-
<div class="job-id">
|
|
41
|
-
<details>
|
|
42
|
-
<summary>
|
|
43
|
-
<code><%= h(job.data[:jid].to_s) %></code>
|
|
44
|
-
</summary>
|
|
45
|
-
<code><%= h((JSON.pretty_generate(job.data[:args]) rescue job.data[:args].to_s)) %></code>
|
|
46
|
-
</details>
|
|
47
|
-
</div>
|
|
48
|
-
</td>
|
|
49
|
-
<td>
|
|
50
|
-
<code><%= h(job.stream) %></code>
|
|
51
|
-
</td>
|
|
52
|
-
<td>
|
|
53
|
-
<code><%= job.seq %></code>
|
|
54
|
-
</td>
|
|
55
|
-
</tr>
|
|
56
|
-
<% end -%>
|
|
57
|
-
</tbody>
|
|
58
|
-
</table>
|
|
76
|
+
<% if @page < @total_pages -%>
|
|
77
|
+
<a hx-get="<%= url_for('/jobs/_enqueued', stream_name: @stream_name, page: @page + 1, limit: @limit) %>"
|
|
78
|
+
hx-target="#enqueued-poller"
|
|
79
|
+
hx-swap="outerHTML"
|
|
80
|
+
class="btn">Next →</a>
|
|
81
|
+
<% else -%>
|
|
82
|
+
<span class="btn btn-disabled">Next →</span>
|
|
83
|
+
<% end -%>
|
|
59
84
|
</div>
|
|
60
|
-
<% end -%>
|
|
85
|
+
<% end -%>
|
|
86
|
+
</div>
|
|
@@ -1,49 +1,54 @@
|
|
|
1
|
-
<div
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<% else -%>
|
|
9
|
-
<div class="table-container">
|
|
10
|
-
<table>
|
|
11
|
-
<thead>
|
|
12
|
-
<tr>
|
|
13
|
-
<th>Job</th>
|
|
14
|
-
<th>JID[Args]</th>
|
|
15
|
-
<th>Stream</th>
|
|
16
|
-
<th>When</th>
|
|
17
|
-
</tr>
|
|
18
|
-
</thead>
|
|
19
|
-
<tbody>
|
|
20
|
-
<% @jobs.each do |job| -%>
|
|
21
|
-
<tr>
|
|
22
|
-
<td>
|
|
23
|
-
<div class="job-class">
|
|
24
|
-
<%= h(job.data[:class].to_s) %>
|
|
25
|
-
</div>
|
|
26
|
-
</td>
|
|
27
|
-
<td>
|
|
28
|
-
<div class="job-id">
|
|
29
|
-
<details>
|
|
30
|
-
<summary>
|
|
31
|
-
<code><%= h(job.data[:jid].to_s) %></code>
|
|
32
|
-
</summary>
|
|
33
|
-
<code><%= h((JSON.pretty_generate(job.data[:args]) rescue job.data[:args].to_s)) %></code>
|
|
34
|
-
</details>
|
|
35
|
-
</div>
|
|
36
|
-
</td>
|
|
37
|
-
<td>
|
|
38
|
-
<code><%= h(job.x_stream) %></code>
|
|
39
|
-
</td>
|
|
40
|
-
<td style="white-space: nowrap;">
|
|
41
|
-
<div class="time-badge"><%= h(time_until(job.execute_at)) %></div>
|
|
42
|
-
<time><%= h(format_timestamp(job.execute_at)) %></time>
|
|
43
|
-
</td>
|
|
44
|
-
</tr>
|
|
45
|
-
<% end -%>
|
|
46
|
-
</tbody>
|
|
47
|
-
</table>
|
|
1
|
+
<div id="scheduled-poller"
|
|
2
|
+
hx-get="<%= url_for('/jobs/_scheduled') %>"
|
|
3
|
+
hx-trigger="every 5s"
|
|
4
|
+
hx-swap="outerHTML">
|
|
5
|
+
<div class="pending">
|
|
6
|
+
<span></span>
|
|
7
|
+
<span class="text-muted"><%= @total %> job(s) scheduled</span>
|
|
48
8
|
</div>
|
|
49
|
-
|
|
9
|
+
|
|
10
|
+
<% if @jobs.empty? -%>
|
|
11
|
+
<div class="alert alert-success">No scheduled jobs found</div>
|
|
12
|
+
<% else -%>
|
|
13
|
+
<div class="table-container">
|
|
14
|
+
<table>
|
|
15
|
+
<thead>
|
|
16
|
+
<tr>
|
|
17
|
+
<th>Job</th>
|
|
18
|
+
<th>JID[Args]</th>
|
|
19
|
+
<th>Stream</th>
|
|
20
|
+
<th>When</th>
|
|
21
|
+
</tr>
|
|
22
|
+
</thead>
|
|
23
|
+
<tbody>
|
|
24
|
+
<% @jobs.each do |job| -%>
|
|
25
|
+
<tr>
|
|
26
|
+
<td>
|
|
27
|
+
<div class="job-class">
|
|
28
|
+
<%= h(job.data[:class].to_s) %>
|
|
29
|
+
</div>
|
|
30
|
+
</td>
|
|
31
|
+
<td>
|
|
32
|
+
<div class="job-id">
|
|
33
|
+
<details>
|
|
34
|
+
<summary>
|
|
35
|
+
<code><%= h(job.data[:jid].to_s) %></code>
|
|
36
|
+
</summary>
|
|
37
|
+
<code><%= h((JSON.pretty_generate(job.data[:args]) rescue job.data[:args].to_s)) %></code>
|
|
38
|
+
</details>
|
|
39
|
+
</div>
|
|
40
|
+
</td>
|
|
41
|
+
<td>
|
|
42
|
+
<code><%= h(job.x_stream) %></code>
|
|
43
|
+
</td>
|
|
44
|
+
<td style="white-space: nowrap;">
|
|
45
|
+
<div class="time-badge"><%= h(time_until(job.execute_at)) %></div>
|
|
46
|
+
<time><%= h(format_timestamp(job.execute_at)) %></time>
|
|
47
|
+
</td>
|
|
48
|
+
</tr>
|
|
49
|
+
<% end -%>
|
|
50
|
+
</tbody>
|
|
51
|
+
</table>
|
|
52
|
+
</div>
|
|
53
|
+
<% end -%>
|
|
54
|
+
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<section>
|
|
2
|
-
<header
|
|
2
|
+
<header><%= render('jobs/_tabs') %></header>
|
|
3
3
|
|
|
4
4
|
<div hx-get="<%= url_for('/jobs/_stats') %>"
|
|
5
5
|
hx-trigger="load, every 5s"
|
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
<div class="alert alert-info">Loading statistics…</div>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
|
-
<div id="content"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
<div id="content">
|
|
11
|
+
<div id="busy-poller"
|
|
12
|
+
hx-get="<%= url_for('/jobs/_busy') %>"
|
|
13
|
+
hx-trigger="load"
|
|
14
|
+
hx-swap="outerHTML">
|
|
15
|
+
<div class="alert alert-info">Loading busy jobs…</div>
|
|
16
|
+
</div>
|
|
15
17
|
</div>
|
|
16
18
|
</section>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<section>
|
|
2
|
-
<header
|
|
2
|
+
<header><%= render('jobs/_tabs') %></header>
|
|
3
3
|
|
|
4
4
|
<div hx-get="<%= url_for('/jobs/_stats') %>"
|
|
5
5
|
hx-trigger="load, every 5s"
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
10
|
<div id="content">
|
|
11
|
-
<div
|
|
12
|
-
hx-
|
|
13
|
-
hx-
|
|
14
|
-
|
|
11
|
+
<div id="dead-poller"
|
|
12
|
+
hx-get="<%= url_for('/jobs/_dead') %>"
|
|
13
|
+
hx-trigger="load"
|
|
14
|
+
hx-swap="outerHTML">
|
|
15
|
+
<div class="alert alert-info">Loading dead jobs…</div>
|
|
15
16
|
</div>
|
|
16
17
|
</div>
|
|
17
18
|
</section>
|