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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3159278d6dd76fc0e54660ab9c11536a63037815771ff04206adca43b800c4ef
4
- data.tar.gz: 05f47e20a15b2f70cbf216713cb4c18da3e3f569b64eb1a271543489fecb95c1
3
+ metadata.gz: 3d16b6a549b38e32fd792d4bff55d990ae5a80d458dcbc47df54759c947536c6
4
+ data.tar.gz: 2ff73287be32fa8a3d3a1089ebd71656bed2ce80742cd11296a3458e0ebeaae3
5
5
  SHA512:
6
- metadata.gz: cdd04812d4cdfa1d3c48ff2a81319f8956b71afee1dded7dbce009797822248e422990f02134fbb4f3c88e6e2e2702c2c91c78d94a6d04e28ebd8d1a2b0022b9
7
- data.tar.gz: 431b97295f3682b1512b09ece023c280997560d4b4f065ef742411fddc63275c8257f0d34d02f6bb0589a555c8677d0515301cabfc8b0768465fd42ff6599a54
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
- _Channel monitoring coming in v0.6.0. Currently shows active message count and distinct channel count on the overview dashboard._
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
+ }
@@ -30,6 +30,12 @@
30
30
  cursor: pointer;
31
31
  }
32
32
 
33
+ .sqw-inline-form {
34
+ display: inline-flex;
35
+ align-items: center;
36
+ gap: 0.4rem;
37
+ }
38
+
33
39
  .sqw-period-filter {
34
40
  display: flex;
35
41
  border: 1px solid var(--border);
@@ -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 = ::SolidCable::Message.distinct.pluck(:channel).sort
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: ::SolidCable::Message.count,
6
- channels: ::SolidCable::Message.distinct.count(:channel)
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><th>Channel</th></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><td class="sqw-monospace"><%= channel %></td></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 &ldquo;#{@search}&rdquo;.".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 &ldquo;#{@search}&rdquo;.".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
- <%== pagy_nav(@pagy) if @pagy.pages > 1 %>
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 &ldquo;#{@search}&rdquo;.".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
- <div class="sqw-inline-stat sqw-inline-stat--cable">
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
- </div>
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 %>
@@ -62,7 +62,7 @@
62
62
  <% end %>
63
63
  </tbody>
64
64
  </table>
65
- <%== pagy_nav(@pagy) if @pagy.pages > 1 %>
65
+ <%== @pagy.series_nav if @pagy.pages > 1 %>
66
66
  </div>
67
67
  <% else %>
68
68
  <div class="sqw-empty">
@@ -66,7 +66,7 @@
66
66
  <% end %>
67
67
  </tbody>
68
68
  </table>
69
- <%== pagy_nav(@pagy) if @pagy.pages > 1 %>
69
+ <%== @pagy.series_nav if @pagy.pages > 1 %>
70
70
  </div>
71
71
  <% else %>
72
72
  <div class="sqw-empty">
@@ -149,7 +149,7 @@
149
149
  <% end %>
150
150
  </tbody>
151
151
  </table>
152
- <%== pagy_nav(@pagy) if @pagy.pages > 1 %>
152
+ <%== @pagy.series_nav if @pagy.pages > 1 %>
153
153
  </div>
154
154
  <% else %>
155
155
  <%= render "empty" %>
@@ -59,7 +59,7 @@
59
59
  <% end %>
60
60
  </tbody>
61
61
  </table>
62
- <%== pagy_nav(@pagy) if @pagy.pages > 1 %>
62
+ <%== @pagy.series_nav if @pagy.pages > 1 %>
63
63
  <% else %>
64
64
  <div class="sqw-empty">
65
65
  <p>No ready jobs in <strong><%= @queue_name %></strong>.</p>
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 "cable", to: "cable#index", as: :cable
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
@@ -1,3 +1,3 @@
1
1
  module SolidStackWeb
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  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.5.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