rails_error_dashboard 0.1.0 → 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 (95) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +305 -703
  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/application_controller.rb +18 -0
  11. data/app/controllers/rails_error_dashboard/errors_controller.rb +140 -4
  12. data/app/helpers/rails_error_dashboard/application_helper.rb +55 -0
  13. data/app/helpers/rails_error_dashboard/backtrace_helper.rb +91 -0
  14. data/app/helpers/rails_error_dashboard/overview_helper.rb +78 -0
  15. data/app/helpers/rails_error_dashboard/user_agent_helper.rb +118 -0
  16. data/app/jobs/rails_error_dashboard/application_job.rb +19 -0
  17. data/app/jobs/rails_error_dashboard/async_error_logging_job.rb +48 -0
  18. data/app/jobs/rails_error_dashboard/baseline_alert_job.rb +263 -0
  19. data/app/jobs/rails_error_dashboard/discord_error_notification_job.rb +4 -8
  20. data/app/jobs/rails_error_dashboard/email_error_notification_job.rb +2 -1
  21. data/app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb +5 -5
  22. data/app/jobs/rails_error_dashboard/slack_error_notification_job.rb +10 -6
  23. data/app/jobs/rails_error_dashboard/webhook_error_notification_job.rb +5 -6
  24. data/app/mailers/rails_error_dashboard/application_mailer.rb +1 -1
  25. data/app/mailers/rails_error_dashboard/error_notification_mailer.rb +1 -1
  26. data/app/models/rails_error_dashboard/cascade_pattern.rb +74 -0
  27. data/app/models/rails_error_dashboard/error_baseline.rb +100 -0
  28. data/app/models/rails_error_dashboard/error_comment.rb +27 -0
  29. data/app/models/rails_error_dashboard/error_log.rb +471 -3
  30. data/app/models/rails_error_dashboard/error_occurrence.rb +49 -0
  31. data/app/views/layouts/rails_error_dashboard.html.erb +816 -178
  32. data/app/views/layouts/rails_error_dashboard_old_backup.html.erb +383 -0
  33. data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.html.erb +3 -10
  34. data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.text.erb +1 -2
  35. data/app/views/rails_error_dashboard/errors/_error_row.html.erb +78 -0
  36. data/app/views/rails_error_dashboard/errors/_pattern_insights.html.erb +209 -0
  37. data/app/views/rails_error_dashboard/errors/_stats.html.erb +34 -0
  38. data/app/views/rails_error_dashboard/errors/_timeline.html.erb +167 -0
  39. data/app/views/rails_error_dashboard/errors/analytics.html.erb +152 -56
  40. data/app/views/rails_error_dashboard/errors/correlation.html.erb +373 -0
  41. data/app/views/rails_error_dashboard/errors/index.html.erb +294 -138
  42. data/app/views/rails_error_dashboard/errors/overview.html.erb +253 -0
  43. data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +399 -0
  44. data/app/views/rails_error_dashboard/errors/show.html.erb +781 -65
  45. data/config/routes.rb +9 -0
  46. data/db/migrate/20251225071314_add_optimized_indexes_to_error_logs.rb +66 -0
  47. data/db/migrate/20251225074653_remove_environment_from_error_logs.rb +26 -0
  48. data/db/migrate/20251225085859_add_enhanced_metrics_to_error_logs.rb +12 -0
  49. data/db/migrate/20251225093603_add_similarity_tracking_to_error_logs.rb +9 -0
  50. data/db/migrate/20251225100236_create_error_occurrences.rb +31 -0
  51. data/db/migrate/20251225101920_create_cascade_patterns.rb +33 -0
  52. data/db/migrate/20251225102500_create_error_baselines.rb +38 -0
  53. data/db/migrate/20251226020000_add_workflow_fields_to_error_logs.rb +27 -0
  54. data/db/migrate/20251226020100_create_error_comments.rb +18 -0
  55. data/lib/generators/rails_error_dashboard/install/install_generator.rb +276 -1
  56. data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +272 -37
  57. data/lib/generators/rails_error_dashboard/solid_queue/solid_queue_generator.rb +36 -0
  58. data/lib/generators/rails_error_dashboard/solid_queue/templates/queue.yml +55 -0
  59. data/lib/rails_error_dashboard/commands/batch_delete_errors.rb +1 -1
  60. data/lib/rails_error_dashboard/commands/batch_resolve_errors.rb +2 -2
  61. data/lib/rails_error_dashboard/commands/log_error.rb +272 -7
  62. data/lib/rails_error_dashboard/commands/resolve_error.rb +16 -0
  63. data/lib/rails_error_dashboard/configuration.rb +90 -5
  64. data/lib/rails_error_dashboard/error_reporter.rb +15 -7
  65. data/lib/rails_error_dashboard/logger.rb +105 -0
  66. data/lib/rails_error_dashboard/middleware/error_catcher.rb +17 -10
  67. data/lib/rails_error_dashboard/plugin.rb +6 -3
  68. data/lib/rails_error_dashboard/plugin_registry.rb +2 -2
  69. data/lib/rails_error_dashboard/plugins/audit_log_plugin.rb +0 -1
  70. data/lib/rails_error_dashboard/plugins/jira_integration_plugin.rb +3 -4
  71. data/lib/rails_error_dashboard/plugins/metrics_plugin.rb +1 -3
  72. data/lib/rails_error_dashboard/queries/analytics_stats.rb +44 -6
  73. data/lib/rails_error_dashboard/queries/baseline_stats.rb +107 -0
  74. data/lib/rails_error_dashboard/queries/co_occurring_errors.rb +86 -0
  75. data/lib/rails_error_dashboard/queries/dashboard_stats.rb +242 -2
  76. data/lib/rails_error_dashboard/queries/error_cascades.rb +74 -0
  77. data/lib/rails_error_dashboard/queries/error_correlation.rb +375 -0
  78. data/lib/rails_error_dashboard/queries/errors_list.rb +106 -10
  79. data/lib/rails_error_dashboard/queries/filter_options.rb +0 -1
  80. data/lib/rails_error_dashboard/queries/platform_comparison.rb +254 -0
  81. data/lib/rails_error_dashboard/queries/similar_errors.rb +93 -0
  82. data/lib/rails_error_dashboard/services/backtrace_parser.rb +113 -0
  83. data/lib/rails_error_dashboard/services/baseline_alert_throttler.rb +88 -0
  84. data/lib/rails_error_dashboard/services/baseline_calculator.rb +269 -0
  85. data/lib/rails_error_dashboard/services/cascade_detector.rb +95 -0
  86. data/lib/rails_error_dashboard/services/pattern_detector.rb +268 -0
  87. data/lib/rails_error_dashboard/services/similarity_calculator.rb +144 -0
  88. data/lib/rails_error_dashboard/value_objects/error_context.rb +27 -1
  89. data/lib/rails_error_dashboard/version.rb +1 -1
  90. data/lib/rails_error_dashboard.rb +57 -7
  91. metadata +69 -10
  92. data/app/models/rails_error_dashboard/application_record.rb +0 -5
  93. data/lib/rails_error_dashboard/queries/developer_insights.rb +0 -277
  94. data/lib/rails_error_dashboard/queries/errors_list_v2.rb +0 -149
  95. data/lib/tasks/rails_error_dashboard_tasks.rake +0 -4
@@ -1,7 +1,7 @@
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 %>
@@ -10,226 +10,719 @@
10
10
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
11
11
  <!-- Bootstrap Icons -->
12
12
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
13
- <!-- Chart.js with date adapter -->
13
+
14
+ <!-- Chart.js -->
14
15
  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
15
16
  <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
16
17
  <script src="https://cdn.jsdelivr.net/npm/chartkick@5.0.1/dist/chartkick.min.js"></script>
17
18
 
19
+ <!-- Catppuccin Mocha Theme - Pure CSS -->
18
20
  <style>
21
+ /* Catppuccin Mocha Color Palette */
19
22
  :root {
20
- --primary-color: #8B5CF6;
21
- --success-color: #10B981;
22
- --danger-color: #EF4444;
23
- --warning-color: #F59E0B;
24
- --info-color: #3B82F6;
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) */
52
+ body {
53
+ background-color: #f3f4f6;
54
+ color: #1f2937;
55
+ transition: background-color 0.3s, color 0.3s;
25
56
  }
26
57
 
27
- /* Light mode (default) */
28
- body {
29
- --bg-color: #F3F4F6;
30
- --text-color: #1F2937;
31
- --card-bg: #FFFFFF;
32
- --sidebar-bg: #FFFFFF;
33
- --sidebar-hover: #F9FAFB;
34
- --border-color: #E5E7EB;
35
- --table-hover: #F9FAFB;
36
- --nav-active-bg: #EDE9FE;
37
- background-color: var(--bg-color);
38
- color: var(--text-color);
39
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
40
- transition: background-color 0.3s ease, color 0.3s ease;
41
- }
42
-
43
- /* Dark mode */
58
+ /* Dark Theme */
44
59
  body.dark-mode {
45
- --bg-color: #1A1B26;
46
- --text-color: #E4E5E9;
47
- --card-bg: #24283B;
48
- --sidebar-bg: #1F2130;
49
- --sidebar-hover: #2A2D3E;
50
- --border-color: #414868;
51
- --table-hover: #2A2D3E;
52
- --nav-active-bg: #2A2D3E;
60
+ background-color: var(--ctp-base);
61
+ color: var(--ctp-text);
53
62
  }
54
63
 
55
- .card {
56
- background-color: var(--card-bg);
57
- color: var(--text-color);
58
- border-color: var(--border-color);
64
+ /* Navbar */
65
+ .navbar {
66
+ background: linear-gradient(135deg, #8B5CF6, #6D28D9) !important;
67
+ color: white !important;
59
68
  }
60
-
61
- .card-header {
62
- background-color: var(--card-bg) !important;
63
- color: var(--text-color);
64
- border-color: var(--border-color);
69
+ .navbar * {
70
+ color: white !important;
71
+ }
72
+ .navbar-brand {
73
+ font-weight: bold;
65
74
  }
66
75
 
67
- .navbar {
68
- background: linear-gradient(135deg, var(--primary-color), #6D28D9);
69
- 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);
70
88
  }
71
89
 
90
+ /* Sidebar */
72
91
  .sidebar {
73
- background: var(--sidebar-bg);
92
+ background: white;
74
93
  min-height: calc(100vh - 56px);
75
- 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);
76
98
  }
77
-
78
99
  .sidebar .nav-link {
79
- color: var(--text-color);
100
+ color: #1f2937;
80
101
  padding: 0.75rem 1.5rem;
81
102
  border-left: 3px solid transparent;
82
103
  transition: all 0.2s;
83
104
  }
84
-
105
+ body.dark-mode .sidebar .nav-link {
106
+ color: var(--ctp-text);
107
+ }
85
108
  .sidebar .nav-link:hover {
86
- background-color: var(--sidebar-hover);
87
- color: var(--primary-color);
88
- 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);
89
117
  }
90
-
91
118
  .sidebar .nav-link.active {
92
- background-color: var(--nav-active-bg);
93
- color: var(--primary-color);
94
- border-left-color: var(--primary-color);
119
+ background-color: #f3f4f6;
120
+ color: #8B5CF6;
121
+ border-left-color: #8B5CF6;
95
122
  font-weight: 600;
96
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
+ }
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
+ }
97
274
 
275
+ /* Stat Cards */
98
276
  .stat-card {
99
277
  border-radius: 0.75rem;
100
- border: none;
101
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
102
278
  transition: transform 0.2s, box-shadow 0.2s;
103
279
  }
104
-
105
280
  .stat-card:hover {
106
281
  transform: translateY(-2px);
107
- box-shadow: 0 4px 6px rgba(0,0,0,0.1);
282
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
108
283
  }
109
284
 
110
- .stat-value {
111
- font-size: 2rem;
112
- 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;
113
300
  }
114
301
 
115
- .stat-label {
116
- color: #6B7280;
117
- font-size: 0.875rem;
118
- text-transform: uppercase;
119
- 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;
120
320
  }
121
321
 
122
- .badge-ios {
123
- 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);
124
336
  }
125
337
 
126
- .badge-android {
127
- background-color: #3DDC84;
128
- 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);
129
361
  }
130
362
 
131
- .badge-api {
132
- 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);
133
383
  }
134
384
 
135
- .table {
136
- color: var(--text-color);
137
- 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;
138
408
  }
139
409
 
140
- .table thead {
141
- background-color: var(--sidebar-hover);
142
- color: var(--text-color);
410
+ /* Chart canvas backgrounds */
411
+ body.dark-mode canvas {
412
+ background-color: transparent !important;
143
413
  }
144
414
 
145
- .table tbody {
146
- background-color: var(--card-bg);
415
+ /* Chart labels and text */
416
+ body.dark-mode .chart-container text {
417
+ fill: var(--ctp-text) !important;
147
418
  }
148
419
 
149
- .table-hover tbody tr {
150
- 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;
151
425
  }
152
426
 
153
- .table-hover tbody tr:hover {
154
- background-color: var(--table-hover) !important;
155
- 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);
156
438
  }
157
439
 
158
- .table-light {
159
- background-color: var(--sidebar-hover) !important;
160
- 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);
161
454
  }
162
455
 
163
- /* Fix table cells in dark mode */
164
- .table td, .table th {
165
- background-color: var(--card-bg) !important;
166
- color: var(--text-color) !important;
167
- 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);
168
462
  }
169
463
 
170
- .table thead th {
171
- background-color: var(--sidebar-hover) !important;
464
+ /* Horizontal rules */
465
+ body.dark-mode hr {
466
+ border-color: var(--ctp-surface2);
467
+ opacity: 1;
172
468
  }
173
469
 
174
- .form-control, .form-select {
175
- background-color: var(--card-bg);
176
- color: var(--text-color);
177
- 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);
178
478
  }
179
479
 
180
- .form-control:focus, .form-select:focus {
181
- background-color: var(--card-bg);
182
- color: var(--text-color);
183
- 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);
184
487
  }
185
488
 
186
- .modal-content {
187
- background-color: var(--card-bg);
188
- 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);
189
495
  }
190
496
 
191
- .modal-header, .modal-footer {
192
- 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);
193
506
  }
194
507
 
195
- .btn-close {
196
- filter: brightness(0) invert(1);
508
+ /* Tooltips */
509
+ body.dark-mode .tooltip-inner {
510
+ background-color: var(--ctp-surface0);
511
+ color: var(--ctp-text);
197
512
  }
198
513
 
199
- body.dark-mode .btn-close {
200
- 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);
201
525
  }
202
526
 
203
- .text-muted {
204
- color: #9CA3AF !important;
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);
205
544
  }
206
545
 
207
- .theme-toggle {
208
- cursor: pointer;
209
- padding: 0.5rem 1rem;
210
- border-radius: 0.5rem;
211
- background-color: rgba(255, 255, 255, 0.1);
212
- color: white;
213
- border: none;
214
- 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;
215
568
  }
216
569
 
217
- .theme-toggle:hover {
218
- 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);
219
592
  }
220
593
 
221
- .error-status-resolved {
222
- 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);
605
+ }
606
+
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;
614
+ }
615
+
616
+ /* Chart.js specific fixes for axis labels */
617
+ body.dark-mode .chartjs-render-monitor {
618
+ background-color: transparent !important;
619
+ }
620
+
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);
625
+ }
626
+
627
+ /* Stronger text colors for better visibility */
628
+ body.dark-mode .text-secondary {
629
+ color: var(--ctp-subtext1) !important;
630
+ }
631
+
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;
642
+ }
643
+
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;
657
+ }
658
+
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;
223
685
  }
224
686
 
225
- .error-status-unresolved {
226
- color: var(--danger-color);
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;
227
691
  }
228
692
 
229
- .chart-container {
230
- position: relative;
231
- height: 300px;
232
- margin: 1rem 0;
693
+ /* ULTRA AGGRESSIVE - Chart.js axis labels and titles */
694
+ body.dark-mode canvas {
695
+ color: var(--ctp-text) !important;
696
+ }
697
+
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;
703
+ }
704
+
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;
233
726
  }
234
727
  </style>
235
728
  </head>
@@ -238,16 +731,20 @@
238
731
  <!-- Top Navbar -->
239
732
  <nav class="navbar navbar-dark">
240
733
  <div class="container-fluid">
241
- <a class="navbar-brand fw-bold" href="<%= root_path %>">
242
- <i class="bi bi-bug-fill"></i>
243
- Error Dashboard
244
- </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>
245
742
  <div class="d-flex align-items-center gap-3">
246
- <button class="theme-toggle" id="themeToggle" onclick="toggleTheme()">
743
+ <button class="theme-toggle" id="themeToggle">
247
744
  <i class="bi bi-moon-fill" id="themeIcon"></i>
248
745
  </button>
249
- <div class="text-white">
250
- <small><%= Rails.env.titleize %> Environment</small>
746
+ <div class="text-white d-none d-md-block">
747
+ <small><%= Rails.env.titleize %></small>
251
748
  </div>
252
749
  </div>
253
750
  </div>
@@ -256,7 +753,7 @@
256
753
  <div class="container-fluid">
257
754
  <div class="row">
258
755
  <!-- Sidebar -->
259
- <nav class="col-md-2 d-md-block sidebar">
756
+ <nav class="col-md-2 d-none d-md-block sidebar">
260
757
  <div class="position-sticky pt-3">
261
758
  <ul class="nav flex-column">
262
759
  <li class="nav-item">
@@ -276,15 +773,11 @@
276
773
  </li>
277
774
  </ul>
278
775
 
279
- <hr class="my-3">
280
-
281
- <h6 class="sidebar-heading px-3 mt-4 mb-2 text-muted text-uppercase">
282
- <small>Quick Filters</small>
283
- </h6>
776
+ <h6 class="mt-4">QUICK FILTERS</h6>
284
777
  <ul class="nav flex-column">
285
778
  <li class="nav-item">
286
- <%= link_to errors_path(unresolved: true), class: "nav-link" do %>
287
- <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
288
781
  <% end %>
289
782
  </li>
290
783
  <li class="nav-item">
@@ -297,11 +790,6 @@
297
790
  <i class="bi bi-phone"></i> Android Errors
298
791
  <% end %>
299
792
  </li>
300
- <li class="nav-item">
301
- <%= link_to errors_path(environment: 'production'), class: "nav-link" do %>
302
- <i class="bi bi-server"></i> Production
303
- <% end %>
304
- </li>
305
793
  </ul>
306
794
  </div>
307
795
  </nav>
@@ -316,36 +804,186 @@
316
804
  <!-- Bootstrap JS -->
317
805
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
318
806
 
319
- <!-- Theme Toggle Script -->
807
+ <!-- Pure JavaScript Theme Toggle -->
320
808
  <script>
321
- // Load theme from localStorage on page load
322
- document.addEventListener('DOMContentLoaded', function() {
323
- 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');
324
812
  if (savedTheme === 'dark') {
325
813
  document.body.classList.add('dark-mode');
326
- updateThemeIcon(true);
327
814
  }
328
- });
329
-
330
- function toggleTheme() {
331
- const body = document.body;
332
- const isDark = body.classList.toggle('dark-mode');
333
-
334
- // Save preference
335
- localStorage.setItem('theme', isDark ? 'dark' : 'light');
815
+ })();
336
816
 
337
- // Update icon
338
- updateThemeIcon(isDark);
339
- }
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
+ }
829
+ }
340
830
 
341
- function updateThemeIcon(isDark) {
342
- const icon = document.getElementById('themeIcon');
343
- if (isDark) {
344
- icon.className = 'bi bi-sun-fill';
345
- } else {
346
- icon.className = 'bi bi-moon-fill';
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
+ }
347
947
  }
348
- }
948
+
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);
986
+ });
349
987
  </script>
350
988
  </body>
351
989
  </html>