cosmonats 0.8.0 → 0.9.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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -11
  3. data/lib/cosmo/api/counter.rb +1 -1
  4. data/lib/cosmo/api/cron/entry.rb +10 -11
  5. data/lib/cosmo/api/cron.rb +3 -3
  6. data/lib/cosmo/api/job.rb +1 -1
  7. data/lib/cosmo/api/kv.rb +3 -3
  8. data/lib/cosmo/api/stats/busy.rb +5 -0
  9. data/lib/cosmo/api/stats/metrics.rb +3 -3
  10. data/lib/cosmo/api/stats.rb +15 -0
  11. data/lib/cosmo/api/stream.rb +7 -2
  12. data/lib/cosmo/client.rb +10 -13
  13. data/lib/cosmo/job/data.rb +1 -1
  14. data/lib/cosmo/job/processor.rb +5 -6
  15. data/lib/cosmo/utils.rb +0 -1
  16. data/lib/cosmo/version.rb +1 -1
  17. data/lib/cosmo/web/assets/app.css +271 -629
  18. data/lib/cosmo/web/assets/app.js +158 -0
  19. data/lib/cosmo/web/assets/fonts/OFL.txt +92 -0
  20. data/lib/cosmo/web/assets/fonts/geist-mono.woff2 +0 -0
  21. data/lib/cosmo/web/assets/fonts/geist.woff2 +0 -0
  22. data/lib/cosmo/web/assets/logo.svg +10 -0
  23. data/lib/cosmo/web/cache.rb +75 -0
  24. data/lib/cosmo/web/chart.rb +62 -38
  25. data/lib/cosmo/web/circuit.rb +55 -0
  26. data/lib/cosmo/web/context.rb +0 -4
  27. data/lib/cosmo/web/controllers/application.rb +16 -0
  28. data/lib/cosmo/web/controllers/batches.rb +1 -0
  29. data/lib/cosmo/web/controllers/crons.rb +9 -2
  30. data/lib/cosmo/web/controllers/jobs.rb +50 -59
  31. data/lib/cosmo/web/controllers/metrics.rb +1 -0
  32. data/lib/cosmo/web/controllers/overview.rb +70 -0
  33. data/lib/cosmo/web/controllers/processes.rb +33 -0
  34. data/lib/cosmo/web/controllers/streams.rb +26 -28
  35. data/lib/cosmo/web/helpers/application.rb +57 -0
  36. data/lib/cosmo/web/renderer.rb +19 -6
  37. data/lib/cosmo/web/throughput.rb +104 -0
  38. data/lib/cosmo/web/views/batches/_table.erb +42 -54
  39. data/lib/cosmo/web/views/batches/index.erb +3 -10
  40. data/lib/cosmo/web/views/crons/_table.erb +32 -53
  41. data/lib/cosmo/web/views/crons/index.erb +2 -9
  42. data/lib/cosmo/web/views/jobs/_args.erb +5 -0
  43. data/lib/cosmo/web/views/jobs/_busy.erb +25 -121
  44. data/lib/cosmo/web/views/jobs/_dead.erb +42 -82
  45. data/lib/cosmo/web/views/jobs/_enqueued.erb +33 -113
  46. data/lib/cosmo/web/views/jobs/_panel.erb +29 -0
  47. data/lib/cosmo/web/views/jobs/_queue_row.erb +18 -0
  48. data/lib/cosmo/web/views/jobs/_queues.erb +20 -0
  49. data/lib/cosmo/web/views/jobs/_scheduled.erb +20 -54
  50. data/lib/cosmo/web/views/jobs/index.erb +4 -17
  51. data/lib/cosmo/web/views/layout.erb +73 -22
  52. data/lib/cosmo/web/views/metrics/_chart.erb +37 -32
  53. data/lib/cosmo/web/views/metrics/_panel.erb +3 -3
  54. data/lib/cosmo/web/views/metrics/_table.erb +24 -40
  55. data/lib/cosmo/web/views/metrics/index.erb +8 -16
  56. data/lib/cosmo/web/views/overview/_badges.erb +3 -0
  57. data/lib/cosmo/web/views/overview/_kpis.erb +19 -0
  58. data/lib/cosmo/web/views/overview/_running.erb +18 -0
  59. data/lib/cosmo/web/views/overview/_throughput.erb +50 -0
  60. data/lib/cosmo/web/views/overview/_workers.erb +25 -0
  61. data/lib/cosmo/web/views/overview/index.erb +28 -0
  62. data/lib/cosmo/web/views/processes/_table.erb +67 -0
  63. data/lib/cosmo/web/views/processes/index.erb +1 -0
  64. data/lib/cosmo/web/views/shared/_pager.erb +22 -0
  65. data/lib/cosmo/web/views/shared/unavailable.erb +7 -0
  66. data/lib/cosmo/web/views/streams/_info.erb +28 -86
  67. data/lib/cosmo/web/views/streams/_list.erb +7 -0
  68. data/lib/cosmo/web/views/streams/_pause_banner.erb +8 -16
  69. data/lib/cosmo/web/views/streams/_stream.erb +52 -0
  70. data/lib/cosmo/web/views/streams/index.erb +3 -11
  71. data/lib/cosmo/web/views/streams/info.erb +3 -11
  72. data/lib/cosmo/web.rb +66 -35
  73. data/sig/cosmo/api/cron/entry.rbs +2 -1
  74. data/sig/cosmo/api/stats/busy.rbs +2 -0
  75. data/sig/cosmo/api/stats/metrics.rbs +1 -1
  76. data/sig/cosmo/api/stats.rbs +4 -0
  77. data/sig/cosmo/api/stream.rbs +2 -0
  78. data/sig/cosmo/client.rbs +1 -0
  79. metadata +31 -18
  80. data/lib/cosmo/utils/overrides.rb +0 -206
  81. data/lib/cosmo/utils/warnings.rb +0 -17
  82. data/lib/cosmo/web/controllers/actions.rb +0 -16
  83. data/lib/cosmo/web/views/actions/index.erb +0 -7
  84. data/lib/cosmo/web/views/jobs/_processes.erb +0 -145
  85. data/lib/cosmo/web/views/jobs/_stats.erb +0 -77
  86. data/lib/cosmo/web/views/jobs/_tabs.erb +0 -14
  87. data/lib/cosmo/web/views/jobs/busy.erb +0 -18
  88. data/lib/cosmo/web/views/jobs/dead.erb +0 -18
  89. data/lib/cosmo/web/views/jobs/enqueued.erb +0 -18
  90. data/lib/cosmo/web/views/jobs/scheduled.erb +0 -18
  91. data/lib/cosmo/web/views/streams/_stream_row.erb +0 -42
  92. data/lib/cosmo/web/views/streams/_table.erb +0 -25
@@ -1,145 +0,0 @@
1
- <% nav = @polling ? { limit: @limit } : { limit: @limit, poll: 0 } -%>
2
- <% states = { "running" => "badge-success", "quiet" => "badge-warning", "stopping" => "badge-danger" } -%>
3
- <% kinds = { "jobs" => "Jobs", "streams" => "Streams", "jobs+streams" => "Jobs & Streams", "none" => "No subscriptions" } -%>
4
- <div id="processes-poller"
5
- <% if @polling -%>
6
- hx-get="<%= url_for('/jobs/_processes', page: @page, limit: @limit) %>"
7
- hx-trigger="every <%= poll_interval %>s"
8
- hx-swap="outerHTML"
9
- <% end -%>
10
- >
11
- <div class="pending">
12
- <span></span>
13
- <span class="poll-status">
14
- <span class="text-muted"><%= @total %> process(es) alive</span>
15
- <% if @polling -%>
16
- <a class="btn btn-sm"
17
- hx-get="<%= url_for('/jobs/_processes', nav.merge(page: @page, poll: 0)) %>"
18
- hx-target="#processes-poller"
19
- hx-swap="outerHTML"
20
- hx-indicator="#global-spinner">&#9209; Stop Polling</a>
21
- <% else -%>
22
- <a class="btn btn-sm btn-primary"
23
- hx-get="<%= url_for('/jobs/_processes', page: @page, limit: @limit) %>"
24
- hx-target="#processes-poller"
25
- hx-swap="outerHTML"
26
- hx-indicator="#global-spinner">&#x25B6; Live Poll</a>
27
- <% end -%>
28
- </span>
29
- </div>
30
-
31
- <% if @groups.empty? -%>
32
- <div class="alert alert-warning">No worker processes are running</div>
33
- <% else -%>
34
- <div class="table-container">
35
- <table>
36
- <thead>
37
- <tr>
38
- <th>Process</th>
39
- <th>Command</th>
40
- <th>Subscriptions</th>
41
- <th>Busy</th>
42
- <th>State</th>
43
- <th>RTT</th>
44
- <th>Reconnects</th>
45
- <th>Memory</th>
46
- <th>Uptime</th>
47
- <th>Last beat</th>
48
- </tr>
49
- </thead>
50
- <tbody>
51
- <% @groups.each do |kind, group| -%>
52
- <tr class="group-row">
53
- <td colspan="10"><%= h(kinds.fetch(kind, kind)) %> <span class="text-muted">(<%= group.size %>)</span></td>
54
- </tr>
55
- <% group.each do |process| -%>
56
- <% nats = Hash(process[:nats]) -%>
57
- <tr>
58
- <td>
59
- <div class="job-class"><%= h(process[:hostname]) %>:<%= h(process[:pid]) %></div>
60
- <div class="text-muted" style="font-size: var(--font-size-small);">
61
- <%= h(nats[:ip] || "unknown IP") %>
62
- <% if nats[:name] -%>&middot; <code><%= h(nats[:name]) %></code><% end -%>
63
- </div>
64
- </td>
65
- <td>
66
- <% cmdline = process[:cmdline].to_s -%>
67
- <div class="job-id">
68
- <% if cmdline.length > 32 -%>
69
- <details>
70
- <summary>
71
- <code class="truncate" title="<%= h(cmdline) %>"><%= h(cmdline) %></code>
72
- </summary>
73
- <code style="font-size: var(--font-size-small);"><%= h(cmdline) %></code>
74
- </details>
75
- <% else -%>
76
- <code style="font-size: var(--font-size-small);"><%= h(cmdline) %></code>
77
- <% end -%>
78
- <div class="text-muted" style="font-size: var(--font-size-small);">
79
- cosmo <%= h(process[:version]) %> &middot; <%= h(process[:ruby]) %>
80
- <% if process[:http_port] -%>&middot; http :<%= h(process[:http_port]) %><% end -%>
81
- </div>
82
- </div>
83
- </td>
84
- <td>
85
- <% Hash(process[:subscriptions]).each do |type, names| -%>
86
- <% next if Array(names).empty? -%>
87
- <div>
88
- <% if kind == "jobs+streams" -%><span class="text-muted"><%= h(type) %>:</span><% end -%>
89
- <code><%= h(Array(names).join(", ")) %></code>
90
- </div>
91
- <% end -%>
92
- </td>
93
- <td><%= h(process[:busy]) %> / <%= h(process[:concurrency]) %></td>
94
- <td><span class="badge <%= states.fetch(process[:state].to_s, 'badge-info') %>"><%= h(process[:state]) %></span></td>
95
- <td><%= nats[:rtt] ? "#{h(nats[:rtt])}ms" : '&mdash;' %></td>
96
- <td><%= h(nats[:reconnects].to_i) %></td>
97
- <td><%= process[:rss] ? format_bytes(process[:rss].to_i * 1024) : '&mdash;' %></td>
98
- <td><%= elapsed(process[:started_at]) %></td>
99
- <td><%= elapsed(process[:beat_at]) %> ago</td>
100
- </tr>
101
- <% end -%>
102
- <% end -%>
103
- </tbody>
104
- </table>
105
- </div>
106
- <% end -%>
107
-
108
- <% if @total_pages.to_i > 1 -%>
109
- <div class="pagination">
110
- <% if @page > 1 -%>
111
- <a hx-get="<%= url_for('/jobs/_processes', nav.merge(page: @page - 1)) %>"
112
- hx-target="#processes-poller"
113
- hx-swap="outerHTML"
114
- hx-indicator="#global-spinner"
115
- class="btn">&#x2190; Prev</a>
116
- <% else -%>
117
- <span class="btn btn-disabled">&#x2190; Prev</span>
118
- <% end -%>
119
-
120
- <% pages(@page, @total_pages).each do |p| -%>
121
- <% if p == :gap -%>
122
- <span class="pagination-gap">&hellip;</span>
123
- <% elsif p == @page -%>
124
- <span class="btn btn-primary btn-disabled"><%= p %></span>
125
- <% else -%>
126
- <a hx-get="<%= url_for('/jobs/_processes', nav.merge(page: p)) %>"
127
- hx-target="#processes-poller"
128
- hx-swap="outerHTML"
129
- hx-indicator="#global-spinner"
130
- class="btn"><%= p %></a>
131
- <% end -%>
132
- <% end -%>
133
-
134
- <% if @page < @total_pages -%>
135
- <a hx-get="<%= url_for('/jobs/_processes', nav.merge(page: @page + 1)) %>"
136
- hx-target="#processes-poller"
137
- hx-swap="outerHTML"
138
- hx-indicator="#global-spinner"
139
- class="btn">Next &#x2192;</a>
140
- <% else -%>
141
- <span class="btn btn-disabled">Next &#x2192;</span>
142
- <% end -%>
143
- </div>
144
- <% end -%>
145
- </div>
@@ -1,77 +0,0 @@
1
- <div class="cards-container">
2
- <article class="stat-card">
3
- <div class="row">
4
- <p>&#x1F4AA; Processed</p>
5
- <h4><%= format_numbers(@processed) %></h4>
6
- </div>
7
- <div class="row">
8
- <p>&#x1F625; Failed</p>
9
- <h4><%= format_numbers(@failed) %></h4>
10
- </div>
11
- <div class="row">
12
- <p>&#x1F504; Retries</p>
13
- <h4><%= format_numbers(@retries) %></h4>
14
- </div>
15
- </article>
16
- <article class="stat-card">
17
- <h3><%= format_numbers(@busy) %></h3>
18
- <p>
19
- <a href="<%= url_for('/jobs/busy') %>"
20
- hx-get="<%= url_for('/jobs/busy') %>"
21
- hx-target="#content"
22
- hx-push-url="true"
23
- hx-indicator="#global-spinner">&#x23F3; Busy</a>
24
- </p>
25
- </article>
26
- <article class="stat-card">
27
- <h3><%= format_numbers(@enqueued) %></h3>
28
- <p>
29
- <a href="<%= url_for('/jobs/enqueued') %>"
30
- hx-get="<%= url_for('/jobs/enqueued') %>"
31
- hx-target="#content"
32
- hx-push-url="true"
33
- hx-indicator="#global-spinner"
34
- class="<%= 'active' if referrer?('/jobs/enqueued') %>">&#x1F4EC; Enqueued</a>
35
- </p>
36
- </article>
37
- <% if Cosmo::Config.scheduled.enabled -%>
38
- <article class="stat-card">
39
- <h3><%= format_numbers(@scheduled) %></h3>
40
- <p>
41
- <a href="<%= url_for('/jobs/scheduled') %>"
42
- hx-get="<%= url_for('/jobs/scheduled') %>"
43
- hx-target="#content"
44
- hx-push-url="true"
45
- hx-indicator="#global-spinner"
46
- class="<%= 'active' if referrer?('/jobs/scheduled') %>">&#x23F0; Scheduled</a>
47
- </p>
48
- </article>
49
- <% end -%>
50
- <% if Cosmo::Config.dead.enabled -%>
51
- <article class="stat-card">
52
- <h3><%= format_numbers(@dead) %></h3>
53
- <p>
54
- <a href="<%= url_for('/jobs/dead') %>"
55
- hx-get="<%= url_for('/jobs/dead') %>"
56
- hx-target="#content"
57
- hx-push-url="true"
58
- hx-indicator="#global-spinner"
59
- class="<%= 'active' if referrer?('/jobs/dead') %>">&#x1F480; Dead</a>
60
- </p>
61
- </article>
62
- <% end -%>
63
- </div>
64
-
65
- <script>
66
- (function () {
67
- const links = document.querySelectorAll('.cards-container a');
68
-
69
- links.forEach(link => {
70
- link.addEventListener('click', () => {
71
- links.forEach(item => item.classList.remove('active'));
72
-
73
- link.classList.add('active');
74
- });
75
- });
76
- })();
77
- </script>
@@ -1,14 +0,0 @@
1
- <nav class="tabs">
2
- <a href="<%= url_for('/jobs') %>"
3
- class="<%= 'active' if path_prefix?('/jobs') %>">&#x1F4CB; Dashboard</a>
4
- <a href="<%= url_for('/batches') %>"
5
- class="<%= 'active' if path_prefix?('/batches') %>">&#x1F4E6; Batches</a>
6
- <% if Cosmo::Config.scheduled.enabled -%>
7
- <a href="<%= url_for('/crons') %>"
8
- class="<%= 'active' if path_prefix?('/crons') %>">&#x23F0; Crons</a>
9
- <% end -%>
10
- <% if Cosmo::Config.metrics.enabled -%>
11
- <a href="<%= url_for('/metrics') %>"
12
- class="<%= 'active' if path_prefix?('/metrics') %>">&#x1F4CA; Metrics</a>
13
- <% end -%>
14
- </nav>
@@ -1,18 +0,0 @@
1
- <section>
2
- <header><%= render('jobs/_tabs') %></header>
3
-
4
- <div hx-get="<%= url_for('/jobs/_stats') %>"
5
- hx-trigger="load, every <%= poll_interval %>s"
6
- hx-swap="innerHTML">
7
- <div class="alert alert-info">Loading statistics…</div>
8
- </div>
9
-
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&hellip;</div>
16
- </div>
17
- </div>
18
- </section>
@@ -1,18 +0,0 @@
1
- <section>
2
- <header><%= render('jobs/_tabs') %></header>
3
-
4
- <div hx-get="<%= url_for('/jobs/_stats') %>"
5
- hx-trigger="load, every <%= poll_interval %>s"
6
- hx-swap="innerHTML">
7
- <div class="alert alert-info">Loading statistics…</div>
8
- </div>
9
-
10
- <div id="content">
11
- <div id="dead-jobs"
12
- hx-get="<%= url_for('/jobs/_dead') %>"
13
- hx-trigger="load"
14
- hx-swap="outerHTML">
15
- <div class="alert alert-info">Loading dead jobs&hellip;</div>
16
- </div>
17
- </div>
18
- </section>
@@ -1,18 +0,0 @@
1
- <section>
2
- <header><%= render('jobs/_tabs') %></header>
3
-
4
- <div hx-get="<%= url_for('/jobs/_stats') %>"
5
- hx-trigger="load, every <%= poll_interval %>s"
6
- hx-swap="innerHTML">
7
- <div class="alert alert-info">Loading statistics…</div>
8
- </div>
9
-
10
- <div id="content">
11
- <div id="enqueued-poller"
12
- hx-get="<%= url_for('/jobs/_enqueued', stream_name: @stream_name) %>"
13
- hx-trigger="load"
14
- hx-swap="outerHTML">
15
- <div class="alert alert-info">Loading enqueued jobs&hellip;</div>
16
- </div>
17
- </div>
18
- </section>
@@ -1,18 +0,0 @@
1
- <section>
2
- <header><%= render('jobs/_tabs') %></header>
3
-
4
- <div hx-get="<%= url_for('/jobs/_stats') %>"
5
- hx-trigger="load, every <%= poll_interval %>s"
6
- hx-swap="innerHTML">
7
- <div class="alert alert-info">Loading statistics…</div>
8
- </div>
9
-
10
- <div id="content">
11
- <div id="scheduled-poller"
12
- hx-get="<%= url_for('/jobs/_scheduled') %>"
13
- hx-trigger="load"
14
- hx-swap="outerHTML">
15
- <div class="alert alert-info">Loading scheduled jobs&hellip;</div>
16
- </div>
17
- </div>
18
- </section>
@@ -1,42 +0,0 @@
1
- <tr id="stream-row-<%= h(@stream[:name]) %>">
2
- <td>
3
- <a href="<%= url_for('/streams/info', { name: u(@stream[:name]) }) %>"
4
- hx-get="<%= url_for('/streams/info', { name: u(@stream[:name]) }) %>"
5
- hx-target="#content"
6
- hx-push-url="true"
7
- class="stream-link"><%= h(@stream[:name]) %></a>
8
- </td>
9
- <td><%= format_numbers(@stream[:messages]) %></td>
10
- <td><%= format_bytes(@stream[:bytes]) %></td>
11
- <td><%= @stream[:consumer_count] %></td>
12
- <td>
13
- <div class="subjects">
14
- <% Array(@stream[:subjects]).each do |subject| -%>
15
- <span class="subject-tag"><%= h(subject) %></span>
16
- <% end -%>
17
- </div>
18
- </td>
19
- <td><code><%= format_numbers(@stream[:first_seq]) %> &rarr; <%= format_numbers(@stream[:last_seq]) %></code></td>
20
- <td>
21
- <% if @stream[:paused] -%>
22
- <span class="badge badge-warning">&#9208; Paused</span>
23
- <% elsif @stream[:consumer_count].to_i > 0 -%>
24
- <span class="badge badge-success">&#9654; Active</span>
25
- <% else -%>
26
- <span class="badge">No consumers</span>
27
- <% end -%>
28
- </td>
29
- <td>
30
- <% if @stream[:paused] -%>
31
- <a hx-patch="<%= url_for('/streams/unpause', { name: u(@stream[:name]) }) %>"
32
- hx-target="#stream-row-<%= h(@stream[:name]) %>"
33
- hx-swap="outerHTML"
34
- class="btn btn-success">&#9654; Resume</a>
35
- <% else -%>
36
- <a hx-patch="<%= url_for('/streams/pause', { name: u(@stream[:name]) }) %>"
37
- hx-target="#stream-row-<%= h(@stream[:name]) %>"
38
- hx-swap="outerHTML"
39
- class="btn btn-warning">&#9208; Pause</a>
40
- <% end -%>
41
- </td>
42
- </tr>
@@ -1,25 +0,0 @@
1
- <% if @streams.empty? -%>
2
- <div class="alert alert-info">No streams found</div>
3
- <% else -%>
4
- <div class="table-container">
5
- <table>
6
- <thead>
7
- <tr>
8
- <th>Name</th>
9
- <th>Messages</th>
10
- <th>Size</th>
11
- <th>Consumers</th>
12
- <th>Subjects</th>
13
- <th>Sequence</th>
14
- <th>Status</th>
15
- <th>Actions</th>
16
- </tr>
17
- </thead>
18
- <tbody>
19
- <% @streams.each do |stream| -%>
20
- <%= render("streams/_stream_row", { stream: stream }) %>
21
- <% end -%>
22
- </tbody>
23
- </table>
24
- </div>
25
- <% end -%>