solid_stack_web 0.5.0 → 0.6.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 +8 -2
- data/app/assets/stylesheets/solid_stack_web/_07_dashboard.css +49 -1
- data/app/assets/stylesheets/solid_stack_web/_08_filters.css +6 -0
- data/app/controllers/solid_stack_web/cable/channel_purges_controller.rb +8 -0
- data/app/controllers/solid_stack_web/cable/purges_controller.rb +9 -0
- data/app/controllers/solid_stack_web/cable_controller.rb +20 -1
- data/app/controllers/solid_stack_web/cable_messages_controller.rb +13 -0
- data/app/helpers/solid_stack_web/application_helper.rb +12 -0
- data/app/models/solid_stack_web/cable_stats.rb +15 -2
- data/app/models/solid_stack_web/cable_timeline.rb +32 -0
- data/app/views/layouts/solid_stack_web/application.html.erb +10 -0
- data/app/views/solid_stack_web/cable/index.html.erb +50 -3
- data/app/views/solid_stack_web/cable_messages/index.html.erb +50 -0
- data/app/views/solid_stack_web/cache_entries/index.html.erb +1 -1
- data/app/views/solid_stack_web/dashboard/index.html.erb +24 -3
- data/app/views/solid_stack_web/failed_jobs/index.html.erb +1 -1
- data/app/views/solid_stack_web/history/index.html.erb +1 -1
- data/app/views/solid_stack_web/jobs/index.html.erb +1 -1
- data/app/views/solid_stack_web/queues/show.html.erb +1 -1
- data/config/routes.rb +4 -1
- data/lib/solid_stack_web/version.rb +1 -1
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3d16b6a549b38e32fd792d4bff55d990ae5a80d458dcbc47df54759c947536c6
|
|
4
|
+
data.tar.gz: 2ff73287be32fa8a3d3a1089ebd71656bed2ce80742cd11296a3458e0ebeaae3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bd5aefe87facc4b0ac5f9c2265fa714d6d70127b4c785fc450a210dbcb0958bd03b7aef09de7b0555d4b54de7defe702a464c296357fdb49411b5b532dce341d
|
|
7
|
+
data.tar.gz: 8a0527f29907807f16711e72bbfc13833a0be47481d4e540e1ad1a324dad63eb73da4c5f5c0043bfcdfd6ae458d5163af66e2e350fd9c5ecd76fe13c70b35913
|
data/README.md
CHANGED
|
@@ -114,7 +114,13 @@ Filters are preserved when switching between status tabs (Ready / Scheduled / Ru
|
|
|
114
114
|
|
|
115
115
|
## Solid Cable
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
### Features
|
|
118
|
+
|
|
119
|
+
- **Dashboard card** — the overview dashboard shows total messages, channels, messages per hour (last 60 minutes), oldest pending message age, and a top-3 channel breakdown by volume
|
|
120
|
+
- **24-hour timeline** — bar chart of message volume on the Cable overview page; each bar represents one hour with a hover tooltip showing the exact count
|
|
121
|
+
- **Channel browser** — `GET /cable` lists all active channels with per-channel message count and last-message timestamp, ordered by most recent activity; supports `?q=` filtering by channel name substring; empty state shown when no messages exist
|
|
122
|
+
- **Per-channel message list** — `GET /cable/channels/:channel_hash` shows a paginated, reverse-chronological list of that channel's `SolidCable::Message` records; each row shows the message ID, a truncated payload preview (120 chars) with the full payload on hover, and a relative sent time with the exact timestamp on hover; supports `?q=` filtering by payload substring; **Purge Channel** button deletes all messages for the channel
|
|
123
|
+
- **Message purge** — "Purge Old" form on the channel browser deletes all messages older than 1, 7, or 30 days; confirmation prompt before any destructive action
|
|
118
124
|
|
|
119
125
|
---
|
|
120
126
|
|
|
@@ -137,7 +143,7 @@ _Channel monitoring coming in v0.6.0. Currently shows active message count and d
|
|
|
137
143
|
"slow_jobs": 7
|
|
138
144
|
},
|
|
139
145
|
"cache": { "entries": 1024, "byte_size": 2097152, "oldest_entry": "2026-05-20T10:00:00Z" },
|
|
140
|
-
"cable": { "messages": 50, "channels": 3 },
|
|
146
|
+
"cable": { "messages": 50, "channels": 3, "messages_per_hour": 12, "oldest_message": "2026-05-20T10:00:00Z", "top_channels": { "ActionCable::Channel::Base": 30, "ChatChannel": 15, "NotificationsChannel": 5 } },
|
|
141
147
|
"generated_at": "2026-05-26T10:00:00Z"
|
|
142
148
|
}
|
|
143
149
|
```
|
|
@@ -114,6 +114,7 @@ a.sqw-inline-stat:hover { opacity: 0.7; text-decoration: none; }
|
|
|
114
114
|
grid-template-columns: 1fr 1fr;
|
|
115
115
|
gap: 0;
|
|
116
116
|
margin-top: 1.5rem;
|
|
117
|
+
margin-bottom: 1.5rem;
|
|
117
118
|
border: 1px solid var(--border);
|
|
118
119
|
border-radius: var(--radius);
|
|
119
120
|
background: var(--surface);
|
|
@@ -121,11 +122,19 @@ a.sqw-inline-stat:hover { opacity: 0.7; text-decoration: none; }
|
|
|
121
122
|
overflow: hidden;
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
.sqw-timeline-grid--single {
|
|
126
|
+
grid-template-columns: 1fr;
|
|
127
|
+
}
|
|
128
|
+
|
|
124
129
|
.sqw-timeline-chart {
|
|
125
130
|
border-top: none;
|
|
126
131
|
color: var(--purple);
|
|
127
132
|
}
|
|
128
133
|
|
|
134
|
+
.sqw-timeline-chart--cable {
|
|
135
|
+
color: var(--info);
|
|
136
|
+
}
|
|
137
|
+
|
|
129
138
|
.sqw-timeline-chart + .sqw-timeline-chart {
|
|
130
139
|
border-left: 1px solid var(--border);
|
|
131
140
|
}
|
|
@@ -160,4 +169,43 @@ a.sqw-inline-stat:hover { opacity: 0.7; text-decoration: none; }
|
|
|
160
169
|
color: var(--muted);
|
|
161
170
|
margin-top: 3px;
|
|
162
171
|
opacity: 0.7;
|
|
163
|
-
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.sqw-card-list {
|
|
175
|
+
padding: 0.75rem 1.25rem 1rem;
|
|
176
|
+
border-top: 1px solid var(--border);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.sqw-card-list__label {
|
|
180
|
+
display: block;
|
|
181
|
+
font-size: 10px;
|
|
182
|
+
font-weight: 500;
|
|
183
|
+
text-transform: uppercase;
|
|
184
|
+
letter-spacing: .06em;
|
|
185
|
+
color: var(--muted);
|
|
186
|
+
margin-bottom: 0.5rem;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.sqw-card-list__row {
|
|
190
|
+
display: flex;
|
|
191
|
+
align-items: center;
|
|
192
|
+
justify-content: space-between;
|
|
193
|
+
gap: 0.5rem;
|
|
194
|
+
padding: 0.2rem 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.sqw-card-list__name {
|
|
198
|
+
font-size: 12px;
|
|
199
|
+
color: var(--text);
|
|
200
|
+
overflow: hidden;
|
|
201
|
+
text-overflow: ellipsis;
|
|
202
|
+
white-space: nowrap;
|
|
203
|
+
min-width: 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.sqw-card-list__count {
|
|
207
|
+
font-size: 12px;
|
|
208
|
+
font-weight: 600;
|
|
209
|
+
color: var(--info);
|
|
210
|
+
flex-shrink: 0;
|
|
211
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
module SolidStackWeb
|
|
2
|
+
class Cable::ChannelPurgesController < ApplicationController
|
|
3
|
+
def destroy
|
|
4
|
+
::SolidCable::Message.where(channel_hash: params[:channel_hash]).delete_all
|
|
5
|
+
redirect_to cable_path, notice: "All messages for this channel have been purged."
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module SolidStackWeb
|
|
2
|
+
class Cable::PurgesController < ApplicationController
|
|
3
|
+
def destroy
|
|
4
|
+
days = [params[:older_than].to_i, 1].max
|
|
5
|
+
::SolidCable::Message.where("created_at < ?", days.days.ago).delete_all
|
|
6
|
+
redirect_to cable_path, notice: "Messages older than #{days} #{days == 1 ? "day" : "days"} purged."
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
module SolidStackWeb
|
|
2
2
|
class CableController < ApplicationController
|
|
3
3
|
def index
|
|
4
|
+
@search = params[:q].presence
|
|
4
5
|
@total_messages = ::SolidCable::Message.count
|
|
5
|
-
@channels =
|
|
6
|
+
@channels = channel_rows
|
|
7
|
+
@timeline = CableTimeline.new
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def channel_rows
|
|
13
|
+
scope = ::SolidCable::Message
|
|
14
|
+
if @search
|
|
15
|
+
scope = scope.where("channel LIKE ?", "%#{::ActiveRecord::Base.sanitize_sql_like(@search)}%")
|
|
16
|
+
end
|
|
17
|
+
scope
|
|
18
|
+
.group(:channel, :channel_hash)
|
|
19
|
+
.order(Arel.sql("MAX(created_at) DESC"))
|
|
20
|
+
.pluck(:channel, :channel_hash, Arel.sql("COUNT(*)"), Arel.sql("MAX(created_at)"))
|
|
21
|
+
.map do |ch, hash, cnt, last_at|
|
|
22
|
+
last_at = Time.zone.parse(last_at) if last_at.is_a?(String)
|
|
23
|
+
{ channel: ch, channel_hash: hash, message_count: cnt, last_message_at: last_at }
|
|
24
|
+
end
|
|
6
25
|
end
|
|
7
26
|
end
|
|
8
27
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module SolidStackWeb
|
|
2
|
+
class CableMessagesController < ApplicationController
|
|
3
|
+
def index
|
|
4
|
+
@search = params[:q].presence
|
|
5
|
+
scope = ::SolidCable::Message.where(channel_hash: params[:channel_hash])
|
|
6
|
+
if @search
|
|
7
|
+
scope = scope.where("payload LIKE ?", "%#{::ActiveRecord::Base.sanitize_sql_like(@search)}%")
|
|
8
|
+
end
|
|
9
|
+
@channel_name = ::SolidCable::Message.where(channel_hash: params[:channel_hash]).pick(:channel) || params[:channel_hash]
|
|
10
|
+
@pagy, @messages = pagy(scope.order(created_at: :desc))
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -42,6 +42,18 @@ module SolidStackWeb
|
|
|
42
42
|
end
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
def cable_messages_timeline_svg(timeline)
|
|
46
|
+
build_sparkline_svg(
|
|
47
|
+
Struct.new(:buckets, :max).new(timeline.message_buckets, timeline.message_max),
|
|
48
|
+
css_class: "sqw-sparkline sqw-sparkline--lg",
|
|
49
|
+
aria_label: "Cable messages over the last 24 hours"
|
|
50
|
+
) do |count, i|
|
|
51
|
+
hours_ago = CableTimeline::HOURS - 1 - i
|
|
52
|
+
label = count == 1 ? "message" : "messages"
|
|
53
|
+
hours_ago.zero? ? "#{count} #{label} this hour" : "#{count} #{label} #{hours_ago}h ago"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
45
57
|
def throughput_sparkline_svg(sparkline)
|
|
46
58
|
build_sparkline_svg(sparkline, aria_label: "Throughput over the last 12 hours") do |count, i|
|
|
47
59
|
hours_ago = SolidStackWeb::ThroughputSparkline::HOURS - i
|
|
@@ -2,9 +2,22 @@ module SolidStackWeb
|
|
|
2
2
|
class CableStats
|
|
3
3
|
def to_h
|
|
4
4
|
{
|
|
5
|
-
messages:
|
|
6
|
-
channels:
|
|
5
|
+
messages: ::SolidCable::Message.count,
|
|
6
|
+
channels: ::SolidCable::Message.distinct.count(:channel),
|
|
7
|
+
messages_per_hour: ::SolidCable::Message.where(created_at: 1.hour.ago..).count,
|
|
8
|
+
oldest_message: ::SolidCable::Message.minimum(:created_at),
|
|
9
|
+
top_channels: top_channels_by_volume
|
|
7
10
|
}
|
|
8
11
|
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def top_channels_by_volume
|
|
16
|
+
::SolidCable::Message
|
|
17
|
+
.group(:channel)
|
|
18
|
+
.order(Arel.sql("count(*) DESC"))
|
|
19
|
+
.limit(3)
|
|
20
|
+
.count
|
|
21
|
+
end
|
|
9
22
|
end
|
|
10
23
|
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module SolidStackWeb
|
|
2
|
+
class CableTimeline
|
|
3
|
+
HOURS = 24
|
|
4
|
+
|
|
5
|
+
def message_buckets
|
|
6
|
+
@message_buckets ||= build_buckets { |rows, from, to| rows.count { |t| t >= from && t < to } }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def message_max
|
|
10
|
+
message_buckets.max || 0
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def rows
|
|
16
|
+
@rows ||= begin
|
|
17
|
+
origin = Time.current - HOURS.hours
|
|
18
|
+
::SolidCable::Message.where(created_at: origin..Time.current).pluck(:created_at)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def build_buckets(&block)
|
|
23
|
+
now = Time.current
|
|
24
|
+
origin = now - HOURS.hours
|
|
25
|
+
HOURS.times.map do |i|
|
|
26
|
+
from = origin + i.hours
|
|
27
|
+
to = origin + (i + 1).hours
|
|
28
|
+
block.call(rows, from, to)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Solid Stack Dashboard</title>
|
|
7
|
+
<link rel="icon" href="data:,">
|
|
7
8
|
<%= csrf_meta_tags %>
|
|
8
9
|
<%= csp_meta_tag %>
|
|
9
10
|
<%= inline_styles %>
|
|
@@ -35,6 +36,15 @@
|
|
|
35
36
|
</nav>
|
|
36
37
|
<% end %>
|
|
37
38
|
|
|
39
|
+
<% if current_section == :cable %>
|
|
40
|
+
<nav class="sqw-subnav">
|
|
41
|
+
<div class="sqw-subnav__inner">
|
|
42
|
+
<%= link_to "Overview", cable_path,
|
|
43
|
+
class: "sqw-subnav__link#{" sqw-subnav__link--active" if controller_name == "cable"}" %>
|
|
44
|
+
</div>
|
|
45
|
+
</nav>
|
|
46
|
+
<% end %>
|
|
47
|
+
|
|
38
48
|
<% if current_section == :queue %>
|
|
39
49
|
<nav class="sqw-subnav">
|
|
40
50
|
<div class="sqw-subnav__inner">
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
<div class="sqw-page-header">
|
|
1
|
+
<div class="sqw-page-header sqw-page-header--split">
|
|
2
2
|
<h1 class="sqw-page-title">Solid Cable</h1>
|
|
3
|
+
<div class="sqw-header-actions">
|
|
4
|
+
<%= form_with url: cable_purge_path, method: :delete, class: "sqw-inline-form",
|
|
5
|
+
data: { turbo_confirm: "Purge these messages? This cannot be undone." } do |f| %>
|
|
6
|
+
<%= f.select :older_than,
|
|
7
|
+
[["Older than 1 day", 1], ["Older than 7 days", 7], ["Older than 30 days", 30]],
|
|
8
|
+
{}, class: "sqw-select" %>
|
|
9
|
+
<%= f.submit "Purge Old", class: "sqw-btn sqw-btn--danger sqw-btn--sm" %>
|
|
10
|
+
<% end %>
|
|
11
|
+
</div>
|
|
3
12
|
</div>
|
|
4
13
|
|
|
5
14
|
<div class="sqw-stats-grid">
|
|
@@ -13,15 +22,53 @@
|
|
|
13
22
|
</div>
|
|
14
23
|
</div>
|
|
15
24
|
|
|
25
|
+
<div class="sqw-timeline-grid sqw-timeline-grid--single" data-controller="sparkline-tooltip">
|
|
26
|
+
<div class="sqw-sparkline-wrap sqw-timeline-chart sqw-timeline-chart--cable">
|
|
27
|
+
<span class="sqw-sparkline-label">Messages — last 24 hours</span>
|
|
28
|
+
<div class="sqw-sparkline-positioner">
|
|
29
|
+
<%= cable_messages_timeline_svg(@timeline) %>
|
|
30
|
+
<div class="sqw-sparkline-tip" data-sparkline-tooltip-target="tip" hidden></div>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="sqw-sparkline-axis">
|
|
33
|
+
<span>24h ago</span>
|
|
34
|
+
<span>12h ago</span>
|
|
35
|
+
<span>now</span>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<form class="sqw-filters" action="<%= cable_path %>" method="get" data-controller="search">
|
|
41
|
+
<input class="sqw-search-input" type="search" name="q" value="<%= @search %>"
|
|
42
|
+
placeholder="Filter by channel…" autocomplete="off" aria-label="Filter by channel"
|
|
43
|
+
data-action="input->search#filter">
|
|
44
|
+
<% if @search.present? %>
|
|
45
|
+
<%= link_to "Clear", cable_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
|
|
46
|
+
<% end %>
|
|
47
|
+
</form>
|
|
48
|
+
|
|
16
49
|
<% if @channels.any? %>
|
|
17
50
|
<table class="sqw-table">
|
|
18
51
|
<thead>
|
|
19
|
-
<tr
|
|
52
|
+
<tr>
|
|
53
|
+
<th>Channel</th>
|
|
54
|
+
<th>Messages</th>
|
|
55
|
+
<th>Last Message</th>
|
|
56
|
+
</tr>
|
|
20
57
|
</thead>
|
|
21
58
|
<tbody>
|
|
22
59
|
<% @channels.each do |channel| %>
|
|
23
|
-
<tr
|
|
60
|
+
<tr>
|
|
61
|
+
<td class="sqw-monospace sqw-truncate" title="<%= channel[:channel] %>">
|
|
62
|
+
<%= link_to channel[:channel], cable_channel_messages_path(channel[:channel_hash]), class: "sqw-link" %>
|
|
63
|
+
</td>
|
|
64
|
+
<td><%= channel[:message_count] %></td>
|
|
65
|
+
<td class="sqw-muted"><%= channel[:last_message_at]&.strftime("%b %d %H:%M") %></td>
|
|
66
|
+
</tr>
|
|
24
67
|
<% end %>
|
|
25
68
|
</tbody>
|
|
26
69
|
</table>
|
|
70
|
+
<% else %>
|
|
71
|
+
<div class="sqw-empty">
|
|
72
|
+
<p><%= @search.present? ? "No channels matching “#{@search}”.".html_safe : "No cable messages." %></p>
|
|
73
|
+
</div>
|
|
27
74
|
<% end %>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<div class="sqw-page-header sqw-page-header--split">
|
|
2
|
+
<h1 class="sqw-page-title sqw-truncate" title="<%= @channel_name %>"><%= @channel_name %></h1>
|
|
3
|
+
<div class="sqw-header-actions">
|
|
4
|
+
<%= button_to "Purge Channel",
|
|
5
|
+
cable_channel_purge_path(params[:channel_hash]),
|
|
6
|
+
method: :delete,
|
|
7
|
+
class: "sqw-btn sqw-btn--danger sqw-btn--sm",
|
|
8
|
+
data: { turbo_confirm: "Delete all messages for this channel? This cannot be undone." } %>
|
|
9
|
+
<%= link_to "← Channels", cable_path, class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<form class="sqw-filters" action="<%= cable_channel_messages_path(params[:channel_hash]) %>" method="get" data-controller="search">
|
|
14
|
+
<input class="sqw-search-input" type="search" name="q" value="<%= @search %>"
|
|
15
|
+
placeholder="Filter by payload…" autocomplete="off" aria-label="Filter by payload"
|
|
16
|
+
data-action="input->search#filter">
|
|
17
|
+
<% if @search.present? %>
|
|
18
|
+
<%= link_to "Clear", cable_channel_messages_path(params[:channel_hash]), class: "sqw-btn sqw-btn--muted sqw-btn--sm" %>
|
|
19
|
+
<% end %>
|
|
20
|
+
</form>
|
|
21
|
+
|
|
22
|
+
<% if @messages.any? %>
|
|
23
|
+
<table class="sqw-table">
|
|
24
|
+
<thead>
|
|
25
|
+
<tr>
|
|
26
|
+
<th>ID</th>
|
|
27
|
+
<th>Payload</th>
|
|
28
|
+
<th>Sent</th>
|
|
29
|
+
</tr>
|
|
30
|
+
</thead>
|
|
31
|
+
<tbody>
|
|
32
|
+
<% @messages.each do |message| %>
|
|
33
|
+
<tr>
|
|
34
|
+
<td class="sqw-muted sqw-monospace"><%= message.id %></td>
|
|
35
|
+
<td class="sqw-monospace sqw-truncate" title="<%= message.payload %>">
|
|
36
|
+
<%= truncate(message.payload.to_s, length: 120) %>
|
|
37
|
+
</td>
|
|
38
|
+
<td class="sqw-muted" title="<%= message.created_at.strftime("%b %d, %Y %H:%M:%S %Z") %>">
|
|
39
|
+
<%= time_ago_in_words(message.created_at) %> ago
|
|
40
|
+
</td>
|
|
41
|
+
</tr>
|
|
42
|
+
<% end %>
|
|
43
|
+
</tbody>
|
|
44
|
+
</table>
|
|
45
|
+
<%== @pagy.series_nav if @pagy.pages > 1 %>
|
|
46
|
+
<% else %>
|
|
47
|
+
<div class="sqw-empty">
|
|
48
|
+
<p><%= @search.present? ? "No messages matching “#{@search}”.".html_safe : "No messages for this channel." %></p>
|
|
49
|
+
</div>
|
|
50
|
+
<% end %>
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
<% end %>
|
|
59
59
|
</tbody>
|
|
60
60
|
</table>
|
|
61
|
-
<%==
|
|
61
|
+
<%== @pagy.series_nav if @pagy.pages > 1 %>
|
|
62
62
|
<% else %>
|
|
63
63
|
<div class="sqw-empty">
|
|
64
64
|
<p><%= @search.present? ? "No entries matching “#{@search}”.".html_safe : "No cache entries." %></p>
|
|
@@ -99,15 +99,36 @@
|
|
|
99
99
|
<%= link_to "View Cable →", cable_path, class: "sqw-gem-card__link" %>
|
|
100
100
|
</div>
|
|
101
101
|
<div class="sqw-gem-card__body">
|
|
102
|
-
|
|
102
|
+
<%= link_to cable_path, class: "sqw-inline-stat sqw-inline-stat--cable" do %>
|
|
103
103
|
<span class="sqw-inline-stat__label">Messages</span>
|
|
104
104
|
<span class="sqw-inline-stat__value"><%= @cable_stats[:messages] %></span>
|
|
105
|
-
|
|
105
|
+
<% end %>
|
|
106
106
|
<div class="sqw-inline-stat sqw-inline-stat--cable">
|
|
107
107
|
<span class="sqw-inline-stat__label">Channels</span>
|
|
108
108
|
<span class="sqw-inline-stat__value"><%= @cable_stats[:channels] %></span>
|
|
109
109
|
</div>
|
|
110
|
+
<div class="sqw-inline-stat sqw-inline-stat--cable">
|
|
111
|
+
<span class="sqw-inline-stat__label">Msg/hr</span>
|
|
112
|
+
<span class="sqw-inline-stat__value"><%= @cable_stats[:messages_per_hour] %></span>
|
|
113
|
+
</div>
|
|
114
|
+
<% if @cable_stats[:oldest_message] %>
|
|
115
|
+
<div class="sqw-inline-stat sqw-inline-stat--cable">
|
|
116
|
+
<span class="sqw-inline-stat__label">Oldest</span>
|
|
117
|
+
<span class="sqw-inline-stat__value sqw-inline-stat__value--sm" title="<%= @cable_stats[:oldest_message].strftime("%b %d, %Y %H:%M") %>"><%= time_ago_in_words(@cable_stats[:oldest_message]) %></span>
|
|
118
|
+
</div>
|
|
119
|
+
<% end %>
|
|
110
120
|
</div>
|
|
121
|
+
<% if @cable_stats[:top_channels].any? %>
|
|
122
|
+
<div class="sqw-card-list">
|
|
123
|
+
<span class="sqw-card-list__label">Top channels</span>
|
|
124
|
+
<% @cable_stats[:top_channels].each do |channel, count| %>
|
|
125
|
+
<div class="sqw-card-list__row">
|
|
126
|
+
<span class="sqw-card-list__name" title="<%= channel %>"><%= channel %></span>
|
|
127
|
+
<span class="sqw-card-list__count"><%= count %></span>
|
|
128
|
+
</div>
|
|
129
|
+
<% end %>
|
|
130
|
+
</div>
|
|
131
|
+
<% end %>
|
|
111
132
|
</div>
|
|
112
133
|
</div>
|
|
113
|
-
<% end %>
|
|
134
|
+
<% end %>
|
data/config/routes.rb
CHANGED
|
@@ -37,5 +37,8 @@ SolidStackWeb::Engine.routes.draw do
|
|
|
37
37
|
get "cache", to: "cache#index", as: :cache
|
|
38
38
|
resources :cache_entries, only: [:index, :show, :destroy], path: "cache/entries"
|
|
39
39
|
resource :cache_flush, only: [:destroy], path: "cache/flush", controller: "cache/flushes"
|
|
40
|
-
get
|
|
40
|
+
get "cable", to: "cable#index", as: :cable
|
|
41
|
+
delete "cable/purge", to: "cable/purges#destroy", as: :cable_purge
|
|
42
|
+
get "cable/channels/:channel_hash", to: "cable_messages#index", as: :cable_channel_messages
|
|
43
|
+
delete "cable/channels/:channel_hash/purge", to: "cable/channel_purges#destroy", as: :cable_channel_purge
|
|
41
44
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: solid_stack_web
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chuck Smith
|
|
@@ -144,7 +144,10 @@ files:
|
|
|
144
144
|
- app/assets/stylesheets/solid_stack_web/_09_detail.css
|
|
145
145
|
- app/assets/stylesheets/solid_stack_web/application.css
|
|
146
146
|
- app/controllers/solid_stack_web/application_controller.rb
|
|
147
|
+
- app/controllers/solid_stack_web/cable/channel_purges_controller.rb
|
|
148
|
+
- app/controllers/solid_stack_web/cable/purges_controller.rb
|
|
147
149
|
- app/controllers/solid_stack_web/cable_controller.rb
|
|
150
|
+
- app/controllers/solid_stack_web/cable_messages_controller.rb
|
|
148
151
|
- app/controllers/solid_stack_web/cache/flushes_controller.rb
|
|
149
152
|
- app/controllers/solid_stack_web/cache_controller.rb
|
|
150
153
|
- app/controllers/solid_stack_web/cache_entries_controller.rb
|
|
@@ -171,6 +174,7 @@ files:
|
|
|
171
174
|
- app/javascript/solid_stack_web/sparkline_tooltip_controller.js
|
|
172
175
|
- app/models/solid_stack_web/alert_webhook.rb
|
|
173
176
|
- app/models/solid_stack_web/cable_stats.rb
|
|
177
|
+
- app/models/solid_stack_web/cable_timeline.rb
|
|
174
178
|
- app/models/solid_stack_web/cache_size_stats.rb
|
|
175
179
|
- app/models/solid_stack_web/cache_stats.rb
|
|
176
180
|
- app/models/solid_stack_web/cache_timeline.rb
|
|
@@ -180,6 +184,7 @@ files:
|
|
|
180
184
|
- app/models/solid_stack_web/throughput_sparkline.rb
|
|
181
185
|
- app/views/layouts/solid_stack_web/application.html.erb
|
|
182
186
|
- app/views/solid_stack_web/cable/index.html.erb
|
|
187
|
+
- app/views/solid_stack_web/cable_messages/index.html.erb
|
|
183
188
|
- app/views/solid_stack_web/cache/index.html.erb
|
|
184
189
|
- app/views/solid_stack_web/cache_entries/index.html.erb
|
|
185
190
|
- app/views/solid_stack_web/cache_entries/show.html.erb
|