rails_error_dashboard 0.1.14 ā 0.1.16
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/app/controllers/rails_error_dashboard/application_controller.rb +6 -0
- data/app/views/layouts/rails_error_dashboard.html.erb +80 -19
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +3 -0
- data/app/views/rails_error_dashboard/errors/_timeline.html.erb +1 -1
- data/app/views/rails_error_dashboard/errors/analytics.html.erb +17 -2
- data/app/views/rails_error_dashboard/errors/index.html.erb +10 -5
- data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +20 -26
- data/app/views/rails_error_dashboard/errors/show.html.erb +121 -5
- data/db/development.sqlite3 +0 -0
- data/lib/rails_error_dashboard/engine.rb +9 -0
- data/lib/rails_error_dashboard/value_objects/error_context.rb +22 -6
- data/lib/rails_error_dashboard/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2bb87db0ac566c74592758f0bb11c4e16f3caff7464d7c428436083adc13edb5
|
|
4
|
+
data.tar.gz: ccead510cbc1f57d1197aafa34e4c43f3f74a59f7b4d56e53ea0b8ebb4fa73e2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 330ec515a3f833517c66926f1c77a3bd9c6cf7db23a5d9e63d2ae796c1fb2fd672dba56839cb7e70cd055e8d6ec46ff8d0974f56af4580463d0bcfa6f3c5b9c5
|
|
7
|
+
data.tar.gz: f19ea45f7eadce30679c0cf0e0e8f117f603fa2d06158783477ad2a6e0451e91a09cb6913eb6ec2a4798ea57d655622d496adce5a4f8cf378a74304ae92cf19c
|
|
@@ -2,6 +2,12 @@ module RailsErrorDashboard
|
|
|
2
2
|
class ApplicationController < ActionController::Base
|
|
3
3
|
include Pagy::Backend
|
|
4
4
|
|
|
5
|
+
# Enable features that are disabled in API-only mode
|
|
6
|
+
# These are ONLY enabled for Error Dashboard routes, not the entire app
|
|
7
|
+
include ActionController::Cookies
|
|
8
|
+
include ActionController::Flash
|
|
9
|
+
include ActionController::RequestForgeryProtection
|
|
10
|
+
|
|
5
11
|
layout "rails_error_dashboard"
|
|
6
12
|
|
|
7
13
|
protect_from_forgery with: :exception
|
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<title><%= content_for?(:page_title) ? "#{content_for(:page_title)} | " : "" %><%= Rails.application.class.module_parent_name %> - Error Dashboard</title>
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
<% if respond_to?(:csrf_meta_tags) %>
|
|
7
|
+
<%= csrf_meta_tags %>
|
|
8
|
+
<% end %>
|
|
9
|
+
<% if respond_to?(:csp_meta_tag) %>
|
|
10
|
+
<%= csp_meta_tag %>
|
|
11
|
+
<% end %>
|
|
8
12
|
|
|
9
13
|
<!-- Bootstrap CSS -->
|
|
10
14
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
@@ -783,12 +787,30 @@
|
|
|
783
787
|
<button class="btn btn-link text-white d-md-none me-2" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebarMenu">
|
|
784
788
|
<i class="bi bi-list fs-4"></i>
|
|
785
789
|
</button>
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
790
|
+
<%
|
|
791
|
+
# Check if main app has a root route defined
|
|
792
|
+
begin
|
|
793
|
+
root_url = main_app.root_path
|
|
794
|
+
has_root = true
|
|
795
|
+
rescue NoMethodError
|
|
796
|
+
has_root = false
|
|
797
|
+
end
|
|
798
|
+
%>
|
|
799
|
+
<% if has_root %>
|
|
800
|
+
<a class="navbar-brand" href="<%= root_url %>" title="Back to <%= Rails.application.class.module_parent_name %>">
|
|
801
|
+
<i class="bi bi-bug-fill"></i>
|
|
802
|
+
<span class="d-none d-sm-inline"><%= Rails.application.class.module_parent_name %></span>
|
|
803
|
+
<span class="d-none d-md-inline text-white-50 mx-2">|</span>
|
|
804
|
+
<span class="d-none d-md-inline">Error Dashboard</span>
|
|
805
|
+
</a>
|
|
806
|
+
<% else %>
|
|
807
|
+
<span class="navbar-brand">
|
|
808
|
+
<i class="bi bi-bug-fill"></i>
|
|
809
|
+
<span class="d-none d-sm-inline"><%= Rails.application.class.module_parent_name %></span>
|
|
810
|
+
<span class="d-none d-md-inline text-white-50 mx-2">|</span>
|
|
811
|
+
<span class="d-none d-md-inline">Error Dashboard</span>
|
|
812
|
+
</span>
|
|
813
|
+
<% end %>
|
|
792
814
|
</div>
|
|
793
815
|
<div class="d-flex align-items-center gap-3">
|
|
794
816
|
<button class="theme-toggle" id="themeToggle">
|
|
@@ -867,6 +889,43 @@
|
|
|
867
889
|
</div>
|
|
868
890
|
</div>
|
|
869
891
|
|
|
892
|
+
<!-- Keyboard Shortcuts Modal -->
|
|
893
|
+
<div class="modal fade" id="keyboardShortcutsModal" tabindex="-1" aria-labelledby="keyboardShortcutsModalLabel" aria-hidden="true">
|
|
894
|
+
<div class="modal-dialog modal-dialog-centered">
|
|
895
|
+
<div class="modal-content">
|
|
896
|
+
<div class="modal-header">
|
|
897
|
+
<h5 class="modal-title" id="keyboardShortcutsModalLabel">
|
|
898
|
+
<i class="bi bi-keyboard"></i> Keyboard Shortcuts
|
|
899
|
+
</h5>
|
|
900
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
901
|
+
</div>
|
|
902
|
+
<div class="modal-body">
|
|
903
|
+
<div class="list-group list-group-flush">
|
|
904
|
+
<div class="list-group-item d-flex justify-content-between align-items-center">
|
|
905
|
+
<span><i class="bi bi-arrow-clockwise text-primary"></i> Refresh page</span>
|
|
906
|
+
<kbd class="bg-secondary text-white px-2 py-1 rounded">R</kbd>
|
|
907
|
+
</div>
|
|
908
|
+
<div class="list-group-item d-flex justify-content-between align-items-center">
|
|
909
|
+
<span><i class="bi bi-search text-primary"></i> Focus search</span>
|
|
910
|
+
<kbd class="bg-secondary text-white px-2 py-1 rounded">/</kbd>
|
|
911
|
+
</div>
|
|
912
|
+
<div class="list-group-item d-flex justify-content-between align-items-center">
|
|
913
|
+
<span><i class="bi bi-graph-up text-primary"></i> Go to analytics</span>
|
|
914
|
+
<kbd class="bg-secondary text-white px-2 py-1 rounded">A</kbd>
|
|
915
|
+
</div>
|
|
916
|
+
<div class="list-group-item d-flex justify-content-between align-items-center">
|
|
917
|
+
<span><i class="bi bi-question-circle text-primary"></i> Show this help</span>
|
|
918
|
+
<kbd class="bg-secondary text-white px-2 py-1 rounded">?</kbd>
|
|
919
|
+
</div>
|
|
920
|
+
</div>
|
|
921
|
+
</div>
|
|
922
|
+
<div class="modal-footer">
|
|
923
|
+
<small class="text-muted">Press <kbd class="bg-secondary text-white px-2 py-1 rounded">?</kbd> anytime to show shortcuts</small>
|
|
924
|
+
</div>
|
|
925
|
+
</div>
|
|
926
|
+
</div>
|
|
927
|
+
</div>
|
|
928
|
+
|
|
870
929
|
<!-- Bootstrap JS -->
|
|
871
930
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
872
931
|
|
|
@@ -1115,17 +1174,19 @@
|
|
|
1115
1174
|
};
|
|
1116
1175
|
|
|
1117
1176
|
// Show flash messages as toasts
|
|
1118
|
-
<% if flash
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1177
|
+
<% if defined?(flash) && flash.present? %>
|
|
1178
|
+
<% if flash[:notice] %>
|
|
1179
|
+
showToast('<%= j flash[:notice] %>', 'success');
|
|
1180
|
+
<% end %>
|
|
1181
|
+
<% if flash[:alert] %>
|
|
1182
|
+
showToast('<%= j flash[:alert] %>', 'danger');
|
|
1183
|
+
<% end %>
|
|
1184
|
+
<% if flash[:success] %>
|
|
1185
|
+
showToast('<%= j flash[:success] %>', 'success');
|
|
1186
|
+
<% end %>
|
|
1187
|
+
<% if flash[:error] %>
|
|
1188
|
+
showToast('<%= j flash[:error] %>', 'danger');
|
|
1189
|
+
<% end %>
|
|
1129
1190
|
<% end %>
|
|
1130
1191
|
});
|
|
1131
1192
|
</script>
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
</td>
|
|
20
20
|
<td onclick="window.location='<%= error_path(error) %>';">
|
|
21
21
|
<code class="text-danger" data-bs-toggle="tooltip" title="<%= error.error_type %>"><%= error.error_type.split('::').last %></code>
|
|
22
|
+
<% if error.recent? %>
|
|
23
|
+
<span class="badge bg-success ms-1" data-bs-toggle="tooltip" title="Error occurred within the last hour">NEW</span>
|
|
24
|
+
<% end %>
|
|
22
25
|
<% if error.respond_to?(:app_version) && error.app_version.present? %>
|
|
23
26
|
<br><small class="badge bg-light text-dark" data-bs-toggle="tooltip" title="App version when error occurred">v<%= error.app_version %></small>
|
|
24
27
|
<% end %>
|
|
@@ -131,14 +131,29 @@
|
|
|
131
131
|
<script>
|
|
132
132
|
document.addEventListener('DOMContentLoaded', function() {
|
|
133
133
|
const colors = window.getChartColors();
|
|
134
|
+
// Distinct, accessible colors for 5 platforms with good contrast
|
|
135
|
+
const platformColors = [
|
|
136
|
+
"#2563EB", // Blue - API
|
|
137
|
+
"#10B981", // Green - Android
|
|
138
|
+
"#F59E0B", // Amber - Background Jobs
|
|
139
|
+
"#8B5CF6", // Purple - Web
|
|
140
|
+
"#EF4444" // Red - iOS
|
|
141
|
+
];
|
|
142
|
+
|
|
134
143
|
new Chartkick.PieChart("errors-by-platform-chart", <%= raw @errors_by_platform.to_json %>, {
|
|
135
|
-
colors:
|
|
144
|
+
colors: platformColors,
|
|
136
145
|
height: "300px",
|
|
137
146
|
legend: "bottom",
|
|
138
147
|
donut: true,
|
|
139
148
|
library: {
|
|
140
149
|
plugins: {
|
|
141
|
-
legend: {
|
|
150
|
+
legend: {
|
|
151
|
+
labels: {
|
|
152
|
+
color: colors.textColor,
|
|
153
|
+
font: { size: 12, weight: 'bold' }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// Tooltip uses global theme-aware defaults from layout
|
|
142
157
|
}
|
|
143
158
|
}
|
|
144
159
|
});
|
|
@@ -530,11 +530,8 @@
|
|
|
530
530
|
// '?' - Show keyboard shortcuts help
|
|
531
531
|
if (e.key === '?') {
|
|
532
532
|
e.preventDefault();
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
'/ - Focus search\n' +
|
|
536
|
-
'a - Analytics page\n' +
|
|
537
|
-
'? - Show this help');
|
|
533
|
+
const modal = new bootstrap.Modal(document.getElementById('keyboardShortcutsModal'));
|
|
534
|
+
modal.show();
|
|
538
535
|
}
|
|
539
536
|
});
|
|
540
537
|
|
|
@@ -570,4 +567,12 @@
|
|
|
570
567
|
});
|
|
571
568
|
}
|
|
572
569
|
});
|
|
570
|
+
|
|
571
|
+
// Update browser tab title with unresolved error count
|
|
572
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
573
|
+
const unresolvedCount = <%= @stats[:unresolved] || 0 %>;
|
|
574
|
+
if (unresolvedCount > 0) {
|
|
575
|
+
document.title = `(${unresolvedCount}) ${document.title}`;
|
|
576
|
+
}
|
|
577
|
+
});
|
|
573
578
|
</script>
|
|
@@ -291,6 +291,19 @@
|
|
|
291
291
|
</div>
|
|
292
292
|
|
|
293
293
|
<script>
|
|
294
|
+
// Platform color mapping - consistent with analytics page
|
|
295
|
+
const platformColorMap = {
|
|
296
|
+
'api': '#2563EB', // Blue
|
|
297
|
+
'android': '#10B981', // Green
|
|
298
|
+
'background_jobs': '#F59E0B', // Amber
|
|
299
|
+
'web': '#8B5CF6', // Purple
|
|
300
|
+
'ios': '#EF4444' // Red
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
function getPlatformColor(platform) {
|
|
304
|
+
return platformColorMap[platform.toLowerCase()] || '#6B7280'; // Gray fallback
|
|
305
|
+
}
|
|
306
|
+
|
|
294
307
|
// Error Rate Chart
|
|
295
308
|
const errorRateCtx = document.getElementById('errorRateChart');
|
|
296
309
|
if (errorRateCtx) {
|
|
@@ -301,16 +314,8 @@
|
|
|
301
314
|
datasets: [{
|
|
302
315
|
label: 'Total Errors',
|
|
303
316
|
data: <%= raw @error_rate_by_platform.values.to_json %>,
|
|
304
|
-
backgroundColor: (
|
|
305
|
-
|
|
306
|
-
const config = getCatppuccinChartConfig(isDark);
|
|
307
|
-
return config.colors.blue.replace('rgb', 'rgba').replace(')', ', 0.5)');
|
|
308
|
-
})(),
|
|
309
|
-
borderColor: (function() {
|
|
310
|
-
const isDark = document.body.classList.contains('dark-mode');
|
|
311
|
-
const config = getCatppuccinChartConfig(isDark);
|
|
312
|
-
return config.colors.blue;
|
|
313
|
-
})(),
|
|
317
|
+
backgroundColor: 'rgba(37, 99, 235, 0.5)', // Blue with transparency
|
|
318
|
+
borderColor: '#2563EB', // Blue
|
|
314
319
|
borderWidth: 1
|
|
315
320
|
}]
|
|
316
321
|
},
|
|
@@ -330,9 +335,6 @@
|
|
|
330
335
|
// Daily Trend Chart
|
|
331
336
|
const dailyTrendCtx = document.getElementById('dailyTrendChart');
|
|
332
337
|
if (dailyTrendCtx) {
|
|
333
|
-
// Use Catppuccin colors from global theme config
|
|
334
|
-
const isDark = document.body.classList.contains('dark-mode');
|
|
335
|
-
|
|
336
338
|
const datasets = <%= raw @daily_trends.map { |platform, data|
|
|
337
339
|
{
|
|
338
340
|
label: platform.to_s.capitalize,
|
|
@@ -342,12 +344,12 @@
|
|
|
342
344
|
}
|
|
343
345
|
}.to_json %>;
|
|
344
346
|
|
|
345
|
-
// Apply
|
|
347
|
+
// Apply platform-specific colors
|
|
346
348
|
datasets.forEach(ds => {
|
|
347
|
-
const color = getPlatformColor(ds.platform
|
|
349
|
+
const color = getPlatformColor(ds.platform);
|
|
348
350
|
ds.borderColor = color;
|
|
349
351
|
// Add transparency for background
|
|
350
|
-
ds.backgroundColor = color
|
|
352
|
+
ds.backgroundColor = color + '1A'; // Add 10% alpha in hex
|
|
351
353
|
});
|
|
352
354
|
|
|
353
355
|
new Chart(dailyTrendCtx, {
|
|
@@ -382,16 +384,8 @@
|
|
|
382
384
|
datasets: [{
|
|
383
385
|
label: 'Hours to Resolve',
|
|
384
386
|
data: times,
|
|
385
|
-
backgroundColor: (
|
|
386
|
-
|
|
387
|
-
const config = getCatppuccinChartConfig(isDark);
|
|
388
|
-
return config.colors.orange.replace('rgb', 'rgba').replace(')', ', 0.5)');
|
|
389
|
-
})(),
|
|
390
|
-
borderColor: (function() {
|
|
391
|
-
const isDark = document.body.classList.contains('dark-mode');
|
|
392
|
-
const config = getCatppuccinChartConfig(isDark);
|
|
393
|
-
return config.colors.orange;
|
|
394
|
-
})(),
|
|
387
|
+
backgroundColor: 'rgba(245, 158, 11, 0.5)', // Amber with transparency
|
|
388
|
+
borderColor: '#F59E0B', // Amber
|
|
395
389
|
borderWidth: 1
|
|
396
390
|
}]
|
|
397
391
|
},
|
|
@@ -26,7 +26,10 @@
|
|
|
26
26
|
<% end %>
|
|
27
27
|
</h2>
|
|
28
28
|
</div>
|
|
29
|
-
<div>
|
|
29
|
+
<div class="d-flex gap-2">
|
|
30
|
+
<button type="button" class="btn btn-outline-secondary" onclick="downloadErrorJSON()" title="Download error details as JSON">
|
|
31
|
+
<i class="bi bi-download"></i> Export JSON
|
|
32
|
+
</button>
|
|
30
33
|
<% if @error.resolved? %>
|
|
31
34
|
<span class="badge bg-success fs-6">
|
|
32
35
|
<i class="bi bi-check-circle"></i> Resolved
|
|
@@ -39,6 +42,60 @@
|
|
|
39
42
|
</div>
|
|
40
43
|
</div>
|
|
41
44
|
|
|
45
|
+
<script>
|
|
46
|
+
function downloadErrorJSON() {
|
|
47
|
+
const errorData = {
|
|
48
|
+
id: <%= @error.id %>,
|
|
49
|
+
error_type: <%= raw @error.error_type.to_json %>,
|
|
50
|
+
message: <%= raw @error.message.to_json %>,
|
|
51
|
+
backtrace: <%= raw @error.backtrace.to_json %>,
|
|
52
|
+
occurred_at: <%= raw @error.occurred_at.to_json %>,
|
|
53
|
+
first_seen_at: <%= raw @error.first_seen_at.to_json %>,
|
|
54
|
+
last_seen_at: <%= raw @error.last_seen_at.to_json %>,
|
|
55
|
+
occurrence_count: <%= @error.occurrence_count %>,
|
|
56
|
+
resolved: <%= @error.resolved? %>,
|
|
57
|
+
resolved_at: <%= raw @error.resolved_at.to_json %>,
|
|
58
|
+
resolved_by_name: <%= raw @error.resolved_by_name.to_json %>,
|
|
59
|
+
platform: <%= raw @error.platform.to_json %>,
|
|
60
|
+
user_id: <%= raw @error.user_id.to_json %>,
|
|
61
|
+
severity: <%= raw @error.severity.to_json %>,
|
|
62
|
+
priority_level: <%= @error.priority_level || 0 %>,
|
|
63
|
+
<% if @error.respond_to?(:app_version) %>
|
|
64
|
+
app_version: <%= raw @error.app_version.to_json %>,
|
|
65
|
+
<% end %>
|
|
66
|
+
<% if @error.respond_to?(:git_commit) %>
|
|
67
|
+
git_commit: <%= raw @error.git_commit.to_json %>,
|
|
68
|
+
<% end %>
|
|
69
|
+
created_at: <%= raw @error.created_at.to_json %>,
|
|
70
|
+
updated_at: <%= raw @error.updated_at.to_json %>
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const jsonString = JSON.stringify(errorData, null, 2);
|
|
74
|
+
const blob = new Blob([jsonString], { type: 'application/json' });
|
|
75
|
+
const url = URL.createObjectURL(blob);
|
|
76
|
+
const link = document.createElement('a');
|
|
77
|
+
link.href = url;
|
|
78
|
+
link.download = `error_${errorData.id}_${errorData.error_type.replace(/[^a-zA-Z0-9]/g, '_')}.json`;
|
|
79
|
+
document.body.appendChild(link);
|
|
80
|
+
link.click();
|
|
81
|
+
document.body.removeChild(link);
|
|
82
|
+
URL.revokeObjectURL(url);
|
|
83
|
+
|
|
84
|
+
// Visual feedback
|
|
85
|
+
const button = event.currentTarget;
|
|
86
|
+
const originalHTML = button.innerHTML;
|
|
87
|
+
button.innerHTML = '<i class="bi bi-check"></i> Downloaded!';
|
|
88
|
+
button.classList.remove('btn-outline-secondary');
|
|
89
|
+
button.classList.add('btn-success');
|
|
90
|
+
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
button.innerHTML = originalHTML;
|
|
93
|
+
button.classList.remove('btn-success');
|
|
94
|
+
button.classList.add('btn-outline-secondary');
|
|
95
|
+
}, 2000);
|
|
96
|
+
}
|
|
97
|
+
</script>
|
|
98
|
+
|
|
42
99
|
<div class="row g-4">
|
|
43
100
|
<!-- Error Information -->
|
|
44
101
|
<div class="col-md-8">
|
|
@@ -46,7 +103,12 @@
|
|
|
46
103
|
<div class="card mb-4">
|
|
47
104
|
<div class="card-header bg-danger text-white">
|
|
48
105
|
<div class="d-flex justify-content-between align-items-center">
|
|
49
|
-
<h5 class="mb-0"
|
|
106
|
+
<h5 class="mb-0">
|
|
107
|
+
<i class="bi bi-bug-fill"></i> <%= @error.error_type %>
|
|
108
|
+
<% if @error.recent? %>
|
|
109
|
+
<span class="badge bg-success ms-2" data-bs-toggle="tooltip" title="Error occurred within the last hour">NEW</span>
|
|
110
|
+
<% end %>
|
|
111
|
+
</h5>
|
|
50
112
|
<button class="btn btn-sm btn-outline-light" onclick="copyToClipboard('<%= j @error.error_type %>', this)" title="Copy error type">
|
|
51
113
|
<i class="bi bi-clipboard"></i>
|
|
52
114
|
</button>
|
|
@@ -402,11 +464,57 @@
|
|
|
402
464
|
</div>
|
|
403
465
|
<div class="mb-3">
|
|
404
466
|
<label for="body" class="form-label">Comment <span class="text-danger">*</span></label>
|
|
405
|
-
|
|
467
|
+
|
|
468
|
+
<!-- Quick Templates -->
|
|
469
|
+
<div class="mb-2">
|
|
470
|
+
<small class="text-muted d-block mb-1">
|
|
471
|
+
<i class="bi bi-lightning-fill"></i> Quick templates:
|
|
472
|
+
</small>
|
|
473
|
+
<div class="d-flex flex-wrap gap-1">
|
|
474
|
+
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="insertTemplate('investigating')">
|
|
475
|
+
<i class="bi bi-search"></i> Investigating
|
|
476
|
+
</button>
|
|
477
|
+
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="insertTemplate('found_fix')">
|
|
478
|
+
<i class="bi bi-wrench"></i> Found Fix
|
|
479
|
+
</button>
|
|
480
|
+
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="insertTemplate('need_info')">
|
|
481
|
+
<i class="bi bi-question-circle"></i> Need Info
|
|
482
|
+
</button>
|
|
483
|
+
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="insertTemplate('duplicate')">
|
|
484
|
+
<i class="bi bi-files"></i> Duplicate
|
|
485
|
+
</button>
|
|
486
|
+
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="insertTemplate('cannot_reproduce')">
|
|
487
|
+
<i class="bi bi-x-circle"></i> Cannot Reproduce
|
|
488
|
+
</button>
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
491
|
+
|
|
492
|
+
<%= text_area_tag :body, nil, class: "form-control", rows: 4, placeholder: "Share your thoughts, findings, or updates...", required: true, id: "comment_body" %>
|
|
406
493
|
</div>
|
|
407
494
|
<%= submit_tag "Post Comment", class: "btn btn-primary" %>
|
|
408
495
|
<% end %>
|
|
409
496
|
</div>
|
|
497
|
+
|
|
498
|
+
<script>
|
|
499
|
+
function insertTemplate(templateType) {
|
|
500
|
+
const textarea = document.getElementById('comment_body');
|
|
501
|
+
const templates = {
|
|
502
|
+
investigating: "š Investigating this issue now. Will update with findings.",
|
|
503
|
+
found_fix: "ā
Found the fix!\n\nRoot cause: \nSolution: \nPR: ",
|
|
504
|
+
need_info: "ā¹ļø Need more information:\n\n- \n- \n\nPlease provide details to help debug this issue.",
|
|
505
|
+
duplicate: "š This appears to be a duplicate of error #\n\nClosing as duplicate.",
|
|
506
|
+
cannot_reproduce: "ā Cannot reproduce this issue.\n\nAttempted:\n- \n- \n\nNeed more details or steps to reproduce."
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const template = templates[templateType];
|
|
510
|
+
if (template) {
|
|
511
|
+
textarea.value = template;
|
|
512
|
+
textarea.focus();
|
|
513
|
+
// Move cursor to end
|
|
514
|
+
textarea.setSelectionRange(textarea.value.length, textarea.value.length);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
</script>
|
|
410
518
|
</div>
|
|
411
519
|
</div>
|
|
412
520
|
<% end %>
|
|
@@ -572,8 +680,16 @@
|
|
|
572
680
|
|
|
573
681
|
<div class="mb-3">
|
|
574
682
|
<small class="text-muted d-block mb-1">First Seen</small>
|
|
575
|
-
|
|
576
|
-
|
|
683
|
+
<% if @related_errors.any? %>
|
|
684
|
+
<%= link_to "#timeline", class: "text-decoration-none", data: { bs_toggle: "tooltip" }, title: "Jump to timeline" do %>
|
|
685
|
+
<strong><%= @error.first_seen_at&.strftime("%B %d, %Y") || 'N/A' %></strong><br>
|
|
686
|
+
<small><%= @error.first_seen_at&.strftime("%I:%M:%S %p %Z") || 'N/A' %></small>
|
|
687
|
+
<i class="bi bi-arrow-down-circle ms-1"></i>
|
|
688
|
+
<% end %>
|
|
689
|
+
<% else %>
|
|
690
|
+
<strong><%= @error.first_seen_at&.strftime("%B %d, %Y") || 'N/A' %></strong><br>
|
|
691
|
+
<small><%= @error.first_seen_at&.strftime("%I:%M:%S %p %Z") || 'N/A' %></small>
|
|
692
|
+
<% end %>
|
|
577
693
|
</div>
|
|
578
694
|
|
|
579
695
|
<div class="mb-3">
|
|
Binary file
|
|
@@ -4,6 +4,15 @@ module RailsErrorDashboard
|
|
|
4
4
|
|
|
5
5
|
# Initialize the engine
|
|
6
6
|
initializer "rails_error_dashboard.middleware" do |app|
|
|
7
|
+
# Enable Flash middleware for Error Dashboard routes in API-only apps
|
|
8
|
+
# This ensures flash messages work even when config.api_only = true
|
|
9
|
+
if app.config.api_only
|
|
10
|
+
# Insert Flash middleware ONLY for Error Dashboard routes
|
|
11
|
+
app.middleware.use ActionDispatch::Flash
|
|
12
|
+
app.middleware.use ActionDispatch::Cookies
|
|
13
|
+
app.middleware.use ActionDispatch::Session::CookieStore
|
|
14
|
+
end
|
|
15
|
+
|
|
7
16
|
# Add error catching middleware if enabled
|
|
8
17
|
if RailsErrorDashboard.configuration.enable_middleware
|
|
9
18
|
app.config.middleware.insert_before 0, RailsErrorDashboard::Middleware::ErrorCatcher
|
|
@@ -46,7 +46,16 @@ module RailsErrorDashboard
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def build_request_url
|
|
49
|
-
|
|
49
|
+
# Handle both full Rails requests and API-only requests
|
|
50
|
+
if @context[:request]
|
|
51
|
+
begin
|
|
52
|
+
return @context[:request].fullpath
|
|
53
|
+
rescue NoMethodError
|
|
54
|
+
# Fallback for minimal request objects
|
|
55
|
+
return @context[:request].path rescue nil
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
50
59
|
return @context[:request_url] if @context[:request_url]
|
|
51
60
|
return "Background Job: #{@context[:job]&.class}" if @context[:job]
|
|
52
61
|
return "Sidekiq: #{@context[:job_class]}" if @context[:job_class]
|
|
@@ -114,10 +123,17 @@ module RailsErrorDashboard
|
|
|
114
123
|
def detect_platform
|
|
115
124
|
# Check if it's from a mobile request
|
|
116
125
|
user_agent = extract_user_agent
|
|
117
|
-
return Services::PlatformDetector.detect(user_agent) if @context[:request]
|
|
118
126
|
|
|
119
|
-
|
|
120
|
-
|
|
127
|
+
return "API" unless user_agent.present? && @context[:request]
|
|
128
|
+
|
|
129
|
+
# Only detect platform if we have a valid user agent
|
|
130
|
+
begin
|
|
131
|
+
Services::PlatformDetector.detect(user_agent)
|
|
132
|
+
rescue => e
|
|
133
|
+
# Fallback to API if platform detection fails
|
|
134
|
+
Rails.logger.debug("[RailsErrorDashboard] Platform detection failed: #{e.message}")
|
|
135
|
+
"API"
|
|
136
|
+
end
|
|
121
137
|
end
|
|
122
138
|
|
|
123
139
|
def extract_controller_name
|
|
@@ -161,8 +177,8 @@ module RailsErrorDashboard
|
|
|
161
177
|
end
|
|
162
178
|
|
|
163
179
|
def extract_session_id
|
|
164
|
-
#
|
|
165
|
-
return @context[:request]&.session&.id if @context[:request]&.session
|
|
180
|
+
# Session is only available in full Rails mode, not API-only
|
|
181
|
+
return @context[:request]&.session&.id if @context[:request]&.respond_to?(:session) && @context[:request]&.session
|
|
166
182
|
|
|
167
183
|
# From explicit context
|
|
168
184
|
return @context[:session_id] if @context[:session_id]
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_error_dashboard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.16
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anjan Jagirdar
|
|
@@ -312,6 +312,7 @@ files:
|
|
|
312
312
|
- app/views/rails_error_dashboard/errors/settings.html.erb
|
|
313
313
|
- app/views/rails_error_dashboard/errors/show.html.erb
|
|
314
314
|
- config/routes.rb
|
|
315
|
+
- db/development.sqlite3
|
|
315
316
|
- db/migrate/20251224000001_create_rails_error_dashboard_error_logs.rb
|
|
316
317
|
- db/migrate/20251224081522_add_better_tracking_to_error_logs.rb
|
|
317
318
|
- db/migrate/20251224101217_add_controller_action_to_error_logs.rb
|
|
@@ -378,7 +379,7 @@ metadata:
|
|
|
378
379
|
source_code_uri: https://github.com/AnjanJ/rails_error_dashboard
|
|
379
380
|
changelog_uri: https://github.com/AnjanJ/rails_error_dashboard/blob/main/CHANGELOG.md
|
|
380
381
|
post_install_message: "āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n
|
|
381
|
-
\ Rails Error Dashboard v0.1.
|
|
382
|
+
\ Rails Error Dashboard v0.1.16\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n\n\U0001F195
|
|
382
383
|
First time? Quick start:\n rails generate rails_error_dashboard:install\n rails
|
|
383
384
|
db:migrate\n # Add to config/routes.rb:\n mount RailsErrorDashboard::Engine
|
|
384
385
|
=> '/error_dashboard'\n\n\U0001F504 Upgrading from v0.1.x?\n rails db:migrate\n
|