rails_error_dashboard 0.6.0 → 0.6.1
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 +37 -12
- data/app/controllers/rails_error_dashboard/errors_controller.rb +17 -0
- data/app/views/layouts/rails_error_dashboard.html.erb +31 -21
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +3 -3
- data/app/views/rails_error_dashboard/errors/_timeline.html.erb +3 -3
- data/app/views/rails_error_dashboard/errors/index.html.erb +64 -38
- data/app/views/rails_error_dashboard/errors/settings.html.erb +23 -1
- data/app/views/rails_error_dashboard/errors/show.html.erb +2 -2
- data/app/views/rails_error_dashboard/errors/swallowed_exceptions.html.erb +12 -12
- data/config/routes.rb +1 -0
- data/lib/rails_error_dashboard/services/error_broadcaster.rb +1 -1
- data/lib/rails_error_dashboard/services/system_health_snapshot.rb +1 -1
- data/lib/rails_error_dashboard/test_error.rb +7 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +1 -0
- 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: 8730984999d2ab508eeefe889bfd320e54cb9395c6baacbe87fef1a3aa20de7a
|
|
4
|
+
data.tar.gz: 6a301193d907d990418f6a74b83ee36a54caeac1b05dc57a75ea24213e9b7f7c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: af05b90ebe19d1b90adb82b7e8d3a4cd57faf695327c3a51c2d36ced221d1951265e4d88d77e4ddb843c465582fe14d6911141e3f081b2de76ca3037a4e637f0
|
|
7
|
+
data.tar.gz: add93453ac6ebee6a3f320fc336fe30c81a952f58fc30b3b8554c410071586da7d9c25da1d886c5976b4ade28cc6504c5b355509a29dcf5025aa8b502d1fff65
|
|
@@ -23,23 +23,27 @@ module RailsErrorDashboard
|
|
|
23
23
|
Rails.logger.error("Params: #{params.inspect}")
|
|
24
24
|
Rails.logger.error(exception.backtrace&.first(10)&.join("\n")) if exception.backtrace
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
render_dashboard_error(
|
|
27
|
+
icon: "bi-exclamation-triangle",
|
|
28
|
+
icon_style: "background: var(--status-warning-bg); color: var(--status-warning);",
|
|
29
|
+
title: "Something went wrong",
|
|
30
|
+
message: "The Error Dashboard encountered an issue displaying this page. Your application is unaffected.",
|
|
31
|
+
detail: exception.message,
|
|
32
|
+
status: :internal_server_error
|
|
33
|
+
)
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
# Handle record not found — return 404 instead of 500
|
|
36
37
|
rescue_from ActiveRecord::RecordNotFound do |exception|
|
|
37
38
|
Rails.logger.warn("[RailsErrorDashboard] Record not found: #{exception.message}")
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
|
|
40
|
+
render_dashboard_error(
|
|
41
|
+
icon: "bi-search",
|
|
42
|
+
title: "The requested error was not found",
|
|
43
|
+
message: "It may have been deleted or the ID is invalid.",
|
|
44
|
+
detail: exception.message,
|
|
45
|
+
status: :not_found
|
|
46
|
+
)
|
|
43
47
|
end
|
|
44
48
|
|
|
45
49
|
# Handle Pagy pagination errors — redirect to page 1
|
|
@@ -47,5 +51,26 @@ module RailsErrorDashboard
|
|
|
47
51
|
Rails.logger.warn("[RailsErrorDashboard] Pagination error: #{exception.message}")
|
|
48
52
|
redirect_to request.path, status: :moved_permanently
|
|
49
53
|
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def render_dashboard_error(icon:, title:, message:, detail: nil, icon_style: nil, status: :internal_server_error)
|
|
58
|
+
set_common_view_variables
|
|
59
|
+
error_html = <<~ERB
|
|
60
|
+
<div class="red-empty-state" style="margin-top: var(--space-6);">
|
|
61
|
+
<div class="red-empty-state-icon"#{icon_style ? " style=\"#{icon_style}\"" : ""}><i class="bi #{icon}"></i></div>
|
|
62
|
+
<div class="red-empty-state-title">#{ERB::Util.html_escape(title)}</div>
|
|
63
|
+
<div class="red-empty-state-message">#{ERB::Util.html_escape(message)}</div>
|
|
64
|
+
#{"<div style=\"font-size: 12px; color: var(--text-tertiary); margin-top: var(--space-2); font-family: var(--font-mono);\">" + ERB::Util.html_escape(detail) + "</div>" if detail}
|
|
65
|
+
<a href="#{errors_path}" class="red-empty-state-cta" style="margin-top: var(--space-4);"><i class="bi bi-arrow-left"></i> Back to errors</a>
|
|
66
|
+
</div>
|
|
67
|
+
ERB
|
|
68
|
+
render html: error_html.html_safe, status: status, layout: "rails_error_dashboard"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def set_common_view_variables
|
|
72
|
+
@applications = Application.ordered_by_name.pluck(:name, :id) rescue []
|
|
73
|
+
@default_credentials_warning = RailsErrorDashboard.configuration.default_credentials? rescue false
|
|
74
|
+
end
|
|
50
75
|
end
|
|
51
76
|
end
|
|
@@ -560,6 +560,23 @@ module RailsErrorDashboard
|
|
|
560
560
|
redirect_back fallback_location: errors_path
|
|
561
561
|
end
|
|
562
562
|
|
|
563
|
+
def test_error
|
|
564
|
+
exception = RailsErrorDashboard::TestError.new(
|
|
565
|
+
"[RED Test] This is a test error sent from the dashboard to verify " \
|
|
566
|
+
"that error capture and notification delivery are working correctly. " \
|
|
567
|
+
"It is safe to resolve or delete this error."
|
|
568
|
+
)
|
|
569
|
+
exception.set_backtrace(caller)
|
|
570
|
+
|
|
571
|
+
Commands::LogError.call(exception, { request: request, source: "dashboard.test_error" })
|
|
572
|
+
|
|
573
|
+
flash[:notice] = "Test error logged successfully. Check your notification channels (Slack, Discord, email, etc.) to confirm delivery."
|
|
574
|
+
redirect_to errors_path(**app_context_params)
|
|
575
|
+
rescue => e
|
|
576
|
+
flash[:alert] = "Failed to log test error: #{e.message}"
|
|
577
|
+
redirect_to settings_path(**app_context_params)
|
|
578
|
+
end
|
|
579
|
+
|
|
563
580
|
def settings
|
|
564
581
|
@config = RailsErrorDashboard.configuration
|
|
565
582
|
end
|
|
@@ -135,9 +135,9 @@
|
|
|
135
135
|
--surface-secondary: #313244;
|
|
136
136
|
--surface-tertiary: #45475a;
|
|
137
137
|
--surface-hover: #313244;
|
|
138
|
-
--text-primary: #
|
|
139
|
-
--text-secondary: #
|
|
140
|
-
--text-tertiary: #
|
|
138
|
+
--text-primary: #ffffff;
|
|
139
|
+
--text-secondary: #cdd6f4;
|
|
140
|
+
--text-tertiary: #a6adc8;
|
|
141
141
|
--border-primary: #313244;
|
|
142
142
|
--border-secondary: #45475a;
|
|
143
143
|
--accent: <%= ac[:dark] %>;
|
|
@@ -279,17 +279,21 @@ dd { color: var(--text-primary); }
|
|
|
279
279
|
.ps-3 { padding-left: var(--space-4); }
|
|
280
280
|
.pe-2 { padding-right: var(--space-2); }
|
|
281
281
|
|
|
282
|
-
/* Display */
|
|
283
|
-
.d-none { display: none; }
|
|
284
|
-
.d-block { display: block; }
|
|
285
|
-
.d-inline { display: inline; }
|
|
286
|
-
.d-inline-block { display: inline-block; }
|
|
287
|
-
@media (min-width: 576px) { .d-sm-inline { display: inline; } .d-sm-block { display: block; } }
|
|
288
|
-
@media (min-width: 768px) { .d-md-block { display: block; } .d-md-none { display: none; } .d-md-flex { display: flex; } .d-md-inline { display: inline; } .ms-sm-auto { margin-left: auto; } .px-md-4 { padding-left: var(--space-6); padding-right: var(--space-6); } }
|
|
282
|
+
/* Display (use !important to match Bootstrap utility behavior) */
|
|
283
|
+
.d-none { display: none !important; }
|
|
284
|
+
.d-block { display: block !important; }
|
|
285
|
+
.d-inline { display: inline !important; }
|
|
286
|
+
.d-inline-block { display: inline-block !important; }
|
|
287
|
+
@media (min-width: 576px) { .d-sm-inline { display: inline !important; } .d-sm-block { display: block !important; } }
|
|
288
|
+
@media (min-width: 768px) { .d-md-block { display: block !important; } .d-md-none { display: none !important; } .d-md-flex { display: flex !important; } .d-md-inline { display: inline !important; } .ms-sm-auto { margin-left: auto; } .px-md-4 { padding-left: var(--space-6); padding-right: var(--space-6); } }
|
|
289
289
|
|
|
290
290
|
/* Text */
|
|
291
|
-
.text-muted { color: var(--text-
|
|
291
|
+
.text-muted { color: var(--text-secondary); }
|
|
292
292
|
.text-secondary { color: var(--text-secondary); }
|
|
293
|
+
|
|
294
|
+
/* Bootstrap compat: table-light thead */
|
|
295
|
+
.table-light, thead.table-light { background: var(--surface-secondary); }
|
|
296
|
+
.table-light th { color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; }
|
|
293
297
|
.text-center { text-align: center; }
|
|
294
298
|
.text-end { text-align: right; }
|
|
295
299
|
.text-start { text-align: left; }
|
|
@@ -632,6 +636,10 @@ dd { color: var(--text-primary); }
|
|
|
632
636
|
.red-sidebar.collapsed { display: none; }
|
|
633
637
|
}
|
|
634
638
|
|
|
639
|
+
/* Error detail responsive grid */
|
|
640
|
+
.red-detail-grid { display: grid; grid-template-columns: 1fr 260px; gap: var(--space-4); }
|
|
641
|
+
@media (max-width: 1023.98px) { .red-detail-grid { grid-template-columns: 1fr; } }
|
|
642
|
+
|
|
635
643
|
/* Env badge */
|
|
636
644
|
.red-env-badge {
|
|
637
645
|
padding: 3px 10px; font-size: 11px; font-weight: 600;
|
|
@@ -884,7 +892,7 @@ details[open] summary { border-bottom: 1px solid var(--border-primary); }
|
|
|
884
892
|
<!-- Sidebar -->
|
|
885
893
|
<nav class="red-sidebar d-none d-md-flex" id="sidebar">
|
|
886
894
|
<!-- Logo -->
|
|
887
|
-
<
|
|
895
|
+
<a href="/" class="red-sidebar-logo" title="Back to <%= Rails.application.class.module_parent_name %>" style="text-decoration: none; color: inherit;">
|
|
888
896
|
<div class="red-sidebar-logo-icon">R</div>
|
|
889
897
|
<div>
|
|
890
898
|
<div style="font-size: 14px; font-weight: 700; color: var(--text-primary); letter-spacing: -0.01em;">RED</div>
|
|
@@ -893,7 +901,7 @@ details[open] summary { border-bottom: 1px solid var(--border-primary); }
|
|
|
893
901
|
end %>
|
|
894
902
|
<div style="font-size: 10px; color: var(--text-tertiary); margin-top: -2px;"><%= sidebar_app_name || Rails.application.class.module_parent_name %> · <%= Rails.env %></div>
|
|
895
903
|
</div>
|
|
896
|
-
</
|
|
904
|
+
</a>
|
|
897
905
|
|
|
898
906
|
<!-- Nav groups -->
|
|
899
907
|
<div style="flex: 1; overflow: auto; padding: var(--space-3) 0;">
|
|
@@ -1062,7 +1070,7 @@ details[open] summary { border-bottom: 1px solid var(--border-primary); }
|
|
|
1062
1070
|
<% if params[:application_id].present? %>
|
|
1063
1071
|
<%= @applications.find { |name, id| id.to_s == params[:application_id].to_s }&.first || 'Unknown' %>
|
|
1064
1072
|
<% else %>
|
|
1065
|
-
All Apps
|
|
1073
|
+
All Apps (<%= @applications.size %>)
|
|
1066
1074
|
<% end %>
|
|
1067
1075
|
</button>
|
|
1068
1076
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="appSwitcher">
|
|
@@ -1094,7 +1102,7 @@ details[open] summary { border-bottom: 1px solid var(--border-primary); }
|
|
|
1094
1102
|
</ul>
|
|
1095
1103
|
</div>
|
|
1096
1104
|
<% end %>
|
|
1097
|
-
<kbd>?</kbd>
|
|
1105
|
+
<button type="button" title="Keyboard shortcuts (?)" data-bs-toggle="modal" data-bs-target="#keyboardShortcutsModal" style="background: none; border: none; padding: 0; cursor: pointer; line-height: 1;"><kbd>?</kbd></button>
|
|
1098
1106
|
<button class="red-theme-toggle" id="themeToggle" title="Toggle theme">
|
|
1099
1107
|
<i class="bi bi-moon" id="themeIcon"></i>
|
|
1100
1108
|
</button>
|
|
@@ -1105,14 +1113,16 @@ details[open] summary { border-bottom: 1px solid var(--border-primary); }
|
|
|
1105
1113
|
<!-- Main content -->
|
|
1106
1114
|
<main style="flex: 1; padding: var(--space-6) var(--space-8); max-width: 1200px; width: 100%; margin: 0 auto;">
|
|
1107
1115
|
<% if @default_credentials_warning %>
|
|
1108
|
-
<div class="alert alert-warning" style="display: flex; align-items: center; gap: 10px; margin-top: var(--space-2); margin-bottom: var(--space-4); border-left: 4px solid var(--status-warning);">
|
|
1109
|
-
<i class="bi bi-exclamation-triangle-fill" style="font-size: 18px;"></i>
|
|
1110
|
-
<div>
|
|
1116
|
+
<div id="security-warning" class="alert alert-warning" style="display: flex; align-items: center; gap: 10px; margin-top: var(--space-2); margin-bottom: var(--space-4); border-left: 4px solid var(--status-warning);">
|
|
1117
|
+
<i class="bi bi-exclamation-triangle-fill" style="font-size: 18px; flex-shrink: 0;"></i>
|
|
1118
|
+
<div style="flex: 1;">
|
|
1111
1119
|
<strong>Security Warning:</strong> You are using default credentials (gandalf/youshallnotpass).
|
|
1112
1120
|
Set <code>ERROR_DASHBOARD_USER</code> and <code>ERROR_DASHBOARD_PASSWORD</code> environment variables,
|
|
1113
1121
|
or configure <code>authenticate_with</code> in your initializer.
|
|
1114
1122
|
</div>
|
|
1123
|
+
<button type="button" onclick="this.parentElement.style.display='none'; try { sessionStorage.setItem('red_dismiss_creds_warning','1') } catch(e) {}" style="background: none; border: none; cursor: pointer; color: var(--text-secondary); font-size: 18px; padding: 0 4px; flex-shrink: 0; line-height: 1;" title="Dismiss for this session">×</button>
|
|
1115
1124
|
</div>
|
|
1125
|
+
<script>try { if (sessionStorage.getItem('red_dismiss_creds_warning') === '1') { document.getElementById('security-warning').style.display = 'none'; } } catch(e) {}</script>
|
|
1116
1126
|
<% end %>
|
|
1117
1127
|
<%= yield %>
|
|
1118
1128
|
</main>
|
|
@@ -1124,7 +1134,7 @@ details[open] summary { border-bottom: 1px solid var(--border-primary); }
|
|
|
1124
1134
|
Built with <a href="https://github.com/AnjanJ/rails_error_dashboard" target="_blank"><strong>RED</strong> — Rails Error Dashboard</a>
|
|
1125
1135
|
</p>
|
|
1126
1136
|
<p style="font-size: 12px; color: var(--text-tertiary); margin: 0;">
|
|
1127
|
-
|
|
1137
|
+
v<%= RailsErrorDashboard::VERSION %> · Built with ❤️ by <a href="https://anjan.dev" target="_blank">Anjan Jagirdar</a>
|
|
1128
1138
|
</p>
|
|
1129
1139
|
</footer>
|
|
1130
1140
|
</div>
|
|
@@ -1133,10 +1143,10 @@ details[open] summary { border-bottom: 1px solid var(--border-primary); }
|
|
|
1133
1143
|
<!-- Mobile Sidebar (Offcanvas) -->
|
|
1134
1144
|
<div class="offcanvas offcanvas-start" tabindex="-1" id="sidebarMenu" aria-labelledby="sidebarMenuLabel">
|
|
1135
1145
|
<div class="offcanvas-header">
|
|
1136
|
-
<
|
|
1146
|
+
<a href="/" style="display: flex; align-items: center; gap: 10px; text-decoration: none; color: inherit;" title="Back to app home">
|
|
1137
1147
|
<div class="red-sidebar-logo-icon">R</div>
|
|
1138
1148
|
<strong>RED</strong>
|
|
1139
|
-
</
|
|
1149
|
+
</a>
|
|
1140
1150
|
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
|
1141
1151
|
</div>
|
|
1142
1152
|
<div class="offcanvas-body">
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
<span style="display: inline-block; padding: 1px 6px; font-size: 9px; font-weight: 600; border-radius: var(--radius-full); background: var(--status-success-bg); color: var(--status-success); margin-left: 4px; text-transform: uppercase;">NEW</span>
|
|
21
21
|
<% end %>
|
|
22
22
|
</div>
|
|
23
|
-
<div style="color: var(--text-tertiary); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
23
|
+
<div style="color: var(--text-tertiary); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="<%= error.message %>"><%= error.message %></div>
|
|
24
24
|
</div>
|
|
25
25
|
</div>
|
|
26
26
|
</td>
|
|
@@ -72,12 +72,12 @@
|
|
|
72
72
|
<%= local_time_ago(error.last_seen_at) %>
|
|
73
73
|
</td>
|
|
74
74
|
<% if local_assigns[:show_application] %>
|
|
75
|
-
<td style="padding: var(--space-3) var(--space-4); font-size: 12px;" onclick="window.location='<%= error_path(error, **app_context) %>'">
|
|
75
|
+
<td style="padding: var(--space-3) var(--space-4); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="<%= error.application&.name || 'Unknown' %>" onclick="window.location='<%= error_path(error, **app_context) %>'">
|
|
76
76
|
<span class="badge bg-info"><%= error.application&.name || 'Unknown' %></span>
|
|
77
77
|
</td>
|
|
78
78
|
<% end %>
|
|
79
79
|
<% if local_assigns[:show_platform] %>
|
|
80
|
-
<td style="padding: var(--space-3) var(--space-4); font-size: 12px;" onclick="window.location='<%= error_path(error, **app_context) %>'">
|
|
80
|
+
<td style="padding: var(--space-3) var(--space-4); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="<%= error.platform || 'API' %>" onclick="window.location='<%= error_path(error, **app_context) %>'">
|
|
81
81
|
<% if error.platform == 'iOS' %>
|
|
82
82
|
<span class="badge badge-ios"><i class="bi bi-apple"></i> iOS</span>
|
|
83
83
|
<% elsif error.platform == 'Android' %>
|
|
@@ -87,11 +87,11 @@
|
|
|
87
87
|
</div>
|
|
88
88
|
|
|
89
89
|
<% if error.resolved? && error.resolution_comment.present? %>
|
|
90
|
-
<div
|
|
91
|
-
<small
|
|
90
|
+
<div style="margin-top: var(--space-2); padding: var(--space-2) var(--space-3); background: var(--status-success-bg); border: 1px solid var(--status-success); border-radius: var(--radius-sm);">
|
|
91
|
+
<small style="color: var(--status-success); font-weight: 600;">
|
|
92
92
|
<i class="bi bi-journal-text"></i> Resolution Notes:
|
|
93
93
|
</small>
|
|
94
|
-
<div
|
|
94
|
+
<div style="font-size: 13px; color: var(--text-primary); margin-top: 4px;">
|
|
95
95
|
<%= auto_link_urls(truncate(error.resolution_comment, length: 200), error: error).html_safe %>
|
|
96
96
|
</div>
|
|
97
97
|
</div>
|
|
@@ -40,20 +40,6 @@
|
|
|
40
40
|
All time
|
|
41
41
|
<% end %>
|
|
42
42
|
</span>
|
|
43
|
-
<div id="batch-actions-inline" style="display: none; gap: 6px;">
|
|
44
|
-
<%= form_with url: batch_action_errors_path, method: :post, id: "batch-form", style: "display: flex; gap: 6px; align-items: center;" do |f| %>
|
|
45
|
-
<% if params[:application_id].present? %>
|
|
46
|
-
<%= hidden_field_tag :application_id, params[:application_id] %>
|
|
47
|
-
<% end %>
|
|
48
|
-
<span id="selected-count" style="font-size: 12px; font-weight: 600; color: var(--text-secondary);"></span>
|
|
49
|
-
<%= button_tag type: "submit", name: "action_type", value: "resolve", class: "btn btn-sm", style: "padding: 4px 12px; font-size: 12px;" do %>
|
|
50
|
-
Resolve
|
|
51
|
-
<% end %>
|
|
52
|
-
<%= button_tag type: "submit", name: "action_type", value: "delete", class: "btn btn-sm", style: "padding: 4px 12px; font-size: 12px; color: var(--status-critical);", data: { confirm: "Are you sure you want to delete the selected errors?" } do %>
|
|
53
|
-
Delete
|
|
54
|
-
<% end %>
|
|
55
|
-
<% end %>
|
|
56
|
-
</div>
|
|
57
43
|
</div>
|
|
58
44
|
|
|
59
45
|
<!-- Spike Detection Alert -->
|
|
@@ -126,7 +112,7 @@
|
|
|
126
112
|
</div>
|
|
127
113
|
|
|
128
114
|
<!-- Advanced filters -->
|
|
129
|
-
<div id="advanced-filters" style="display:
|
|
115
|
+
<div id="advanced-filters" style="display: none; gap: var(--space-3); flex-wrap: wrap; margin-bottom: var(--space-4); padding: var(--space-3) var(--space-4); background: var(--surface-primary); border-radius: var(--radius-md); border: 1px solid var(--border-primary);">
|
|
130
116
|
<% if @applications.size > 1 %>
|
|
131
117
|
<%= select_tag :application_id, options_for_select([['All Apps', '']] + @applications, params[:application_id]), class: "form-select", style: "width: auto; min-width: 120px;" %>
|
|
132
118
|
<% end %>
|
|
@@ -209,24 +195,44 @@
|
|
|
209
195
|
<div class="card" style="overflow: hidden;">
|
|
210
196
|
<% if @errors.any? %>
|
|
211
197
|
<div data-loading-target="content">
|
|
212
|
-
<
|
|
198
|
+
<div style="overflow-x: auto;">
|
|
199
|
+
<table class="table table-hover" style="margin-bottom: 0; width: 100%; table-layout: fixed;">
|
|
213
200
|
<thead>
|
|
214
|
-
<tr style="border-bottom: 1px solid var(--border-primary);">
|
|
215
|
-
<th style="width:
|
|
201
|
+
<tr id="thead-columns" style="border-bottom: 1px solid var(--border-primary);">
|
|
202
|
+
<th style="width: 36px; padding: var(--space-3) var(--space-4); text-align: center;">
|
|
216
203
|
<input type="checkbox" id="select-all" class="form-check-input" style="accent-color: var(--accent);">
|
|
217
204
|
</th>
|
|
218
205
|
<th style="padding: var(--space-3) var(--space-4); text-align: left;">Error</th>
|
|
219
|
-
<th style="padding: var(--space-3) var(--space-4); text-align: left; width:
|
|
220
|
-
<th style="padding: var(--space-3) var(--space-4); text-align: right; width:
|
|
221
|
-
<th style="padding: var(--space-3) var(--space-4); text-align: right; width:
|
|
222
|
-
<th style="padding: var(--space-3) var(--space-4); text-align: right; width:
|
|
206
|
+
<th style="padding: var(--space-3) var(--space-4); text-align: left; width: 100px;">Status</th>
|
|
207
|
+
<th style="padding: var(--space-3) var(--space-4); text-align: right; width: 70px;">Events</th>
|
|
208
|
+
<th style="padding: var(--space-3) var(--space-4); text-align: right; width: 55px;">Users</th>
|
|
209
|
+
<th style="padding: var(--space-3) var(--space-4); text-align: right; width: 85px;">Last seen</th>
|
|
223
210
|
<% if @applications.size > 1 && params[:application_id].blank? %>
|
|
224
|
-
<th style="padding: var(--space-3) var(--space-4); width:
|
|
211
|
+
<th style="padding: var(--space-3) var(--space-4); width: 100px;">App</th>
|
|
225
212
|
<% end %>
|
|
226
213
|
<% if @platforms.size > 1 %>
|
|
227
|
-
<th style="padding: var(--space-3) var(--space-4); width:
|
|
214
|
+
<th style="padding: var(--space-3) var(--space-4); width: 100px;">Platform</th>
|
|
228
215
|
<% end %>
|
|
229
216
|
</tr>
|
|
217
|
+
<tr id="thead-batch" style="display: none; border-bottom: 1px solid var(--border-primary); background: var(--surface-secondary);">
|
|
218
|
+
<th style="width: 40px; padding: var(--space-3) var(--space-4); text-align: center;">
|
|
219
|
+
<input type="checkbox" id="select-all-batch" class="form-check-input" style="accent-color: var(--accent);" checked>
|
|
220
|
+
</th>
|
|
221
|
+
<th colspan="99" style="padding: var(--space-3) var(--space-4);">
|
|
222
|
+
<%= form_with url: batch_action_errors_path, method: :post, id: "batch-form", style: "display: flex; gap: 8px; align-items: center;" do |f| %>
|
|
223
|
+
<% if params[:application_id].present? %>
|
|
224
|
+
<%= hidden_field_tag :application_id, params[:application_id] %>
|
|
225
|
+
<% end %>
|
|
226
|
+
<span id="selected-count" style="font-size: 13px; font-weight: 600; color: var(--text-primary);"></span>
|
|
227
|
+
<%= button_tag type: "submit", name: "action_type", value: "resolve", class: "btn btn-sm", style: "padding: 4px 12px; font-size: 12px;" do %>
|
|
228
|
+
<i class="bi bi-check-circle"></i> Resolve
|
|
229
|
+
<% end %>
|
|
230
|
+
<%= button_tag type: "submit", name: "action_type", value: "delete", class: "btn btn-sm", style: "padding: 4px 12px; font-size: 12px; color: var(--status-critical);", data: { confirm: "Are you sure you want to delete the selected errors?" } do %>
|
|
231
|
+
<i class="bi bi-trash"></i> Delete
|
|
232
|
+
<% end %>
|
|
233
|
+
<% end %>
|
|
234
|
+
</th>
|
|
235
|
+
</tr>
|
|
230
236
|
</thead>
|
|
231
237
|
<tbody id="error_list">
|
|
232
238
|
<% @errors.each do |error| %>
|
|
@@ -234,11 +240,13 @@
|
|
|
234
240
|
<% end %>
|
|
235
241
|
</tbody>
|
|
236
242
|
</table>
|
|
243
|
+
</div>
|
|
237
244
|
|
|
238
245
|
<!-- Pagination -->
|
|
239
246
|
<% if @pagy.pages > 1 %>
|
|
240
|
-
<div style="padding: var(--space-4); border-top: 1px solid var(--border-primary);">
|
|
241
|
-
|
|
247
|
+
<div style="padding: var(--space-4); border-top: 1px solid var(--border-primary); display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--space-3);">
|
|
248
|
+
<span style="font-size: 12px; color: var(--text-tertiary);">Showing <%= @pagy.from %>–<%= @pagy.to %> of <%= @pagy.count %> errors</span>
|
|
249
|
+
<div><%== @pagy.series_nav(:bootstrap) %></div>
|
|
242
250
|
</div>
|
|
243
251
|
<% end %>
|
|
244
252
|
</div>
|
|
@@ -271,7 +279,9 @@
|
|
|
271
279
|
<script>
|
|
272
280
|
document.addEventListener('DOMContentLoaded', function() {
|
|
273
281
|
var selectAllCheckbox = document.getElementById('select-all');
|
|
274
|
-
var
|
|
282
|
+
var selectAllBatchCheckbox = document.getElementById('select-all-batch');
|
|
283
|
+
var theadColumns = document.getElementById('thead-columns');
|
|
284
|
+
var theadBatch = document.getElementById('thead-batch');
|
|
275
285
|
var selectedCountSpan = document.getElementById('selected-count');
|
|
276
286
|
var batchForm = document.getElementById('batch-form');
|
|
277
287
|
|
|
@@ -282,31 +292,47 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
282
292
|
function updateBatchToolbar() {
|
|
283
293
|
var checkedBoxes = document.querySelectorAll('.error-checkbox:checked');
|
|
284
294
|
var count = checkedBoxes.length;
|
|
295
|
+
var boxes = getErrorCheckboxes();
|
|
296
|
+
var allChecked = boxes.length > 0 && Array.from(boxes).every(function(cb) { return cb.checked; });
|
|
297
|
+
var someChecked = Array.from(boxes).some(function(cb) { return cb.checked; });
|
|
298
|
+
|
|
285
299
|
if (count > 0) {
|
|
286
|
-
|
|
300
|
+
theadColumns.style.display = 'none';
|
|
301
|
+
theadBatch.style.display = '';
|
|
287
302
|
selectedCountSpan.textContent = count + ' selected';
|
|
303
|
+
selectAllBatchCheckbox.checked = allChecked;
|
|
304
|
+
selectAllBatchCheckbox.indeterminate = someChecked && !allChecked;
|
|
288
305
|
} else {
|
|
289
|
-
|
|
306
|
+
theadColumns.style.display = '';
|
|
307
|
+
theadBatch.style.display = 'none';
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (selectAllCheckbox) {
|
|
311
|
+
selectAllCheckbox.checked = allChecked;
|
|
312
|
+
selectAllCheckbox.indeterminate = someChecked && !allChecked;
|
|
290
313
|
}
|
|
291
314
|
}
|
|
292
315
|
|
|
316
|
+
function handleSelectAll(checked) {
|
|
317
|
+
getErrorCheckboxes().forEach(function(cb) { cb.checked = checked; });
|
|
318
|
+
updateBatchToolbar();
|
|
319
|
+
}
|
|
320
|
+
|
|
293
321
|
if (selectAllCheckbox) {
|
|
294
322
|
selectAllCheckbox.addEventListener('change', function() {
|
|
295
|
-
|
|
296
|
-
|
|
323
|
+
handleSelectAll(selectAllCheckbox.checked);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (selectAllBatchCheckbox) {
|
|
328
|
+
selectAllBatchCheckbox.addEventListener('change', function() {
|
|
329
|
+
handleSelectAll(selectAllBatchCheckbox.checked);
|
|
297
330
|
});
|
|
298
331
|
}
|
|
299
332
|
|
|
300
333
|
document.addEventListener('change', function(e) {
|
|
301
334
|
if (e.target.classList.contains('error-checkbox')) {
|
|
302
335
|
updateBatchToolbar();
|
|
303
|
-
var boxes = getErrorCheckboxes();
|
|
304
|
-
var allChecked = Array.from(boxes).every(function(cb) { return cb.checked; });
|
|
305
|
-
var someChecked = Array.from(boxes).some(function(cb) { return cb.checked; });
|
|
306
|
-
if (selectAllCheckbox) {
|
|
307
|
-
selectAllCheckbox.checked = allChecked;
|
|
308
|
-
selectAllCheckbox.indeterminate = someChecked && !allChecked;
|
|
309
|
-
}
|
|
310
336
|
}
|
|
311
337
|
});
|
|
312
338
|
|
|
@@ -169,7 +169,10 @@
|
|
|
169
169
|
%>
|
|
170
170
|
|
|
171
171
|
<div>
|
|
172
|
-
<
|
|
172
|
+
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-4);">
|
|
173
|
+
<h1 style="font-size: 20px; font-weight: 700; margin: 0;">Settings</h1>
|
|
174
|
+
<span style="font-family: var(--font-mono); font-size: 12px; font-weight: 500; color: var(--text-tertiary); padding: 2px 10px; border: 1px solid var(--border-primary); border-radius: var(--radius-full);">v<%= RailsErrorDashboard::VERSION %></span>
|
|
175
|
+
</div>
|
|
173
176
|
|
|
174
177
|
<p style="font-size: 13px; color: var(--text-tertiary); margin-bottom: var(--space-6);">
|
|
175
178
|
Read-only view of current configuration. Edit via <code>config/initializers/rails_error_dashboard.rb</code> or environment variables.
|
|
@@ -255,6 +258,25 @@
|
|
|
255
258
|
</div>
|
|
256
259
|
</div>
|
|
257
260
|
|
|
261
|
+
<!-- Test Error -->
|
|
262
|
+
<div class="card" style="margin-bottom: var(--space-4);">
|
|
263
|
+
<div style="padding: var(--space-4) var(--space-6); border-bottom: 1px solid var(--border-primary);">
|
|
264
|
+
<span style="font-size: 14px; font-weight: 600; color: var(--text-primary);"><i class="bi bi-send-check" style="margin-right: 6px;"></i> Test Notifications</span>
|
|
265
|
+
</div>
|
|
266
|
+
<div style="padding: var(--space-5) var(--space-6);">
|
|
267
|
+
<p style="font-size: 13px; color: var(--text-secondary); margin-bottom: var(--space-4);">
|
|
268
|
+
Send a test error through the full capture and notification pipeline. This creates a <code>RailsErrorDashboard::TestError</code> in the
|
|
269
|
+
error log and dispatches notifications to all configured channels (Slack, Discord, PagerDuty, email, webhooks).
|
|
270
|
+
The test error is clearly marked and safe to resolve or delete.
|
|
271
|
+
</p>
|
|
272
|
+
<%= form_tag test_error_errors_path(application_id: @current_application_id.presence), method: :post, style: "display: inline;" do %>
|
|
273
|
+
<button type="submit" class="btn btn-outline" onclick="return confirm('This will create a test error and send notifications to all configured channels. Continue?')">
|
|
274
|
+
<i class="bi bi-send" style="margin-right: 4px;"></i> Send Test Error
|
|
275
|
+
</button>
|
|
276
|
+
<% end %>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
|
|
258
280
|
<!-- Help Text -->
|
|
259
281
|
<div class="alert alert-light border">
|
|
260
282
|
<h6 class="alert-heading">
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
</div>
|
|
88
88
|
|
|
89
89
|
<!-- Main content: tabs + sidebar -->
|
|
90
|
-
<div
|
|
90
|
+
<div class="red-detail-grid">
|
|
91
91
|
<!-- Left: tabbed content -->
|
|
92
92
|
<div>
|
|
93
93
|
<!-- Tab bar -->
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
</div>
|
|
163
163
|
|
|
164
164
|
<!-- Right: sidebar -->
|
|
165
|
-
<div class="error-sidebar" style="display: flex; flex-direction: column; gap: var(--space-4); min-width: 0;">
|
|
165
|
+
<div class="error-sidebar" style="display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; overflow-wrap: break-word;">
|
|
166
166
|
<%= render "sidebar_metadata", error: @error, related_errors: @related_errors %>
|
|
167
167
|
|
|
168
168
|
<!-- Baseline Statistics -->
|
|
@@ -79,23 +79,23 @@
|
|
|
79
79
|
<div class="card-body p-0">
|
|
80
80
|
<div class="table-responsive">
|
|
81
81
|
<table class="table table-hover mb-0">
|
|
82
|
-
<thead
|
|
83
|
-
<tr>
|
|
84
|
-
<th>Exception Class</th>
|
|
85
|
-
<th width="250">Raise Location</th>
|
|
86
|
-
<th width="250">Rescue Location</th>
|
|
87
|
-
<th width="100">Raises</th>
|
|
88
|
-
<th width="100">Rescues</th>
|
|
89
|
-
<th width="100">Ratio</th>
|
|
90
|
-
<th width="140">Last Seen</th>
|
|
82
|
+
<thead>
|
|
83
|
+
<tr style="border-bottom: 1px solid var(--border-primary);">
|
|
84
|
+
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;">Exception Class</th>
|
|
85
|
+
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="250">Raise Location</th>
|
|
86
|
+
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="250">Rescue Location</th>
|
|
87
|
+
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="100">Raises</th>
|
|
88
|
+
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="100">Rescues</th>
|
|
89
|
+
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="100">Ratio</th>
|
|
90
|
+
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="140">Last Seen</th>
|
|
91
91
|
</tr>
|
|
92
92
|
</thead>
|
|
93
93
|
<tbody>
|
|
94
94
|
<% @entries.each do |entry| %>
|
|
95
95
|
<tr>
|
|
96
96
|
<td><code style="font-size: 0.85em;"><%= entry[:exception_class] %></code></td>
|
|
97
|
-
<td
|
|
98
|
-
<td
|
|
97
|
+
<td style="color: var(--text-secondary); font-size: 12px; word-break: break-all;"><%= entry[:raise_location] %></td>
|
|
98
|
+
<td style="color: var(--text-secondary); font-size: 12px; word-break: break-all;"><%= entry[:rescue_location] || "Unknown" %></td>
|
|
99
99
|
<td><span class="badge bg-info text-dark"><%= number_with_delimiter(entry[:raise_count]) %></span></td>
|
|
100
100
|
<td><span class="badge bg-warning text-dark"><%= number_with_delimiter(entry[:rescue_count]) %></span></td>
|
|
101
101
|
<td>
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
<%= ratio_pct %>%
|
|
105
105
|
</span>
|
|
106
106
|
</td>
|
|
107
|
-
<td><%= local_time_ago(entry[:last_seen]) %></td>
|
|
107
|
+
<td style="color: var(--text-secondary); font-size: 12px;"><%= local_time_ago(entry[:last_seen]) %></td>
|
|
108
108
|
</tr>
|
|
109
109
|
<% end %>
|
|
110
110
|
</tbody>
|
data/config/routes.rb
CHANGED
|
@@ -113,7 +113,7 @@ module RailsErrorDashboard
|
|
|
113
113
|
server.pubsub
|
|
114
114
|
@broadcast_unavailable_until = nil
|
|
115
115
|
true
|
|
116
|
-
rescue => e
|
|
116
|
+
rescue LoadError, StandardError => e
|
|
117
117
|
@broadcast_unavailable_until = Time.current + 60
|
|
118
118
|
RailsErrorDashboard::Logger.debug("[RailsErrorDashboard] Broadcast not available (pausing 60s): #{e.message}")
|
|
119
119
|
false
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require "rails_error_dashboard/version"
|
|
2
2
|
require "rails_error_dashboard/engine"
|
|
3
3
|
require "rails_error_dashboard/configuration_error"
|
|
4
|
+
require "rails_error_dashboard/test_error"
|
|
4
5
|
require "rails_error_dashboard/configuration"
|
|
5
6
|
require "rails_error_dashboard/logger"
|
|
6
7
|
require "rails_error_dashboard/manual_error_reporter"
|
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.6.
|
|
4
|
+
version: 0.6.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anjan Jagirdar
|
|
@@ -480,6 +480,7 @@ files:
|
|
|
480
480
|
- lib/rails_error_dashboard/subscribers/breadcrumb_subscriber.rb
|
|
481
481
|
- lib/rails_error_dashboard/subscribers/issue_tracker_subscriber.rb
|
|
482
482
|
- lib/rails_error_dashboard/subscribers/rack_attack_subscriber.rb
|
|
483
|
+
- lib/rails_error_dashboard/test_error.rb
|
|
483
484
|
- lib/rails_error_dashboard/value_objects/error_context.rb
|
|
484
485
|
- lib/rails_error_dashboard/version.rb
|
|
485
486
|
- lib/tasks/error_dashboard.rake
|
|
@@ -496,7 +497,7 @@ metadata:
|
|
|
496
497
|
funding_uri: https://github.com/sponsors/AnjanJ
|
|
497
498
|
post_install_message: |
|
|
498
499
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
499
|
-
RED (Rails Error Dashboard) v0.6.
|
|
500
|
+
RED (Rails Error Dashboard) v0.6.1
|
|
500
501
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
501
502
|
|
|
502
503
|
First install:
|