newshound 0.1.1 → 0.2.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 +152 -155
- data/lib/generators/newshound/install/install_generator.rb +9 -50
- data/lib/generators/newshound/install/templates/newshound.rb +27 -33
- data/lib/newshound/authorization.rb +46 -0
- data/lib/newshound/configuration.rb +12 -18
- data/lib/newshound/middleware/banner_injector.rb +288 -0
- data/lib/newshound/railtie.rb +32 -36
- data/lib/newshound/version.rb +1 -1
- data/lib/newshound.rb +5 -43
- data/newshound.gemspec +2 -7
- metadata +6 -52
- data/lib/newshound/daily_report_job.rb +0 -31
- data/lib/newshound/scheduler.rb +0 -42
- data/lib/newshound/slack_notifier.rb +0 -44
- data/lib/newshound/transport/base.rb +0 -28
- data/lib/newshound/transport/slack.rb +0 -67
- data/lib/newshound/transport/sns.rb +0 -115
@@ -0,0 +1,288 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Newshound
|
4
|
+
module Middleware
|
5
|
+
class BannerInjector
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
status, headers, response = @app.call(env)
|
12
|
+
|
13
|
+
# Only inject into HTML responses
|
14
|
+
return [status, headers, response] unless html_response?(headers)
|
15
|
+
return [status, headers, response] unless status == 200
|
16
|
+
|
17
|
+
# Check authorization
|
18
|
+
controller = env['action_controller.instance']
|
19
|
+
return [status, headers, response] unless controller
|
20
|
+
return [status, headers, response] unless Newshound::Authorization.authorized?(controller)
|
21
|
+
|
22
|
+
# Get banner HTML
|
23
|
+
banner_html = generate_banner_html
|
24
|
+
|
25
|
+
# Inject banner after <body> tag
|
26
|
+
new_response = inject_banner(response, banner_html)
|
27
|
+
|
28
|
+
# Update Content-Length header
|
29
|
+
if headers['Content-Length']
|
30
|
+
headers['Content-Length'] = new_response.bytesize.to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
[status, headers, [new_response]]
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def html_response?(headers)
|
39
|
+
content_type = headers['Content-Type']
|
40
|
+
content_type && content_type.include?('text/html')
|
41
|
+
end
|
42
|
+
|
43
|
+
def inject_banner(response, banner_html)
|
44
|
+
body = response_body(response)
|
45
|
+
|
46
|
+
# Inject after <body> tag
|
47
|
+
body.sub(/(<body[^>]*>)/i, "\\1\n#{banner_html}")
|
48
|
+
end
|
49
|
+
|
50
|
+
def response_body(response)
|
51
|
+
body = ""
|
52
|
+
response.each { |part| body << part }
|
53
|
+
body
|
54
|
+
end
|
55
|
+
|
56
|
+
def generate_banner_html
|
57
|
+
exception_reporter = Newshound::ExceptionReporter.new
|
58
|
+
que_reporter = Newshound::QueReporter.new
|
59
|
+
|
60
|
+
exception_data = exception_reporter.report
|
61
|
+
job_data = que_reporter.report
|
62
|
+
|
63
|
+
# Generate HTML from template
|
64
|
+
render_banner(exception_data, job_data)
|
65
|
+
end
|
66
|
+
|
67
|
+
def render_banner(exception_data, job_data)
|
68
|
+
<<~HTML
|
69
|
+
<div id="newshound-banner" class="newshound-banner newshound-collapsed">
|
70
|
+
#{render_styles}
|
71
|
+
<div class="newshound-header" onclick="document.getElementById('newshound-banner').classList.toggle('newshound-collapsed')">
|
72
|
+
<span class="newshound-title">
|
73
|
+
🐕 Newshound
|
74
|
+
#{summary_badge(exception_data, job_data)}
|
75
|
+
</span>
|
76
|
+
<span class="newshound-toggle">▼</span>
|
77
|
+
</div>
|
78
|
+
<div class="newshound-content">
|
79
|
+
#{render_exceptions(exception_data)}
|
80
|
+
#{render_jobs(job_data)}
|
81
|
+
</div>
|
82
|
+
</div>
|
83
|
+
HTML
|
84
|
+
end
|
85
|
+
|
86
|
+
def render_styles
|
87
|
+
<<~CSS
|
88
|
+
<style>
|
89
|
+
.newshound-banner {
|
90
|
+
position: fixed;
|
91
|
+
top: 0;
|
92
|
+
left: 0;
|
93
|
+
right: 0;
|
94
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
95
|
+
color: white;
|
96
|
+
z-index: 10000;
|
97
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
98
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
99
|
+
font-size: 14px;
|
100
|
+
}
|
101
|
+
.newshound-header {
|
102
|
+
padding: 12px 20px;
|
103
|
+
cursor: pointer;
|
104
|
+
display: flex;
|
105
|
+
justify-content: space-between;
|
106
|
+
align-items: center;
|
107
|
+
user-select: none;
|
108
|
+
}
|
109
|
+
.newshound-title {
|
110
|
+
font-weight: 600;
|
111
|
+
display: flex;
|
112
|
+
align-items: center;
|
113
|
+
gap: 10px;
|
114
|
+
}
|
115
|
+
.newshound-badge {
|
116
|
+
display: inline-block;
|
117
|
+
padding: 2px 8px;
|
118
|
+
border-radius: 12px;
|
119
|
+
font-size: 12px;
|
120
|
+
font-weight: 600;
|
121
|
+
background: rgba(255,255,255,0.2);
|
122
|
+
}
|
123
|
+
.newshound-badge.error {
|
124
|
+
background: #ef4444;
|
125
|
+
}
|
126
|
+
.newshound-badge.warning {
|
127
|
+
background: #f59e0b;
|
128
|
+
}
|
129
|
+
.newshound-badge.success {
|
130
|
+
background: #10b981;
|
131
|
+
}
|
132
|
+
.newshound-toggle {
|
133
|
+
transition: transform 0.3s;
|
134
|
+
}
|
135
|
+
.newshound-banner.newshound-collapsed .newshound-toggle {
|
136
|
+
transform: rotate(-90deg);
|
137
|
+
}
|
138
|
+
.newshound-content {
|
139
|
+
max-height: 400px;
|
140
|
+
overflow-y: auto;
|
141
|
+
border-top: 1px solid rgba(255,255,255,0.2);
|
142
|
+
transition: max-height 0.3s ease-out;
|
143
|
+
}
|
144
|
+
.newshound-banner.newshound-collapsed .newshound-content {
|
145
|
+
max-height: 0;
|
146
|
+
overflow: hidden;
|
147
|
+
border-top: none;
|
148
|
+
}
|
149
|
+
.newshound-section {
|
150
|
+
padding: 15px 20px;
|
151
|
+
border-bottom: 1px solid rgba(255,255,255,0.1);
|
152
|
+
}
|
153
|
+
.newshound-section:last-child {
|
154
|
+
border-bottom: none;
|
155
|
+
}
|
156
|
+
.newshound-section-title {
|
157
|
+
font-weight: 600;
|
158
|
+
margin-bottom: 10px;
|
159
|
+
font-size: 15px;
|
160
|
+
}
|
161
|
+
.newshound-item {
|
162
|
+
background: rgba(255,255,255,0.1);
|
163
|
+
padding: 10px;
|
164
|
+
margin-bottom: 8px;
|
165
|
+
border-radius: 6px;
|
166
|
+
font-size: 13px;
|
167
|
+
}
|
168
|
+
.newshound-item:last-child {
|
169
|
+
margin-bottom: 0;
|
170
|
+
}
|
171
|
+
.newshound-item-title {
|
172
|
+
font-weight: 600;
|
173
|
+
margin-bottom: 4px;
|
174
|
+
}
|
175
|
+
.newshound-item-detail {
|
176
|
+
opacity: 0.9;
|
177
|
+
font-size: 12px;
|
178
|
+
}
|
179
|
+
.newshound-grid {
|
180
|
+
display: grid;
|
181
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
182
|
+
gap: 10px;
|
183
|
+
}
|
184
|
+
.newshound-stat {
|
185
|
+
background: rgba(255,255,255,0.1);
|
186
|
+
padding: 12px;
|
187
|
+
border-radius: 6px;
|
188
|
+
text-align: center;
|
189
|
+
}
|
190
|
+
.newshound-stat-value {
|
191
|
+
font-size: 24px;
|
192
|
+
font-weight: 700;
|
193
|
+
display: block;
|
194
|
+
}
|
195
|
+
.newshound-stat-label {
|
196
|
+
font-size: 11px;
|
197
|
+
opacity: 0.8;
|
198
|
+
text-transform: uppercase;
|
199
|
+
letter-spacing: 0.5px;
|
200
|
+
}
|
201
|
+
</style>
|
202
|
+
CSS
|
203
|
+
end
|
204
|
+
|
205
|
+
def summary_badge(exception_data, job_data)
|
206
|
+
exception_count = exception_data[:exceptions]&.length || 0
|
207
|
+
failed_jobs = job_data.dig(:queue_stats, :failed) || 0
|
208
|
+
|
209
|
+
if exception_count > 0 || failed_jobs > 10
|
210
|
+
badge_class = "error"
|
211
|
+
text = "#{exception_count} exceptions, #{failed_jobs} failed jobs"
|
212
|
+
elsif failed_jobs > 5
|
213
|
+
badge_class = "warning"
|
214
|
+
text = "#{failed_jobs} failed jobs"
|
215
|
+
else
|
216
|
+
badge_class = "success"
|
217
|
+
text = "All clear"
|
218
|
+
end
|
219
|
+
|
220
|
+
%(<span class="newshound-badge #{badge_class}">#{text}</span>)
|
221
|
+
end
|
222
|
+
|
223
|
+
def render_exceptions(data)
|
224
|
+
exceptions = data[:exceptions] || []
|
225
|
+
|
226
|
+
if exceptions.empty?
|
227
|
+
return %(<div class="newshound-section"><div class="newshound-section-title">✅ Exceptions</div><div class="newshound-item">No exceptions in the last 24 hours</div></div>)
|
228
|
+
end
|
229
|
+
|
230
|
+
items = exceptions.take(5).map do |ex|
|
231
|
+
<<~HTML
|
232
|
+
<div class="newshound-item">
|
233
|
+
<div class="newshound-item-title">#{escape_html(ex[:title])}</div>
|
234
|
+
<div class="newshound-item-detail">
|
235
|
+
#{escape_html(ex[:message])} • #{escape_html(ex[:location])} • #{escape_html(ex[:time])}
|
236
|
+
</div>
|
237
|
+
</div>
|
238
|
+
HTML
|
239
|
+
end.join
|
240
|
+
|
241
|
+
<<~HTML
|
242
|
+
<div class="newshound-section">
|
243
|
+
<div class="newshound-section-title">⚠️ Recent Exceptions (#{exceptions.length})</div>
|
244
|
+
#{items}
|
245
|
+
</div>
|
246
|
+
HTML
|
247
|
+
end
|
248
|
+
|
249
|
+
def render_jobs(data)
|
250
|
+
stats = data[:queue_stats] || {}
|
251
|
+
|
252
|
+
<<~HTML
|
253
|
+
<div class="newshound-section">
|
254
|
+
<div class="newshound-section-title">📊 Job Queue Status</div>
|
255
|
+
<div class="newshound-grid">
|
256
|
+
<div class="newshound-stat">
|
257
|
+
<span class="newshound-stat-value">#{stats[:ready_to_run] || 0}</span>
|
258
|
+
<span class="newshound-stat-label">Ready</span>
|
259
|
+
</div>
|
260
|
+
<div class="newshound-stat">
|
261
|
+
<span class="newshound-stat-value">#{stats[:scheduled] || 0}</span>
|
262
|
+
<span class="newshound-stat-label">Scheduled</span>
|
263
|
+
</div>
|
264
|
+
<div class="newshound-stat">
|
265
|
+
<span class="newshound-stat-value">#{stats[:failed] || 0}</span>
|
266
|
+
<span class="newshound-stat-label">Failed</span>
|
267
|
+
</div>
|
268
|
+
<div class="newshound-stat">
|
269
|
+
<span class="newshound-stat-value">#{stats[:completed_today] || 0}</span>
|
270
|
+
<span class="newshound-stat-label">Completed Today</span>
|
271
|
+
</div>
|
272
|
+
</div>
|
273
|
+
</div>
|
274
|
+
HTML
|
275
|
+
end
|
276
|
+
|
277
|
+
def escape_html(text)
|
278
|
+
return "" unless text
|
279
|
+
text.to_s
|
280
|
+
.gsub('&', '&')
|
281
|
+
.gsub('<', '<')
|
282
|
+
.gsub('>', '>')
|
283
|
+
.gsub('"', '"')
|
284
|
+
.gsub("'", ''')
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
data/lib/newshound/railtie.rb
CHANGED
@@ -4,59 +4,55 @@ require "rails/railtie"
|
|
4
4
|
|
5
5
|
module Newshound
|
6
6
|
class Railtie < Rails::Railtie
|
7
|
+
# Register middleware to inject banner
|
8
|
+
initializer "newshound.middleware" do |app|
|
9
|
+
if Newshound.configuration.enabled
|
10
|
+
app.middleware.use Newshound::Middleware::BannerInjector
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
7
14
|
rake_tasks do
|
8
15
|
namespace :newshound do
|
9
|
-
desc "Send daily report immediately"
|
10
|
-
task report_now: :environment do
|
11
|
-
puts "Sending Newshound daily report..."
|
12
|
-
Newshound.report!
|
13
|
-
puts "Report sent successfully!"
|
14
|
-
rescue StandardError => e
|
15
|
-
puts "Failed to send report: #{e.message}"
|
16
|
-
end
|
17
|
-
|
18
|
-
desc "Schedule daily report job"
|
19
|
-
task schedule: :environment do
|
20
|
-
puts "Scheduling Newshound daily report..."
|
21
|
-
Newshound::Scheduler.run_now!
|
22
|
-
puts "Job enqueued successfully!"
|
23
|
-
end
|
24
|
-
|
25
16
|
desc "Show current configuration"
|
26
17
|
task config: :environment do
|
27
18
|
config = Newshound.configuration
|
28
19
|
puts "Newshound Configuration:"
|
29
20
|
puts " Enabled: #{config.enabled}"
|
30
|
-
puts " Slack Webhook: #{config.slack_webhook_url.present? ? '[CONFIGURED]' : '[NOT SET]'}"
|
31
|
-
puts " Slack Channel: #{config.slack_channel}"
|
32
|
-
puts " Report Time: #{config.report_time}"
|
33
21
|
puts " Exception Limit: #{config.exception_limit}"
|
34
|
-
puts "
|
35
|
-
puts "
|
22
|
+
puts " Authorized Roles: #{config.authorized_roles.join(', ')}"
|
23
|
+
puts " Current User Method: #{config.current_user_method}"
|
24
|
+
puts " Custom Authorization: #{config.authorization_block.present? ? 'Yes' : 'No'}"
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "Test exception reporter"
|
28
|
+
task test_exceptions: :environment do
|
29
|
+
reporter = Newshound::ExceptionReporter.new
|
30
|
+
data = reporter.report
|
31
|
+
puts "Exception Report:"
|
32
|
+
puts " Total exceptions: #{data[:exceptions]&.length || 0}"
|
33
|
+
data[:exceptions]&.each_with_index do |ex, i|
|
34
|
+
puts " #{i + 1}. #{ex[:title]} - #{ex[:message]}"
|
35
|
+
end
|
36
36
|
end
|
37
|
-
end
|
38
|
-
end
|
39
37
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
else
|
50
|
-
Rails.logger.warn "Newshound: que-scheduler not found. Daily reports will need to be triggered manually."
|
38
|
+
desc "Test job reporter"
|
39
|
+
task test_jobs: :environment do
|
40
|
+
reporter = Newshound::QueReporter.new
|
41
|
+
data = reporter.report
|
42
|
+
puts "Job Queue Report:"
|
43
|
+
puts " Ready to run: #{data.dig(:queue_stats, :ready_to_run)}"
|
44
|
+
puts " Scheduled: #{data.dig(:queue_stats, :scheduled)}"
|
45
|
+
puts " Failed: #{data.dig(:queue_stats, :failed)}"
|
46
|
+
puts " Completed today: #{data.dig(:queue_stats, :completed_today)}"
|
51
47
|
end
|
52
48
|
end
|
53
49
|
end
|
54
50
|
|
55
51
|
config.after_initialize do
|
56
52
|
if Newshound.configuration.valid?
|
57
|
-
Rails.logger.info "Newshound initialized
|
53
|
+
Rails.logger.info "Newshound initialized - banner will be shown to authorized users"
|
58
54
|
else
|
59
|
-
Rails.logger.warn "Newshound is
|
55
|
+
Rails.logger.warn "Newshound is disabled"
|
60
56
|
end
|
61
57
|
end
|
62
58
|
end
|
data/lib/newshound/version.rb
CHANGED
data/lib/newshound.rb
CHANGED
@@ -4,9 +4,8 @@ require_relative "newshound/version"
|
|
4
4
|
require_relative "newshound/configuration"
|
5
5
|
require_relative "newshound/exception_reporter"
|
6
6
|
require_relative "newshound/que_reporter"
|
7
|
-
require_relative "newshound/
|
8
|
-
require_relative "newshound/
|
9
|
-
require_relative "newshound/scheduler"
|
7
|
+
require_relative "newshound/authorization"
|
8
|
+
require_relative "newshound/middleware/banner_injector"
|
10
9
|
require_relative "newshound/railtie" if defined?(Rails)
|
11
10
|
|
12
11
|
module Newshound
|
@@ -21,46 +20,9 @@ module Newshound
|
|
21
20
|
yield(configuration)
|
22
21
|
end
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
exception_report = ExceptionReporter.new.generate_report
|
28
|
-
que_report = QueReporter.new.generate_report
|
29
|
-
|
30
|
-
message = format_daily_report(exception_report, que_report)
|
31
|
-
slack_notifier.post(message)
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
def format_daily_report(exception_report, que_report)
|
37
|
-
{
|
38
|
-
blocks: [
|
39
|
-
{
|
40
|
-
type: "header",
|
41
|
-
text: {
|
42
|
-
type: "plain_text",
|
43
|
-
text: "🐕 Daily Newshound Report",
|
44
|
-
emoji: true
|
45
|
-
}
|
46
|
-
},
|
47
|
-
{
|
48
|
-
type: "section",
|
49
|
-
text: {
|
50
|
-
type: "mrkdwn",
|
51
|
-
text: "*Date:* #{Date.current.strftime('%B %d, %Y')}"
|
52
|
-
}
|
53
|
-
},
|
54
|
-
{
|
55
|
-
type: "divider"
|
56
|
-
},
|
57
|
-
*exception_report,
|
58
|
-
{
|
59
|
-
type: "divider"
|
60
|
-
},
|
61
|
-
*que_report
|
62
|
-
]
|
63
|
-
}
|
23
|
+
# Allow setting custom authorization logic
|
24
|
+
def authorize_with(&block)
|
25
|
+
configuration.authorize_with(&block)
|
64
26
|
end
|
65
27
|
end
|
66
28
|
end
|
data/newshound.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.authors = ["salbanez"]
|
9
9
|
spec.email = ["salbanez@example.com"]
|
10
10
|
|
11
|
-
spec.summary = "
|
12
|
-
spec.description = "Newshound
|
11
|
+
spec.summary = "Real-time web UI banner for monitoring Que jobs and exception tracking"
|
12
|
+
spec.description = "Newshound displays exceptions and job statuses in a collapsible banner for authorized users in your Rails app"
|
13
13
|
spec.homepage = "https://github.com/salbanez/newshound"
|
14
14
|
spec.license = "MIT"
|
15
15
|
spec.required_ruby_version = ">= 2.7.0"
|
@@ -29,11 +29,6 @@ Gem::Specification.new do |spec|
|
|
29
29
|
|
30
30
|
# Runtime dependencies
|
31
31
|
spec.add_dependency "rails", ">= 6.0"
|
32
|
-
spec.add_dependency "slack-ruby-client", "~> 2.0"
|
33
32
|
spec.add_dependency "que", ">= 1.0"
|
34
|
-
spec.add_dependency "que-scheduler", ">= 4.0"
|
35
33
|
spec.add_dependency "exception-track", ">= 0.1"
|
36
|
-
|
37
|
-
# Optional dependency for SNS transport
|
38
|
-
spec.add_development_dependency "aws-sdk-sns", "~> 1.0"
|
39
34
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: newshound
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- salbanez
|
@@ -23,20 +23,6 @@ dependencies:
|
|
23
23
|
- - ">="
|
24
24
|
- !ruby/object:Gem::Version
|
25
25
|
version: '6.0'
|
26
|
-
- !ruby/object:Gem::Dependency
|
27
|
-
name: slack-ruby-client
|
28
|
-
requirement: !ruby/object:Gem::Requirement
|
29
|
-
requirements:
|
30
|
-
- - "~>"
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: '2.0'
|
33
|
-
type: :runtime
|
34
|
-
prerelease: false
|
35
|
-
version_requirements: !ruby/object:Gem::Requirement
|
36
|
-
requirements:
|
37
|
-
- - "~>"
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: '2.0'
|
40
26
|
- !ruby/object:Gem::Dependency
|
41
27
|
name: que
|
42
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -51,20 +37,6 @@ dependencies:
|
|
51
37
|
- - ">="
|
52
38
|
- !ruby/object:Gem::Version
|
53
39
|
version: '1.0'
|
54
|
-
- !ruby/object:Gem::Dependency
|
55
|
-
name: que-scheduler
|
56
|
-
requirement: !ruby/object:Gem::Requirement
|
57
|
-
requirements:
|
58
|
-
- - ">="
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
version: '4.0'
|
61
|
-
type: :runtime
|
62
|
-
prerelease: false
|
63
|
-
version_requirements: !ruby/object:Gem::Requirement
|
64
|
-
requirements:
|
65
|
-
- - ">="
|
66
|
-
- !ruby/object:Gem::Version
|
67
|
-
version: '4.0'
|
68
40
|
- !ruby/object:Gem::Dependency
|
69
41
|
name: exception-track
|
70
42
|
requirement: !ruby/object:Gem::Requirement
|
@@ -79,22 +51,8 @@ dependencies:
|
|
79
51
|
- - ">="
|
80
52
|
- !ruby/object:Gem::Version
|
81
53
|
version: '0.1'
|
82
|
-
|
83
|
-
|
84
|
-
requirement: !ruby/object:Gem::Requirement
|
85
|
-
requirements:
|
86
|
-
- - "~>"
|
87
|
-
- !ruby/object:Gem::Version
|
88
|
-
version: '1.0'
|
89
|
-
type: :development
|
90
|
-
prerelease: false
|
91
|
-
version_requirements: !ruby/object:Gem::Requirement
|
92
|
-
requirements:
|
93
|
-
- - "~>"
|
94
|
-
- !ruby/object:Gem::Version
|
95
|
-
version: '1.0'
|
96
|
-
description: Newshound sniffs out exceptions and job statuses in your Rails app and
|
97
|
-
reports them daily to Slack
|
54
|
+
description: Newshound displays exceptions and job statuses in a collapsible banner
|
55
|
+
for authorized users in your Rails app
|
98
56
|
email:
|
99
57
|
- salbanez@example.com
|
100
58
|
executables: []
|
@@ -110,16 +68,12 @@ files:
|
|
110
68
|
- lib/generators/newshound/install/install_generator.rb
|
111
69
|
- lib/generators/newshound/install/templates/newshound.rb
|
112
70
|
- lib/newshound.rb
|
71
|
+
- lib/newshound/authorization.rb
|
113
72
|
- lib/newshound/configuration.rb
|
114
|
-
- lib/newshound/daily_report_job.rb
|
115
73
|
- lib/newshound/exception_reporter.rb
|
74
|
+
- lib/newshound/middleware/banner_injector.rb
|
116
75
|
- lib/newshound/que_reporter.rb
|
117
76
|
- lib/newshound/railtie.rb
|
118
|
-
- lib/newshound/scheduler.rb
|
119
|
-
- lib/newshound/slack_notifier.rb
|
120
|
-
- lib/newshound/transport/base.rb
|
121
|
-
- lib/newshound/transport/slack.rb
|
122
|
-
- lib/newshound/transport/sns.rb
|
123
77
|
- lib/newshound/version.rb
|
124
78
|
- newshound.gemspec
|
125
79
|
homepage: https://github.com/salbanez/newshound
|
@@ -145,5 +99,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
145
99
|
requirements: []
|
146
100
|
rubygems_version: 3.6.9
|
147
101
|
specification_version: 4
|
148
|
-
summary:
|
102
|
+
summary: Real-time web UI banner for monitoring Que jobs and exception tracking
|
149
103
|
test_files: []
|
@@ -1,31 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Newshound
|
4
|
-
if defined?(::Que::Job)
|
5
|
-
class DailyReportJob < ::Que::Job
|
6
|
-
def run
|
7
|
-
return unless Newshound.configuration.valid?
|
8
|
-
|
9
|
-
Newshound.report!
|
10
|
-
|
11
|
-
destroy
|
12
|
-
rescue StandardError => e
|
13
|
-
Rails.logger.error "Newshound::DailyReportJob failed: #{e.message}"
|
14
|
-
Rails.logger.error e.backtrace.join("\n")
|
15
|
-
|
16
|
-
# Re-raise to let Que handle retry logic
|
17
|
-
raise
|
18
|
-
end
|
19
|
-
end
|
20
|
-
else
|
21
|
-
class DailyReportJob
|
22
|
-
def self.enqueue(*args)
|
23
|
-
Rails.logger.warn "Que is not available. DailyReportJob cannot be enqueued."
|
24
|
-
end
|
25
|
-
|
26
|
-
def run
|
27
|
-
Rails.logger.warn "Que is not available. DailyReportJob cannot be run."
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
data/lib/newshound/scheduler.rb
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Newshound
|
4
|
-
class Scheduler
|
5
|
-
def self.schedule_daily_report
|
6
|
-
return unless defined?(::Que::Scheduler)
|
7
|
-
|
8
|
-
config = Newshound.configuration
|
9
|
-
return unless config.valid?
|
10
|
-
|
11
|
-
# Note: Que-scheduler uses a YAML config file (config/que_schedule.yml)
|
12
|
-
# This method returns the configuration that should be added to that file
|
13
|
-
# or can be used to manually schedule the job
|
14
|
-
schedule_config = {
|
15
|
-
"newshound_daily_report" => {
|
16
|
-
"class" => "Newshound::DailyReportJob",
|
17
|
-
"cron" => build_cron_expression(config.report_time),
|
18
|
-
"queue" => "default",
|
19
|
-
"args" => []
|
20
|
-
}
|
21
|
-
}
|
22
|
-
|
23
|
-
# Log the configuration for visibility
|
24
|
-
if defined?(Rails) && Rails.logger
|
25
|
-
Rails.logger.info "Newshound daily report scheduled for #{config.report_time} (cron: #{schedule_config['newshound_daily_report']['cron']})"
|
26
|
-
end
|
27
|
-
|
28
|
-
schedule_config
|
29
|
-
end
|
30
|
-
|
31
|
-
def self.build_cron_expression(time_string)
|
32
|
-
# Convert "09:00" format to cron expression
|
33
|
-
# Format: minute hour * * *
|
34
|
-
hour, minute = time_string.split(":").map(&:to_i)
|
35
|
-
"#{minute} #{hour} * * *"
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.run_now!
|
39
|
-
Newshound::DailyReportJob.enqueue
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "slack-ruby-client"
|
4
|
-
|
5
|
-
module Newshound
|
6
|
-
class SlackNotifier
|
7
|
-
attr_reader :configuration, :logger, :transport
|
8
|
-
|
9
|
-
def initialize(configuration: nil, logger: nil, transport: nil)
|
10
|
-
@configuration = configuration || Newshound.configuration
|
11
|
-
@logger = logger || (defined?(Rails) ? Rails.logger : Logger.new(STDOUT))
|
12
|
-
@transport = transport || build_transport
|
13
|
-
end
|
14
|
-
|
15
|
-
def post(message)
|
16
|
-
return unless configuration.valid?
|
17
|
-
|
18
|
-
transport.deliver(message)
|
19
|
-
rescue StandardError => e
|
20
|
-
logger.error "Newshound: Failed to send notification: #{e.message}"
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def build_transport
|
26
|
-
case configuration.transport_adapter
|
27
|
-
when :sns, "sns"
|
28
|
-
require_relative "transport/sns"
|
29
|
-
Transport::Sns.new(configuration: configuration, logger: logger)
|
30
|
-
when :slack, "slack", nil
|
31
|
-
require_relative "transport/slack"
|
32
|
-
Transport::Slack.new(configuration: configuration, logger: logger)
|
33
|
-
else
|
34
|
-
if configuration.transport_adapter.is_a?(Class)
|
35
|
-
configuration.transport_adapter.new(configuration: configuration, logger: logger)
|
36
|
-
elsif configuration.transport_adapter.respond_to?(:new)
|
37
|
-
configuration.transport_adapter.new(configuration: configuration, logger: logger)
|
38
|
-
else
|
39
|
-
raise ArgumentError, "Invalid transport adapter: #{configuration.transport_adapter}"
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|