solid_objects 0.12.1 → 0.13.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/README.md +58 -11
  4. data/docs/adr/0009-realtime-updates.md +5 -1
  5. data/docs/architecture.md +3 -2
  6. data/docs/authorization.md +11 -4
  7. data/docs/correctness.md +3 -3
  8. data/docs/dashboard.md +200 -0
  9. data/docs/database-schema.md +5 -4
  10. data/docs/realtime.md +22 -14
  11. data/docs/roadmap.md +24 -6
  12. data/docs/security.md +7 -7
  13. data/examples/application/README.md +5 -5
  14. data/examples/application/app/actors/chat_room_actor.rb +1 -1
  15. data/examples/application/app/actors/shopping_cart_actor.rb +2 -2
  16. data/lib/solid_objects/actor.rb +1 -1
  17. data/lib/solid_objects/version.rb +1 -1
  18. data/lib/solid_objects/web/action.rb +109 -0
  19. data/lib/solid_objects/web/application.rb +239 -0
  20. data/lib/solid_objects/web/csrf_protection.rb +130 -0
  21. data/lib/solid_objects/web/helpers.rb +227 -0
  22. data/lib/solid_objects/web/paginator.rb +64 -0
  23. data/lib/solid_objects/web/route.rb +55 -0
  24. data/lib/solid_objects/web/router.rb +46 -0
  25. data/lib/solid_objects/web/statistics.rb +78 -0
  26. data/lib/solid_objects/web.rb +222 -0
  27. data/sig/generated/lib/solid_objects/web/action.rbs +78 -0
  28. data/sig/generated/lib/solid_objects/web/application.rbs +50 -0
  29. data/sig/generated/lib/solid_objects/web/csrf_protection.rbs +55 -0
  30. data/sig/generated/lib/solid_objects/web/helpers.rbs +118 -0
  31. data/sig/generated/lib/solid_objects/web/paginator.rbs +54 -0
  32. data/sig/generated/lib/solid_objects/web/route.rbs +45 -0
  33. data/sig/generated/lib/solid_objects/web/router.rbs +29 -0
  34. data/sig/generated/lib/solid_objects/web/statistics.rbs +46 -0
  35. data/sig/generated/lib/solid_objects/web.rbs +129 -0
  36. data/web/assets/javascripts/application.js +118 -0
  37. data/web/assets/javascripts/charts.js +190 -0
  38. data/web/assets/stylesheets/application.css +527 -0
  39. data/web/views/_messages.erb +29 -0
  40. data/web/views/_navigation.erb +15 -0
  41. data/web/views/_paging.erb +17 -0
  42. data/web/views/_status_filter.erb +8 -0
  43. data/web/views/_summary.erb +39 -0
  44. data/web/views/broadcasts.erb +35 -0
  45. data/web/views/dashboard.erb +128 -0
  46. data/web/views/dead_letter.erb +40 -0
  47. data/web/views/dead_letters.erb +42 -0
  48. data/web/views/effects.erb +35 -0
  49. data/web/views/instance.erb +139 -0
  50. data/web/views/instances.erb +49 -0
  51. data/web/views/layout.erb +22 -0
  52. data/web/views/mailbox.erb +35 -0
  53. data/web/views/message.erb +34 -0
  54. data/web/views/processes.erb +37 -0
  55. data/web/views/reminders.erb +37 -0
  56. metadata +55 -2
@@ -0,0 +1,128 @@
1
+ <% if SolidObjects::Web.charts? %>
2
+ <section class="charts">
3
+ <article class="panel chart-panel">
4
+ <h2>Instances by actor type</h2>
5
+ <%= chart("instances_by_type", @instances_by_type) %>
6
+ </article>
7
+ <article class="panel chart-panel">
8
+ <h2>Mailbox</h2>
9
+ <%= chart("mailbox_depth", {
10
+ "Ready" => @statistics.dig(:mailbox, :ready),
11
+ "Due" => @statistics.dig(:mailbox, :due),
12
+ "Claimed" => @statistics.dig(:mailbox, :claimed)
13
+ }) %>
14
+ </article>
15
+ <article class="panel chart-panel chart-wide">
16
+ <h2>Outboxes and reminders by status</h2>
17
+ <%= chart("work_by_status", {
18
+ "Effects" => @statistics.fetch(:effects),
19
+ "Broadcasts" => @statistics.fetch(:broadcasts),
20
+ "Reminders" => @statistics.fetch(:reminders).except(:due)
21
+ }) %>
22
+ </article>
23
+ </section>
24
+ <% end %>
25
+
26
+ <section class="cards">
27
+ <article class="card">
28
+ <h2>Instances</h2>
29
+ <dl>
30
+ <div><dt>Total</dt><dd><%= number(@statistics.dig(:instances, :total)) %></dd></div>
31
+ <div><dt>Activated</dt><dd><%= number(@statistics.dig(:instances, :activated)) %></dd></div>
32
+ <div><dt>Paused</dt><dd><%= number(@statistics.dig(:instances, :paused)) %></dd></div>
33
+ </dl>
34
+ </article>
35
+
36
+ <article class="card">
37
+ <h2>Mailbox</h2>
38
+ <dl>
39
+ <div><dt>Ready</dt><dd><%= number(@statistics.dig(:mailbox, :ready)) %></dd></div>
40
+ <div><dt>Due</dt><dd><%= number(@statistics.dig(:mailbox, :due)) %></dd></div>
41
+ <div><dt>Claimed</dt><dd><%= number(@statistics.dig(:mailbox, :claimed)) %></dd></div>
42
+ <div><dt>Oldest due</dt><dd><%= duration(@statistics.dig(:mailbox, :latency)) %></dd></div>
43
+ </dl>
44
+ </article>
45
+
46
+ <article class="card">
47
+ <h2>Effects</h2>
48
+ <dl>
49
+ <% @statistics.fetch(:effects).each do |status, count| %>
50
+ <div><dt><%= h(status) %></dt><dd><%= number(count) %></dd></div>
51
+ <% end %>
52
+ </dl>
53
+ </article>
54
+
55
+ <article class="card">
56
+ <h2>Broadcasts</h2>
57
+ <dl>
58
+ <% @statistics.fetch(:broadcasts).each do |status, count| %>
59
+ <div><dt><%= h(status) %></dt><dd><%= number(count) %></dd></div>
60
+ <% end %>
61
+ </dl>
62
+ </article>
63
+
64
+ <article class="card">
65
+ <h2>Reminders</h2>
66
+ <dl>
67
+ <% @statistics.fetch(:reminders).each do |status, count| %>
68
+ <div><dt><%= h(status) %></dt><dd><%= number(count) %></dd></div>
69
+ <% end %>
70
+ </dl>
71
+ </article>
72
+
73
+ <article class="card">
74
+ <h2>Processes</h2>
75
+ <dl>
76
+ <% @statistics.fetch(:processes).each do |state, count| %>
77
+ <div><dt><%= h(state) %></dt><dd><%= number(count) %></dd></div>
78
+ <% end %>
79
+ </dl>
80
+ </article>
81
+ </section>
82
+
83
+ <section class="panel">
84
+ <h2>Processes</h2>
85
+ <% if @processes.empty? %>
86
+ <p class="empty">No process has registered. Start one with <code>solid_objects start</code>.</p>
87
+ <% else %>
88
+ <table>
89
+ <thead>
90
+ <tr><th>Kind</th><th>Host</th><th>PID</th><th>State</th><th>Last heartbeat</th></tr>
91
+ </thead>
92
+ <tbody>
93
+ <% @processes.each do |process_record| %>
94
+ <tr>
95
+ <td><%= h(process_record.kind) %></td>
96
+ <td><%= h(process_record.hostname) %></td>
97
+ <td><%= h(process_record.pid) %></td>
98
+ <td><%= status_label(process_record.shutdown_state) %></td>
99
+ <td><%= relative_time(process_record.last_heartbeat_at) %></td>
100
+ </tr>
101
+ <% end %>
102
+ </tbody>
103
+ </table>
104
+ <% end %>
105
+ </section>
106
+
107
+ <section class="panel">
108
+ <h2>Recent dead letters</h2>
109
+ <% if @dead_letters.empty? %>
110
+ <p class="empty">No message has exhausted its attempts.</p>
111
+ <% else %>
112
+ <table>
113
+ <thead>
114
+ <tr><th>Actor</th><th>Operation</th><th>Exception</th><th>Last failed</th></tr>
115
+ </thead>
116
+ <tbody>
117
+ <% @dead_letters.each do |dead_letter| %>
118
+ <tr>
119
+ <td><a href="<%= h(path_to("/dead_letters/#{dead_letter.id}")) %>"><%= actor_label(dead_letter) %></a></td>
120
+ <td><%= h(dead_letter.operation) %></td>
121
+ <td><%= h(dead_letter.exception_class) %></td>
122
+ <td><%= relative_time(dead_letter.last_failed_at) %></td>
123
+ </tr>
124
+ <% end %>
125
+ </tbody>
126
+ </table>
127
+ <% end %>
128
+ </section>
@@ -0,0 +1,40 @@
1
+ <section class="panel">
2
+ <div class="panel-head">
3
+ <h2><%= h(@dead_letter.operation) %></h2>
4
+ <div class="actions">
5
+ <a href="<%= h(path_to("/instances/#{@dead_letter.instance_id}")) %>"><%= actor_label(@dead_letter) %></a>
6
+ <% if @dead_letter.retried_message_id %>
7
+ <a href="<%= h(path_to("/messages/#{@dead_letter.retried_message_id}")) %>">Retried</a>
8
+ <% else %>
9
+ <%= form_to("/dead_letters/#{@dead_letter.id}/retry") %>
10
+ <button type="submit">Retry</button>
11
+ </form>
12
+ <% end %>
13
+ </div>
14
+ </div>
15
+
16
+ <% if @error %>
17
+ <p class="note note-error">The retry was refused. <%= h(@error) %></p>
18
+ <% end %>
19
+
20
+ <p class="note">
21
+ A retry enqueues the original operation again under an idempotency key, so
22
+ pressing it twice produces one message rather than two.
23
+ </p>
24
+
25
+ <dl class="attributes">
26
+ <div><dt>Attempts</dt><dd><%= number(@dead_letter.attempts) %></dd></div>
27
+ <div><dt>Exception</dt><dd><%= h(@dead_letter.exception_class) %></dd></div>
28
+ <div><dt>First failed</dt><dd><%= relative_time(@dead_letter.first_failed_at) %></dd></div>
29
+ <div><dt>Last failed</dt><dd><%= relative_time(@dead_letter.last_failed_at) %></dd></div>
30
+ </dl>
31
+
32
+ <h3>Message</h3>
33
+ <pre class="payload"><%= h(truncate(@dead_letter.exception_message)) %></pre>
34
+
35
+ <h3>Arguments</h3>
36
+ <%= json_block(@dead_letter.arguments) %>
37
+
38
+ <h3>Backtrace</h3>
39
+ <pre class="payload"><%= h(truncate(Array(@dead_letter.backtrace).join("\n"))) %></pre>
40
+ </section>
@@ -0,0 +1,42 @@
1
+ <section class="panel">
2
+ <h2>Dead letters</h2>
3
+
4
+ <% if @paginator.records.empty? %>
5
+ <p class="empty">No message has exhausted its attempts.</p>
6
+ <% else %>
7
+ <table>
8
+ <thead>
9
+ <tr>
10
+ <th>Actor</th>
11
+ <th>Operation</th>
12
+ <th>Exception</th>
13
+ <th>Attempts</th>
14
+ <th>Last failed</th>
15
+ <th>Retry</th>
16
+ </tr>
17
+ </thead>
18
+ <tbody>
19
+ <% @paginator.records.each do |dead_letter| %>
20
+ <tr>
21
+ <td><a href="<%= h(path_to("/dead_letters/#{dead_letter.id}")) %>"><%= actor_label(dead_letter) %></a></td>
22
+ <td><%= h(dead_letter.operation) %></td>
23
+ <td><%= h(dead_letter.exception_class) %></td>
24
+ <td><%= number(dead_letter.attempts) %></td>
25
+ <td><%= relative_time(dead_letter.last_failed_at) %></td>
26
+ <td>
27
+ <% if dead_letter.retried_message_id %>
28
+ <a href="<%= h(path_to("/messages/#{dead_letter.retried_message_id}")) %>">retried</a>
29
+ <% else %>
30
+ <%= form_to("/dead_letters/#{dead_letter.id}/retry") %>
31
+ <button type="submit">Retry</button>
32
+ </form>
33
+ <% end %>
34
+ </td>
35
+ </tr>
36
+ <% end %>
37
+ </tbody>
38
+ </table>
39
+ <% end %>
40
+
41
+ <%= erb(:_paging) %>
42
+ </section>
@@ -0,0 +1,35 @@
1
+ <section class="panel">
2
+ <h2>Effects</h2>
3
+ <%= erb(:_status_filter, statuses: SolidObjects::Web::Statistics::EFFECT_STATUSES, selected: @status) %>
4
+
5
+ <% if @paginator.records.empty? %>
6
+ <p class="empty">No effect matches this filter.</p>
7
+ <% else %>
8
+ <table>
9
+ <thead>
10
+ <tr>
11
+ <th>Name</th>
12
+ <th>Actor</th>
13
+ <th>Status</th>
14
+ <th>Attempts</th>
15
+ <th>Available</th>
16
+ <th>Completed</th>
17
+ </tr>
18
+ </thead>
19
+ <tbody>
20
+ <% @paginator.records.each do |effect| %>
21
+ <tr>
22
+ <td><%= h(effect.name) %></td>
23
+ <td><a href="<%= h(path_to("/instances/#{effect.instance_id}")) %>">instance <%= number(effect.instance_id) %></a></td>
24
+ <td><%= status_label(effect.status) %></td>
25
+ <td><%= number(effect.attempt_count) %> / <%= number(effect.max_attempts) %></td>
26
+ <td><%= relative_time(effect.available_at) %></td>
27
+ <td><%= relative_time(effect.completed_at) %></td>
28
+ </tr>
29
+ <% end %>
30
+ </tbody>
31
+ </table>
32
+ <% end %>
33
+
34
+ <%= erb(:_paging) %>
35
+ </section>
@@ -0,0 +1,139 @@
1
+ <section class="panel">
2
+ <div class="panel-head">
3
+ <h2><%= actor_label(@instance) %></h2>
4
+ <div class="actions">
5
+ <%= status_label(lease_state(@instance)) %>
6
+ <% if @instance.paused_at %>
7
+ <%= form_to("/instances/#{@instance.id}/resume") %>
8
+ <button type="submit">Resume</button>
9
+ </form>
10
+ <% else %>
11
+ <%= form_to("/instances/#{@instance.id}/pause") %>
12
+ <button type="submit" class="danger">Pause</button>
13
+ </form>
14
+ <% end %>
15
+ </div>
16
+ </div>
17
+
18
+ <p class="note">
19
+ A paused instance is not claimed again. A pass already in flight finishes its
20
+ turn, and a synchronous caller waiting on this instance times out rather than
21
+ receiving a result.
22
+ </p>
23
+
24
+ <dl class="attributes">
25
+ <div><dt>State version</dt><dd><%= number(@instance.state_version) %></dd></div>
26
+ <div><dt>Next sequence</dt><dd><%= number(@instance.next_message_sequence) %></dd></div>
27
+ <div><dt>Activation generation</dt><dd><%= number(@instance.activation_generation) %></dd></div>
28
+ <div><dt>Activation owner</dt><dd><%= h(@instance.activation_owner_id || "none") %></dd></div>
29
+ <div><dt>Lease expires</dt><dd><%= relative_time(@instance.activation_expires_at) %></dd></div>
30
+ <div><dt>Last used</dt><dd><%= relative_time(@instance.last_used_at) %></dd></div>
31
+ <div><dt>Last claimed</dt><dd><%= relative_time(@instance.last_claimed_at) %></dd></div>
32
+ <div><dt>Paused</dt><dd><%= relative_time(@instance.paused_at) %></dd></div>
33
+ </dl>
34
+
35
+ <h3>State</h3>
36
+ <%= json_block(@instance.state) %>
37
+ </section>
38
+
39
+ <section class="panel">
40
+ <h2>Mailbox</h2>
41
+ <%= erb(:_messages, title: "Ready", messages: @ready_messages, empty: "No message is waiting.") %>
42
+ <%= erb(:_messages, title: "Claimed", messages: @claimed_messages, empty: "No message is claimed.") %>
43
+ <%= erb(:_messages, title: "Recent", messages: @recent_messages, empty: "This instance has no message history.") %>
44
+ </section>
45
+
46
+ <section class="panel">
47
+ <h2>Reminders</h2>
48
+ <% if @reminders.empty? %>
49
+ <p class="empty">No reminder is scheduled.</p>
50
+ <% else %>
51
+ <table>
52
+ <thead>
53
+ <tr><th>Name</th><th>Operation</th><th>Status</th><th>Next run</th><th>Interval</th></tr>
54
+ </thead>
55
+ <tbody>
56
+ <% @reminders.each do |reminder| %>
57
+ <tr>
58
+ <td><%= h(reminder.name) %></td>
59
+ <td><%= h(reminder.operation) %></td>
60
+ <td><%= status_label(reminder.status) %></td>
61
+ <td><%= relative_time(reminder.next_run_at) %></td>
62
+ <td><%= duration(reminder.interval_seconds) %></td>
63
+ </tr>
64
+ <% end %>
65
+ </tbody>
66
+ </table>
67
+ <% end %>
68
+ </section>
69
+
70
+ <section class="panel">
71
+ <h2>Effects</h2>
72
+ <% if @effects.empty? %>
73
+ <p class="empty">This instance has emitted no effect.</p>
74
+ <% else %>
75
+ <table>
76
+ <thead>
77
+ <tr><th>Name</th><th>Status</th><th>Attempts</th><th>Available</th><th>Completed</th></tr>
78
+ </thead>
79
+ <tbody>
80
+ <% @effects.each do |effect| %>
81
+ <tr>
82
+ <td><%= h(effect.name) %></td>
83
+ <td><%= status_label(effect.status) %></td>
84
+ <td><%= number(effect.attempt_count) %> / <%= number(effect.max_attempts) %></td>
85
+ <td><%= relative_time(effect.available_at) %></td>
86
+ <td><%= relative_time(effect.completed_at) %></td>
87
+ </tr>
88
+ <% end %>
89
+ </tbody>
90
+ </table>
91
+ <% end %>
92
+ </section>
93
+
94
+ <section class="panel">
95
+ <h2>Broadcasts</h2>
96
+ <% if @broadcasts.empty? %>
97
+ <p class="empty">This instance has broadcast nothing.</p>
98
+ <% else %>
99
+ <table>
100
+ <thead>
101
+ <tr><th>Observable</th><th>Status</th><th>State version</th><th>Attempts</th><th>Delivered</th></tr>
102
+ </thead>
103
+ <tbody>
104
+ <% @broadcasts.each do |broadcast| %>
105
+ <tr>
106
+ <td><%= h(broadcast.observable_name) %></td>
107
+ <td><%= status_label(broadcast.status) %></td>
108
+ <td><%= number(broadcast.state_version) %></td>
109
+ <td><%= number(broadcast.attempt_count) %></td>
110
+ <td><%= relative_time(broadcast.delivered_at) %></td>
111
+ </tr>
112
+ <% end %>
113
+ </tbody>
114
+ </table>
115
+ <% end %>
116
+ </section>
117
+
118
+ <section class="panel">
119
+ <h2>Dead letters</h2>
120
+ <% if @dead_letters.empty? %>
121
+ <p class="empty">No message has exhausted its attempts.</p>
122
+ <% else %>
123
+ <table>
124
+ <thead>
125
+ <tr><th>Operation</th><th>Exception</th><th>Attempts</th><th>Last failed</th></tr>
126
+ </thead>
127
+ <tbody>
128
+ <% @dead_letters.each do |dead_letter| %>
129
+ <tr>
130
+ <td><a href="<%= h(path_to("/dead_letters/#{dead_letter.id}")) %>"><%= h(dead_letter.operation) %></a></td>
131
+ <td><%= h(dead_letter.exception_class) %></td>
132
+ <td><%= number(dead_letter.attempts) %></td>
133
+ <td><%= relative_time(dead_letter.last_failed_at) %></td>
134
+ </tr>
135
+ <% end %>
136
+ </tbody>
137
+ </table>
138
+ <% end %>
139
+ </section>
@@ -0,0 +1,49 @@
1
+ <section class="panel">
2
+ <h2>Instances</h2>
3
+
4
+ <form class="filters-form" method="get" action="<%= h(path_to("/instances")) %>">
5
+ <label>
6
+ Actor type
7
+ <input type="search" name="actor_type" list="actor-types" value="<%= h(url_params("actor_type")) %>" />
8
+ </label>
9
+ <datalist id="actor-types">
10
+ <% @actor_types.each do |actor_type| %>
11
+ <option value="<%= h(actor_type) %>"></option>
12
+ <% end %>
13
+ </datalist>
14
+ <label>
15
+ Actor id contains
16
+ <input type="search" name="actor_id" value="<%= h(url_params("actor_id")) %>" />
17
+ </label>
18
+ <button type="submit">Filter</button>
19
+ </form>
20
+
21
+ <% if @paginator.records.empty? %>
22
+ <p class="empty">No instance matches this filter.</p>
23
+ <% else %>
24
+ <table>
25
+ <thead>
26
+ <tr>
27
+ <th>Actor</th>
28
+ <th>Lease</th>
29
+ <th>State version</th>
30
+ <th>Next sequence</th>
31
+ <th>Last used</th>
32
+ </tr>
33
+ </thead>
34
+ <tbody>
35
+ <% @paginator.records.each do |instance| %>
36
+ <tr data-instance-row="<%= h(instance.id) %>">
37
+ <td><%= instance_link(instance) %></td>
38
+ <td><%= status_label(lease_state(instance)) %></td>
39
+ <td><%= number(instance.state_version) %></td>
40
+ <td><%= number(instance.next_message_sequence) %></td>
41
+ <td><%= relative_time(instance.last_used_at) %></td>
42
+ </tr>
43
+ <% end %>
44
+ </tbody>
45
+ </table>
46
+ <% end %>
47
+
48
+ <%= erb(:_paging) %>
49
+ </section>
@@ -0,0 +1,22 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="robots" content="noindex, nofollow" />
7
+ <title>Solid Objects</title>
8
+ <link rel="stylesheet" href="<%= h(path_to("/stylesheets/application.css")) %>" />
9
+ <script src="<%= h(path_to("/javascripts/application.js")) %>" nonce="<%= h(csp_nonce) %>" defer></script>
10
+ <% if SolidObjects::Web.charts? && current_tab?("/") %>
11
+ <script src="<%= h(chart_library_source) %>"<%= chart_library_integrity_attributes %> defer></script>
12
+ <script src="<%= h(path_to("/javascripts/charts.js")) %>" nonce="<%= h(csp_nonce) %>" defer></script>
13
+ <% end %>
14
+ </head>
15
+ <body data-stats-path="<%= h(path_to("/stats")) %>">
16
+ <%= erb(:_navigation) %>
17
+ <main class="page">
18
+ <%= erb(:_summary) %>
19
+ <%= locals.fetch(:content) %>
20
+ </main>
21
+ </body>
22
+ </html>
@@ -0,0 +1,35 @@
1
+ <section class="panel">
2
+ <h2>Mailbox</h2>
3
+ <%= erb(:_status_filter, statuses: SolidObjects::Web::Application::MAILBOX_MEMBERSHIPS, selected: @membership, all: false) %>
4
+
5
+ <% if @paginator.records.empty? %>
6
+ <p class="empty">No message is <%= h(@membership) %>.</p>
7
+ <% else %>
8
+ <table>
9
+ <thead>
10
+ <tr>
11
+ <th>Actor</th>
12
+ <th>Sequence</th>
13
+ <th>Operation</th>
14
+ <th>Delivery</th>
15
+ <th>Attempts</th>
16
+ <th>Available</th>
17
+ </tr>
18
+ </thead>
19
+ <tbody>
20
+ <% @paginator.records.each do |message| %>
21
+ <tr>
22
+ <td><a href="<%= h(path_to("/instances/#{message.instance_id}")) %>"><%= actor_label(message) %></a></td>
23
+ <td><a href="<%= h(path_to("/messages/#{message.id}")) %>"><%= number(message.sequence) %></a></td>
24
+ <td><%= h(message.operation) %></td>
25
+ <td><%= status_label(message.delivery_mode) %></td>
26
+ <td><%= number(message.attempt_count) %> / <%= number(message.max_attempts) %></td>
27
+ <td><%= relative_time(message.available_at) %></td>
28
+ </tr>
29
+ <% end %>
30
+ </tbody>
31
+ </table>
32
+ <% end %>
33
+
34
+ <%= erb(:_paging) %>
35
+ </section>
@@ -0,0 +1,34 @@
1
+ <section class="panel">
2
+ <div class="panel-head">
3
+ <h2><%= h(@message.operation) %></h2>
4
+ <div class="actions">
5
+ <%= status_label(@message.delivery_mode) %>
6
+ <a href="<%= h(path_to("/instances/#{@message.instance_id}")) %>"><%= actor_label(@message) %></a>
7
+ </div>
8
+ </div>
9
+
10
+ <dl class="attributes">
11
+ <div><dt>Sequence</dt><dd><%= number(@message.sequence) %></dd></div>
12
+ <div><dt>Attempts</dt><dd><%= number(@message.attempt_count) %> / <%= number(@message.max_attempts) %></dd></div>
13
+ <div><dt>Request id</dt><dd><%= h(@message.request_id) %></dd></div>
14
+ <div><dt>Idempotency key</dt><dd><%= h(@message.idempotency_key || "none") %></dd></div>
15
+ <div><dt>Enqueued</dt><dd><%= relative_time(@message.enqueued_at) %></dd></div>
16
+ <div><dt>Available</dt><dd><%= relative_time(@message.available_at) %></dd></div>
17
+ <div><dt>Started</dt><dd><%= relative_time(@message.started_at) %></dd></div>
18
+ <div><dt>Completed</dt><dd><%= relative_time(@message.completed_at) %></dd></div>
19
+ <div><dt>Rejected</dt><dd><%= relative_time(@message.rejected_at) %></dd></div>
20
+ <div><dt>Last failed</dt><dd><%= relative_time(@message.last_failed_at) %></dd></div>
21
+ </dl>
22
+
23
+ <h3>Arguments</h3>
24
+ <%= json_block(@message.arguments) %>
25
+
26
+ <h3>Result</h3>
27
+ <%= json_block(@message.result) %>
28
+
29
+ <h3>Error</h3>
30
+ <%= json_block(@message.error) %>
31
+
32
+ <h3>Rejection</h3>
33
+ <%= json_block(@message.rejection) %>
34
+ </section>
@@ -0,0 +1,37 @@
1
+ <section class="panel">
2
+ <h2>Processes</h2>
3
+ <%= erb(:_status_filter, statuses: SolidObjects::Web::Statistics::PROCESS_STATES, selected: @status) %>
4
+
5
+ <% if @paginator.records.empty? %>
6
+ <p class="empty">No process matches this filter.</p>
7
+ <% else %>
8
+ <table>
9
+ <thead>
10
+ <tr>
11
+ <th>Kind</th>
12
+ <th>Host</th>
13
+ <th>PID</th>
14
+ <th>State</th>
15
+ <th>Started</th>
16
+ <th>Last heartbeat</th>
17
+ <th>Activated instances</th>
18
+ </tr>
19
+ </thead>
20
+ <tbody>
21
+ <% @paginator.records.each do |process_record| %>
22
+ <tr>
23
+ <td><%= h(process_record.kind) %></td>
24
+ <td><%= h(process_record.hostname) %></td>
25
+ <td><%= h(process_record.pid) %></td>
26
+ <td><%= status_label(process_record.shutdown_state) %></td>
27
+ <td><%= relative_time(process_record.started_at) %></td>
28
+ <td><%= relative_time(process_record.last_heartbeat_at) %></td>
29
+ <td><%= number(@activated_counts.fetch(process_record.id, 0)) %></td>
30
+ </tr>
31
+ <% end %>
32
+ </tbody>
33
+ </table>
34
+ <% end %>
35
+
36
+ <%= erb(:_paging) %>
37
+ </section>
@@ -0,0 +1,37 @@
1
+ <section class="panel">
2
+ <h2>Reminders</h2>
3
+ <%= erb(:_status_filter, statuses: SolidObjects::Web::Statistics::REMINDER_STATUSES, selected: @status) %>
4
+
5
+ <% if @paginator.records.empty? %>
6
+ <p class="empty">No reminder matches this filter.</p>
7
+ <% else %>
8
+ <table>
9
+ <thead>
10
+ <tr>
11
+ <th>Actor</th>
12
+ <th>Name</th>
13
+ <th>Operation</th>
14
+ <th>Status</th>
15
+ <th>Next run</th>
16
+ <th>Interval</th>
17
+ <th>Occurrence</th>
18
+ </tr>
19
+ </thead>
20
+ <tbody>
21
+ <% @paginator.records.each do |reminder| %>
22
+ <tr>
23
+ <td><a href="<%= h(path_to("/instances/#{reminder.instance_id}")) %>"><%= actor_label(reminder) %></a></td>
24
+ <td><%= h(reminder.name) %></td>
25
+ <td><%= h(reminder.operation) %></td>
26
+ <td><%= status_label(reminder.status) %></td>
27
+ <td><%= relative_time(reminder.next_run_at) %></td>
28
+ <td><%= duration(reminder.interval_seconds) %></td>
29
+ <td><%= number(reminder.occurrence) %></td>
30
+ </tr>
31
+ <% end %>
32
+ </tbody>
33
+ </table>
34
+ <% end %>
35
+
36
+ <%= erb(:_paging) %>
37
+ </section>