railswatch 1.0.0

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 (138) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +485 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/config/railswatch_manifest.js +0 -0
  6. data/app/assets/images/activity.svg +13 -0
  7. data/app/assets/images/bot.svg +1 -0
  8. data/app/assets/images/close.svg +13 -0
  9. data/app/assets/images/details.svg +3 -0
  10. data/app/assets/images/download.svg +3 -0
  11. data/app/assets/images/export.svg +13 -0
  12. data/app/assets/images/external.svg +1 -0
  13. data/app/assets/images/git.svg +1 -0
  14. data/app/assets/images/github.svg +1 -0
  15. data/app/assets/images/home.svg +16 -0
  16. data/app/assets/images/import.svg +13 -0
  17. data/app/assets/images/menu.svg +16 -0
  18. data/app/assets/images/moon.svg +3 -0
  19. data/app/assets/images/stat.svg +1 -0
  20. data/app/assets/images/sun.svg +4 -0
  21. data/app/assets/images/user.svg +1 -0
  22. data/app/controllers/railswatch/base_controller.rb +35 -0
  23. data/app/controllers/railswatch/concerns/csv_exportable.rb +31 -0
  24. data/app/controllers/railswatch/railswatch_controller.rb +183 -0
  25. data/app/engine_assets/javascripts/apex_ext.js +30 -0
  26. data/app/engine_assets/javascripts/application.js +9 -0
  27. data/app/engine_assets/javascripts/autoupdate.js +79 -0
  28. data/app/engine_assets/javascripts/charts.js +279 -0
  29. data/app/engine_assets/javascripts/navbar.js +11 -0
  30. data/app/engine_assets/javascripts/panel.js +43 -0
  31. data/app/engine_assets/javascripts/table.js +12 -0
  32. data/app/engine_assets/javascripts/theme.js +43 -0
  33. data/app/engine_assets/stylesheets/panel.css +111 -0
  34. data/app/engine_assets/stylesheets/responsive.css +102 -0
  35. data/app/engine_assets/stylesheets/style.css +960 -0
  36. data/app/helpers/railswatch/railswatch_helper.rb +338 -0
  37. data/app/views/railswatch/_panel.html.erb +15 -0
  38. data/app/views/railswatch/layouts/railswatch.html.erb +81 -0
  39. data/app/views/railswatch/railswatch/_card.html.erb +7 -0
  40. data/app/views/railswatch/railswatch/_chart.html.erb +13 -0
  41. data/app/views/railswatch/railswatch/_crashes_table_content.html.erb +62 -0
  42. data/app/views/railswatch/railswatch/_custom_events_table_content.html.erb +27 -0
  43. data/app/views/railswatch/railswatch/_delayed_job_table_content.html.erb +52 -0
  44. data/app/views/railswatch/railswatch/_export.html.erb +4 -0
  45. data/app/views/railswatch/railswatch/_grape_requests_table_content.html.erb +31 -0
  46. data/app/views/railswatch/railswatch/_overview.html.erb +124 -0
  47. data/app/views/railswatch/railswatch/_rake_tasks_table_content.html.erb +25 -0
  48. data/app/views/railswatch/railswatch/_recent_requests_table_content.html.erb +28 -0
  49. data/app/views/railswatch/railswatch/_recent_row.html.erb +41 -0
  50. data/app/views/railswatch/railswatch/_requests_table_content.html.erb +51 -0
  51. data/app/views/railswatch/railswatch/_sidekiq_jobs_table_content.html.erb +50 -0
  52. data/app/views/railswatch/railswatch/_summary.html.erb +50 -0
  53. data/app/views/railswatch/railswatch/_table.html.erb +30 -0
  54. data/app/views/railswatch/railswatch/_trace.html.erb +78 -0
  55. data/app/views/railswatch/railswatch/crashes.html.erb +2 -0
  56. data/app/views/railswatch/railswatch/custom.html.erb +6 -0
  57. data/app/views/railswatch/railswatch/delayed_job.html.erb +6 -0
  58. data/app/views/railswatch/railswatch/grape.html.erb +6 -0
  59. data/app/views/railswatch/railswatch/index.html.erb +9 -0
  60. data/app/views/railswatch/railswatch/rake.html.erb +6 -0
  61. data/app/views/railswatch/railswatch/recent.html.erb +2 -0
  62. data/app/views/railswatch/railswatch/requests.html.erb +2 -0
  63. data/app/views/railswatch/railswatch/resources.html.erb +28 -0
  64. data/app/views/railswatch/railswatch/sidekiq.html.erb +6 -0
  65. data/app/views/railswatch/railswatch/slow.html.erb +2 -0
  66. data/app/views/railswatch/railswatch/summary.js.erb +3 -0
  67. data/app/views/railswatch/railswatch/trace.js.erb +9 -0
  68. data/app/views/railswatch/shared/_header.html.erb +39 -0
  69. data/app/views/railswatch/shared/_page_header.html.erb +23 -0
  70. data/config/routes.rb +27 -0
  71. data/lib/generators/railswatch/install/USAGE +19 -0
  72. data/lib/generators/railswatch/install/install_generator.rb +46 -0
  73. data/lib/generators/railswatch/install/templates/create_railswatch_tables.rb +140 -0
  74. data/lib/generators/railswatch/install/templates/initializer.rb +87 -0
  75. data/lib/railswatch/data_source.rb +106 -0
  76. data/lib/railswatch/engine.rb +103 -0
  77. data/lib/railswatch/events/record.rb +63 -0
  78. data/lib/railswatch/extensions/trace.rb +14 -0
  79. data/lib/railswatch/extensions/trace_db.rb +21 -0
  80. data/lib/railswatch/gems/custom_ext.rb +38 -0
  81. data/lib/railswatch/gems/delayed_job_ext.rb +70 -0
  82. data/lib/railswatch/gems/grape_ext.rb +64 -0
  83. data/lib/railswatch/gems/rake_ext.rb +69 -0
  84. data/lib/railswatch/gems/sidekiq_ext.rb +55 -0
  85. data/lib/railswatch/instrument/metrics_collector.rb +70 -0
  86. data/lib/railswatch/interface.rb +9 -0
  87. data/lib/railswatch/models/application_record.rb +31 -0
  88. data/lib/railswatch/models/base_record.rb +59 -0
  89. data/lib/railswatch/models/collection.rb +37 -0
  90. data/lib/railswatch/models/custom_record.rb +32 -0
  91. data/lib/railswatch/models/delayed_job_record.rb +39 -0
  92. data/lib/railswatch/models/event_record.rb +11 -0
  93. data/lib/railswatch/models/grape_record.rb +61 -0
  94. data/lib/railswatch/models/rake_record.rb +33 -0
  95. data/lib/railswatch/models/request_record.rb +105 -0
  96. data/lib/railswatch/models/resource_record.rb +33 -0
  97. data/lib/railswatch/models/sidekiq_record.rb +41 -0
  98. data/lib/railswatch/models/trace_record.rb +21 -0
  99. data/lib/railswatch/pruner.rb +47 -0
  100. data/lib/railswatch/rails/middleware.rb +117 -0
  101. data/lib/railswatch/rails/query_builder.rb +20 -0
  102. data/lib/railswatch/reports/annotations_report.rb +13 -0
  103. data/lib/railswatch/reports/base_report.rb +48 -0
  104. data/lib/railswatch/reports/breakdown_report.rb +11 -0
  105. data/lib/railswatch/reports/crash_report.rb +11 -0
  106. data/lib/railswatch/reports/overview_report.rb +88 -0
  107. data/lib/railswatch/reports/percentile_report.rb +16 -0
  108. data/lib/railswatch/reports/recent_requests_report.rb +21 -0
  109. data/lib/railswatch/reports/requests_report.rb +75 -0
  110. data/lib/railswatch/reports/resources_report.rb +42 -0
  111. data/lib/railswatch/reports/response_time_report.rb +27 -0
  112. data/lib/railswatch/reports/slow_requests_report.rb +21 -0
  113. data/lib/railswatch/reports/throughput_report.rb +16 -0
  114. data/lib/railswatch/reports/trace_report.rb +17 -0
  115. data/lib/railswatch/system_monitor/resources_monitor.rb +88 -0
  116. data/lib/railswatch/thread/current_request.rb +37 -0
  117. data/lib/railswatch/utils.rb +58 -0
  118. data/lib/railswatch/version.rb +7 -0
  119. data/lib/railswatch/widgets/base.rb +17 -0
  120. data/lib/railswatch/widgets/card.rb +19 -0
  121. data/lib/railswatch/widgets/chart.rb +33 -0
  122. data/lib/railswatch/widgets/crashes_table.rb +27 -0
  123. data/lib/railswatch/widgets/custom_events_table.rb +48 -0
  124. data/lib/railswatch/widgets/delayed_job_table.rb +31 -0
  125. data/lib/railswatch/widgets/grape_requests_table.rb +31 -0
  126. data/lib/railswatch/widgets/percentile_card.rb +23 -0
  127. data/lib/railswatch/widgets/rake_tasks_table.rb +31 -0
  128. data/lib/railswatch/widgets/recent_requests_table.rb +35 -0
  129. data/lib/railswatch/widgets/requests_table.rb +27 -0
  130. data/lib/railswatch/widgets/resource_chart.rb +116 -0
  131. data/lib/railswatch/widgets/response_time_chart.rb +29 -0
  132. data/lib/railswatch/widgets/sidekiq_jobs_table.rb +31 -0
  133. data/lib/railswatch/widgets/slow_requests_table.rb +33 -0
  134. data/lib/railswatch/widgets/table.rb +43 -0
  135. data/lib/railswatch/widgets/throughput_chart.rb +29 -0
  136. data/lib/railswatch.rb +184 -0
  137. data/lib/tasks/railswatch.rake +9 -0
  138. metadata +445 -0
@@ -0,0 +1,960 @@
1
+ :root {
2
+ color-scheme: light;
3
+ --rm-font-heading: "Space Grotesk", "IBM Plex Sans", sans-serif;
4
+ --rm-font-body: "IBM Plex Sans", sans-serif;
5
+ --rm-font-mono: "IBM Plex Mono", monospace;
6
+ --rm-bg: #f5f7fb;
7
+ --rm-bg-elevated: rgba(255, 255, 255, 0.82);
8
+ --rm-surface: rgba(255, 255, 255, 0.9);
9
+ --rm-surface-strong: #ffffff;
10
+ --rm-border: rgba(25, 37, 62, 0.1);
11
+ --rm-border-strong: rgba(25, 37, 62, 0.16);
12
+ --rm-text: #0f172a;
13
+ --rm-text-soft: #475569;
14
+ --rm-text-faint: #64748b;
15
+ --rm-accent: #0f766e;
16
+ --rm-accent-2: #2563eb;
17
+ --rm-danger: #dc2626;
18
+ --rm-warning: #d97706;
19
+ --rm-success: #0f766e;
20
+ --rm-shadow: 0 24px 70px rgba(15, 23, 42, 0.1);
21
+ --rm-shadow-soft: 0 12px 32px rgba(15, 23, 42, 0.08);
22
+ --rm-chart-grid: rgba(148, 163, 184, 0.16);
23
+ --rm-chart-text: #64748b;
24
+ --rm-chart-surface: rgba(255, 255, 255, 0.94);
25
+ --rm-backdrop-line: rgba(148, 163, 184, 0.14);
26
+ }
27
+
28
+ html[data-theme="dark"] {
29
+ color-scheme: dark;
30
+ --rm-bg: #07111f;
31
+ --rm-bg-elevated: rgba(8, 15, 30, 0.82);
32
+ --rm-surface: rgba(12, 22, 39, 0.9);
33
+ --rm-surface-strong: #0f1b30;
34
+ --rm-border: rgba(148, 163, 184, 0.14);
35
+ --rm-border-strong: rgba(148, 163, 184, 0.22);
36
+ --rm-text: #e2e8f0;
37
+ --rm-text-soft: #cbd5e1;
38
+ --rm-text-faint: #94a3b8;
39
+ --rm-accent: #34d399;
40
+ --rm-accent-2: #60a5fa;
41
+ --rm-danger: #f87171;
42
+ --rm-warning: #fbbf24;
43
+ --rm-success: #34d399;
44
+ --rm-shadow: 0 30px 80px rgba(2, 6, 23, 0.45);
45
+ --rm-shadow-soft: 0 16px 40px rgba(2, 6, 23, 0.35);
46
+ --rm-chart-grid: rgba(148, 163, 184, 0.12);
47
+ --rm-chart-text: #94a3b8;
48
+ --rm-chart-surface: rgba(15, 23, 42, 0.96);
49
+ --rm-backdrop-line: rgba(148, 163, 184, 0.1);
50
+ }
51
+
52
+ html,
53
+ body {
54
+ background: var(--rm-bg);
55
+ color: var(--rm-text);
56
+ font-family: var(--rm-font-body);
57
+ }
58
+
59
+ body.rm-body {
60
+ position: relative;
61
+ overflow-x: hidden;
62
+ }
63
+
64
+ a {
65
+ color: inherit;
66
+ }
67
+
68
+ .rm-backdrop {
69
+ position: fixed;
70
+ inset: 0;
71
+ z-index: 0;
72
+ pointer-events: none;
73
+ overflow: hidden;
74
+ }
75
+
76
+ .rm-backdrop__orb {
77
+ position: absolute;
78
+ border-radius: 999px;
79
+ filter: blur(10px);
80
+ opacity: 0.6;
81
+ }
82
+
83
+ .rm-backdrop__orb--one {
84
+ top: -6rem;
85
+ left: -4rem;
86
+ width: 22rem;
87
+ height: 22rem;
88
+ background: radial-gradient(circle, rgba(37, 99, 235, 0.22) 0%, rgba(37, 99, 235, 0) 72%);
89
+ }
90
+
91
+ .rm-backdrop__orb--two {
92
+ right: -5rem;
93
+ top: 8rem;
94
+ width: 24rem;
95
+ height: 24rem;
96
+ background: radial-gradient(circle, rgba(15, 118, 110, 0.18) 0%, rgba(15, 118, 110, 0) 72%);
97
+ }
98
+
99
+ .rm-backdrop__grid {
100
+ position: absolute;
101
+ inset: 0;
102
+ background-image:
103
+ linear-gradient(var(--rm-backdrop-line) 1px, transparent 1px),
104
+ linear-gradient(90deg, var(--rm-backdrop-line) 1px, transparent 1px);
105
+ background-size: 48px 48px;
106
+ mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.4), transparent 85%);
107
+ }
108
+
109
+ .main-content.rm-main-content,
110
+ .rm-footer {
111
+ position: relative;
112
+ z-index: 1;
113
+ }
114
+
115
+ .rm-shell {
116
+ padding: 1.5rem;
117
+ }
118
+
119
+ .rm-container {
120
+ max-width: 1600px;
121
+ }
122
+
123
+ .rm-content-shell {
124
+ display: grid;
125
+ gap: 1rem;
126
+ }
127
+
128
+ .rm-topbar {
129
+ position: sticky;
130
+ top: 1rem;
131
+ z-index: 10;
132
+ display: grid;
133
+ grid-template-columns: 1fr auto;
134
+ gap: 1rem;
135
+ padding: 1rem 1.2rem;
136
+ margin-bottom: 1rem;
137
+ background: var(--rm-bg-elevated);
138
+ border: 1px solid var(--rm-border);
139
+ border-radius: 1.6rem;
140
+ box-shadow: var(--rm-shadow-soft);
141
+ backdrop-filter: blur(18px);
142
+ }
143
+
144
+ .rm-topbar__brand,
145
+ .rm-topbar__controls {
146
+ display: flex;
147
+ align-items: center;
148
+ gap: 0.75rem;
149
+ }
150
+
151
+ .rm-topbar__controls {
152
+ justify-content: flex-end;
153
+ flex-wrap: wrap;
154
+ }
155
+
156
+ .rm-brand {
157
+ display: inline-flex;
158
+ align-items: center;
159
+ gap: 0.85rem;
160
+ text-decoration: none;
161
+ color: inherit;
162
+ }
163
+
164
+ .rm-brand:hover {
165
+ color: inherit;
166
+ }
167
+
168
+ .rm-brand__mark {
169
+ display: inline-flex;
170
+ align-items: center;
171
+ justify-content: center;
172
+ width: 2.7rem;
173
+ height: 2.7rem;
174
+ border-radius: 1rem;
175
+ background: linear-gradient(135deg, var(--rm-accent-2), var(--rm-accent));
176
+ color: white;
177
+ font-family: var(--rm-font-heading);
178
+ font-size: 0.95rem;
179
+ letter-spacing: 0.08em;
180
+ }
181
+
182
+ .rm-brand__copy {
183
+ display: flex;
184
+ flex-direction: column;
185
+ line-height: 1.1;
186
+ }
187
+
188
+ .rm-brand__copy strong {
189
+ font-family: var(--rm-font-heading);
190
+ font-size: 1.1rem;
191
+ }
192
+
193
+ .rm-brand__copy small {
194
+ color: var(--rm-text-faint);
195
+ font-size: 0.78rem;
196
+ }
197
+
198
+ .rm-nav {
199
+ grid-column: 1 / -1;
200
+ display: flex;
201
+ gap: 0.55rem;
202
+ flex-wrap: wrap;
203
+ }
204
+
205
+ .rm-nav__link {
206
+ display: inline-flex;
207
+ align-items: center;
208
+ min-height: 2.4rem;
209
+ padding: 0.55rem 0.9rem;
210
+ border-radius: 999px;
211
+ background: transparent;
212
+ border: 1px solid transparent;
213
+ color: var(--rm-text-soft);
214
+ font-weight: 500;
215
+ text-decoration: none;
216
+ transition: all 0.2s ease;
217
+ }
218
+
219
+ .rm-nav__link:hover,
220
+ .rm-nav__link.is-active {
221
+ color: var(--rm-text);
222
+ background: rgba(37, 99, 235, 0.09);
223
+ border-color: rgba(37, 99, 235, 0.18);
224
+ }
225
+
226
+ .rm-nav__link.is-active {
227
+ box-shadow: inset 0 0 0 1px rgba(37, 99, 235, 0.12);
228
+ }
229
+
230
+ .rm-icon-button {
231
+ display: inline-flex;
232
+ align-items: center;
233
+ gap: 0.5rem;
234
+ min-height: 2.6rem;
235
+ padding: 0.55rem 0.85rem;
236
+ border: 1px solid var(--rm-border);
237
+ border-radius: 999px;
238
+ background: var(--rm-surface);
239
+ color: var(--rm-text);
240
+ box-shadow: var(--rm-shadow-soft);
241
+ cursor: pointer;
242
+ text-decoration: none;
243
+ transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease;
244
+ }
245
+
246
+ .rm-icon-button:hover {
247
+ transform: translateY(-1px);
248
+ border-color: var(--rm-border-strong);
249
+ }
250
+
251
+ .rm-icon-button svg {
252
+ width: 1rem;
253
+ height: 1rem;
254
+ }
255
+
256
+ .rm-icon-button--ghost {
257
+ box-shadow: none;
258
+ }
259
+
260
+ .rm-mobile-nav-toggle {
261
+ display: none;
262
+ padding-inline: 0.8rem;
263
+ }
264
+
265
+ .rm-mobile-nav-toggle__line {
266
+ display: block;
267
+ width: 1rem;
268
+ height: 2px;
269
+ margin: 2px 0;
270
+ border-radius: 999px;
271
+ background: currentColor;
272
+ }
273
+
274
+ .rm-theme-toggle__icon {
275
+ display: inline-flex;
276
+ align-items: center;
277
+ }
278
+
279
+ html[data-theme="light"] .rm-theme-toggle__icon--moon,
280
+ html[data-theme="dark"] .rm-theme-toggle__icon--sun {
281
+ display: none;
282
+ }
283
+
284
+ .rm-page-header {
285
+ display: flex;
286
+ justify-content: space-between;
287
+ gap: 1rem;
288
+ align-items: flex-start;
289
+ padding: 1.5rem 1.6rem;
290
+ background: var(--rm-bg-elevated);
291
+ border: 1px solid var(--rm-border);
292
+ border-radius: 1.75rem;
293
+ box-shadow: var(--rm-shadow-soft);
294
+ backdrop-filter: blur(18px);
295
+ }
296
+
297
+ .rm-page-header__copy {
298
+ max-width: 58rem;
299
+ }
300
+
301
+ .rm-page-header__eyebrow,
302
+ .rm-section-heading__eyebrow {
303
+ margin-bottom: 0.35rem;
304
+ color: var(--rm-accent);
305
+ font-family: var(--rm-font-mono);
306
+ font-size: 0.78rem;
307
+ letter-spacing: 0.08em;
308
+ text-transform: uppercase;
309
+ }
310
+
311
+ .rm-page-header__title-row {
312
+ display: flex;
313
+ gap: 0.75rem;
314
+ align-items: center;
315
+ flex-wrap: wrap;
316
+ }
317
+
318
+ .rm-page-header__title,
319
+ .rm-section-heading__title,
320
+ .rm-resource-group__title {
321
+ margin: 0;
322
+ color: var(--rm-text);
323
+ font-family: var(--rm-font-heading);
324
+ font-size: clamp(1.6rem, 3vw, 2.5rem);
325
+ line-height: 1.05;
326
+ }
327
+
328
+ .rm-page-header__description,
329
+ .rm-widget-card__description,
330
+ .rm-empty-state,
331
+ .rm-section-heading__meta {
332
+ margin: 0.55rem 0 0;
333
+ color: var(--rm-text-faint);
334
+ font-size: 0.96rem;
335
+ line-height: 1.6;
336
+ }
337
+
338
+ .rm-page-header__actions {
339
+ flex-shrink: 0;
340
+ }
341
+
342
+ .rm-badge {
343
+ display: inline-flex;
344
+ align-items: center;
345
+ padding: 0.35rem 0.7rem;
346
+ border-radius: 999px;
347
+ background: rgba(15, 118, 110, 0.12);
348
+ color: var(--rm-success);
349
+ font-family: var(--rm-font-mono);
350
+ font-size: 0.75rem;
351
+ letter-spacing: 0.06em;
352
+ text-transform: uppercase;
353
+ }
354
+
355
+ .rm-surface {
356
+ background: var(--rm-surface);
357
+ border: 1px solid var(--rm-border);
358
+ border-radius: 1.5rem;
359
+ box-shadow: var(--rm-shadow);
360
+ backdrop-filter: blur(18px);
361
+ }
362
+
363
+ .rm-overview-grid {
364
+ display: grid;
365
+ grid-template-columns: minmax(0, 1.35fr) minmax(300px, 0.8fr);
366
+ gap: 1rem;
367
+ }
368
+
369
+ .rm-overview-panel {
370
+ padding: 1.3rem;
371
+ }
372
+
373
+ .rm-overview-panel--metrics {
374
+ grid-column: 1 / -1;
375
+ }
376
+
377
+ .rm-section-heading {
378
+ display: flex;
379
+ justify-content: space-between;
380
+ gap: 1rem;
381
+ align-items: flex-start;
382
+ margin-bottom: 1rem;
383
+ }
384
+
385
+ .rm-section-heading__title {
386
+ font-size: 1.35rem;
387
+ }
388
+
389
+ .rm-kpi-grid {
390
+ display: grid;
391
+ grid-template-columns: repeat(5, minmax(0, 1fr));
392
+ gap: 0.85rem;
393
+ }
394
+
395
+ .rm-kpi-card,
396
+ .rm-metric-card {
397
+ position: relative;
398
+ overflow: hidden;
399
+ }
400
+
401
+ .rm-kpi-card {
402
+ display: flex;
403
+ flex-direction: column;
404
+ gap: 0.45rem;
405
+ padding: 1.1rem;
406
+ border-radius: 1.25rem;
407
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.28), rgba(255, 255, 255, 0.08));
408
+ border: 1px solid var(--rm-border);
409
+ }
410
+
411
+ .rm-kpi-card::after,
412
+ .rm-metric-card::after {
413
+ content: "";
414
+ position: absolute;
415
+ inset: auto -2rem -2rem auto;
416
+ width: 7rem;
417
+ height: 7rem;
418
+ border-radius: 999px;
419
+ opacity: 0.08;
420
+ background: currentColor;
421
+ }
422
+
423
+ .rm-kpi-card[data-tone="healthy"],
424
+ .rm-metric-card[data-tone="healthy"] {
425
+ color: var(--rm-success);
426
+ }
427
+
428
+ .rm-kpi-card[data-tone="warning"],
429
+ .rm-metric-card[data-tone="warning"] {
430
+ color: var(--rm-warning);
431
+ }
432
+
433
+ .rm-kpi-card[data-tone="critical"],
434
+ .rm-metric-card[data-tone="critical"] {
435
+ color: var(--rm-danger);
436
+ }
437
+
438
+ .rm-kpi-card[data-tone="neutral"],
439
+ .rm-metric-card[data-tone="neutral"] {
440
+ color: var(--rm-accent-2);
441
+ }
442
+
443
+ .rm-kpi-card__label,
444
+ .rm-metric-card__label,
445
+ .rm-insight-item__label {
446
+ font-family: var(--rm-font-mono);
447
+ font-size: 0.78rem;
448
+ letter-spacing: 0.05em;
449
+ text-transform: uppercase;
450
+ }
451
+
452
+ .rm-kpi-card__value,
453
+ .rm-metric-card__value,
454
+ .rm-insight-item__value,
455
+ .rm-ranking-item__value {
456
+ color: var(--rm-text);
457
+ font-family: var(--rm-font-heading);
458
+ font-size: clamp(1.2rem, 2vw, 2rem);
459
+ line-height: 1.05;
460
+ }
461
+
462
+ .rm-kpi-card__meta,
463
+ .rm-metric-card__caption,
464
+ .rm-insight-item__meta,
465
+ .rm-ranking-item__meta {
466
+ color: var(--rm-text-faint);
467
+ font-size: 0.9rem;
468
+ }
469
+
470
+ .rm-insight-list,
471
+ .rm-ranking-list {
472
+ display: grid;
473
+ gap: 0.75rem;
474
+ }
475
+
476
+ .rm-insight-item,
477
+ .rm-ranking-item {
478
+ display: grid;
479
+ gap: 0.25rem;
480
+ padding: 1rem 1.05rem;
481
+ border-radius: 1.1rem;
482
+ background: rgba(255, 255, 255, 0.04);
483
+ border: 1px solid var(--rm-border);
484
+ }
485
+
486
+ .rm-ranking-item {
487
+ grid-template-columns: auto minmax(0, 1fr) auto;
488
+ align-items: center;
489
+ gap: 0.85rem;
490
+ }
491
+
492
+ .rm-ranking-item__index {
493
+ display: inline-flex;
494
+ align-items: center;
495
+ justify-content: center;
496
+ width: 2rem;
497
+ height: 2rem;
498
+ border-radius: 999px;
499
+ background: rgba(37, 99, 235, 0.12);
500
+ color: var(--rm-accent-2);
501
+ font-family: var(--rm-font-mono);
502
+ }
503
+
504
+ .rm-ranking-item__title {
505
+ color: var(--rm-text);
506
+ }
507
+
508
+ .row,
509
+ .rm-resource-grid {
510
+ display: grid;
511
+ grid-template-columns: repeat(12, minmax(0, 1fr));
512
+ gap: 1rem;
513
+ }
514
+
515
+ .rm-widget-stack {
516
+ display: grid;
517
+ gap: 1rem;
518
+ }
519
+
520
+ .row > .widget {
521
+ grid-column: span 12;
522
+ min-width: 0;
523
+ }
524
+
525
+ .row > .widget:only-child {
526
+ grid-column: 1 / -1;
527
+ }
528
+
529
+ .row > .widget:nth-child(1):nth-last-child(2),
530
+ .row > .widget:nth-child(2):nth-last-child(1),
531
+ .rm-resource-grid > * {
532
+ grid-column: span 6;
533
+ }
534
+
535
+ .row > .widget:nth-child(1):nth-last-child(3),
536
+ .row > .widget:nth-child(2):nth-last-child(2),
537
+ .row > .widget:nth-child(3):nth-last-child(1) {
538
+ grid-column: span 4;
539
+ }
540
+
541
+ .rm-widget-card {
542
+ height: 100%;
543
+ padding: 1.2rem;
544
+ }
545
+
546
+ .rm-widget-card--table {
547
+ margin-bottom: 1rem;
548
+ }
549
+
550
+ .rm-widget-card__header {
551
+ display: flex;
552
+ justify-content: space-between;
553
+ gap: 1rem;
554
+ align-items: flex-start;
555
+ margin-bottom: 1rem;
556
+ }
557
+
558
+ .rm-widget-card__actions,
559
+ .rm-widget-card__toolbar {
560
+ display: flex;
561
+ align-items: center;
562
+ gap: 0.75rem;
563
+ }
564
+
565
+ .rm-widget-card__title {
566
+ margin: 0;
567
+ color: var(--rm-text);
568
+ font-family: var(--rm-font-heading);
569
+ font-size: 1.15rem;
570
+ }
571
+
572
+ .rm-widget-card__body {
573
+ min-width: 0;
574
+ }
575
+
576
+ .rm-metric-card {
577
+ display: flex;
578
+ flex-direction: column;
579
+ justify-content: flex-end;
580
+ gap: 0.55rem;
581
+ min-height: 12rem;
582
+ }
583
+
584
+ .rm-table-shell {
585
+ overflow: auto;
586
+ border-radius: 1rem;
587
+ border: 1px solid var(--rm-border);
588
+ }
589
+
590
+ table {
591
+ width: 100%;
592
+ margin: 0;
593
+ background: transparent !important;
594
+ color: var(--rm-text) !important;
595
+ }
596
+
597
+ table thead th {
598
+ position: sticky;
599
+ top: 0;
600
+ z-index: 1;
601
+ background: var(--rm-surface-strong) !important;
602
+ color: var(--rm-text-soft) !important;
603
+ border-bottom: 1px solid var(--rm-border) !important;
604
+ font-size: 0.78rem;
605
+ text-transform: uppercase;
606
+ letter-spacing: 0.05em;
607
+ }
608
+
609
+ table tbody td {
610
+ border-color: var(--rm-border) !important;
611
+ vertical-align: middle !important;
612
+ color: var(--rm-text-soft);
613
+ }
614
+
615
+ table tbody tr:hover,
616
+ table tbody tr:hover td {
617
+ background-color: rgba(37, 99, 235, 0.06) !important;
618
+ }
619
+
620
+ table th[data-sort] {
621
+ cursor: pointer;
622
+ }
623
+
624
+ .attention {
625
+ background: rgba(217, 119, 6, 0.08) !important;
626
+ }
627
+
628
+ .nowrap {
629
+ white-space: nowrap;
630
+ }
631
+
632
+ .very-small-text {
633
+ font-size: 0.7rem;
634
+ line-height: 1.45;
635
+ }
636
+
637
+ .stats_icon svg,
638
+ .stats_icon_max svg,
639
+ .home_icon svg,
640
+ .download_icon svg,
641
+ .external_icon svg,
642
+ .details_icon svg,
643
+ .user-agent-icon svg {
644
+ width: 1rem;
645
+ height: 1rem;
646
+ }
647
+
648
+ .stats_icon_max,
649
+ .details_icon,
650
+ .user-agent-icon,
651
+ .download_icon,
652
+ .external_icon {
653
+ display: inline-flex;
654
+ align-items: center;
655
+ justify-content: center;
656
+ }
657
+
658
+ .details_icon {
659
+ width: 1rem;
660
+ height: 1rem;
661
+ color: var(--rm-accent-2);
662
+ }
663
+
664
+ .with_external_icon {
665
+ display: inline-flex;
666
+ align-items: center;
667
+ gap: 0.25rem;
668
+ }
669
+
670
+ .external_icon {
671
+ opacity: 0;
672
+ color: var(--rm-text-faint);
673
+ transition: opacity 0.2s ease;
674
+ }
675
+
676
+ .with_external_icon:hover .external_icon {
677
+ opacity: 1;
678
+ }
679
+
680
+ .user-agent-icon-user svg path {
681
+ fill: var(--rm-success) !important;
682
+ }
683
+
684
+ railswatch-chart:not(:defined) {
685
+ display: block;
686
+ opacity: 0;
687
+ }
688
+
689
+ .chart {
690
+ min-height: 320px;
691
+ }
692
+
693
+ .chart_mini {
694
+ min-height: 220px;
695
+ }
696
+
697
+ .rm-toggle {
698
+ display: inline-flex;
699
+ align-items: center;
700
+ gap: 0.55rem;
701
+ padding: 0.45rem 0.7rem;
702
+ border-radius: 999px;
703
+ background: rgba(37, 99, 235, 0.07);
704
+ color: var(--rm-text-soft);
705
+ cursor: pointer;
706
+ }
707
+
708
+ .rm-toggle input {
709
+ accent-color: var(--rm-accent-2);
710
+ }
711
+
712
+ .rm-panel-section-title {
713
+ margin: 1.1rem 0 0.75rem;
714
+ color: var(--rm-text);
715
+ font-family: var(--rm-font-heading);
716
+ font-size: 1.05rem;
717
+ }
718
+
719
+ .loader-wrapper {
720
+ position: fixed;
721
+ inset: 0;
722
+ display: none;
723
+ justify-content: center;
724
+ align-items: center;
725
+ background: rgba(15, 23, 42, 0.18);
726
+ backdrop-filter: blur(4px);
727
+ z-index: 60;
728
+ }
729
+
730
+ .loader-wrapper.is-active {
731
+ display: flex;
732
+ }
733
+
734
+ .loader-wrapper .loader {
735
+ width: 4rem;
736
+ height: 4rem;
737
+ }
738
+
739
+ body.has-sticky-footer {
740
+ display: flex;
741
+ min-height: 100vh;
742
+ flex-direction: column;
743
+ }
744
+
745
+ .main-content {
746
+ flex: 1;
747
+ }
748
+
749
+ .rm-footer {
750
+ background: transparent;
751
+ padding: 1rem 1.5rem 1.6rem;
752
+ }
753
+
754
+ .rm-footer__inner {
755
+ display: flex;
756
+ justify-content: space-between;
757
+ gap: 1rem;
758
+ align-items: center;
759
+ padding: 0.9rem 1.2rem;
760
+ color: var(--rm-text-faint);
761
+ background: var(--rm-bg-elevated);
762
+ border: 1px solid var(--rm-border);
763
+ border-radius: 1.25rem;
764
+ box-shadow: var(--rm-shadow-soft);
765
+ }
766
+
767
+ .rm-footer a {
768
+ color: var(--rm-text-soft);
769
+ text-decoration: underline;
770
+ }
771
+
772
+ .rm-resource-group {
773
+ display: grid;
774
+ gap: 0.9rem;
775
+ }
776
+
777
+ .rm-resource-group__header {
778
+ padding-top: 0.25rem;
779
+ }
780
+
781
+ .is-size-8,
782
+ .is-size-8 * {
783
+ font-size: 0.72rem !important;
784
+ line-height: 1.6 !important;
785
+ }
786
+
787
+ .is-size-9,
788
+ .is-size-9 * {
789
+ font-size: 0.65rem !important;
790
+ line-height: 1.45 !important;
791
+ }
792
+
793
+ .is-sr-only {
794
+ position: absolute;
795
+ width: 1px;
796
+ height: 1px;
797
+ padding: 0;
798
+ margin: -1px;
799
+ overflow: hidden;
800
+ clip: rect(0, 0, 0, 0);
801
+ white-space: nowrap;
802
+ border: 0;
803
+ }
804
+
805
+ /* ── Bulma overrides: pre / code ─────────────────────── */
806
+ pre {
807
+ background: var(--rm-surface-strong) !important;
808
+ color: var(--rm-text) !important;
809
+ border-color: var(--rm-border) !important;
810
+ }
811
+
812
+ pre code {
813
+ background: transparent !important;
814
+ color: inherit !important;
815
+ }
816
+
817
+ code:not(pre code) {
818
+ background: var(--rm-surface-strong) !important;
819
+ color: var(--rm-accent) !important;
820
+ border-color: var(--rm-border) !important;
821
+ }
822
+
823
+ /* ── Bulma overrides: dark-mode badge / background helpers ── */
824
+ html[data-theme="dark"] .has-background-success {
825
+ background-color: rgba(52, 211, 153, 0.14) !important;
826
+ color: var(--rm-success) !important;
827
+ }
828
+
829
+ html[data-theme="dark"] .has-background-warning {
830
+ background-color: rgba(251, 191, 36, 0.14) !important;
831
+ color: var(--rm-warning) !important;
832
+ }
833
+
834
+ html[data-theme="dark"] .has-background-danger {
835
+ background-color: rgba(248, 113, 113, 0.14) !important;
836
+ color: var(--rm-danger) !important;
837
+ }
838
+
839
+ html[data-theme="dark"] .has-background-light {
840
+ background-color: rgba(148, 163, 184, 0.1) !important;
841
+ color: var(--rm-text-soft) !important;
842
+ }
843
+
844
+ html[data-theme="dark"] .has-text-white-bis,
845
+ html[data-theme="dark"] .has-text-black-ter {
846
+ color: var(--rm-text) !important;
847
+ }
848
+
849
+ /* ── Bulma overrides: plain .tag in dark mode ────────── */
850
+ html[data-theme="dark"] .tag:not([class*="is-"]) {
851
+ background-color: rgba(148, 163, 184, 0.14) !important;
852
+ color: var(--rm-text) !important;
853
+ }
854
+
855
+ /* ── Bulma overrides: semantic tag colors in dark mode ── */
856
+ html[data-theme="dark"] .tag.is-success {
857
+ background-color: rgba(52, 211, 153, 0.2) !important;
858
+ color: var(--rm-success) !important;
859
+ }
860
+
861
+ html[data-theme="dark"] .tag.is-warning {
862
+ background-color: rgba(251, 191, 36, 0.2) !important;
863
+ color: var(--rm-warning) !important;
864
+ }
865
+
866
+ html[data-theme="dark"] .tag.is-danger {
867
+ background-color: rgba(248, 113, 113, 0.22) !important;
868
+ color: var(--rm-danger) !important;
869
+ }
870
+
871
+ html[data-theme="dark"] .tag.is-info,
872
+ html[data-theme="dark"] .tag.is-info.is-light {
873
+ background-color: rgba(96, 165, 250, 0.18) !important;
874
+ color: var(--rm-accent-2) !important;
875
+ }
876
+
877
+ /* ── Bulma overrides: table row hover in dark mode ─────── */
878
+ html[data-theme="dark"] table tbody tr:hover,
879
+ html[data-theme="dark"] table tbody tr:hover td {
880
+ background-color: rgba(148, 163, 184, 0.1) !important;
881
+ }
882
+
883
+ /* ── Bulma overrides: table link colors ─────────────────── */
884
+ table tbody a {
885
+ color: var(--rm-text-soft);
886
+ }
887
+
888
+ table tbody a:hover {
889
+ color: var(--rm-text) !important;
890
+ }
891
+
892
+ /* ── Request context panel ───────────────────────────────── */
893
+ .rm-context-grid {
894
+ display: flex;
895
+ flex-direction: column;
896
+ gap: 0.4rem;
897
+ margin-bottom: 0.5rem;
898
+ }
899
+
900
+ .rm-context-row {
901
+ display: flex;
902
+ gap: 0.75rem;
903
+ align-items: baseline;
904
+ font-size: 0.8rem;
905
+ }
906
+
907
+ .rm-context-label {
908
+ flex-shrink: 0;
909
+ width: 6rem;
910
+ font-weight: 600;
911
+ color: var(--rm-text-faint);
912
+ font-family: var(--rm-font-mono);
913
+ font-size: 0.72rem;
914
+ text-transform: uppercase;
915
+ letter-spacing: 0.04em;
916
+ }
917
+
918
+ .rm-context-value {
919
+ color: var(--rm-text);
920
+ word-break: break-word;
921
+ display: flex;
922
+ flex-wrap: wrap;
923
+ gap: 0.4rem;
924
+ }
925
+
926
+ .rm-context-ua {
927
+ color: var(--rm-text-soft);
928
+ font-size: 0.75rem;
929
+ }
930
+
931
+ .rm-context-kv {
932
+ background: var(--rm-surface);
933
+ border: 1px solid var(--rm-border);
934
+ border-radius: 0.35rem;
935
+ padding: 0.1rem 0.4rem;
936
+ font-family: var(--rm-font-mono);
937
+ font-size: 0.72rem;
938
+ white-space: nowrap;
939
+ }
940
+
941
+ /* ── Backtrace blocks ────────────────────────────────────── */
942
+ .rm-backtrace {
943
+ font-family: var(--rm-font-mono);
944
+ font-size: 0.72rem;
945
+ line-height: 1.55;
946
+ white-space: pre-wrap;
947
+ word-break: break-all;
948
+ background: var(--rm-surface) !important;
949
+ color: var(--rm-text) !important;
950
+ border: 1px solid var(--rm-border) !important;
951
+ border-radius: 0.5rem;
952
+ padding: 0.65rem 0.85rem;
953
+ margin-bottom: 0.5rem;
954
+ }
955
+
956
+ .rm-backtrace-inline {
957
+ max-height: 8rem;
958
+ overflow-y: auto;
959
+ margin-top: 0.3rem;
960
+ }