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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +66 -21
  3. data/app/assets/stylesheets/rails_error_dashboard/_catppuccin_mocha.scss +107 -0
  4. data/app/assets/stylesheets/rails_error_dashboard/_components.scss +625 -0
  5. data/app/assets/stylesheets/rails_error_dashboard/_layout.scss +257 -0
  6. data/app/assets/stylesheets/rails_error_dashboard/_theme_variables.scss +203 -0
  7. data/app/assets/stylesheets/rails_error_dashboard/application.css +926 -15
  8. data/app/assets/stylesheets/rails_error_dashboard/application.css.map +7 -0
  9. data/app/assets/stylesheets/rails_error_dashboard/application.scss +61 -0
  10. data/app/controllers/rails_error_dashboard/errors_controller.rb +94 -1
  11. data/app/helpers/rails_error_dashboard/application_helper.rb +42 -4
  12. data/app/helpers/rails_error_dashboard/backtrace_helper.rb +91 -0
  13. data/app/helpers/rails_error_dashboard/overview_helper.rb +78 -0
  14. data/app/helpers/rails_error_dashboard/user_agent_helper.rb +118 -0
  15. data/app/models/rails_error_dashboard/error_comment.rb +27 -0
  16. data/app/models/rails_error_dashboard/error_log.rb +145 -0
  17. data/app/views/layouts/rails_error_dashboard.html.erb +796 -299
  18. data/app/views/layouts/rails_error_dashboard_old_backup.html.erb +383 -0
  19. data/app/views/rails_error_dashboard/errors/_error_row.html.erb +2 -0
  20. data/app/views/rails_error_dashboard/errors/_pattern_insights.html.erb +4 -4
  21. data/app/views/rails_error_dashboard/errors/_timeline.html.erb +167 -0
  22. data/app/views/rails_error_dashboard/errors/analytics.html.erb +138 -22
  23. data/app/views/rails_error_dashboard/errors/index.html.erb +83 -4
  24. data/app/views/rails_error_dashboard/errors/overview.html.erb +253 -0
  25. data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +29 -18
  26. data/app/views/rails_error_dashboard/errors/show.html.erb +353 -54
  27. data/config/routes.rb +7 -0
  28. data/db/migrate/20251226020000_add_workflow_fields_to_error_logs.rb +27 -0
  29. data/db/migrate/20251226020100_create_error_comments.rb +18 -0
  30. data/lib/generators/rails_error_dashboard/install/install_generator.rb +8 -2
  31. data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +21 -0
  32. data/lib/rails_error_dashboard/commands/batch_delete_errors.rb +1 -1
  33. data/lib/rails_error_dashboard/commands/batch_resolve_errors.rb +2 -2
  34. data/lib/rails_error_dashboard/commands/log_error.rb +47 -9
  35. data/lib/rails_error_dashboard/commands/resolve_error.rb +1 -1
  36. data/lib/rails_error_dashboard/configuration.rb +8 -0
  37. data/lib/rails_error_dashboard/error_reporter.rb +4 -4
  38. data/lib/rails_error_dashboard/logger.rb +105 -0
  39. data/lib/rails_error_dashboard/middleware/error_catcher.rb +2 -2
  40. data/lib/rails_error_dashboard/plugin.rb +3 -3
  41. data/lib/rails_error_dashboard/plugin_registry.rb +2 -2
  42. data/lib/rails_error_dashboard/plugins/jira_integration_plugin.rb +1 -1
  43. data/lib/rails_error_dashboard/plugins/metrics_plugin.rb +1 -1
  44. data/lib/rails_error_dashboard/queries/dashboard_stats.rb +109 -1
  45. data/lib/rails_error_dashboard/queries/errors_list.rb +61 -6
  46. data/lib/rails_error_dashboard/services/backtrace_parser.rb +113 -0
  47. data/lib/rails_error_dashboard/version.rb +1 -1
  48. data/lib/rails_error_dashboard.rb +2 -0
  49. metadata +18 -2
  50. data/lib/tasks/rails_error_dashboard_tasks.rake +0 -4
@@ -1,351 +1,750 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
- <title>Error Dashboard - Audio Intelli API</title>
4
+ <title>Error Dashboard</title>
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1">
6
6
  <%= csrf_meta_tags %>
7
7
  <%= csp_meta_tag %>
8
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
9
  <!-- Bootstrap CSS -->
22
10
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
23
11
  <!-- Bootstrap Icons -->
24
12
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
25
- <!-- Chart.js with date adapter -->
13
+
14
+ <!-- Chart.js -->
26
15
  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
27
16
  <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
28
17
  <script src="https://cdn.jsdelivr.net/npm/chartkick@5.0.1/dist/chartkick.min.js"></script>
29
18
 
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
-
19
+ <!-- Catppuccin Mocha Theme - Pure CSS -->
35
20
  <style>
21
+ /* Catppuccin Mocha Color Palette */
36
22
  :root {
37
- --primary-color: #8B5CF6;
38
- --success-color: #10B981;
39
- --danger-color: #EF4444;
40
- --warning-color: #F59E0B;
41
- --info-color: #3B82F6;
42
- }
43
-
44
- /* Light mode (default) */
23
+ --ctp-rosewater: #f5e0dc;
24
+ --ctp-flamingo: #f2cdcd;
25
+ --ctp-pink: #f5c2e7;
26
+ --ctp-mauve: #cba6f7;
27
+ --ctp-red: #f38ba8;
28
+ --ctp-maroon: #eba0ac;
29
+ --ctp-peach: #fab387;
30
+ --ctp-yellow: #f9e2af;
31
+ --ctp-green: #a6e3a1;
32
+ --ctp-teal: #94e2d5;
33
+ --ctp-sky: #89dceb;
34
+ --ctp-sapphire: #74c7ec;
35
+ --ctp-blue: #89b4fa;
36
+ --ctp-lavender: #b4befe;
37
+ --ctp-text: #cdd6f4;
38
+ --ctp-subtext1: #bac2de;
39
+ --ctp-subtext0: #a6adc8;
40
+ --ctp-overlay2: #9399b2;
41
+ --ctp-overlay1: #7f849c;
42
+ --ctp-overlay0: #6c7086;
43
+ --ctp-surface2: #585b70;
44
+ --ctp-surface1: #45475a;
45
+ --ctp-surface0: #313244;
46
+ --ctp-base: #1e1e2e;
47
+ --ctp-mantle: #181825;
48
+ --ctp-crust: #11111b;
49
+ }
50
+
51
+ /* Light Theme (Default) */
45
52
  body {
46
- --bg-color: #F3F4F6;
47
- --text-color: #1F2937;
48
- --card-bg: #FFFFFF;
49
- --sidebar-bg: #FFFFFF;
50
- --sidebar-hover: #F9FAFB;
51
- --border-color: #E5E7EB;
52
- --table-hover: #F9FAFB;
53
- --nav-active-bg: #EDE9FE;
54
- background-color: var(--bg-color);
55
- color: var(--text-color);
56
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
57
- transition: background-color 0.3s ease, color 0.3s ease;
58
- }
59
-
60
- /* Dark mode - support both class and data attribute for immediate loading */
61
- body.dark-mode,
62
- html[data-theme="dark"] body {
63
- --bg-color: #1A1B26;
64
- --text-color: #E4E5E9;
65
- --card-bg: #24283B;
66
- --sidebar-bg: #1F2130;
67
- --sidebar-hover: #2A2D3E;
68
- --border-color: #414868;
69
- --table-hover: #2A2D3E;
70
- --nav-active-bg: #2A2D3E;
53
+ background-color: #f3f4f6;
54
+ color: #1f2937;
55
+ transition: background-color 0.3s, color 0.3s;
71
56
  }
72
57
 
73
- .card {
74
- background-color: var(--card-bg);
75
- color: var(--text-color);
76
- border-color: var(--border-color);
58
+ /* Dark Theme */
59
+ body.dark-mode {
60
+ background-color: var(--ctp-base);
61
+ color: var(--ctp-text);
77
62
  }
78
63
 
79
- .card-header {
80
- background-color: var(--card-bg) !important;
81
- color: var(--text-color);
82
- border-color: var(--border-color);
64
+ /* Navbar */
65
+ .navbar {
66
+ background: linear-gradient(135deg, #8B5CF6, #6D28D9) !important;
67
+ color: white !important;
68
+ }
69
+ .navbar * {
70
+ color: white !important;
71
+ }
72
+ .navbar-brand {
73
+ font-weight: bold;
83
74
  }
84
75
 
85
- .navbar {
86
- background: linear-gradient(135deg, var(--primary-color), #6D28D9);
87
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
76
+ /* Theme Toggle Button */
77
+ .theme-toggle {
78
+ cursor: pointer;
79
+ padding: 0.5rem 1rem;
80
+ border-radius: 0.5rem;
81
+ background-color: rgba(255, 255, 255, 0.1);
82
+ color: white;
83
+ border: none;
84
+ transition: background-color 0.2s;
85
+ }
86
+ .theme-toggle:hover {
87
+ background-color: rgba(255, 255, 255, 0.2);
88
88
  }
89
89
 
90
+ /* Sidebar */
90
91
  .sidebar {
91
- background: var(--sidebar-bg);
92
+ background: white;
92
93
  min-height: calc(100vh - 56px);
93
- box-shadow: 2px 0 4px rgba(0,0,0,0.05);
94
+ box-shadow: 2px 0 4px rgba(0, 0, 0, 0.05);
95
+ }
96
+ body.dark-mode .sidebar {
97
+ background: var(--ctp-mantle);
94
98
  }
95
-
96
99
  .sidebar .nav-link {
97
- color: var(--text-color);
100
+ color: #1f2937;
98
101
  padding: 0.75rem 1.5rem;
99
102
  border-left: 3px solid transparent;
100
103
  transition: all 0.2s;
101
104
  }
102
-
105
+ body.dark-mode .sidebar .nav-link {
106
+ color: var(--ctp-text);
107
+ }
103
108
  .sidebar .nav-link:hover {
104
- background-color: var(--sidebar-hover);
105
- color: var(--primary-color);
106
- border-left-color: var(--primary-color);
109
+ background-color: #f3f4f6;
110
+ color: #8B5CF6;
111
+ border-left-color: #8B5CF6;
112
+ }
113
+ body.dark-mode .sidebar .nav-link:hover {
114
+ background-color: var(--ctp-surface0);
115
+ color: var(--ctp-mauve);
116
+ border-left-color: var(--ctp-mauve);
107
117
  }
108
-
109
118
  .sidebar .nav-link.active {
110
- background-color: var(--nav-active-bg);
111
- color: var(--primary-color);
112
- border-left-color: var(--primary-color);
119
+ background-color: #f3f4f6;
120
+ color: #8B5CF6;
121
+ border-left-color: #8B5CF6;
113
122
  font-weight: 600;
114
123
  }
124
+ body.dark-mode .sidebar .nav-link.active {
125
+ background-color: var(--ctp-surface0);
126
+ color: var(--ctp-mauve);
127
+ border-left-color: var(--ctp-mauve);
128
+ }
115
129
 
130
+ /* Cards */
131
+ .card {
132
+ background: white;
133
+ border: 1px solid #e5e7eb;
134
+ transition: background-color 0.3s, border-color 0.3s;
135
+ }
136
+ body.dark-mode .card {
137
+ background: var(--ctp-surface0);
138
+ border-color: var(--ctp-surface2);
139
+ color: var(--ctp-text);
140
+ }
141
+ body.dark-mode .card-header {
142
+ background-color: var(--ctp-surface1);
143
+ border-color: var(--ctp-surface2);
144
+ color: var(--ctp-text);
145
+ }
146
+
147
+ /* Tables */
148
+ body.dark-mode .table {
149
+ color: var(--ctp-text);
150
+ }
151
+ body.dark-mode .table-hover tbody tr:hover {
152
+ background-color: var(--ctp-surface1);
153
+ }
154
+
155
+ /* Badges - Platform Colors */
156
+ .badge-ios {
157
+ background-color: #000;
158
+ color: white;
159
+ }
160
+ body.dark-mode .badge-ios {
161
+ background-color: var(--ctp-overlay0);
162
+ color: var(--ctp-text);
163
+ border: 1px solid var(--ctp-surface2);
164
+ }
165
+ .badge-android {
166
+ background-color: #3DDC84;
167
+ color: white;
168
+ }
169
+ body.dark-mode .badge-android {
170
+ background-color: rgba(166, 227, 161, 0.2);
171
+ color: var(--ctp-green);
172
+ border: 1px solid var(--ctp-green);
173
+ }
174
+ .badge-web {
175
+ background-color: #3B82F6;
176
+ color: white;
177
+ }
178
+ body.dark-mode .badge-web {
179
+ background-color: rgba(137, 180, 250, 0.2);
180
+ color: var(--ctp-blue);
181
+ border: 1px solid var(--ctp-blue);
182
+ }
183
+ .badge-api {
184
+ background-color: #8B5CF6;
185
+ color: white;
186
+ }
187
+ body.dark-mode .badge-api {
188
+ background-color: rgba(116, 199, 236, 0.2);
189
+ color: var(--ctp-sapphire);
190
+ border: 1px solid var(--ctp-sapphire);
191
+ }
192
+
193
+ /* Forms */
194
+ body.dark-mode .form-control,
195
+ body.dark-mode .form-select {
196
+ background-color: var(--ctp-surface0);
197
+ border-color: var(--ctp-surface2);
198
+ color: var(--ctp-text);
199
+ }
200
+ body.dark-mode .form-control:focus,
201
+ body.dark-mode .form-select:focus {
202
+ background-color: var(--ctp-surface1);
203
+ border-color: var(--ctp-mauve);
204
+ color: var(--ctp-text);
205
+ }
206
+
207
+ /* Code Blocks */
208
+ .code-block,
209
+ pre,
210
+ code {
211
+ background-color: #f9fafb;
212
+ color: #1f2937;
213
+ }
214
+ body.dark-mode .code-block,
215
+ body.dark-mode pre,
216
+ body.dark-mode code {
217
+ background-color: var(--ctp-mantle) !important;
218
+ color: var(--ctp-text) !important;
219
+ }
220
+
221
+ /* Alerts */
222
+ body.dark-mode .alert {
223
+ background-color: var(--ctp-surface0);
224
+ border-color: var(--ctp-surface2);
225
+ color: var(--ctp-text);
226
+ }
227
+
228
+ /* Text colors */
229
+ body.dark-mode .text-muted {
230
+ color: var(--ctp-subtext0) !important;
231
+ }
232
+ body.dark-mode .text-primary {
233
+ color: var(--ctp-mauve) !important;
234
+ }
235
+ body.dark-mode .text-danger {
236
+ color: var(--ctp-red) !important;
237
+ }
238
+ body.dark-mode .text-success {
239
+ color: var(--ctp-green) !important;
240
+ }
241
+ body.dark-mode .text-warning {
242
+ color: var(--ctp-peach) !important;
243
+ }
244
+
245
+ /* Override Bootstrap bg-white and bg-light */
246
+ body.dark-mode .bg-white {
247
+ background-color: var(--ctp-surface0) !important;
248
+ }
249
+ body.dark-mode .bg-light {
250
+ background-color: var(--ctp-surface1) !important;
251
+ color: var(--ctp-text) !important;
252
+ }
253
+
254
+ /* Borders */
255
+ body.dark-mode .border {
256
+ border-color: var(--ctp-surface2) !important;
257
+ }
258
+ body.dark-mode .rounded {
259
+ border-color: var(--ctp-surface2) !important;
260
+ }
261
+
262
+ /* Quick Filters Sidebar Section */
263
+ .sidebar h6 {
264
+ font-size: 0.75rem;
265
+ text-transform: uppercase;
266
+ letter-spacing: 0.05em;
267
+ padding: 0.75rem 1.5rem;
268
+ margin: 0;
269
+ color: #6B7280;
270
+ }
271
+ body.dark-mode .sidebar h6 {
272
+ color: var(--ctp-subtext0);
273
+ }
274
+
275
+ /* Stat Cards */
116
276
  .stat-card {
117
277
  border-radius: 0.75rem;
118
- border: none;
119
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
120
278
  transition: transform 0.2s, box-shadow 0.2s;
121
279
  }
122
-
123
280
  .stat-card:hover {
124
281
  transform: translateY(-2px);
125
- box-shadow: 0 4px 6px rgba(0,0,0,0.1);
282
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
126
283
  }
127
284
 
128
- .stat-value {
129
- font-size: 2rem;
130
- font-weight: 700;
285
+ /* Badges for severity */
286
+ .badge.bg-danger {
287
+ background-color: #EF4444 !important;
288
+ }
289
+ .badge.bg-warning {
290
+ background-color: #F59E0B !important;
291
+ }
292
+ .badge.bg-info {
293
+ background-color: #3B82F6 !important;
294
+ }
295
+ .badge.bg-secondary {
296
+ background-color: #6B7280 !important;
297
+ }
298
+ .badge.bg-success {
299
+ background-color: #10B981 !important;
131
300
  }
132
301
 
133
- .stat-label {
134
- color: #6B7280;
135
- font-size: 0.875rem;
136
- text-transform: uppercase;
137
- letter-spacing: 0.05em;
302
+ body.dark-mode .badge.bg-danger {
303
+ background-color: var(--ctp-red) !important;
304
+ color: var(--ctp-base) !important;
305
+ }
306
+ body.dark-mode .badge.bg-warning {
307
+ background-color: var(--ctp-peach) !important;
308
+ color: var(--ctp-base) !important;
309
+ }
310
+ body.dark-mode .badge.bg-info {
311
+ background-color: var(--ctp-blue) !important;
312
+ color: var(--ctp-base) !important;
313
+ }
314
+ body.dark-mode .badge.bg-secondary {
315
+ background-color: var(--ctp-overlay1) !important;
316
+ }
317
+ body.dark-mode .badge.bg-success {
318
+ background-color: var(--ctp-green) !important;
319
+ color: var(--ctp-base) !important;
138
320
  }
139
321
 
140
- .badge-ios {
141
- background-color: #000000;
322
+ /* Links */
323
+ a {
324
+ color: #8B5CF6;
325
+ text-decoration: none;
326
+ }
327
+ a:hover {
328
+ color: #6D28D9;
329
+ text-decoration: underline;
330
+ }
331
+ body.dark-mode a {
332
+ color: var(--ctp-mauve);
333
+ }
334
+ body.dark-mode a:hover {
335
+ color: var(--ctp-pink);
142
336
  }
143
337
 
144
- .badge-android {
145
- background-color: #3DDC84;
146
- color: #000;
338
+ /* Buttons */
339
+ body.dark-mode .btn-primary {
340
+ background-color: var(--ctp-mauve);
341
+ border-color: var(--ctp-mauve);
342
+ color: var(--ctp-base);
343
+ }
344
+ body.dark-mode .btn-primary:hover {
345
+ background-color: var(--ctp-pink);
346
+ border-color: var(--ctp-pink);
347
+ }
348
+ body.dark-mode .btn-outline-primary {
349
+ color: var(--ctp-mauve);
350
+ border-color: var(--ctp-mauve);
351
+ }
352
+ body.dark-mode .btn-outline-primary:hover {
353
+ background-color: var(--ctp-mauve);
354
+ color: var(--ctp-base);
355
+ }
356
+ body.dark-mode .btn-secondary,
357
+ body.dark-mode .btn-outline-secondary {
358
+ background-color: var(--ctp-surface1);
359
+ border-color: var(--ctp-surface2);
360
+ color: var(--ctp-text);
147
361
  }
148
362
 
149
- .badge-api {
150
- background-color: var(--info-color);
363
+ /* Modal dialogs */
364
+ body.dark-mode .modal-content {
365
+ background-color: var(--ctp-surface0);
366
+ color: var(--ctp-text);
367
+ border-color: var(--ctp-surface2);
368
+ }
369
+ body.dark-mode .modal-header {
370
+ background-color: var(--ctp-surface1);
371
+ border-bottom-color: var(--ctp-surface2);
372
+ color: var(--ctp-text);
373
+ }
374
+ body.dark-mode .modal-footer {
375
+ background-color: var(--ctp-surface1);
376
+ border-top-color: var(--ctp-surface2);
377
+ }
378
+ body.dark-mode .modal-title {
379
+ color: var(--ctp-text);
380
+ }
381
+ body.dark-mode .btn-close {
382
+ filter: invert(1);
151
383
  }
152
384
 
153
- .table {
154
- color: var(--text-color);
155
- background-color: var(--card-bg);
385
+ /* Table headers - CRITICAL FIX */
386
+ body.dark-mode thead,
387
+ body.dark-mode thead th {
388
+ background-color: var(--ctp-surface1) !important;
389
+ color: var(--ctp-text) !important;
390
+ border-color: var(--ctp-surface2) !important;
391
+ }
392
+ body.dark-mode tbody tr {
393
+ border-color: var(--ctp-surface2) !important;
394
+ background-color: transparent !important;
395
+ }
396
+ body.dark-mode tbody td {
397
+ border-color: var(--ctp-surface2) !important;
398
+ background-color: transparent !important;
399
+ color: var(--ctp-text) !important;
400
+ }
401
+ body.dark-mode tbody th {
402
+ background-color: var(--ctp-surface0) !important;
403
+ color: var(--ctp-text) !important;
404
+ border-color: var(--ctp-surface2) !important;
405
+ }
406
+ body.dark-mode table {
407
+ color: var(--ctp-text) !important;
156
408
  }
157
409
 
158
- .table thead {
159
- background-color: var(--sidebar-hover);
160
- color: var(--text-color);
410
+ /* Chart canvas backgrounds */
411
+ body.dark-mode canvas {
412
+ background-color: transparent !important;
161
413
  }
162
414
 
163
- .table tbody {
164
- background-color: var(--card-bg);
415
+ /* Chart labels and text */
416
+ body.dark-mode .chart-container text {
417
+ fill: var(--ctp-text) !important;
165
418
  }
166
419
 
167
- .table-hover tbody tr {
168
- background-color: var(--card-bg);
420
+ /* Form placeholders */
421
+ body.dark-mode .form-control::placeholder,
422
+ body.dark-mode .form-select::placeholder {
423
+ color: var(--ctp-overlay0);
424
+ opacity: 1;
169
425
  }
170
426
 
171
- .table-hover tbody tr:hover {
172
- background-color: var(--table-hover) !important;
173
- cursor: pointer;
427
+ /* Dropdown menus */
428
+ body.dark-mode .dropdown-menu {
429
+ background-color: var(--ctp-surface0);
430
+ border-color: var(--ctp-surface2);
431
+ }
432
+ body.dark-mode .dropdown-item {
433
+ color: var(--ctp-text);
434
+ }
435
+ body.dark-mode .dropdown-item:hover {
436
+ background-color: var(--ctp-surface1);
437
+ color: var(--ctp-mauve);
174
438
  }
175
439
 
176
- .table-light {
177
- background-color: var(--sidebar-hover) !important;
178
- color: var(--text-color) !important;
440
+ /* Pagination */
441
+ body.dark-mode .pagination .page-link {
442
+ background-color: var(--ctp-surface0);
443
+ border-color: var(--ctp-surface2);
444
+ color: var(--ctp-text);
445
+ }
446
+ body.dark-mode .pagination .page-link:hover {
447
+ background-color: var(--ctp-surface1);
448
+ color: var(--ctp-mauve);
449
+ }
450
+ body.dark-mode .pagination .page-item.active .page-link {
451
+ background-color: var(--ctp-mauve);
452
+ border-color: var(--ctp-mauve);
453
+ color: var(--ctp-base);
179
454
  }
180
455
 
181
- /* Fix table cells in dark mode */
182
- .table td, .table th {
183
- background-color: var(--card-bg) !important;
184
- color: var(--text-color) !important;
185
- border-color: var(--border-color) !important;
456
+ /* Progress bars */
457
+ body.dark-mode .progress {
458
+ background-color: var(--ctp-surface1);
459
+ }
460
+ body.dark-mode .progress-bar {
461
+ background-color: var(--ctp-mauve);
186
462
  }
187
463
 
188
- .table thead th {
189
- background-color: var(--sidebar-hover) !important;
464
+ /* Horizontal rules */
465
+ body.dark-mode hr {
466
+ border-color: var(--ctp-surface2);
467
+ opacity: 1;
190
468
  }
191
469
 
192
- .form-control, .form-select {
193
- background-color: var(--card-bg);
194
- color: var(--text-color);
195
- border-color: var(--border-color);
470
+ /* List groups */
471
+ body.dark-mode .list-group-item {
472
+ background-color: var(--ctp-surface0);
473
+ border-color: var(--ctp-surface2);
474
+ color: var(--ctp-text);
475
+ }
476
+ body.dark-mode .list-group-item:hover {
477
+ background-color: var(--ctp-surface1);
196
478
  }
197
479
 
198
- .form-control:focus, .form-select:focus {
199
- background-color: var(--card-bg);
200
- color: var(--text-color);
201
- border-color: var(--primary-color);
480
+ /* Offcanvas (mobile menu) */
481
+ body.dark-mode .offcanvas {
482
+ background-color: var(--ctp-mantle);
483
+ color: var(--ctp-text);
484
+ }
485
+ body.dark-mode .offcanvas-header {
486
+ border-bottom-color: var(--ctp-surface2);
202
487
  }
203
488
 
204
- .modal-content {
205
- background-color: var(--card-bg);
206
- color: var(--text-color);
489
+ /* Small text and labels */
490
+ body.dark-mode small {
491
+ color: var(--ctp-subtext1) !important;
492
+ }
493
+ body.dark-mode label {
494
+ color: var(--ctp-text);
207
495
  }
208
496
 
209
- .modal-header, .modal-footer {
210
- border-color: var(--border-color);
497
+ /* Breadcrumbs */
498
+ body.dark-mode .breadcrumb {
499
+ background-color: var(--ctp-surface0);
500
+ }
501
+ body.dark-mode .breadcrumb-item {
502
+ color: var(--ctp-text);
503
+ }
504
+ body.dark-mode .breadcrumb-item.active {
505
+ color: var(--ctp-subtext0);
211
506
  }
212
507
 
213
- .btn-close {
214
- filter: brightness(0) invert(1);
508
+ /* Tooltips */
509
+ body.dark-mode .tooltip-inner {
510
+ background-color: var(--ctp-surface0);
511
+ color: var(--ctp-text);
215
512
  }
216
513
 
217
- body.dark-mode .btn-close,
218
- html[data-theme="dark"] body .btn-close {
219
- filter: brightness(0) invert(1);
514
+ /* Checkboxes and radios */
515
+ body.dark-mode .form-check-input {
516
+ background-color: var(--ctp-surface1);
517
+ border-color: var(--ctp-surface2);
518
+ }
519
+ body.dark-mode .form-check-input:checked {
520
+ background-color: var(--ctp-mauve);
521
+ border-color: var(--ctp-mauve);
522
+ }
523
+ body.dark-mode .form-check-label {
524
+ color: var(--ctp-text);
220
525
  }
221
526
 
222
- .text-muted {
223
- color: #D1D5DB !important; /* Lighter gray for better contrast in dark mode */
527
+ /* Nav tabs */
528
+ body.dark-mode .nav-tabs {
529
+ border-bottom-color: var(--ctp-surface2);
530
+ }
531
+ body.dark-mode .nav-tabs .nav-link {
532
+ color: var(--ctp-text);
533
+ background-color: transparent;
534
+ border-color: transparent;
535
+ }
536
+ body.dark-mode .nav-tabs .nav-link:hover {
537
+ border-color: var(--ctp-surface2);
538
+ background-color: var(--ctp-surface1);
539
+ }
540
+ body.dark-mode .nav-tabs .nav-link.active {
541
+ background-color: var(--ctp-surface0);
542
+ border-color: var(--ctp-surface2) var(--ctp-surface2) var(--ctp-surface0);
543
+ color: var(--ctp-mauve);
224
544
  }
225
545
 
226
- .theme-toggle {
227
- cursor: pointer;
228
- padding: 0.5rem 1rem;
229
- border-radius: 0.5rem;
230
- background-color: rgba(255, 255, 255, 0.1);
231
- color: white;
232
- border: none;
233
- transition: background-color 0.2s;
546
+ /* Collapsible sections - AGGRESSIVE */
547
+ body.dark-mode .accordion-item {
548
+ background-color: var(--ctp-surface0) !important;
549
+ border-color: var(--ctp-surface2) !important;
550
+ }
551
+ body.dark-mode .accordion-button {
552
+ background-color: var(--ctp-surface1) !important;
553
+ color: var(--ctp-text) !important;
554
+ }
555
+ body.dark-mode .accordion-button.bg-light {
556
+ background-color: var(--ctp-surface1) !important;
557
+ }
558
+ body.dark-mode .accordion-button:not(.collapsed) {
559
+ background-color: var(--ctp-surface0) !important;
560
+ color: var(--ctp-mauve) !important;
561
+ }
562
+ body.dark-mode .accordion-button::after {
563
+ filter: invert(1);
564
+ }
565
+ body.dark-mode .accordion-body {
566
+ background-color: var(--ctp-surface0) !important;
567
+ color: var(--ctp-text) !important;
234
568
  }
235
569
 
236
- .theme-toggle:hover {
237
- background-color: rgba(255, 255, 255, 0.2);
570
+ /* Heatmap specific styling - AGGRESSIVE */
571
+ body.dark-mode .heatmap-cell {
572
+ border-color: var(--ctp-surface2) !important;
573
+ color: var(--ctp-text) !important;
574
+ }
575
+ body.dark-mode .heatmap-hour {
576
+ color: var(--ctp-sky) !important;
577
+ font-weight: 600 !important;
578
+ font-size: 0.75rem !important;
579
+ }
580
+ body.dark-mode .heatmap-count {
581
+ color: var(--ctp-peach) !important;
582
+ font-weight: 600 !important;
583
+ }
584
+ body.dark-mode .heatmap-count.text-white {
585
+ color: var(--ctp-text) !important;
586
+ }
587
+ body.dark-mode .heatmap-count.text-dark {
588
+ color: var(--ctp-peach) !important;
589
+ }
590
+ body.dark-mode .heatmap-grid {
591
+ background-color: var(--ctp-surface0);
238
592
  }
239
593
 
240
- .error-status-resolved {
241
- color: var(--success-color);
594
+ /* Definition lists (dl, dt, dd) - used in Request Context */
595
+ body.dark-mode dl {
596
+ color: var(--ctp-text);
597
+ }
598
+ body.dark-mode dt {
599
+ color: var(--ctp-subtext1);
600
+ font-weight: 600;
601
+ }
602
+ body.dark-mode dd {
603
+ color: var(--ctp-text);
604
+ background-color: var(--ctp-surface0);
242
605
  }
243
606
 
244
- .error-status-unresolved {
245
- color: var(--danger-color);
607
+ /* Override any remaining white backgrounds */
608
+ body.dark-mode div[style*="background-color: white"],
609
+ body.dark-mode div[style*="background-color: #fff"],
610
+ body.dark-mode div[style*="background-color:#fff"],
611
+ body.dark-mode div[style*="background: white"],
612
+ body.dark-mode div[style*="background: #fff"] {
613
+ background-color: var(--ctp-surface0) !important;
246
614
  }
247
615
 
248
- .chart-container {
249
- position: relative;
250
- height: 300px;
251
- margin: 1rem 0;
616
+ /* Chart.js specific fixes for axis labels */
617
+ body.dark-mode .chartjs-render-monitor {
618
+ background-color: transparent !important;
252
619
  }
253
620
 
254
- /* Real-time updates - new error animation */
255
- @keyframes slideInFade {
256
- 0% {
257
- transform: translateY(-20px);
258
- opacity: 0;
259
- background-color: #FEF3C7;
260
- }
261
- 10% {
262
- transform: translateY(0);
263
- opacity: 1;
264
- background-color: #FEF3C7;
265
- }
266
- 100% {
267
- background-color: transparent;
268
- }
621
+ /* Make sure all headings are visible */
622
+ body.dark-mode h1, body.dark-mode h2, body.dark-mode h3,
623
+ body.dark-mode h4, body.dark-mode h5, body.dark-mode h6 {
624
+ color: var(--ctp-text);
269
625
  }
270
626
 
271
- /* Highlight new errors */
272
- #error_list tr:first-child.new-error {
273
- animation: slideInFade 3s ease-out;
627
+ /* Stronger text colors for better visibility */
628
+ body.dark-mode .text-secondary {
629
+ color: var(--ctp-subtext1) !important;
274
630
  }
275
631
 
276
- /* Live indicator pulse */
277
- @keyframes pulse {
278
- 0%, 100% {
279
- opacity: 1;
280
- }
281
- 50% {
282
- opacity: 0.5;
283
- }
632
+ /* SVG text elements (for charts) - MORE AGGRESSIVE */
633
+ body.dark-mode svg text {
634
+ fill: var(--ctp-text) !important;
635
+ }
636
+ body.dark-mode svg .domain,
637
+ body.dark-mode svg .tick line {
638
+ stroke: var(--ctp-surface2) !important;
639
+ }
640
+ body.dark-mode svg tspan {
641
+ fill: var(--ctp-text) !important;
284
642
  }
285
643
 
286
- #live-indicator {
287
- animation: pulse 2s ease-in-out infinite;
644
+ /* Details/Summary (collapsible sections) */
645
+ body.dark-mode details {
646
+ background-color: var(--ctp-surface0) !important;
647
+ border-color: var(--ctp-surface2) !important;
648
+ }
649
+ body.dark-mode summary {
650
+ background-color: var(--ctp-surface0) !important;
651
+ color: var(--ctp-text) !important;
652
+ border-color: var(--ctp-surface2) !important;
653
+ }
654
+ body.dark-mode details[open] summary {
655
+ background-color: var(--ctp-surface1) !important;
656
+ border-bottom-color: var(--ctp-surface2) !important;
288
657
  }
289
658
 
290
- /* Updated stat card pulse */
291
- @keyframes statPulse {
292
- 0%, 100% {
293
- transform: scale(1);
294
- }
295
- 50% {
296
- transform: scale(1.05);
297
- }
659
+ /* Button-like summary elements */
660
+ body.dark-mode .btn.collapsed,
661
+ body.dark-mode [data-bs-toggle="collapse"] {
662
+ background-color: var(--ctp-surface0) !important;
663
+ color: var(--ctp-text) !important;
664
+ border-color: var(--ctp-surface2) !important;
665
+ }
666
+
667
+ /* Specific override for white backgrounds in backtrace sections */
668
+ body.dark-mode .card .card-body > div[style*="background"],
669
+ body.dark-mode .card-body > button[style*="background"] {
670
+ background-color: var(--ctp-surface0) !important;
671
+ }
672
+
673
+ /* Error header/banner */
674
+ body.dark-mode .alert-danger {
675
+ background-color: rgba(243, 139, 168, 0.2) !important;
676
+ border-color: var(--ctp-red) !important;
677
+ color: var(--ctp-text) !important;
678
+ }
679
+
680
+ /* Chartkick specific - force text visibility */
681
+ body.dark-mode #chart-1 text,
682
+ body.dark-mode [id^="chart-"] text {
683
+ fill: var(--ctp-text) !important;
684
+ color: var(--ctp-text) !important;
298
685
  }
299
686
 
300
- .stat-card.updated {
301
- animation: statPulse 0.5s ease-out;
687
+ /* Force all text in charts to be visible */
688
+ body.dark-mode canvas + div text,
689
+ body.dark-mode .chartjs-size-monitor text {
690
+ color: var(--ctp-text) !important;
302
691
  }
303
692
 
304
- /* Loading indicator */
305
- #loading-indicator {
306
- position: fixed;
307
- top: 0;
308
- left: 0;
309
- right: 0;
310
- height: 3px;
311
- background: linear-gradient(90deg, #8B5CF6 0%, #6D28D9 100%);
312
- transform: scaleX(0);
313
- transform-origin: left;
314
- transition: transform 0.3s ease;
315
- z-index: 9999;
316
- display: none;
693
+ /* ULTRA AGGRESSIVE - Chart.js axis labels and titles */
694
+ body.dark-mode canvas {
695
+ color: var(--ctp-text) !important;
317
696
  }
318
697
 
319
- #loading-indicator.active {
320
- display: block;
321
- animation: loadingProgress 2s ease-in-out forwards;
698
+ /* Target Google Charts (alternative library) */
699
+ body.dark-mode svg > g > g > text,
700
+ body.dark-mode svg g text {
701
+ fill: var(--ctp-text) !important;
702
+ font-size: 12px !important;
322
703
  }
323
704
 
324
- @keyframes loadingProgress {
325
- 0% { transform: scaleX(0); }
326
- 50% { transform: scaleX(0.7); }
327
- 100% { transform: scaleX(1); opacity: 0; }
705
+ /* Chart.js specific selectors - NUCLEAR OPTION */
706
+ body.dark-mode .chartjs-render-monitor + div text,
707
+ body.dark-mode [class*="chart"] text,
708
+ body.dark-mode div[id*="chart"] text {
709
+ fill: var(--ctp-text) !important;
710
+ color: var(--ctp-text) !important;
711
+ }
712
+
713
+ /* Ensure axis tick labels are visible */
714
+ body.dark-mode g.tick text,
715
+ body.dark-mode .tick text,
716
+ body.dark-mode text.highcharts-axis-title,
717
+ body.dark-mode .highcharts-axis-labels text {
718
+ fill: var(--ctp-text) !important;
719
+ color: var(--ctp-text) !important;
720
+ }
721
+
722
+ /* Force visibility on ALL text elements inside chart containers */
723
+ body.dark-mode .card-body text,
724
+ body.dark-mode .card text {
725
+ fill: var(--ctp-text) !important;
328
726
  }
329
727
  </style>
330
728
  </head>
331
729
 
332
730
  <body>
333
- <!-- Loading Indicator -->
334
- <div id="loading-indicator"></div>
335
-
336
731
  <!-- Top Navbar -->
337
732
  <nav class="navbar navbar-dark">
338
733
  <div class="container-fluid">
339
- <a class="navbar-brand fw-bold" href="<%= root_path %>">
340
- <i class="bi bi-bug-fill"></i>
341
- Error Dashboard
342
- </a>
734
+ <div class="d-flex align-items-center">
735
+ <button class="btn btn-link text-white d-md-none me-2" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebarMenu">
736
+ <i class="bi bi-list fs-4"></i>
737
+ </button>
738
+ <a class="navbar-brand" href="<%= root_path %>">
739
+ <i class="bi bi-bug-fill"></i> Error Dashboard
740
+ </a>
741
+ </div>
343
742
  <div class="d-flex align-items-center gap-3">
344
- <button class="theme-toggle" id="themeToggle" onclick="toggleTheme()">
743
+ <button class="theme-toggle" id="themeToggle">
345
744
  <i class="bi bi-moon-fill" id="themeIcon"></i>
346
745
  </button>
347
- <div class="text-white">
348
- <small><%= Rails.env.titleize %> Environment</small>
746
+ <div class="text-white d-none d-md-block">
747
+ <small><%= Rails.env.titleize %></small>
349
748
  </div>
350
749
  </div>
351
750
  </div>
@@ -354,7 +753,7 @@
354
753
  <div class="container-fluid">
355
754
  <div class="row">
356
755
  <!-- Sidebar -->
357
- <nav class="col-md-2 d-md-block sidebar">
756
+ <nav class="col-md-2 d-none d-md-block sidebar">
358
757
  <div class="position-sticky pt-3">
359
758
  <ul class="nav flex-column">
360
759
  <li class="nav-item">
@@ -372,31 +771,13 @@
372
771
  <i class="bi bi-graph-up"></i> Analytics
373
772
  <% end %>
374
773
  </li>
375
- <% if RailsErrorDashboard.configuration.enable_platform_comparison %>
376
- <li class="nav-item">
377
- <%= link_to platform_comparison_errors_path, class: "nav-link #{request.path == platform_comparison_errors_path ? 'active' : ''}" do %>
378
- <i class="bi bi-phone"></i> Platform Health
379
- <% end %>
380
- </li>
381
- <% end %>
382
- <% if RailsErrorDashboard.configuration.enable_error_correlation %>
383
- <li class="nav-item">
384
- <%= link_to correlation_errors_path, class: "nav-link #{request.path == correlation_errors_path ? 'active' : ''}" do %>
385
- <i class="bi bi-diagram-3"></i> Correlation
386
- <% end %>
387
- </li>
388
- <% end %>
389
774
  </ul>
390
775
 
391
- <hr class="my-3">
392
-
393
- <h6 class="sidebar-heading px-3 mt-4 mb-2 text-muted text-uppercase">
394
- <small>Quick Filters</small>
395
- </h6>
776
+ <h6 class="mt-4">QUICK FILTERS</h6>
396
777
  <ul class="nav flex-column">
397
778
  <li class="nav-item">
398
- <%= link_to errors_path(unresolved: true), class: "nav-link" do %>
399
- <i class="bi bi-exclamation-circle text-danger"></i> Unresolved
779
+ <%= link_to errors_path(status: 'unresolved'), class: "nav-link" do %>
780
+ <i class="bi bi-exclamation-circle"></i> Unresolved
400
781
  <% end %>
401
782
  </li>
402
783
  <li class="nav-item">
@@ -423,69 +804,185 @@
423
804
  <!-- Bootstrap JS -->
424
805
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
425
806
 
426
- <!-- Theme Toggle Script -->
807
+ <!-- Pure JavaScript Theme Toggle -->
427
808
  <script>
428
- // Load theme from localStorage on page load
429
- document.addEventListener('DOMContentLoaded', function() {
430
- const savedTheme = localStorage.getItem('theme') || 'light';
809
+ // Load saved theme on page load (before DOMContentLoaded to prevent flash)
810
+ (function() {
811
+ const savedTheme = localStorage.getItem('theme');
431
812
  if (savedTheme === 'dark') {
432
813
  document.body.classList.add('dark-mode');
433
- document.documentElement.setAttribute('data-theme', 'dark');
434
- updateThemeIcon(true);
435
- } else {
436
- // Ensure light mode is clean
437
- document.body.classList.remove('dark-mode');
438
- document.documentElement.removeAttribute('data-theme');
439
- updateThemeIcon(false);
440
814
  }
441
- });
442
-
443
- function toggleTheme() {
444
- const body = document.body;
445
- const isDark = body.classList.toggle('dark-mode');
446
-
447
- // Sync with html data attribute
448
- if (isDark) {
449
- document.documentElement.setAttribute('data-theme', 'dark');
450
- } else {
451
- document.documentElement.removeAttribute('data-theme');
452
- }
453
-
454
- // Save preference
455
- localStorage.setItem('theme', isDark ? 'dark' : 'light');
456
-
457
- // Update icon
458
- updateThemeIcon(isDark);
459
- }
815
+ })();
460
816
 
461
- function updateThemeIcon(isDark) {
462
- const icon = document.getElementById('themeIcon');
463
- if (isDark) {
464
- icon.className = 'bi bi-sun-fill';
465
- } else {
466
- icon.className = 'bi bi-moon-fill';
817
+ // Theme toggle after DOM loads
818
+ document.addEventListener('DOMContentLoaded', function() {
819
+ const themeToggle = document.getElementById('themeToggle');
820
+ const themeIcon = document.getElementById('themeIcon');
821
+
822
+ // Update icon based on current theme
823
+ function updateIcon() {
824
+ if (document.body.classList.contains('dark-mode')) {
825
+ themeIcon.className = 'bi bi-sun-fill';
826
+ } else {
827
+ themeIcon.className = 'bi bi-moon-fill';
828
+ }
467
829
  }
468
- }
469
-
470
- // Loading indicator for form submissions and link clicks
471
- const loadingIndicator = document.getElementById('loading-indicator');
472
830
 
473
- // Show loading on form submit
474
- document.addEventListener('submit', function() {
475
- loadingIndicator.classList.add('active');
476
- });
477
-
478
- // Show loading on link clicks (except anchors)
479
- document.addEventListener('click', function(e) {
480
- const link = e.target.closest('a');
481
- if (link && link.href && !link.href.startsWith('#') && !link.target) {
482
- loadingIndicator.classList.add('active');
831
+ // Set initial icon
832
+ updateIcon();
833
+
834
+ // Toggle theme on button click
835
+ themeToggle.addEventListener('click', function() {
836
+ console.log('🎨 Theme toggle clicked');
837
+
838
+ document.body.classList.toggle('dark-mode');
839
+ const isDark = document.body.classList.contains('dark-mode');
840
+
841
+ console.log('Dark mode:', isDark);
842
+
843
+ // Save preference
844
+ localStorage.setItem('theme', isDark ? 'dark' : 'light');
845
+ console.log('💾 Saved to localStorage:', isDark ? 'dark' : 'light');
846
+
847
+ // Update icon
848
+ updateIcon();
849
+ console.log('✅ Theme toggled successfully');
850
+
851
+ // Reapply chart theme
852
+ applyChartTheme();
853
+
854
+ // Reload page to update charts properly
855
+ setTimeout(() => location.reload(), 300);
856
+ });
857
+
858
+ // Chart.js theme colors - ULTRA AGGRESSIVE setup
859
+ function applyChartTheme() {
860
+ if (typeof Chart !== 'undefined') {
861
+ const isDark = document.body.classList.contains('dark-mode');
862
+ const textColor = isDark ? '#cdd6f4' : '#1f2937';
863
+ const gridColor = isDark ? 'rgba(88, 91, 112, 0.2)' : 'rgba(0, 0, 0, 0.1)';
864
+
865
+ console.log('📊 Setting Chart.js theme:', isDark ? 'DARK' : 'light', '| Text:', textColor);
866
+
867
+ // Global defaults
868
+ Chart.defaults.color = textColor;
869
+ Chart.defaults.borderColor = gridColor;
870
+ Chart.defaults.font = Chart.defaults.font || {};
871
+ Chart.defaults.font.color = textColor;
872
+
873
+ // Scale defaults (axes) - AGGRESSIVE
874
+ if (Chart.defaults.scale) {
875
+ Chart.defaults.scale.ticks = Chart.defaults.scale.ticks || {};
876
+ Chart.defaults.scale.ticks.color = textColor;
877
+ Chart.defaults.scale.ticks.font = Chart.defaults.scale.ticks.font || {};
878
+ Chart.defaults.scale.ticks.font.color = textColor;
879
+
880
+ Chart.defaults.scale.grid = Chart.defaults.scale.grid || {};
881
+ Chart.defaults.scale.grid.color = gridColor;
882
+
883
+ // Axis title (xtitle, ytitle)
884
+ Chart.defaults.scale.title = Chart.defaults.scale.title || {};
885
+ Chart.defaults.scale.title.color = textColor;
886
+ Chart.defaults.scale.title.font = Chart.defaults.scale.title.font || {};
887
+ Chart.defaults.scale.title.font.size = 14;
888
+ }
889
+
890
+ // X and Y axis specific
891
+ if (Chart.defaults.scales) {
892
+ // X axis
893
+ Chart.defaults.scales.x = Chart.defaults.scales.x || {};
894
+ Chart.defaults.scales.x.ticks = Chart.defaults.scales.x.ticks || {};
895
+ Chart.defaults.scales.x.ticks.color = textColor;
896
+ Chart.defaults.scales.x.title = Chart.defaults.scales.x.title || {};
897
+ Chart.defaults.scales.x.title.color = textColor;
898
+ Chart.defaults.scales.x.grid = Chart.defaults.scales.x.grid || {};
899
+ Chart.defaults.scales.x.grid.color = gridColor;
900
+
901
+ // Y axis
902
+ Chart.defaults.scales.y = Chart.defaults.scales.y || {};
903
+ Chart.defaults.scales.y.ticks = Chart.defaults.scales.y.ticks || {};
904
+ Chart.defaults.scales.y.ticks.color = textColor;
905
+ Chart.defaults.scales.y.title = Chart.defaults.scales.y.title || {};
906
+ Chart.defaults.scales.y.title.color = textColor;
907
+ Chart.defaults.scales.y.grid = Chart.defaults.scales.y.grid || {};
908
+ Chart.defaults.scales.y.grid.color = gridColor;
909
+ }
910
+
911
+ // Plugin defaults
912
+ if (Chart.defaults.plugins) {
913
+ // Legend
914
+ if (Chart.defaults.plugins.legend) {
915
+ Chart.defaults.plugins.legend.labels = Chart.defaults.plugins.legend.labels || {};
916
+ Chart.defaults.plugins.legend.labels.color = textColor;
917
+ Chart.defaults.plugins.legend.labels.font = Chart.defaults.plugins.legend.labels.font || {};
918
+ Chart.defaults.plugins.legend.labels.font.color = textColor;
919
+ }
920
+
921
+ // Tooltip
922
+ if (Chart.defaults.plugins.tooltip) {
923
+ Chart.defaults.plugins.tooltip.backgroundColor = isDark ? '#313244' : 'rgba(0, 0, 0, 0.8)';
924
+ Chart.defaults.plugins.tooltip.titleColor = textColor;
925
+ Chart.defaults.plugins.tooltip.bodyColor = textColor;
926
+ Chart.defaults.plugins.tooltip.borderColor = isDark ? '#585b70' : 'rgba(0, 0, 0, 0.1)';
927
+ Chart.defaults.plugins.tooltip.borderWidth = 1;
928
+ }
929
+
930
+ // Title plugin
931
+ if (Chart.defaults.plugins.title) {
932
+ Chart.defaults.plugins.title.color = textColor;
933
+ Chart.defaults.plugins.title.font = Chart.defaults.plugins.title.font || {};
934
+ Chart.defaults.plugins.title.font.color = textColor;
935
+ }
936
+ }
937
+
938
+ console.log('✅ Chart.js theme applied - all text should be:', textColor);
939
+ } else {
940
+ console.warn('⚠️ Chart.js not loaded yet');
941
+ }
942
+
943
+ // Also update Google Charts if present
944
+ if (typeof google !== 'undefined' && google.visualization) {
945
+ console.log('📊 Google Charts detected - applying theme');
946
+ }
483
947
  }
484
- });
485
948
 
486
- // Hide loading when page loads
487
- window.addEventListener('load', function() {
488
- loadingIndicator.classList.remove('active');
949
+ // Apply chart theme on load
950
+ applyChartTheme();
951
+
952
+ // NUCLEAR OPTION: Watch for chart creation and force colors
953
+ const observer = new MutationObserver(function(mutations) {
954
+ mutations.forEach(function(mutation) {
955
+ mutation.addedNodes.forEach(function(node) {
956
+ if (node.tagName === 'CANVAS') {
957
+ console.log('🎨 New chart detected, forcing theme...');
958
+ setTimeout(applyChartTheme, 100);
959
+ }
960
+ });
961
+ });
962
+ });
963
+
964
+ observer.observe(document.body, {
965
+ childList: true,
966
+ subtree: true
967
+ });
968
+
969
+ // Also listen for Chartkick chart creation events
970
+ document.addEventListener('chartkick:load', function() {
971
+ console.log('📊 Chartkick loaded, applying theme');
972
+ applyChartTheme();
973
+ });
974
+
975
+ // Force reapply every 500ms for the first 3 seconds (in case charts load late)
976
+ let attempts = 0;
977
+ const forceInterval = setInterval(function() {
978
+ attempts++;
979
+ applyChartTheme();
980
+ console.log('🔄 Force applying theme (attempt', attempts, ')');
981
+ if (attempts >= 6) {
982
+ clearInterval(forceInterval);
983
+ console.log('✅ Stopped force applying');
984
+ }
985
+ }, 500);
489
986
  });
490
987
  </script>
491
988
  </body>