rails-health-checker 0.1.0 → 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 +55 -18
- metadata +25 -69
- data/CHANGELOG.md +0 -37
- data/COMMANDS.md +0 -118
- data/Gemfile +0 -6
- data/Gemfile.lock +0 -222
- data/LICENSE +0 -21
- data/Rakefile +0 -6
- data/SECURITY.md +0 -41
- data/TESTING.md +0 -64
- data/TEST_RESULTS.md +0 -51
- data/example_usage.rb +0 -23
- data/lib/rails_health_checker/checker.rb +0 -88
- data/lib/rails_health_checker/dashboard_middleware.rb +0 -503
- data/lib/rails_health_checker/gem_analyzer.rb +0 -39
- data/lib/rails_health_checker/health_middleware.rb +0 -53
- data/lib/rails_health_checker/job_analyzer.rb +0 -108
- data/lib/rails_health_checker/railtie.rb +0 -11
- data/lib/rails_health_checker/report_generator.rb +0 -499
- data/lib/rails_health_checker/system_analyzer.rb +0 -182
- data/lib/rails_health_checker/tasks.rb +0 -63
- data/lib/rails_health_checker/version.rb +0 -3
- data/lib/rails_health_checker.rb +0 -17
- data/rails_health_checker.gemspec +0 -33
- data/simple_test.rb +0 -52
- data/test_gem.rb +0 -100
|
@@ -1,503 +0,0 @@
|
|
|
1
|
-
module RailsHealthChecker
|
|
2
|
-
class DashboardMiddleware
|
|
3
|
-
def initialize(app)
|
|
4
|
-
@app = app
|
|
5
|
-
require 'rack/auth/basic'
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def call(env)
|
|
9
|
-
if env['PATH_INFO'] == '/health'
|
|
10
|
-
authenticate(env) ? dashboard_response : unauthorized_response
|
|
11
|
-
else
|
|
12
|
-
@app.call(env)
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
def authenticate(env)
|
|
19
|
-
auth = Rack::Auth::Basic::Request.new(env)
|
|
20
|
-
auth.provided? && auth.basic? && auth.credentials &&
|
|
21
|
-
auth.credentials == [username, password]
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def username
|
|
25
|
-
ENV['HEALTH_USERNAME'] || 'admin'
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def password
|
|
29
|
-
ENV['HEALTH_PASSWORD'] || 'health123'
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def unauthorized_response
|
|
33
|
-
[
|
|
34
|
-
401,
|
|
35
|
-
{
|
|
36
|
-
'Content-Type' => 'text/html',
|
|
37
|
-
'WWW-Authenticate' => 'Basic realm="Health Dashboard"'
|
|
38
|
-
},
|
|
39
|
-
['<h1>401 Unauthorized</h1><p>Please provide valid credentials.</p>']
|
|
40
|
-
]
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def dashboard_response
|
|
44
|
-
results = RailsHealthChecker::Checker.new.run
|
|
45
|
-
|
|
46
|
-
[
|
|
47
|
-
200,
|
|
48
|
-
{ 'Content-Type' => 'text/html' },
|
|
49
|
-
[generate_dashboard_html(results)]
|
|
50
|
-
]
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def generate_dashboard_html(results)
|
|
54
|
-
<<~HTML
|
|
55
|
-
<!DOCTYPE html>
|
|
56
|
-
<html>
|
|
57
|
-
<head>
|
|
58
|
-
<title>Rails Health Dashboard</title>
|
|
59
|
-
<meta charset="utf-8">
|
|
60
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
61
|
-
<style>
|
|
62
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
63
|
-
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; }
|
|
64
|
-
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
|
65
|
-
.header { text-align: center; margin-bottom: 30px; }
|
|
66
|
-
.header h1 { color: #2d3748; font-size: 2.5rem; margin-bottom: 10px; }
|
|
67
|
-
.timestamp { color: #718096; font-size: 0.9rem; }
|
|
68
|
-
.search-container { margin: 20px 0; text-align: center; }
|
|
69
|
-
.search-box { padding: 12px 20px; border: 2px solid #e2e8f0; border-radius: 25px; width: 300px; font-size: 1rem; outline: none; }
|
|
70
|
-
.search-box:focus { border-color: #4299e1; }
|
|
71
|
-
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-bottom: 30px; }
|
|
72
|
-
.card { background: white; border-radius: 12px; padding: 24px; box-shadow: 0 4px 6px rgba(0,0,0,0.05); border: 1px solid #e2e8f0; }
|
|
73
|
-
.card h3 { color: #2d3748; margin-bottom: 16px; font-size: 1.2rem; display: flex; align-items: center; gap: 8px; }
|
|
74
|
-
.status-healthy { color: #38a169; }
|
|
75
|
-
.status-warning { color: #d69e2e; }
|
|
76
|
-
.status-error { color: #e53e3e; }
|
|
77
|
-
.status-loaded { color: #38a169; }
|
|
78
|
-
.status-missing { color: #e53e3e; }
|
|
79
|
-
.status-not_loaded { color: #718096; }
|
|
80
|
-
.metric { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid #f7fafc; }
|
|
81
|
-
.metric:last-child { border-bottom: none; }
|
|
82
|
-
.metric-label { color: #4a5568; }
|
|
83
|
-
.metric-value { font-weight: 600; }
|
|
84
|
-
.lib-item { padding: 6px 12px; margin: 2px 0; background: #f7fafc; border-radius: 4px; display: flex; justify-content: space-between; align-items: center; font-size: 0.9rem; }
|
|
85
|
-
.lib-required { border-left: 3px solid #4299e1; }
|
|
86
|
-
.lib-optional { border-left: 3px solid #38a169; }
|
|
87
|
-
.lib-missing { background: #fed7d7; }
|
|
88
|
-
.expandable { max-height: 200px; overflow-y: auto; }
|
|
89
|
-
.health-score { text-align: center; padding: 20px; }
|
|
90
|
-
.score-circle { width: 120px; height: 120px; border-radius: 50%; margin: 0 auto 16px; display: flex; align-items: center; justify-content: center; font-size: 2rem; font-weight: bold; color: white; }
|
|
91
|
-
.score-excellent { background: linear-gradient(135deg, #38a169, #48bb78); }
|
|
92
|
-
.score-good { background: linear-gradient(135deg, #d69e2e, #ecc94b); }
|
|
93
|
-
.score-warning { background: linear-gradient(135deg, #ed8936, #f6ad55); }
|
|
94
|
-
.score-critical { background: linear-gradient(135deg, #e53e3e, #fc8181); }
|
|
95
|
-
.gem-list { max-height: 200px; overflow-y: auto; }
|
|
96
|
-
.gem-item { padding: 8px 12px; margin: 4px 0; background: #f7fafc; border-radius: 6px; display: flex; justify-content: space-between; }
|
|
97
|
-
.gem-outdated { background: #fed7d7; }
|
|
98
|
-
.actions { background: white; border-radius: 12px; padding: 24px; box-shadow: 0 4px 6px rgba(0,0,0,0.05); }
|
|
99
|
-
.action-item { padding: 12px; margin: 8px 0; border-radius: 8px; display: flex; align-items: center; gap: 12px; }
|
|
100
|
-
.action-critical { background: #fed7d7; border-left: 4px solid #e53e3e; }
|
|
101
|
-
.action-high { background: #fef5e7; border-left: 4px solid #d69e2e; }
|
|
102
|
-
.action-medium { background: #e6fffa; border-left: 4px solid #38a169; }
|
|
103
|
-
.refresh-btn { position: fixed; bottom: 20px; right: 20px; background: #4299e1; color: white; border: none; padding: 12px 24px; border-radius: 25px; cursor: pointer; box-shadow: 0 4px 12px rgba(66, 153, 225, 0.3); }
|
|
104
|
-
.refresh-btn:hover { background: #3182ce; }
|
|
105
|
-
.auto-refresh-btn { position: fixed; bottom: 20px; right: 180px; background: #38a169; color: white; border: none; padding: 12px 24px; border-radius: 25px; cursor: pointer; box-shadow: 0 4px 12px rgba(56, 161, 105, 0.3); }
|
|
106
|
-
.auto-refresh-btn:hover { background: #2f855a; }
|
|
107
|
-
.auto-refresh-btn.disabled { background: #718096; }
|
|
108
|
-
.auto-refresh-btn.disabled:hover { background: #4a5568; }
|
|
109
|
-
.hidden { display: none; }
|
|
110
|
-
</style>
|
|
111
|
-
</head>
|
|
112
|
-
<body>
|
|
113
|
-
<div class="container">
|
|
114
|
-
<div class="header">
|
|
115
|
-
<h1>🏥 Rails Health Dashboard</h1>
|
|
116
|
-
<div class="timestamp">Last updated: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}</div>
|
|
117
|
-
<div class="search-container">
|
|
118
|
-
<input type="text" class="search-box" placeholder="Search libraries (e.g., sidekiq, redis, puma...)" id="libSearch">
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
|
|
122
|
-
<div class="grid">
|
|
123
|
-
#{system_overview_card(results)}
|
|
124
|
-
#{libraries_card(results)}
|
|
125
|
-
#{database_card(results)}
|
|
126
|
-
#{gems_card(results)}
|
|
127
|
-
#{security_card(results)}
|
|
128
|
-
#{jobs_card(results)}
|
|
129
|
-
</div>
|
|
130
|
-
|
|
131
|
-
#{health_score_card(results)}
|
|
132
|
-
#{priority_actions_card(results)}
|
|
133
|
-
</div>
|
|
134
|
-
|
|
135
|
-
<button class="auto-refresh-btn" id="autoRefreshBtn" onclick="toggleAutoRefresh()">⏸️ Auto-Refresh: ON</button>
|
|
136
|
-
<button class="refresh-btn" onclick="location.reload()">🔄 Refresh</button>
|
|
137
|
-
|
|
138
|
-
<script>
|
|
139
|
-
let autoRefreshEnabled = true;
|
|
140
|
-
let refreshTimer;
|
|
141
|
-
|
|
142
|
-
function startAutoRefresh() {
|
|
143
|
-
if (autoRefreshEnabled) {
|
|
144
|
-
refreshTimer = setTimeout(() => location.reload(), 30000);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function toggleAutoRefresh() {
|
|
149
|
-
const btn = document.getElementById('autoRefreshBtn');
|
|
150
|
-
autoRefreshEnabled = !autoRefreshEnabled;
|
|
151
|
-
|
|
152
|
-
if (autoRefreshEnabled) {
|
|
153
|
-
btn.textContent = '⏸️ Auto-Refresh: ON';
|
|
154
|
-
btn.classList.remove('disabled');
|
|
155
|
-
startAutoRefresh();
|
|
156
|
-
} else {
|
|
157
|
-
btn.textContent = '▶️ Auto-Refresh: OFF';
|
|
158
|
-
btn.classList.add('disabled');
|
|
159
|
-
clearTimeout(refreshTimer);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Start auto-refresh on page load
|
|
164
|
-
startAutoRefresh();
|
|
165
|
-
|
|
166
|
-
// Search functionality
|
|
167
|
-
document.getElementById('libSearch').addEventListener('input', function(e) {
|
|
168
|
-
const searchTerm = e.target.value.toLowerCase();
|
|
169
|
-
const libItems = document.querySelectorAll('.lib-item');
|
|
170
|
-
|
|
171
|
-
libItems.forEach(item => {
|
|
172
|
-
const libName = item.querySelector('.lib-name').textContent.toLowerCase();
|
|
173
|
-
const libPurpose = item.querySelector('.lib-purpose').textContent.toLowerCase();
|
|
174
|
-
|
|
175
|
-
if (libName.includes(searchTerm) || libPurpose.includes(searchTerm)) {
|
|
176
|
-
item.classList.remove('hidden');
|
|
177
|
-
} else {
|
|
178
|
-
item.classList.add('hidden');
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
</script>
|
|
183
|
-
</body>
|
|
184
|
-
</html>
|
|
185
|
-
HTML
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def system_overview_card(results)
|
|
189
|
-
rails_status = results[:rails_version][:status]
|
|
190
|
-
ruby_status = results[:ruby_version][:status]
|
|
191
|
-
system = results[:system]
|
|
192
|
-
|
|
193
|
-
<<~HTML
|
|
194
|
-
<div class="card">
|
|
195
|
-
<h3>🔧 System Overview</h3>
|
|
196
|
-
<div class="metric">
|
|
197
|
-
<span class="metric-label">Rails Version</span>
|
|
198
|
-
<span class="metric-value status-#{rails_status}">#{results[:rails_version][:current]}</span>
|
|
199
|
-
</div>
|
|
200
|
-
<div class="metric">
|
|
201
|
-
<span class="metric-label">Ruby Version</span>
|
|
202
|
-
<span class="metric-value status-#{ruby_status}">#{results[:ruby_version][:current]}</span>
|
|
203
|
-
</div>
|
|
204
|
-
<div class="metric">
|
|
205
|
-
<span class="metric-label">Environment</span>
|
|
206
|
-
<span class="metric-value">#{system[:environment_info][:rails_env]}</span>
|
|
207
|
-
</div>
|
|
208
|
-
<div class="metric">
|
|
209
|
-
<span class="metric-label">Web Server</span>
|
|
210
|
-
<span class="metric-value">#{system[:server_requirements][:web_server].capitalize}</span>
|
|
211
|
-
</div>
|
|
212
|
-
<div class="metric">
|
|
213
|
-
<span class="metric-label">Database</span>
|
|
214
|
-
<span class="metric-value">#{system[:server_requirements][:database]}</span>
|
|
215
|
-
</div>
|
|
216
|
-
<div class="metric">
|
|
217
|
-
<span class="metric-label">Cache Store</span>
|
|
218
|
-
<span class="metric-value">#{system[:server_requirements][:cache_store][:type].split('::').last}</span>
|
|
219
|
-
</div>
|
|
220
|
-
<div style="font-size: 0.8rem; color: #718096; margin-top: 4px;">#{system[:server_requirements][:cache_store][:explanation]}</div>
|
|
221
|
-
<div style="font-size: 0.8rem; color: #4299e1; margin-top: 2px;">💡 #{system[:server_requirements][:cache_store][:recommendation]}</div>
|
|
222
|
-
</div>
|
|
223
|
-
HTML
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
def database_card(results)
|
|
227
|
-
db_status = results[:database][:status]
|
|
228
|
-
status_class = db_status == 'healthy' ? 'status-healthy' : 'status-error'
|
|
229
|
-
|
|
230
|
-
<<~HTML
|
|
231
|
-
<div class="card">
|
|
232
|
-
<h3>💾 Database</h3>
|
|
233
|
-
<div class="metric">
|
|
234
|
-
<span class="metric-label">Connection Status</span>
|
|
235
|
-
<span class="metric-value #{status_class}">#{db_status.capitalize}</span>
|
|
236
|
-
</div>
|
|
237
|
-
<div class="metric">
|
|
238
|
-
<span class="metric-label">Connected</span>
|
|
239
|
-
<span class="metric-value">#{results[:database][:connected] ? '✅ Yes' : '❌ No'}</span>
|
|
240
|
-
</div>
|
|
241
|
-
</div>
|
|
242
|
-
HTML
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
def gems_card(results)
|
|
246
|
-
outdated_count = results[:gems][:outdated]
|
|
247
|
-
total_count = results[:gems][:total]
|
|
248
|
-
|
|
249
|
-
<<~HTML
|
|
250
|
-
<div class="card">
|
|
251
|
-
<h3>📦 Gem Dependencies</h3>
|
|
252
|
-
<div class="metric">
|
|
253
|
-
<span class="metric-label">Total Gems</span>
|
|
254
|
-
<span class="metric-value">#{total_count}</span>
|
|
255
|
-
</div>
|
|
256
|
-
<div class="metric">
|
|
257
|
-
<span class="metric-label">Outdated</span>
|
|
258
|
-
<span class="metric-value status-#{outdated_count > 0 ? 'warning' : 'healthy'}">#{outdated_count}</span>
|
|
259
|
-
</div>
|
|
260
|
-
<div class="gem-list">
|
|
261
|
-
#{gem_list_html(results[:gems][:details])}
|
|
262
|
-
</div>
|
|
263
|
-
</div>
|
|
264
|
-
HTML
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
def gem_list_html(gems)
|
|
268
|
-
gems.first(10).map do |gem|
|
|
269
|
-
css_class = gem[:outdated] ? 'gem-item gem-outdated' : 'gem-item'
|
|
270
|
-
status = gem[:outdated] ? '⚠️' : '✅'
|
|
271
|
-
<<~HTML
|
|
272
|
-
<div class="#{css_class}">
|
|
273
|
-
<span>#{gem[:name]}</span>
|
|
274
|
-
<span>#{status} #{gem[:version]}</span>
|
|
275
|
-
</div>
|
|
276
|
-
HTML
|
|
277
|
-
end.join
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
def security_card(results)
|
|
281
|
-
security_status = results[:security][:status]
|
|
282
|
-
outdated_count = results[:security][:outdated_count]
|
|
283
|
-
|
|
284
|
-
<<~HTML
|
|
285
|
-
<div class="card">
|
|
286
|
-
<h3>🔒 Security</h3>
|
|
287
|
-
<div class="metric">
|
|
288
|
-
<span class="metric-label">Status</span>
|
|
289
|
-
<span class="metric-value status-#{security_status == 'secure' ? 'healthy' : 'warning'}">#{security_status.capitalize}</span>
|
|
290
|
-
</div>
|
|
291
|
-
<div class="metric">
|
|
292
|
-
<span class="metric-label">Issues Found</span>
|
|
293
|
-
<span class="metric-value">#{outdated_count}</span>
|
|
294
|
-
</div>
|
|
295
|
-
</div>
|
|
296
|
-
HTML
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
def health_score_card(results)
|
|
300
|
-
score = calculate_health_score(results)
|
|
301
|
-
score_class, score_text = get_score_class_and_text(score)
|
|
302
|
-
reasons = get_dashboard_score_reasons(results)
|
|
303
|
-
|
|
304
|
-
<<~HTML
|
|
305
|
-
<div class="card health-score">
|
|
306
|
-
<h3>🏆 Overall Health Score</h3>
|
|
307
|
-
<div class="score-circle #{score_class}">#{score}</div>
|
|
308
|
-
<div style="font-size: 1.2rem; font-weight: 600; color: #2d3748; margin-bottom: 16px;">#{score_text}</div>
|
|
309
|
-
#{reasons}
|
|
310
|
-
</div>
|
|
311
|
-
HTML
|
|
312
|
-
end
|
|
313
|
-
|
|
314
|
-
def priority_actions_card(results)
|
|
315
|
-
actions = generate_priority_actions(results)
|
|
316
|
-
|
|
317
|
-
<<~HTML
|
|
318
|
-
<div class="actions">
|
|
319
|
-
<h3>🎯 Priority Actions</h3>
|
|
320
|
-
#{actions.empty? ? '<p style="text-align: center; color: #38a169; font-size: 1.1rem;">✅ No critical actions required!</p>' : actions.join}
|
|
321
|
-
</div>
|
|
322
|
-
HTML
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
def calculate_health_score(results)
|
|
326
|
-
score = 100
|
|
327
|
-
score -= 20 if results[:rails_version][:status] == 'outdated'
|
|
328
|
-
score -= 15 if results[:ruby_version][:status] == 'outdated'
|
|
329
|
-
score -= 30 if results[:database][:status] == 'unhealthy'
|
|
330
|
-
score -= (results[:gems][:outdated] * 2)
|
|
331
|
-
score -= (results[:security][:outdated_count] * 3)
|
|
332
|
-
[score, 0].max
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
def get_score_class_and_text(score)
|
|
336
|
-
case score
|
|
337
|
-
when 90..100 then ['score-excellent', 'Excellent']
|
|
338
|
-
when 70..89 then ['score-good', 'Good']
|
|
339
|
-
when 50..69 then ['score-warning', 'Needs Attention']
|
|
340
|
-
else ['score-critical', 'Critical']
|
|
341
|
-
end
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
def jobs_card(results)
|
|
345
|
-
jobs = results[:jobs]
|
|
346
|
-
job_status = jobs[:status]
|
|
347
|
-
status_class = case job_status
|
|
348
|
-
when 'healthy' then 'status-healthy'
|
|
349
|
-
when 'warning' then 'status-warning'
|
|
350
|
-
when 'critical', 'error' then 'status-error'
|
|
351
|
-
else 'status-warning'
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
<<~HTML
|
|
355
|
-
<div class="card">
|
|
356
|
-
<h3>⚙️ Background Jobs</h3>
|
|
357
|
-
<div class="metric">
|
|
358
|
-
<span class="metric-label">Overall Status</span>
|
|
359
|
-
<span class="metric-value #{status_class}">#{job_status.capitalize}</span>
|
|
360
|
-
</div>
|
|
361
|
-
#{job_details_html(jobs)}
|
|
362
|
-
</div>
|
|
363
|
-
HTML
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
def job_details_html(jobs)
|
|
367
|
-
details = []
|
|
368
|
-
|
|
369
|
-
if jobs[:sidekiq][:available]
|
|
370
|
-
sidekiq = jobs[:sidekiq]
|
|
371
|
-
if sidekiq[:error]
|
|
372
|
-
details << '<div class="metric"><span class="metric-label">Sidekiq</span><span class="metric-value status-error">Error</span></div>'
|
|
373
|
-
details << "<div style=\"font-size: 0.8rem; color: #e53e3e; margin-top: 4px;\">#{sidekiq[:error]}</div>"
|
|
374
|
-
else
|
|
375
|
-
details << "<div class=\"metric\"><span class=\"metric-label\">Sidekiq Enqueued</span><span class=\"metric-value\">#{sidekiq[:enqueued]}</span></div>"
|
|
376
|
-
details << "<div class=\"metric\"><span class=\"metric-label\">Sidekiq Failed</span><span class=\"metric-value\">#{sidekiq[:failed]}</span></div>"
|
|
377
|
-
end
|
|
378
|
-
end
|
|
379
|
-
|
|
380
|
-
if jobs[:resque][:available]
|
|
381
|
-
resque = jobs[:resque]
|
|
382
|
-
if resque[:error]
|
|
383
|
-
details << '<div class="metric"><span class="metric-label">Resque</span><span class="metric-value status-error">Error</span></div>'
|
|
384
|
-
details << "<div style=\"font-size: 0.8rem; color: #e53e3e; margin-top: 4px;\">#{resque[:error]}</div>"
|
|
385
|
-
else
|
|
386
|
-
details << "<div class=\"metric\"><span class=\"metric-label\">Resque Pending</span><span class=\"metric-value\">#{resque[:pending]}</span></div>"
|
|
387
|
-
details << "<div class=\"metric\"><span class=\"metric-label\">Resque Failed</span><span class=\"metric-value\">#{resque[:failed]}</span></div>"
|
|
388
|
-
end
|
|
389
|
-
end
|
|
390
|
-
|
|
391
|
-
if jobs[:active_job][:available]
|
|
392
|
-
active_job = jobs[:active_job]
|
|
393
|
-
details << "<div class=\"metric\"><span class=\"metric-label\">ActiveJob Adapter</span><span class=\"metric-value\">#{active_job[:adapter]}</span></div>"
|
|
394
|
-
end
|
|
395
|
-
|
|
396
|
-
details.empty? ? '<div class="metric"><span class="metric-label">Status</span><span class="metric-value">Not Configured</span></div>' : details.join
|
|
397
|
-
end
|
|
398
|
-
|
|
399
|
-
def generate_priority_actions(results)
|
|
400
|
-
actions = []
|
|
401
|
-
|
|
402
|
-
if results[:database][:status] == 'unhealthy'
|
|
403
|
-
actions << '<div class="action-item action-critical">🔴 <strong>CRITICAL:</strong> Fix database connection immediately</div>'
|
|
404
|
-
end
|
|
405
|
-
|
|
406
|
-
if results[:jobs][:status] == 'critical'
|
|
407
|
-
actions << '<div class="action-item action-critical">🔴 <strong>CRITICAL:</strong> Background job system failure</div>'
|
|
408
|
-
end
|
|
409
|
-
|
|
410
|
-
if results[:rails_version][:status] == 'outdated'
|
|
411
|
-
actions << '<div class="action-item action-high">🟡 <strong>HIGH:</strong> Update Rails framework</div>'
|
|
412
|
-
end
|
|
413
|
-
|
|
414
|
-
if results[:security][:outdated_count] > 5
|
|
415
|
-
actions << '<div class="action-item action-high">🟡 <strong>HIGH:</strong> Address security vulnerabilities</div>'
|
|
416
|
-
end
|
|
417
|
-
|
|
418
|
-
if results[:jobs][:status] == 'warning'
|
|
419
|
-
actions << '<div class="action-item action-medium">🟡 <strong>MEDIUM:</strong> Check background job queues</div>'
|
|
420
|
-
end
|
|
421
|
-
|
|
422
|
-
if results[:gems][:outdated] > 10
|
|
423
|
-
actions << '<div class="action-item action-medium">🟢 <strong>MEDIUM:</strong> Update outdated gems</div>'
|
|
424
|
-
end
|
|
425
|
-
|
|
426
|
-
actions
|
|
427
|
-
end
|
|
428
|
-
|
|
429
|
-
def get_dashboard_score_reasons(results)
|
|
430
|
-
score = calculate_health_score(results)
|
|
431
|
-
return '' if score >= 90
|
|
432
|
-
|
|
433
|
-
reasons = []
|
|
434
|
-
|
|
435
|
-
if results[:database][:status] == 'unhealthy'
|
|
436
|
-
reasons << '<div style="color: #e53e3e; font-size: 0.9rem; margin: 4px 0;">❌ Database connection failed</div>'
|
|
437
|
-
end
|
|
438
|
-
|
|
439
|
-
if results[:rails_version][:status] == 'outdated'
|
|
440
|
-
reasons << '<div style="color: #d69e2e; font-size: 0.9rem; margin: 4px 0;">⚠️ Rails version outdated</div>'
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
if results[:gems][:outdated] > 20
|
|
444
|
-
reasons << '<div style="color: #d69e2e; font-size: 0.9rem; margin: 4px 0;">⚠️ Many outdated gems (#{results[:gems][:outdated]})</div>'
|
|
445
|
-
end
|
|
446
|
-
|
|
447
|
-
if results[:security][:outdated_count] > 10
|
|
448
|
-
reasons << '<div style="color: #d69e2e; font-size: 0.9rem; margin: 4px 0;">⚠️ Security vulnerabilities (#{results[:security][:outdated_count]})</div>'
|
|
449
|
-
end
|
|
450
|
-
|
|
451
|
-
if results[:jobs][:status] == 'critical'
|
|
452
|
-
reasons << '<div style="color: #e53e3e; font-size: 0.9rem; margin: 4px 0;">❌ Background jobs critical</div>'
|
|
453
|
-
end
|
|
454
|
-
|
|
455
|
-
return '' if reasons.empty?
|
|
456
|
-
|
|
457
|
-
'<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #e2e8f0;">' +
|
|
458
|
-
'<div style="font-size: 0.9rem; font-weight: 600; color: #4a5568; margin-bottom: 8px;">Issues affecting score:</div>' +
|
|
459
|
-
reasons.join +
|
|
460
|
-
'</div>'
|
|
461
|
-
end
|
|
462
|
-
|
|
463
|
-
def libraries_card(results)
|
|
464
|
-
system = results[:system]
|
|
465
|
-
required_libs = system[:required_libs]
|
|
466
|
-
optional_libs = system[:optional_libs]
|
|
467
|
-
|
|
468
|
-
<<~HTML
|
|
469
|
-
<div class="card">
|
|
470
|
-
<h3>📚 Required Libraries</h3>
|
|
471
|
-
<div class="expandable">
|
|
472
|
-
#{library_list_html(required_libs, 'required')}
|
|
473
|
-
</div>
|
|
474
|
-
<h3 style="margin-top: 20px;">🔌 Optional Libraries</h3>
|
|
475
|
-
<div class="expandable">
|
|
476
|
-
#{library_list_html(optional_libs, 'optional')}
|
|
477
|
-
</div>
|
|
478
|
-
</div>
|
|
479
|
-
HTML
|
|
480
|
-
end
|
|
481
|
-
|
|
482
|
-
def library_list_html(libraries, type)
|
|
483
|
-
libraries.map do |name, info|
|
|
484
|
-
status_class = "status-#{info[:status]}"
|
|
485
|
-
lib_class = "lib-item lib-#{type}"
|
|
486
|
-
lib_class += " lib-missing" if info[:status] == 'missing'
|
|
487
|
-
|
|
488
|
-
version_text = info[:version] ? " (#{info[:version]})" : ''
|
|
489
|
-
status_text = info[:status] == 'loaded' ? '✅' : (info[:status] == 'missing' ? '❌' : '⏸️')
|
|
490
|
-
|
|
491
|
-
<<~HTML
|
|
492
|
-
<div class="#{lib_class}">
|
|
493
|
-
<div>
|
|
494
|
-
<span class="lib-name">#{name}#{version_text}</span>
|
|
495
|
-
<div class="lib-purpose" style="font-size: 0.8rem; color: #718096;">#{info[:purpose]}</div>
|
|
496
|
-
</div>
|
|
497
|
-
<span class="#{status_class}">#{status_text}</span>
|
|
498
|
-
</div>
|
|
499
|
-
HTML
|
|
500
|
-
end.join
|
|
501
|
-
end
|
|
502
|
-
end
|
|
503
|
-
end
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
module RailsHealthChecker
|
|
2
|
-
class GemAnalyzer
|
|
3
|
-
def analyze
|
|
4
|
-
gems = Bundler.load.specs
|
|
5
|
-
outdated = check_outdated_gems
|
|
6
|
-
|
|
7
|
-
{
|
|
8
|
-
total: gems.count,
|
|
9
|
-
outdated: outdated.count,
|
|
10
|
-
vulnerable: check_vulnerable_gems.count,
|
|
11
|
-
details: gem_details(gems, outdated)
|
|
12
|
-
}
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
private
|
|
16
|
-
|
|
17
|
-
def check_outdated_gems
|
|
18
|
-
`bundle outdated --parseable`.split("\n").map do |line|
|
|
19
|
-
line.split(" ")[0] if line.include?("(")
|
|
20
|
-
end.compact
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def check_vulnerable_gems
|
|
24
|
-
# Simple check - in production, integrate with bundler-audit
|
|
25
|
-
[]
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def gem_details(gems, outdated)
|
|
29
|
-
gems.map do |gem|
|
|
30
|
-
{
|
|
31
|
-
name: gem.name,
|
|
32
|
-
version: gem.version.to_s,
|
|
33
|
-
outdated: outdated.include?(gem.name),
|
|
34
|
-
path: gem.gem_dir
|
|
35
|
-
}
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
module RailsHealthChecker
|
|
2
|
-
class HealthMiddleware
|
|
3
|
-
def initialize(app)
|
|
4
|
-
@app = app
|
|
5
|
-
end
|
|
6
|
-
|
|
7
|
-
def call(env)
|
|
8
|
-
if env['PATH_INFO'] == '/health'
|
|
9
|
-
health_check_response
|
|
10
|
-
else
|
|
11
|
-
@app.call(env)
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
private
|
|
16
|
-
|
|
17
|
-
def health_check_response
|
|
18
|
-
status = perform_quick_health_check
|
|
19
|
-
|
|
20
|
-
[
|
|
21
|
-
status[:code],
|
|
22
|
-
{ 'Content-Type' => 'application/json' },
|
|
23
|
-
[status[:body].to_json]
|
|
24
|
-
]
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def perform_quick_health_check
|
|
28
|
-
begin
|
|
29
|
-
db_healthy = ActiveRecord::Base.connection.active?
|
|
30
|
-
|
|
31
|
-
{
|
|
32
|
-
code: 200,
|
|
33
|
-
body: {
|
|
34
|
-
status: 'healthy',
|
|
35
|
-
timestamp: Time.current.iso8601,
|
|
36
|
-
database: db_healthy ? 'connected' : 'disconnected',
|
|
37
|
-
rails_version: Rails.version,
|
|
38
|
-
ruby_version: RUBY_VERSION
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
rescue => e
|
|
42
|
-
{
|
|
43
|
-
code: 503,
|
|
44
|
-
body: {
|
|
45
|
-
status: 'unhealthy',
|
|
46
|
-
timestamp: Time.current.iso8601,
|
|
47
|
-
error: e.message
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|