rails_error_dashboard 0.1.1 → 0.1.3
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 +66 -21
- data/app/assets/stylesheets/rails_error_dashboard/_catppuccin_mocha.scss +107 -0
- data/app/assets/stylesheets/rails_error_dashboard/_components.scss +625 -0
- data/app/assets/stylesheets/rails_error_dashboard/_layout.scss +257 -0
- data/app/assets/stylesheets/rails_error_dashboard/_theme_variables.scss +203 -0
- data/app/assets/stylesheets/rails_error_dashboard/application.css +926 -15
- data/app/assets/stylesheets/rails_error_dashboard/application.css.map +7 -0
- data/app/assets/stylesheets/rails_error_dashboard/application.scss +61 -0
- data/app/controllers/rails_error_dashboard/errors_controller.rb +94 -1
- data/app/helpers/rails_error_dashboard/application_helper.rb +42 -4
- data/app/helpers/rails_error_dashboard/backtrace_helper.rb +91 -0
- data/app/helpers/rails_error_dashboard/overview_helper.rb +78 -0
- data/app/helpers/rails_error_dashboard/user_agent_helper.rb +118 -0
- data/app/models/rails_error_dashboard/error_comment.rb +27 -0
- data/app/models/rails_error_dashboard/error_log.rb +145 -0
- data/app/views/layouts/rails_error_dashboard.html.erb +796 -299
- data/app/views/layouts/rails_error_dashboard_old_backup.html.erb +383 -0
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +2 -0
- data/app/views/rails_error_dashboard/errors/_pattern_insights.html.erb +4 -4
- data/app/views/rails_error_dashboard/errors/_timeline.html.erb +167 -0
- data/app/views/rails_error_dashboard/errors/analytics.html.erb +138 -22
- data/app/views/rails_error_dashboard/errors/index.html.erb +83 -4
- data/app/views/rails_error_dashboard/errors/overview.html.erb +253 -0
- data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +29 -18
- data/app/views/rails_error_dashboard/errors/show.html.erb +353 -54
- data/config/routes.rb +7 -0
- data/db/migrate/20251226020000_add_workflow_fields_to_error_logs.rb +27 -0
- data/db/migrate/20251226020100_create_error_comments.rb +18 -0
- data/lib/generators/rails_error_dashboard/install/install_generator.rb +8 -2
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +21 -0
- data/lib/rails_error_dashboard/commands/batch_delete_errors.rb +1 -1
- data/lib/rails_error_dashboard/commands/batch_resolve_errors.rb +2 -2
- data/lib/rails_error_dashboard/commands/log_error.rb +47 -9
- data/lib/rails_error_dashboard/commands/resolve_error.rb +1 -1
- data/lib/rails_error_dashboard/configuration.rb +8 -0
- data/lib/rails_error_dashboard/error_reporter.rb +4 -4
- data/lib/rails_error_dashboard/logger.rb +105 -0
- data/lib/rails_error_dashboard/middleware/error_catcher.rb +2 -2
- data/lib/rails_error_dashboard/plugin.rb +3 -3
- data/lib/rails_error_dashboard/plugin_registry.rb +2 -2
- data/lib/rails_error_dashboard/plugins/jira_integration_plugin.rb +1 -1
- data/lib/rails_error_dashboard/plugins/metrics_plugin.rb +1 -1
- data/lib/rails_error_dashboard/queries/dashboard_stats.rb +109 -1
- data/lib/rails_error_dashboard/queries/errors_list.rb +61 -6
- data/lib/rails_error_dashboard/services/backtrace_parser.rb +113 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +2 -0
- metadata +18 -2
- data/lib/tasks/rails_error_dashboard_tasks.rake +0 -4
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Error Dashboard - Audio Intelli API</title>
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<%= csrf_meta_tags %>
|
|
7
|
+
<%= csp_meta_tag %>
|
|
8
|
+
|
|
9
|
+
<!-- Apply theme immediately to prevent flash of wrong theme -->
|
|
10
|
+
<script>
|
|
11
|
+
// This runs BEFORE body renders to prevent flash
|
|
12
|
+
(function() {
|
|
13
|
+
const savedTheme = localStorage.getItem('theme');
|
|
14
|
+
if (savedTheme === 'dark') {
|
|
15
|
+
// Add to html element so we can style body
|
|
16
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
17
|
+
}
|
|
18
|
+
})();
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<!-- Bootstrap CSS -->
|
|
22
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
23
|
+
<!-- Bootstrap Icons -->
|
|
24
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
|
25
|
+
<!-- Chart.js with date adapter -->
|
|
26
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
27
|
+
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
|
|
28
|
+
<script src="https://cdn.jsdelivr.net/npm/chartkick@5.0.1/dist/chartkick.min.js"></script>
|
|
29
|
+
|
|
30
|
+
<!-- Turbo for real-time updates -->
|
|
31
|
+
<script type="module">
|
|
32
|
+
import * as Turbo from 'https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.12/+esm'
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<!-- Rails Error Dashboard Styles - Catppuccin Mocha Theme -->
|
|
36
|
+
<style>
|
|
37
|
+
<%= File.read(RailsErrorDashboard::Engine.root.join("app/assets/stylesheets/rails_error_dashboard/application.css")) %>
|
|
38
|
+
</style>
|
|
39
|
+
</head>
|
|
40
|
+
|
|
41
|
+
<body>
|
|
42
|
+
<!-- Loading Indicator -->
|
|
43
|
+
<div id="loading-indicator"></div>
|
|
44
|
+
|
|
45
|
+
<!-- Top Navbar -->
|
|
46
|
+
<nav class="navbar navbar-dark">
|
|
47
|
+
<div class="container-fluid">
|
|
48
|
+
<div class="d-flex align-items-center">
|
|
49
|
+
<!-- Mobile menu toggle -->
|
|
50
|
+
<button class="btn btn-link text-white d-md-none me-2" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebarMenu" aria-controls="sidebarMenu">
|
|
51
|
+
<i class="bi bi-list fs-4"></i>
|
|
52
|
+
</button>
|
|
53
|
+
<a class="navbar-brand fw-bold" href="<%= root_path %>">
|
|
54
|
+
<i class="bi bi-bug-fill"></i>
|
|
55
|
+
Error Dashboard
|
|
56
|
+
</a>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="d-flex align-items-center gap-3">
|
|
59
|
+
<button class="theme-toggle" id="themeToggle" onclick="toggleTheme()">
|
|
60
|
+
<i class="bi bi-moon-fill" id="themeIcon"></i>
|
|
61
|
+
</button>
|
|
62
|
+
<div class="text-white d-none d-md-block">
|
|
63
|
+
<small><%= Rails.env.titleize %> Environment</small>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</nav>
|
|
68
|
+
|
|
69
|
+
<div class="container-fluid">
|
|
70
|
+
<div class="row">
|
|
71
|
+
<!-- Sidebar - Desktop -->
|
|
72
|
+
<nav class="col-md-2 d-none d-md-block sidebar" id="sidebarDesktop">
|
|
73
|
+
<div class="position-sticky pt-3">
|
|
74
|
+
<ul class="nav flex-column">
|
|
75
|
+
<li class="nav-item">
|
|
76
|
+
<%= link_to root_path, class: "nav-link #{request.path == root_path ? 'active' : ''}" do %>
|
|
77
|
+
<i class="bi bi-speedometer2"></i> Overview
|
|
78
|
+
<% end %>
|
|
79
|
+
</li>
|
|
80
|
+
<li class="nav-item">
|
|
81
|
+
<%= link_to errors_path, class: "nav-link #{request.path == errors_path ? 'active' : ''}" do %>
|
|
82
|
+
<i class="bi bi-list-ul"></i> All Errors
|
|
83
|
+
<% end %>
|
|
84
|
+
</li>
|
|
85
|
+
<li class="nav-item">
|
|
86
|
+
<%= link_to analytics_errors_path, class: "nav-link #{request.path == analytics_errors_path ? 'active' : ''}" do %>
|
|
87
|
+
<i class="bi bi-graph-up"></i> Analytics
|
|
88
|
+
<% end %>
|
|
89
|
+
</li>
|
|
90
|
+
<% if RailsErrorDashboard.configuration.enable_platform_comparison %>
|
|
91
|
+
<li class="nav-item">
|
|
92
|
+
<%= link_to platform_comparison_errors_path, class: "nav-link #{request.path == platform_comparison_errors_path ? 'active' : ''}" do %>
|
|
93
|
+
<i class="bi bi-phone"></i> Platform Health
|
|
94
|
+
<% end %>
|
|
95
|
+
</li>
|
|
96
|
+
<% end %>
|
|
97
|
+
<% if RailsErrorDashboard.configuration.enable_error_correlation %>
|
|
98
|
+
<li class="nav-item">
|
|
99
|
+
<%= link_to correlation_errors_path, class: "nav-link #{request.path == correlation_errors_path ? 'active' : ''}" do %>
|
|
100
|
+
<i class="bi bi-diagram-3"></i> Correlation
|
|
101
|
+
<% end %>
|
|
102
|
+
</li>
|
|
103
|
+
<% end %>
|
|
104
|
+
</ul>
|
|
105
|
+
|
|
106
|
+
<hr class="my-3">
|
|
107
|
+
|
|
108
|
+
<h6 class="sidebar-heading px-3 mt-4 mb-2 text-muted text-uppercase">
|
|
109
|
+
<small>Quick Filters</small>
|
|
110
|
+
</h6>
|
|
111
|
+
<ul class="nav flex-column">
|
|
112
|
+
<li class="nav-item">
|
|
113
|
+
<%= link_to errors_path(unresolved: true), class: "nav-link" do %>
|
|
114
|
+
<i class="bi bi-exclamation-circle text-danger"></i> Unresolved
|
|
115
|
+
<% end %>
|
|
116
|
+
</li>
|
|
117
|
+
<li class="nav-item">
|
|
118
|
+
<%= link_to errors_path(platform: 'iOS'), class: "nav-link" do %>
|
|
119
|
+
<i class="bi bi-phone"></i> iOS Errors
|
|
120
|
+
<% end %>
|
|
121
|
+
</li>
|
|
122
|
+
<li class="nav-item">
|
|
123
|
+
<%= link_to errors_path(platform: 'Android'), class: "nav-link" do %>
|
|
124
|
+
<i class="bi bi-phone"></i> Android Errors
|
|
125
|
+
<% end %>
|
|
126
|
+
</li>
|
|
127
|
+
</ul>
|
|
128
|
+
</div>
|
|
129
|
+
</nav>
|
|
130
|
+
|
|
131
|
+
<!-- Sidebar - Mobile (Offcanvas) -->
|
|
132
|
+
<div class="offcanvas offcanvas-start" tabindex="-1" id="sidebarMenu" aria-labelledby="sidebarMenuLabel">
|
|
133
|
+
<div class="offcanvas-header">
|
|
134
|
+
<h5 class="offcanvas-title" id="sidebarMenuLabel">
|
|
135
|
+
<i class="bi bi-bug-fill"></i> Error Dashboard
|
|
136
|
+
</h5>
|
|
137
|
+
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
|
138
|
+
</div>
|
|
139
|
+
<div class="offcanvas-body">
|
|
140
|
+
<ul class="nav flex-column">
|
|
141
|
+
<li class="nav-item">
|
|
142
|
+
<%= link_to root_path, class: "nav-link #{request.path == root_path ? 'active' : ''}", data: { bs_dismiss: "offcanvas" } do %>
|
|
143
|
+
<i class="bi bi-speedometer2"></i> Overview
|
|
144
|
+
<% end %>
|
|
145
|
+
</li>
|
|
146
|
+
<li class="nav-item">
|
|
147
|
+
<%= link_to errors_path, class: "nav-link #{request.path == errors_path ? 'active' : ''}", data: { bs_dismiss: "offcanvas" } do %>
|
|
148
|
+
<i class="bi bi-list-ul"></i> All Errors
|
|
149
|
+
<% end %>
|
|
150
|
+
</li>
|
|
151
|
+
<li class="nav-item">
|
|
152
|
+
<%= link_to analytics_errors_path, class: "nav-link #{request.path == analytics_errors_path ? 'active' : ''}", data: { bs_dismiss: "offcanvas" } do %>
|
|
153
|
+
<i class="bi bi-graph-up"></i> Analytics
|
|
154
|
+
<% end %>
|
|
155
|
+
</li>
|
|
156
|
+
<% if RailsErrorDashboard.configuration.enable_platform_comparison %>
|
|
157
|
+
<li class="nav-item">
|
|
158
|
+
<%= link_to platform_comparison_errors_path, class: "nav-link #{request.path == platform_comparison_errors_path ? 'active' : ''}", data: { bs_dismiss: "offcanvas" } do %>
|
|
159
|
+
<i class="bi bi-phone"></i> Platform Health
|
|
160
|
+
<% end %>
|
|
161
|
+
</li>
|
|
162
|
+
<% end %>
|
|
163
|
+
<% if RailsErrorDashboard.configuration.enable_error_correlation %>
|
|
164
|
+
<li class="nav-item">
|
|
165
|
+
<%= link_to correlation_errors_path, class: "nav-link #{request.path == correlation_errors_path ? 'active' : ''}", data: { bs_dismiss: "offcanvas" } do %>
|
|
166
|
+
<i class="bi bi-diagram-3"></i> Correlation
|
|
167
|
+
<% end %>
|
|
168
|
+
</li>
|
|
169
|
+
<% end %>
|
|
170
|
+
</ul>
|
|
171
|
+
|
|
172
|
+
<hr class="my-3">
|
|
173
|
+
|
|
174
|
+
<h6 class="sidebar-heading px-3 mt-4 mb-2 text-muted text-uppercase">
|
|
175
|
+
<small>Quick Filters</small>
|
|
176
|
+
</h6>
|
|
177
|
+
<ul class="nav flex-column">
|
|
178
|
+
<li class="nav-item">
|
|
179
|
+
<%= link_to errors_path(unresolved: true), class: "nav-link", data: { bs_dismiss: "offcanvas" } do %>
|
|
180
|
+
<i class="bi bi-exclamation-circle text-danger"></i> Unresolved
|
|
181
|
+
<% end %>
|
|
182
|
+
</li>
|
|
183
|
+
<li class="nav-item">
|
|
184
|
+
<%= link_to errors_path(platform: 'iOS'), class: "nav-link", data: { bs_dismiss: "offcanvas" } do %>
|
|
185
|
+
<i class="bi bi-phone"></i> iOS Errors
|
|
186
|
+
<% end %>
|
|
187
|
+
</li>
|
|
188
|
+
<li class="nav-item">
|
|
189
|
+
<%= link_to errors_path(platform: 'Android'), class: "nav-link", data: { bs_dismiss: "offcanvas" } do %>
|
|
190
|
+
<i class="bi bi-phone"></i> Android Errors
|
|
191
|
+
<% end %>
|
|
192
|
+
</li>
|
|
193
|
+
</ul>
|
|
194
|
+
|
|
195
|
+
<hr class="my-3">
|
|
196
|
+
|
|
197
|
+
<div class="px-3">
|
|
198
|
+
<small class="text-muted">Environment</small>
|
|
199
|
+
<div class="fw-bold"><%= Rails.env.titleize %></div>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<!-- Main content -->
|
|
205
|
+
<main class="col-md-10 ms-sm-auto px-md-4">
|
|
206
|
+
<%= yield %>
|
|
207
|
+
</main>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<!-- Bootstrap JS -->
|
|
212
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
213
|
+
|
|
214
|
+
<!-- Theme Toggle Script -->
|
|
215
|
+
<script>
|
|
216
|
+
// Catppuccin Mocha Chart Colors
|
|
217
|
+
function getCatppuccinChartConfig(isDark) {
|
|
218
|
+
if (!isDark) {
|
|
219
|
+
// Light theme - keep existing for now
|
|
220
|
+
return {
|
|
221
|
+
textColor: '#1F2937',
|
|
222
|
+
gridColor: 'rgba(0, 0, 0, 0.1)',
|
|
223
|
+
tooltipBg: 'rgba(0, 0, 0, 0.8)',
|
|
224
|
+
colors: {
|
|
225
|
+
red: '#EF4444',
|
|
226
|
+
green: '#10B981',
|
|
227
|
+
yellow: '#F59E0B',
|
|
228
|
+
blue: '#3B82F6',
|
|
229
|
+
purple: '#8B5CF6',
|
|
230
|
+
pink: '#EC4899',
|
|
231
|
+
teal: '#14B8A6',
|
|
232
|
+
orange: '#F97316',
|
|
233
|
+
sapphire: '#3B82F6'
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Dark theme - Catppuccin Mocha
|
|
239
|
+
return {
|
|
240
|
+
textColor: '#cdd6f4', // ctp-text
|
|
241
|
+
gridColor: 'rgba(88, 91, 112, 0.3)', // ctp-surface2 with opacity
|
|
242
|
+
tooltipBg: '#313244', // ctp-surface0
|
|
243
|
+
colors: {
|
|
244
|
+
red: '#f38ba8', // ctp-red
|
|
245
|
+
green: '#a6e3a1', // ctp-green
|
|
246
|
+
yellow: '#f9e2af', // ctp-yellow
|
|
247
|
+
blue: '#89b4fa', // ctp-blue
|
|
248
|
+
purple: '#cba6f7', // ctp-mauve
|
|
249
|
+
pink: '#f5c2e7', // ctp-pink
|
|
250
|
+
teal: '#94e2d5', // ctp-teal
|
|
251
|
+
orange: '#fab387', // ctp-peach
|
|
252
|
+
sapphire: '#74c7ec' // ctp-sapphire
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Get platform-specific color
|
|
258
|
+
function getPlatformColor(platform, isDark) {
|
|
259
|
+
const config = getCatppuccinChartConfig(isDark);
|
|
260
|
+
const platformMap = {
|
|
261
|
+
'ios': isDark ? config.textColor : '#000000',
|
|
262
|
+
'android': config.colors.green,
|
|
263
|
+
'web': config.colors.blue,
|
|
264
|
+
'api': config.colors.sapphire
|
|
265
|
+
};
|
|
266
|
+
return platformMap[platform.toLowerCase()] || config.colors.purple;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Update Chart.js defaults for dark/light theme
|
|
270
|
+
function updateChartTheme(isDark) {
|
|
271
|
+
if (typeof Chart !== 'undefined') {
|
|
272
|
+
const config = getCatppuccinChartConfig(isDark);
|
|
273
|
+
|
|
274
|
+
Chart.defaults.color = config.textColor;
|
|
275
|
+
Chart.defaults.borderColor = config.gridColor;
|
|
276
|
+
Chart.defaults.scale.grid.color = config.gridColor;
|
|
277
|
+
Chart.defaults.scale.ticks.color = config.textColor;
|
|
278
|
+
Chart.defaults.plugins.legend.labels.color = config.textColor;
|
|
279
|
+
Chart.defaults.plugins.tooltip.backgroundColor = config.tooltipBg;
|
|
280
|
+
Chart.defaults.plugins.tooltip.titleColor = config.textColor;
|
|
281
|
+
Chart.defaults.plugins.tooltip.bodyColor = config.textColor;
|
|
282
|
+
Chart.defaults.plugins.tooltip.borderColor = config.gridColor;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Load theme from localStorage on page load
|
|
287
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
288
|
+
console.log('📄 DOMContentLoaded - Loading theme from localStorage');
|
|
289
|
+
|
|
290
|
+
const savedTheme = localStorage.getItem('theme') || 'light';
|
|
291
|
+
const isDark = savedTheme === 'dark';
|
|
292
|
+
|
|
293
|
+
console.log('Saved theme:', savedTheme, '| isDark:', isDark);
|
|
294
|
+
|
|
295
|
+
if (isDark) {
|
|
296
|
+
document.body.classList.add('dark-mode');
|
|
297
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
298
|
+
console.log('✅ Applied dark theme (body.dark-mode + html[data-theme=dark])');
|
|
299
|
+
updateThemeIcon(true);
|
|
300
|
+
} else {
|
|
301
|
+
// Ensure light mode is clean
|
|
302
|
+
document.body.classList.remove('dark-mode');
|
|
303
|
+
document.documentElement.removeAttribute('data-theme');
|
|
304
|
+
console.log('✅ Applied light theme (removed classes)');
|
|
305
|
+
updateThemeIcon(false);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Update Chart.js theme
|
|
309
|
+
updateChartTheme(isDark);
|
|
310
|
+
console.log('📊 Chart.js theme updated');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
function toggleTheme() {
|
|
314
|
+
try {
|
|
315
|
+
console.log('🎨 Toggle theme clicked');
|
|
316
|
+
|
|
317
|
+
const body = document.body;
|
|
318
|
+
const isDark = body.classList.toggle('dark-mode');
|
|
319
|
+
|
|
320
|
+
console.log('Dark mode:', isDark);
|
|
321
|
+
|
|
322
|
+
// Sync with html data attribute
|
|
323
|
+
if (isDark) {
|
|
324
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
325
|
+
console.log('✅ Set data-theme=dark');
|
|
326
|
+
} else {
|
|
327
|
+
document.documentElement.removeAttribute('data-theme');
|
|
328
|
+
console.log('✅ Removed data-theme');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Save preference
|
|
332
|
+
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
|
333
|
+
console.log('💾 Saved to localStorage:', isDark ? 'dark' : 'light');
|
|
334
|
+
|
|
335
|
+
// Update icon
|
|
336
|
+
updateThemeIcon(isDark);
|
|
337
|
+
console.log('🌙 Updated icon');
|
|
338
|
+
|
|
339
|
+
// Update Chart.js theme
|
|
340
|
+
updateChartTheme(isDark);
|
|
341
|
+
console.log('📊 Updated Chart.js');
|
|
342
|
+
|
|
343
|
+
// Reload page to apply chart theme (Chart.js requires re-render)
|
|
344
|
+
console.log('🔄 Reloading page...');
|
|
345
|
+
setTimeout(() => location.reload(), 100);
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.error('❌ Error in toggleTheme:', error);
|
|
348
|
+
alert('Error toggling theme: ' + error.message);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function updateThemeIcon(isDark) {
|
|
353
|
+
const icon = document.getElementById('themeIcon');
|
|
354
|
+
if (isDark) {
|
|
355
|
+
icon.className = 'bi bi-sun-fill';
|
|
356
|
+
} else {
|
|
357
|
+
icon.className = 'bi bi-moon-fill';
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Loading indicator for form submissions and link clicks
|
|
362
|
+
const loadingIndicator = document.getElementById('loading-indicator');
|
|
363
|
+
|
|
364
|
+
// Show loading on form submit
|
|
365
|
+
document.addEventListener('submit', function() {
|
|
366
|
+
loadingIndicator.classList.add('active');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// Show loading on link clicks (except anchors)
|
|
370
|
+
document.addEventListener('click', function(e) {
|
|
371
|
+
const link = e.target.closest('a');
|
|
372
|
+
if (link && link.href && !link.href.startsWith('#') && !link.target) {
|
|
373
|
+
loadingIndicator.classList.add('active');
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// Hide loading when page loads
|
|
378
|
+
window.addEventListener('load', function() {
|
|
379
|
+
loadingIndicator.classList.remove('active');
|
|
380
|
+
});
|
|
381
|
+
</script>
|
|
382
|
+
</body>
|
|
383
|
+
</html>
|
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
<span class="badge badge-ios">iOS</span>
|
|
44
44
|
<% elsif error.platform == 'Android' %>
|
|
45
45
|
<span class="badge badge-android">Android</span>
|
|
46
|
+
<% elsif error.platform == 'Web' %>
|
|
47
|
+
<span class="badge badge-web">Web</span>
|
|
46
48
|
<% else %>
|
|
47
49
|
<span class="badge badge-api"><%= error.platform || 'API' %></span>
|
|
48
50
|
<% end %>
|
|
@@ -91,15 +91,15 @@
|
|
|
91
91
|
<% max_count = pattern_data[:hourly_distribution].values.max || 1 %>
|
|
92
92
|
<% intensity = max_count > 0 ? (count.to_f / max_count * 100).round : 0 %>
|
|
93
93
|
<% is_peak = pattern_data[:peak_hours].include?(hour) %>
|
|
94
|
-
<div class="text-center m-1" style="width: 45px;">
|
|
95
|
-
<div class="rounded p-2 <%= is_peak ? 'border border-danger border-2' : '' %>"
|
|
94
|
+
<div class="text-center m-1 heatmap-cell-wrapper" style="width: 45px;">
|
|
95
|
+
<div class="rounded p-2 heatmap-cell <%= is_peak ? 'border border-danger border-2' : '' %>"
|
|
96
96
|
style="background-color: rgba(220, 53, 69, <%= intensity / 100.0 %>); min-height: 50px; display: flex; align-items: center; justify-content: center;"
|
|
97
97
|
title="<%= hour %>:00 - <%= count %> errors">
|
|
98
|
-
<small class="fw-bold <%= intensity > 50 ? 'text-white' : 'text-dark' %>">
|
|
98
|
+
<small class="fw-bold heatmap-count <%= intensity > 50 ? 'text-white' : 'text-dark' %>">
|
|
99
99
|
<%= count %>
|
|
100
100
|
</small>
|
|
101
101
|
</div>
|
|
102
|
-
<small class="
|
|
102
|
+
<small class="heatmap-hour"><%= hour %></small>
|
|
103
103
|
</div>
|
|
104
104
|
<% end %>
|
|
105
105
|
</div>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<%# Timeline view for related errors leading up to this error %>
|
|
2
|
+
<% if @related_errors.any? %>
|
|
3
|
+
<div class="card">
|
|
4
|
+
<div class="card-header bg-white">
|
|
5
|
+
<h5 class="mb-0">
|
|
6
|
+
<i class="bi bi-clock-history"></i> Timeline
|
|
7
|
+
<small class="text-muted ms-2">Recent related errors</small>
|
|
8
|
+
</h5>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="card-body">
|
|
11
|
+
<div class="timeline">
|
|
12
|
+
<% @related_errors.each_with_index do |error, index| %>
|
|
13
|
+
<div class="timeline-item <%= index == @related_errors.length - 1 ? 'timeline-item-last' : '' %>">
|
|
14
|
+
<div class="timeline-marker <%= error.id == @error.id ? 'timeline-marker-current' : '' %>">
|
|
15
|
+
<% if error.id == @error.id %>
|
|
16
|
+
<i class="bi bi-exclamation-circle-fill text-danger"></i>
|
|
17
|
+
<% elsif error.resolved? %>
|
|
18
|
+
<i class="bi bi-check-circle-fill text-success"></i>
|
|
19
|
+
<% else %>
|
|
20
|
+
<i class="bi bi-circle-fill text-secondary"></i>
|
|
21
|
+
<% end %>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="timeline-content">
|
|
25
|
+
<div class="d-flex justify-content-between align-items-start mb-1">
|
|
26
|
+
<div class="flex-grow-1">
|
|
27
|
+
<% if error.id == @error.id %>
|
|
28
|
+
<strong class="text-danger">
|
|
29
|
+
<i class="bi bi-arrow-right-circle"></i> Current Error
|
|
30
|
+
</strong>
|
|
31
|
+
<% else %>
|
|
32
|
+
<%= link_to error_path(error), class: "text-decoration-none" do %>
|
|
33
|
+
<strong><%= error.error_type %></strong>
|
|
34
|
+
<% end %>
|
|
35
|
+
<% end %>
|
|
36
|
+
|
|
37
|
+
<% if error.platform.present? %>
|
|
38
|
+
<% if error.platform == 'iOS' %>
|
|
39
|
+
<span class="badge badge-ios ms-2">iOS</span>
|
|
40
|
+
<% elsif error.platform == 'Android' %>
|
|
41
|
+
<span class="badge badge-android ms-2">Android</span>
|
|
42
|
+
<% else %>
|
|
43
|
+
<span class="badge badge-api ms-2"><%= error.platform %></span>
|
|
44
|
+
<% end %>
|
|
45
|
+
<% end %>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<small class="text-muted">
|
|
49
|
+
<%= time_ago_in_words(error.occurred_at) %> ago
|
|
50
|
+
</small>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<% if error.message.present? %>
|
|
54
|
+
<p class="mb-1 text-muted small">
|
|
55
|
+
<%= truncate(error.message, length: 100) %>
|
|
56
|
+
</p>
|
|
57
|
+
<% end %>
|
|
58
|
+
|
|
59
|
+
<div class="d-flex align-items-center gap-2 small">
|
|
60
|
+
<% if error.user_id %>
|
|
61
|
+
<span class="text-muted">
|
|
62
|
+
<i class="bi bi-person"></i>
|
|
63
|
+
User: <%= error.user_id %>
|
|
64
|
+
</span>
|
|
65
|
+
<% end %>
|
|
66
|
+
|
|
67
|
+
<% if error.occurrence_count > 1 %>
|
|
68
|
+
<span class="text-muted">
|
|
69
|
+
<i class="bi bi-arrow-repeat"></i>
|
|
70
|
+
<%= error.occurrence_count %>x
|
|
71
|
+
</span>
|
|
72
|
+
<% end %>
|
|
73
|
+
|
|
74
|
+
<% if error.resolved? %>
|
|
75
|
+
<span class="badge bg-success">
|
|
76
|
+
<i class="bi bi-check-circle"></i> Resolved
|
|
77
|
+
</span>
|
|
78
|
+
<% end %>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
<% end %>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<style>
|
|
88
|
+
.timeline {
|
|
89
|
+
position: relative;
|
|
90
|
+
padding-left: 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.timeline-item {
|
|
94
|
+
position: relative;
|
|
95
|
+
padding-left: 40px;
|
|
96
|
+
padding-bottom: 30px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.timeline-item-last {
|
|
100
|
+
padding-bottom: 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.timeline-item::before {
|
|
104
|
+
content: '';
|
|
105
|
+
position: absolute;
|
|
106
|
+
left: 11px;
|
|
107
|
+
top: 30px;
|
|
108
|
+
bottom: 0;
|
|
109
|
+
width: 2px;
|
|
110
|
+
background: var(--bs-border-color);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.timeline-item-last::before {
|
|
114
|
+
display: none;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.timeline-marker {
|
|
118
|
+
position: absolute;
|
|
119
|
+
left: 0;
|
|
120
|
+
top: 0;
|
|
121
|
+
width: 24px;
|
|
122
|
+
height: 24px;
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
justify-content: center;
|
|
126
|
+
background: white;
|
|
127
|
+
border: 2px solid var(--bs-border-color);
|
|
128
|
+
border-radius: 50%;
|
|
129
|
+
z-index: 1;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.timeline-marker-current {
|
|
133
|
+
border-color: var(--bs-danger);
|
|
134
|
+
background: var(--bs-danger-bg-subtle);
|
|
135
|
+
box-shadow: 0 0 0 4px var(--bs-danger-bg-subtle);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.timeline-marker i {
|
|
139
|
+
font-size: 12px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.timeline-content {
|
|
143
|
+
background: var(--bs-body-bg);
|
|
144
|
+
padding: 12px;
|
|
145
|
+
border-radius: 8px;
|
|
146
|
+
border: 1px solid var(--bs-border-color);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.timeline-item:hover .timeline-content {
|
|
150
|
+
border-color: var(--bs-primary);
|
|
151
|
+
background: var(--bs-primary-bg-subtle);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* Dark mode support */
|
|
155
|
+
body.dark-mode .timeline-marker,
|
|
156
|
+
html[data-theme="dark"] body .timeline-marker {
|
|
157
|
+
background: var(--card-bg);
|
|
158
|
+
border-color: var(--border-color);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
body.dark-mode .timeline-marker-current,
|
|
162
|
+
html[data-theme="dark"] body .timeline-marker-current {
|
|
163
|
+
border-color: var(--bs-danger);
|
|
164
|
+
background: rgba(220, 53, 69, 0.2);
|
|
165
|
+
}
|
|
166
|
+
</style>
|
|
167
|
+
<% end %>
|