rails_orbit 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +8 -0
- data/app/assets/stylesheets/rails_orbit/application.css +63 -0
- data/app/controllers/rails_orbit/dashboard_controller.rb +50 -12
- data/app/helpers/rails_orbit/icon_helper.rb +1 -0
- data/app/views/layouts/rails_orbit/application.html.erb +2 -2
- data/app/views/rails_orbit/dashboard/errors.html.erb +35 -3
- data/lib/rails_orbit/backtrace.rb +122 -0
- data/lib/rails_orbit/version.rb +1 -1
- data/lib/rails_orbit.rb +1 -0
- data/public/assets/rails_orbit/application.css +63 -0
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f45fb851da082bb63b955526d1adf53dacbbc76938550fb52bf38fb0b7a328b5
|
|
4
|
+
data.tar.gz: 91a250041985e6af4d89e92dbad0597536c3889e33c074ee19082e7cb6bac950
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5c4df5011c201fc5f641199f5a752d8ab93e340bafc59daa5b20273660ec041f17083dcb9d1b788397ed4803ccd47f00b5ff8ca39208a9443d044c683f3173f8
|
|
7
|
+
data.tar.gz: e2a4ec065bc47db14bfecdcf757c89ff243cefc1b5a3a3a8b1ab9f4d80f20496c7792a858189d519b95812c02872d7a761f71580e644c3c653036297ff3c6cc7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.0] - 2026-06-10
|
|
4
|
+
|
|
5
|
+
- Errors page now shows the `file:line in method` where each exception originated, taken from the first application frame of its most recent occurrence
|
|
6
|
+
- Errors page now shows an expandable full backtrace with application frames highlighted and gem frames shortened
|
|
7
|
+
- Dashboard CSS/JS asset URLs are versioned so browsers fetch fresh assets after an upgrade instead of serving a stale cached copy
|
|
8
|
+
- Dashboard icons now carry intrinsic dimensions so they stay correctly sized even if the stylesheet loads late
|
|
9
|
+
|
|
3
10
|
## [0.1.0] - 2026-03-24
|
|
4
11
|
|
|
5
12
|
- Initial release
|
data/README.md
CHANGED
|
@@ -52,6 +52,12 @@ export ORBIT_PASSWORD=secret
|
|
|
52
52
|
|
|
53
53
|
Visit `/orbit` in your browser. That is it.
|
|
54
54
|
|
|
55
|
+
### Screenshot
|
|
56
|
+
|
|
57
|
+
Overview with the range picker and interactive charts (example from a demo workload):
|
|
58
|
+
|
|
59
|
+

|
|
60
|
+
|
|
55
61
|
### Useful Commands
|
|
56
62
|
|
|
57
63
|
```bash
|
|
@@ -103,6 +109,8 @@ Exceptions grouped by class for faster triage:
|
|
|
103
109
|
|
|
104
110
|
- Each exception class shows occurrence count and "last seen" time
|
|
105
111
|
- Up to 5 recent messages displayed per group
|
|
112
|
+
- **Error location** — the exact `file:line in method` where each exception originated, taken from the first application frame of its most recent occurrence
|
|
113
|
+
- **Full traceback** — expand any error to see the complete backtrace, with application frames highlighted and gem frames de-emphasized and shortened
|
|
106
114
|
- Resolved status shown if solid_errors supports it
|
|
107
115
|
- Scoped to the selected date range
|
|
108
116
|
|
|
@@ -467,6 +467,69 @@
|
|
|
467
467
|
color: var(--orbit-text-muted);
|
|
468
468
|
}
|
|
469
469
|
|
|
470
|
+
/* ── Error message, location & traceback ─────────────────── */
|
|
471
|
+
.orbit-error__message {
|
|
472
|
+
color: var(--orbit-text);
|
|
473
|
+
word-break: break-word;
|
|
474
|
+
}
|
|
475
|
+
.orbit-error__location {
|
|
476
|
+
display: flex;
|
|
477
|
+
align-items: center;
|
|
478
|
+
gap: 0.35rem;
|
|
479
|
+
margin-top: 0.3rem;
|
|
480
|
+
font-family: var(--orbit-mono);
|
|
481
|
+
font-size: 0.72rem;
|
|
482
|
+
color: var(--orbit-text-muted);
|
|
483
|
+
word-break: break-all;
|
|
484
|
+
}
|
|
485
|
+
.orbit-error__location-icon {
|
|
486
|
+
width: 0.72rem;
|
|
487
|
+
height: 0.72rem;
|
|
488
|
+
flex-shrink: 0;
|
|
489
|
+
color: var(--orbit-text-muted);
|
|
490
|
+
}
|
|
491
|
+
.orbit-error__path { color: var(--orbit-primary); }
|
|
492
|
+
.orbit-error__line { color: var(--orbit-warning); font-weight: 600; }
|
|
493
|
+
.orbit-error__method { color: var(--orbit-text-muted); }
|
|
494
|
+
|
|
495
|
+
.orbit-trace {
|
|
496
|
+
margin-top: 0.4rem;
|
|
497
|
+
}
|
|
498
|
+
.orbit-trace__summary {
|
|
499
|
+
display: inline-block;
|
|
500
|
+
cursor: pointer;
|
|
501
|
+
font-size: 0.68rem;
|
|
502
|
+
color: var(--orbit-text-muted);
|
|
503
|
+
user-select: none;
|
|
504
|
+
padding: 0.1rem 0;
|
|
505
|
+
}
|
|
506
|
+
.orbit-trace__summary:hover { color: var(--orbit-text); }
|
|
507
|
+
.orbit-trace__frames {
|
|
508
|
+
list-style: none;
|
|
509
|
+
margin: 0.4rem 0 0;
|
|
510
|
+
padding: 0.5rem 0.75rem;
|
|
511
|
+
background: var(--orbit-surface);
|
|
512
|
+
border: 1px solid var(--orbit-border);
|
|
513
|
+
border-radius: var(--orbit-radius);
|
|
514
|
+
max-height: 18rem;
|
|
515
|
+
overflow: auto;
|
|
516
|
+
}
|
|
517
|
+
.orbit-trace__frame {
|
|
518
|
+
font-family: var(--orbit-mono);
|
|
519
|
+
font-size: 0.7rem;
|
|
520
|
+
line-height: 1.7;
|
|
521
|
+
color: var(--orbit-text-muted);
|
|
522
|
+
word-break: break-all;
|
|
523
|
+
opacity: 0.65;
|
|
524
|
+
}
|
|
525
|
+
.orbit-trace__frame--app {
|
|
526
|
+
color: var(--orbit-text);
|
|
527
|
+
opacity: 1;
|
|
528
|
+
}
|
|
529
|
+
.orbit-trace__frame--app .orbit-trace__file { color: var(--orbit-primary); }
|
|
530
|
+
.orbit-trace__line { color: var(--orbit-warning); }
|
|
531
|
+
.orbit-trace__method { color: var(--orbit-text-muted); }
|
|
532
|
+
|
|
470
533
|
/* ── Empty state ─────────────────────────────────────────── */
|
|
471
534
|
.orbit-empty {
|
|
472
535
|
text-align: center;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
module RailsOrbit
|
|
2
2
|
class DashboardController < ApplicationController
|
|
3
|
+
MAX_ERRORS_PER_GROUP = 5
|
|
4
|
+
|
|
3
5
|
before_action :set_time_range
|
|
4
6
|
|
|
5
7
|
def overview
|
|
@@ -57,25 +59,61 @@ module RailsOrbit
|
|
|
57
59
|
end
|
|
58
60
|
|
|
59
61
|
def errors
|
|
60
|
-
|
|
61
|
-
all_errors = SolidErrors::Error.where(created_at: @time_range.since..).order(created_at: :desc).limit(200)
|
|
62
|
-
@grouped_errors = all_errors.group_by(&:exception_class).map do |klass, records|
|
|
63
|
-
{
|
|
64
|
-
exception_class: klass,
|
|
65
|
-
count: records.size,
|
|
66
|
-
last_seen: records.first.created_at,
|
|
67
|
-
resolved: records.first.respond_to?(:resolved_at) && records.first.resolved_at.present?,
|
|
68
|
-
records: records.first(5),
|
|
69
|
-
}
|
|
70
|
-
end.sort_by { |g| -g[:count] }
|
|
71
|
-
else
|
|
62
|
+
unless defined?(SolidErrors)
|
|
72
63
|
@grouped_errors = []
|
|
73
64
|
flash.now[:warning] = "solid_errors is not installed or not configured."
|
|
65
|
+
return
|
|
74
66
|
end
|
|
67
|
+
|
|
68
|
+
all_errors = SolidErrors::Error
|
|
69
|
+
.where(created_at: @time_range.since..)
|
|
70
|
+
.order(created_at: :desc)
|
|
71
|
+
.limit(200)
|
|
72
|
+
.to_a
|
|
73
|
+
|
|
74
|
+
groups = all_errors.group_by(&:exception_class)
|
|
75
|
+
displayed = groups.values.flat_map { |records| records.first(MAX_ERRORS_PER_GROUP) }
|
|
76
|
+
traces = backtraces_for(displayed)
|
|
77
|
+
|
|
78
|
+
@grouped_errors = groups.map do |klass, records|
|
|
79
|
+
{
|
|
80
|
+
exception_class: klass,
|
|
81
|
+
count: records.size,
|
|
82
|
+
last_seen: records.first.created_at,
|
|
83
|
+
resolved: records.first.respond_to?(:resolved_at) && records.first.resolved_at.present?,
|
|
84
|
+
records: records.first(MAX_ERRORS_PER_GROUP).map { |error| present_error(error, traces[error.id]) },
|
|
85
|
+
}
|
|
86
|
+
end.sort_by { |g| -g[:count] }
|
|
75
87
|
end
|
|
76
88
|
|
|
77
89
|
private
|
|
78
90
|
|
|
91
|
+
def present_error(error, backtrace)
|
|
92
|
+
{
|
|
93
|
+
message: error.message,
|
|
94
|
+
created_at: error.created_at,
|
|
95
|
+
location: backtrace&.top_location,
|
|
96
|
+
frames: backtrace&.frames || [],
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Parses the latest occurrence's backtrace for each displayed error, keyed
|
|
101
|
+
# by error id. Done in two bounded queries (one row per error) to avoid an
|
|
102
|
+
# N+1, and degrades gracefully if this solid_errors version has no
|
|
103
|
+
# occurrences table.
|
|
104
|
+
def backtraces_for(errors)
|
|
105
|
+
return {} if errors.empty? || !defined?(SolidErrors::Occurrence)
|
|
106
|
+
|
|
107
|
+
ids = errors.map(&:id)
|
|
108
|
+
latest_ids = SolidErrors::Occurrence.where(error_id: ids).group(:error_id).maximum(:id).values
|
|
109
|
+
return {} if latest_ids.empty?
|
|
110
|
+
|
|
111
|
+
SolidErrors::Occurrence
|
|
112
|
+
.where(id: latest_ids)
|
|
113
|
+
.pluck(:error_id, :backtrace)
|
|
114
|
+
.to_h { |error_id, raw| [error_id, RailsOrbit::Backtrace.parse(raw)] }
|
|
115
|
+
end
|
|
116
|
+
|
|
79
117
|
def set_time_range
|
|
80
118
|
@time_range = TimeRange.new(params[:range])
|
|
81
119
|
@range_key = @time_range.key
|
|
@@ -8,6 +8,7 @@ module RailsOrbit
|
|
|
8
8
|
orbit: '<svg class="orbit-nav__logo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><circle cx="12" cy="12" r="8" stroke-dasharray="4 3"/></svg>',
|
|
9
9
|
delta_up: '<svg class="orbit-delta__icon" viewBox="0 0 12 12" fill="currentColor"><path d="M6 2L10 7H2z"/></svg>',
|
|
10
10
|
delta_down:'<svg class="orbit-delta__icon" viewBox="0 0 12 12" fill="currentColor"><path d="M6 10L2 5h8z"/></svg>',
|
|
11
|
+
file: '<svg class="%{css_class}" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>',
|
|
11
12
|
}.freeze
|
|
12
13
|
|
|
13
14
|
def orbit_icon(name, css_class: "orbit-icon")
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title><%= RailsOrbit.configuration.dashboard_title %></title>
|
|
7
|
-
<%= stylesheet_link_tag "/assets/rails_orbit/application.css", media: "all" %>
|
|
8
|
-
<%= javascript_include_tag "/assets/rails_orbit/application.js", defer: true %>
|
|
7
|
+
<%= stylesheet_link_tag "/assets/rails_orbit/application.css?v=#{RailsOrbit::VERSION}", media: "all" %>
|
|
8
|
+
<%= javascript_include_tag "/assets/rails_orbit/application.js?v=#{RailsOrbit::VERSION}", defer: true %>
|
|
9
9
|
<%= csrf_meta_tags %>
|
|
10
10
|
</head>
|
|
11
11
|
<body class="orbit-body">
|
|
@@ -24,15 +24,47 @@
|
|
|
24
24
|
<table class="orbit-table orbit-table--compact">
|
|
25
25
|
<thead>
|
|
26
26
|
<tr>
|
|
27
|
-
<th>Message</th>
|
|
27
|
+
<th>Message & Location</th>
|
|
28
28
|
<th class="orbit-table__right">Occurred At</th>
|
|
29
29
|
</tr>
|
|
30
30
|
</thead>
|
|
31
31
|
<tbody>
|
|
32
32
|
<% group[:records].each do |error| %>
|
|
33
33
|
<tr>
|
|
34
|
-
<td
|
|
35
|
-
|
|
34
|
+
<td>
|
|
35
|
+
<div class="orbit-error__message"><%= truncate(error[:message], length: 140) %></div>
|
|
36
|
+
|
|
37
|
+
<% if (loc = error[:location]) %>
|
|
38
|
+
<div class="orbit-error__location" title="<%= loc.to_s %>">
|
|
39
|
+
<%= orbit_icon(:file, css_class: "orbit-error__location-icon") %>
|
|
40
|
+
<% if loc.parsed? %>
|
|
41
|
+
<span class="orbit-error__path"><%= loc.display_path %></span><span class="orbit-error__line">:<%= loc.line %></span><% if loc.method_name %>
|
|
42
|
+
<span class="orbit-error__method">in <%= loc.method_name %></span><% end %>
|
|
43
|
+
<% else %>
|
|
44
|
+
<span class="orbit-error__path"><%= loc.raw %></span>
|
|
45
|
+
<% end %>
|
|
46
|
+
</div>
|
|
47
|
+
<% end %>
|
|
48
|
+
|
|
49
|
+
<% if error[:frames].any? %>
|
|
50
|
+
<details class="orbit-trace">
|
|
51
|
+
<summary class="orbit-trace__summary"><%= pluralize(error[:frames].size, "frame") %> · view traceback</summary>
|
|
52
|
+
<ol class="orbit-trace__frames">
|
|
53
|
+
<% error[:frames].each do |frame| %>
|
|
54
|
+
<li class="orbit-trace__frame<%= " orbit-trace__frame--app" if frame.application? %>">
|
|
55
|
+
<% if frame.parsed? %>
|
|
56
|
+
<span class="orbit-trace__file"><%= frame.display_path %></span><span class="orbit-trace__line">:<%= frame.line %></span><% if frame.method_name %>
|
|
57
|
+
<span class="orbit-trace__method">in <%= frame.method_name %></span><% end %>
|
|
58
|
+
<% else %>
|
|
59
|
+
<span class="orbit-trace__file"><%= frame.raw %></span>
|
|
60
|
+
<% end %>
|
|
61
|
+
</li>
|
|
62
|
+
<% end %>
|
|
63
|
+
</ol>
|
|
64
|
+
</details>
|
|
65
|
+
<% end %>
|
|
66
|
+
</td>
|
|
67
|
+
<td class="orbit-table__right orbit-table__mono"><%= error[:created_at].strftime("%b %d %H:%M:%S") %></td>
|
|
36
68
|
</tr>
|
|
37
69
|
<% end %>
|
|
38
70
|
</tbody>
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
module RailsOrbit
|
|
2
|
+
# Parses a raw exception backtrace (the newline-joined Ruby backtrace that
|
|
3
|
+
# solid_errors stores on each occurrence) into structured frames, flagging
|
|
4
|
+
# which frames belong to the host application versus third-party gems.
|
|
5
|
+
#
|
|
6
|
+
# It reads no source files — only file, line, and method are extracted — so
|
|
7
|
+
# the dashboard stays fast and works even when the rendering server does not
|
|
8
|
+
# have the source on disk. It also depends only on the raw text, never on
|
|
9
|
+
# solid_errors' own Backtrace/BacktraceLine classes, so it is unaffected by
|
|
10
|
+
# internal changes in that gem.
|
|
11
|
+
class Backtrace
|
|
12
|
+
# Matches "path/to/file.rb:42:in `method'" or ":in 'method'" (Ruby 3.4+),
|
|
13
|
+
# with the method portion optional. A leading "X:" Windows drive is allowed.
|
|
14
|
+
LINE_FORMAT = /\A((?:[A-Za-z]:)?[^:]+):(\d+)(?::in [`']([^']+)')?\z/
|
|
15
|
+
|
|
16
|
+
# A single backtrace frame. `parsed?` is false for lines we could not parse
|
|
17
|
+
# (kept verbatim in `raw` so nothing is silently dropped).
|
|
18
|
+
Frame = Struct.new(:file, :line, :method_name, :application, :raw, keyword_init: true) do
|
|
19
|
+
def application?
|
|
20
|
+
application
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def parsed?
|
|
24
|
+
!file.nil?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def display_path
|
|
28
|
+
return raw unless parsed?
|
|
29
|
+
|
|
30
|
+
application ? Backtrace.relative_to_app(file) : Backtrace.shorten_gem(file)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def to_s
|
|
34
|
+
return raw unless parsed?
|
|
35
|
+
|
|
36
|
+
suffix = method_name ? " in #{method_name}" : ""
|
|
37
|
+
"#{display_path}:#{line}#{suffix}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.parse(raw)
|
|
42
|
+
frames = String(raw).split("\n").filter_map { |line| parse_line(line) }
|
|
43
|
+
new(frames)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.parse_line(line)
|
|
47
|
+
stripped = line.to_s.strip
|
|
48
|
+
return nil if stripped.empty?
|
|
49
|
+
|
|
50
|
+
if (match = stripped.match(LINE_FORMAT))
|
|
51
|
+
file = match[1]
|
|
52
|
+
Frame.new(
|
|
53
|
+
file: file,
|
|
54
|
+
line: match[2].to_i,
|
|
55
|
+
method_name: match[3],
|
|
56
|
+
application: application_file?(file),
|
|
57
|
+
raw: stripped
|
|
58
|
+
)
|
|
59
|
+
else
|
|
60
|
+
Frame.new(file: nil, line: nil, method_name: nil, application: false, raw: stripped)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# A frame is "application" code when it lives under the app root but not in
|
|
65
|
+
# vendored gems. Gem frames and stdlib frames are not application frames.
|
|
66
|
+
def self.application_file?(file)
|
|
67
|
+
root = app_root
|
|
68
|
+
return false unless root && file.start_with?(root)
|
|
69
|
+
|
|
70
|
+
!file.start_with?(File.join(root, "vendor"))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def self.relative_to_app(file)
|
|
74
|
+
root = app_root
|
|
75
|
+
return file unless root && file.start_with?(root)
|
|
76
|
+
|
|
77
|
+
file.sub(/\A#{Regexp.escape(root)}\/?/, "")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Trims the gem install prefix so "/.../gems/foo-1.0/lib/x.rb" reads as
|
|
81
|
+
# "foo-1.0/lib/x.rb". Falls back to the full path if no prefix matches.
|
|
82
|
+
def self.shorten_gem(file)
|
|
83
|
+
gem_paths.each do |path|
|
|
84
|
+
return file.sub(/\A#{Regexp.escape(path)}\/?(?:gems\/)?/, "") if file.start_with?(path)
|
|
85
|
+
end
|
|
86
|
+
file
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.app_root
|
|
90
|
+
return unless defined?(Rails) && Rails.respond_to?(:root) && Rails.root
|
|
91
|
+
|
|
92
|
+
Rails.root.to_s
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.gem_paths
|
|
96
|
+
return [] unless defined?(Gem)
|
|
97
|
+
|
|
98
|
+
Gem.path.map(&:to_s)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
attr_reader :frames
|
|
102
|
+
|
|
103
|
+
def initialize(frames)
|
|
104
|
+
@frames = frames
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def application_frames
|
|
108
|
+
@application_frames ||= frames.select(&:application?)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# The most relevant "where did this happen" frame: the first application
|
|
112
|
+
# frame, or the first parseable frame if the trace is entirely framework/gem
|
|
113
|
+
# code, or nil when there is nothing usable.
|
|
114
|
+
def top_location
|
|
115
|
+
application_frames.first || frames.find(&:parsed?)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def empty?
|
|
119
|
+
frames.empty?
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
data/lib/rails_orbit/version.rb
CHANGED
data/lib/rails_orbit.rb
CHANGED
|
@@ -467,6 +467,69 @@
|
|
|
467
467
|
color: var(--orbit-text-muted);
|
|
468
468
|
}
|
|
469
469
|
|
|
470
|
+
/* ── Error message, location & traceback ─────────────────── */
|
|
471
|
+
.orbit-error__message {
|
|
472
|
+
color: var(--orbit-text);
|
|
473
|
+
word-break: break-word;
|
|
474
|
+
}
|
|
475
|
+
.orbit-error__location {
|
|
476
|
+
display: flex;
|
|
477
|
+
align-items: center;
|
|
478
|
+
gap: 0.35rem;
|
|
479
|
+
margin-top: 0.3rem;
|
|
480
|
+
font-family: var(--orbit-mono);
|
|
481
|
+
font-size: 0.72rem;
|
|
482
|
+
color: var(--orbit-text-muted);
|
|
483
|
+
word-break: break-all;
|
|
484
|
+
}
|
|
485
|
+
.orbit-error__location-icon {
|
|
486
|
+
width: 0.72rem;
|
|
487
|
+
height: 0.72rem;
|
|
488
|
+
flex-shrink: 0;
|
|
489
|
+
color: var(--orbit-text-muted);
|
|
490
|
+
}
|
|
491
|
+
.orbit-error__path { color: var(--orbit-primary); }
|
|
492
|
+
.orbit-error__line { color: var(--orbit-warning); font-weight: 600; }
|
|
493
|
+
.orbit-error__method { color: var(--orbit-text-muted); }
|
|
494
|
+
|
|
495
|
+
.orbit-trace {
|
|
496
|
+
margin-top: 0.4rem;
|
|
497
|
+
}
|
|
498
|
+
.orbit-trace__summary {
|
|
499
|
+
display: inline-block;
|
|
500
|
+
cursor: pointer;
|
|
501
|
+
font-size: 0.68rem;
|
|
502
|
+
color: var(--orbit-text-muted);
|
|
503
|
+
user-select: none;
|
|
504
|
+
padding: 0.1rem 0;
|
|
505
|
+
}
|
|
506
|
+
.orbit-trace__summary:hover { color: var(--orbit-text); }
|
|
507
|
+
.orbit-trace__frames {
|
|
508
|
+
list-style: none;
|
|
509
|
+
margin: 0.4rem 0 0;
|
|
510
|
+
padding: 0.5rem 0.75rem;
|
|
511
|
+
background: var(--orbit-surface);
|
|
512
|
+
border: 1px solid var(--orbit-border);
|
|
513
|
+
border-radius: var(--orbit-radius);
|
|
514
|
+
max-height: 18rem;
|
|
515
|
+
overflow: auto;
|
|
516
|
+
}
|
|
517
|
+
.orbit-trace__frame {
|
|
518
|
+
font-family: var(--orbit-mono);
|
|
519
|
+
font-size: 0.7rem;
|
|
520
|
+
line-height: 1.7;
|
|
521
|
+
color: var(--orbit-text-muted);
|
|
522
|
+
word-break: break-all;
|
|
523
|
+
opacity: 0.65;
|
|
524
|
+
}
|
|
525
|
+
.orbit-trace__frame--app {
|
|
526
|
+
color: var(--orbit-text);
|
|
527
|
+
opacity: 1;
|
|
528
|
+
}
|
|
529
|
+
.orbit-trace__frame--app .orbit-trace__file { color: var(--orbit-primary); }
|
|
530
|
+
.orbit-trace__line { color: var(--orbit-warning); }
|
|
531
|
+
.orbit-trace__method { color: var(--orbit-text-muted); }
|
|
532
|
+
|
|
470
533
|
/* ── Empty state ─────────────────────────────────────────── */
|
|
471
534
|
.orbit-empty {
|
|
472
535
|
text-align: center;
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_orbit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- dev-ham
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-06-10 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: rails
|
|
@@ -223,6 +224,7 @@ files:
|
|
|
223
224
|
- lib/generators/rails_orbit/templates/create_orbit_metrics.rb.erb
|
|
224
225
|
- lib/generators/rails_orbit/templates/initializer.rb
|
|
225
226
|
- lib/rails_orbit.rb
|
|
227
|
+
- lib/rails_orbit/backtrace.rb
|
|
226
228
|
- lib/rails_orbit/configuration.rb
|
|
227
229
|
- lib/rails_orbit/database_setup.rb
|
|
228
230
|
- lib/rails_orbit/engine.rb
|
|
@@ -244,6 +246,7 @@ metadata:
|
|
|
244
246
|
changelog_uri: https://github.com/dev-ham/rails_orbit/blob/main/CHANGELOG.md
|
|
245
247
|
bug_tracker_uri: https://github.com/dev-ham/rails_orbit/issues
|
|
246
248
|
rubygems_mfa_required: 'true'
|
|
249
|
+
post_install_message:
|
|
247
250
|
rdoc_options: []
|
|
248
251
|
require_paths:
|
|
249
252
|
- lib
|
|
@@ -258,7 +261,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
258
261
|
- !ruby/object:Gem::Version
|
|
259
262
|
version: '0'
|
|
260
263
|
requirements: []
|
|
261
|
-
rubygems_version:
|
|
264
|
+
rubygems_version: 3.3.27
|
|
265
|
+
signing_key:
|
|
262
266
|
specification_version: 4
|
|
263
267
|
summary: Observability dashboard for solid_queue, solid_cache, and solid_errors
|
|
264
268
|
test_files: []
|