error_radar 1.0.0 → 1.0.1
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 +13 -0
- data/app/controllers/error_radar/guide_controller.rb +50 -0
- data/app/views/error_radar/guide/index.html.erb +643 -0
- data/app/views/layouts/error_radar/application.html.erb +1 -0
- data/config/routes.rb +1 -0
- data/lib/error_radar/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fa57ddbff3b17adcf34ffe470d8737be97f101ab213d90fa5f8539935b7a0e9b
|
|
4
|
+
data.tar.gz: 3212bdf3d554b2c1f660bf42fd687afeb0707933c7cd67ca983b1bbbb01c7848
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 31f315fbcdadf71bc578b82d8afb954f95410bda352889107240973832f4984d4fa4002a599a6abdc7852a38a1b6ebaeed0874acfe79d1fb69ff5c2f9c3fe956
|
|
7
|
+
data.tar.gz: '04749c0c4ed4aaa062bbbd0bc30cfbed743b61ae072e3300c67c7cf6f7518e52fab74664efbe36465f4f5102016605d136fe454c103102026e80f064de8e5957'
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.1] - 2026-07-03
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Usage Guide page** at `/error_radar/guide` — a built-in reference screen accessible
|
|
9
|
+
from the nav bar. Shows:
|
|
10
|
+
- Live status badges (ON / OFF / CONFIGURED) for every feature, read directly from
|
|
11
|
+
the current `ErrorRadar.config` values at request time
|
|
12
|
+
- Feature descriptions, use-case explanations, and code snippets to enable each one
|
|
13
|
+
- Which features can be toggled on/off and how
|
|
14
|
+
- Upgrade generator reference table (which generator is needed for which feature)
|
|
15
|
+
- Full configuration reference table with current values (sensitive values shown as `*** (set)`)
|
|
16
|
+
- **"Guide" link** added to the nav bar.
|
|
17
|
+
|
|
5
18
|
## [1.0.0] - 2026-07-03
|
|
6
19
|
|
|
7
20
|
### Added
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ErrorRadar
|
|
4
|
+
class GuideController < ApplicationController
|
|
5
|
+
before_action :authenticate_request!
|
|
6
|
+
|
|
7
|
+
def index
|
|
8
|
+
cfg = ErrorRadar.config
|
|
9
|
+
|
|
10
|
+
@sections = {
|
|
11
|
+
auto_capture: {
|
|
12
|
+
middleware: cfg.install_middleware,
|
|
13
|
+
active_job: cfg.install_active_job,
|
|
14
|
+
sidekiq: cfg.install_sidekiq && defined?(::Sidekiq),
|
|
15
|
+
rake: cfg.install_rake,
|
|
16
|
+
rails_admin: cfg.install_rails_admin && defined?(::RailsAdmin)
|
|
17
|
+
},
|
|
18
|
+
notifications: {
|
|
19
|
+
slack: cfg.slack_webhook_url.to_s.start_with?('http'),
|
|
20
|
+
discord: cfg.discord_webhook_url.to_s.start_with?('http'),
|
|
21
|
+
email: cfg.email_recipients.any?,
|
|
22
|
+
webhooks: cfg.webhook_urls.any?,
|
|
23
|
+
callbacks: cfg.error_callbacks.any?,
|
|
24
|
+
spike_alerts: Array(cfg.notify_on).map(&:to_sym).include?(:spike),
|
|
25
|
+
notify_on: Array(cfg.notify_on).map(&:to_s).join(', ')
|
|
26
|
+
},
|
|
27
|
+
api: {
|
|
28
|
+
rest_api: true,
|
|
29
|
+
api_secured: cfg.api_token.present?,
|
|
30
|
+
github: cfg.github_token.present? && cfg.github_repo.present?,
|
|
31
|
+
github_repo: cfg.github_repo
|
|
32
|
+
},
|
|
33
|
+
performance: {
|
|
34
|
+
async_capture: cfg.async_capture,
|
|
35
|
+
occurrence_tracking: cfg.track_occurrences,
|
|
36
|
+
retention_days: cfg.retention_days,
|
|
37
|
+
max_records: cfg.max_records
|
|
38
|
+
},
|
|
39
|
+
team: {
|
|
40
|
+
digest: cfg.digest_enabled,
|
|
41
|
+
v100_migrated: begin
|
|
42
|
+
ErrorLog.column_names.include?('assigned_to')
|
|
43
|
+
rescue StandardError
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
<%
|
|
2
|
+
cfg = ErrorRadar.config
|
|
3
|
+
s = @sections
|
|
4
|
+
|
|
5
|
+
on = ->(v) { v ? ['ON', '#16a34a', '#dcfce7'] : ['OFF', '#6b7280', '#f3f4f6'] }
|
|
6
|
+
cfg_badge = ->(v) do
|
|
7
|
+
v.present? ? ['CONFIGURED', '#16a34a', '#dcfce7'] : ['NOT SET', '#dc2626', '#fee2e2']
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
badge = ->(label, bg, fg='#fff') do
|
|
11
|
+
"<span style='display:inline-block;padding:2px 10px;border-radius:10px;font-size:11px;font-weight:700;background:#{bg};color:#{fg}'>#{label}</span>"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
status = ->(v) do
|
|
15
|
+
l, bg = on.call(v)
|
|
16
|
+
badge.call(l, bg)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
code = ->(str) do
|
|
20
|
+
"<pre style='margin:0;background:#1e293b;color:#e2e8f0;padding:12px 14px;border-radius:8px;font-size:12px;overflow-x:auto;white-space:pre-wrap'>#{ERB::Util.html_escape(str)}</pre>"
|
|
21
|
+
end
|
|
22
|
+
%>
|
|
23
|
+
|
|
24
|
+
<style>
|
|
25
|
+
.guide-wrap { display:grid;grid-template-columns:220px 1fr;gap:28px;align-items:start }
|
|
26
|
+
.guide-sidebar { position:sticky;top:20px }
|
|
27
|
+
.guide-sidebar a { display:block;padding:6px 12px;font-size:13px;color:#6b7280;border-radius:6px;margin-bottom:2px }
|
|
28
|
+
.guide-sidebar a:hover { background:#f3f4f6;color:#1f2937 }
|
|
29
|
+
.guide-sidebar .sid-section { font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#9ca3af;padding:14px 12px 4px }
|
|
30
|
+
.guide-section { margin-bottom:32px;scroll-margin-top:20px }
|
|
31
|
+
.guide-section h2 { font-size:16px;margin:0 0 16px;padding-bottom:10px;border-bottom:2px solid #e5e7eb;display:flex;align-items:center;gap:8px }
|
|
32
|
+
.guide-cards { display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:14px }
|
|
33
|
+
.guide-card { background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:16px;box-shadow:0 1px 2px rgba(0,0,0,.04) }
|
|
34
|
+
.guide-card-head { display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px }
|
|
35
|
+
.guide-card-title { font-size:13px;font-weight:700;color:#111827 }
|
|
36
|
+
.guide-card-icon { font-size:16px;margin-right:6px }
|
|
37
|
+
.guide-card-desc { font-size:12px;color:#6b7280;line-height:1.6;margin-bottom:10px }
|
|
38
|
+
.guide-card-code { font-size:11px }
|
|
39
|
+
.guide-note { font-size:12px;color:#6b7280;background:#f9fafb;border-left:3px solid #d1d5db;padding:10px 14px;border-radius:0 8px 8px 0;margin-bottom:16px }
|
|
40
|
+
.cfg-table { width:100%;border-collapse:collapse;font-size:12px }
|
|
41
|
+
.cfg-table th { text-align:left;padding:7px 10px;background:#f9fafb;color:#6b7280;font-weight:600;border-bottom:2px solid #e5e7eb }
|
|
42
|
+
.cfg-table td { padding:7px 10px;border-bottom:1px solid #f3f4f6;vertical-align:top }
|
|
43
|
+
.cfg-table td code { background:#f1f5f9;padding:1px 6px;border-radius:4px;font-size:11px }
|
|
44
|
+
.cfg-table tr:hover td { background:#fafafa }
|
|
45
|
+
</style>
|
|
46
|
+
|
|
47
|
+
<div style="margin-bottom:20px">
|
|
48
|
+
<h1 style="margin:0 0 4px">📖 Usage Guide</h1>
|
|
49
|
+
<div class="sub">Integration overview, feature status, and configuration reference</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div class="guide-wrap">
|
|
53
|
+
|
|
54
|
+
<%# ── Sidebar ── %>
|
|
55
|
+
<div class="guide-sidebar panel">
|
|
56
|
+
<div class="sid-section">Navigation</div>
|
|
57
|
+
<a href="#getting-started">🚀 Getting started</a>
|
|
58
|
+
<a href="#auto-capture">⚡ Auto-capture</a>
|
|
59
|
+
<a href="#notifications">🔔 Notifications</a>
|
|
60
|
+
<a href="#api">🔌 API & GitHub</a>
|
|
61
|
+
<a href="#performance">⚙️ Performance</a>
|
|
62
|
+
<a href="#team">👥 Team workflow</a>
|
|
63
|
+
<div class="sid-section">Reference</div>
|
|
64
|
+
<a href="#config-ref">🗂 Config reference</a>
|
|
65
|
+
<div class="sid-section">Quick setup</div>
|
|
66
|
+
<a href="#generators">🛠 Generators</a>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<%# ── Main content ── %>
|
|
70
|
+
<div>
|
|
71
|
+
|
|
72
|
+
<%# ── Getting started ── %>
|
|
73
|
+
<div class="guide-section" id="getting-started">
|
|
74
|
+
<h2>🚀 Getting started</h2>
|
|
75
|
+
<div class="guide-note">
|
|
76
|
+
Add to <code>Gemfile</code>: <code>gem 'error_radar'</code>, then run
|
|
77
|
+
<code>bundle install</code> and the install generator below.
|
|
78
|
+
</div>
|
|
79
|
+
<div class="guide-cards">
|
|
80
|
+
<div class="guide-card">
|
|
81
|
+
<div class="guide-card-head">
|
|
82
|
+
<div class="guide-card-title"><span class="guide-card-icon">1️⃣</span>Install</div>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="guide-card-desc">Generate the initializer and base migration.</div>
|
|
85
|
+
<div class="guide-card-code">
|
|
86
|
+
<%= raw code.call("bin/rails generate error_radar:install\nbin/rails db:migrate") %>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="guide-card">
|
|
90
|
+
<div class="guide-card-head">
|
|
91
|
+
<div class="guide-card-title"><span class="guide-card-icon">2️⃣</span>Mount the engine</div>
|
|
92
|
+
</div>
|
|
93
|
+
<div class="guide-card-desc">Add to <code>config/routes.rb</code>. The dashboard will be at <code>/error_radar</code>.</div>
|
|
94
|
+
<div class="guide-card-code">
|
|
95
|
+
<%= raw code.call("# config/routes.rb\nmount ErrorRadar::Engine => '/error_radar'") %>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="guide-card">
|
|
99
|
+
<div class="guide-card-head">
|
|
100
|
+
<div class="guide-card-title"><span class="guide-card-icon">3️⃣</span>Configure</div>
|
|
101
|
+
</div>
|
|
102
|
+
<div class="guide-card-desc">Edit <code>config/initializers/error_radar.rb</code> — generated by the install command.</div>
|
|
103
|
+
<div class="guide-card-code">
|
|
104
|
+
<%= raw code.call("ErrorRadar.configure do |config|\n config.enabled = !Rails.env.test?\n # ... see sections below\nend") %>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="guide-card">
|
|
108
|
+
<div class="guide-card-head">
|
|
109
|
+
<div class="guide-card-title"><span class="guide-card-icon">4️⃣</span>Protect the dashboard</div>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="guide-card-desc">Run a <code>before_action</code> to restrict access. Raise or redirect inside the lambda to deny.</div>
|
|
112
|
+
<div class="guide-card-code">
|
|
113
|
+
<%= raw code.call("config.authenticate = ->(ctrl) {\n ctrl.send(:authenticate_admin!)\n}\nconfig.current_user = ->(ctrl) {\n ctrl.current_admin&.email\n}") %>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<%# ── Auto-capture ── %>
|
|
120
|
+
<div class="guide-section" id="auto-capture">
|
|
121
|
+
<h2>⚡ Auto-capture <span style="font-size:12px;font-weight:400;color:#6b7280">— zero-config exception collection</span></h2>
|
|
122
|
+
<div class="guide-note">
|
|
123
|
+
All integrations are <strong>enabled by default</strong>. They activate only when the underlying library is present.
|
|
124
|
+
Set any flag to <code>false</code> to disable it.
|
|
125
|
+
</div>
|
|
126
|
+
<div class="guide-cards">
|
|
127
|
+
|
|
128
|
+
<div class="guide-card">
|
|
129
|
+
<div class="guide-card-head">
|
|
130
|
+
<div class="guide-card-title"><span class="guide-card-icon">🌐</span>Rack middleware</div>
|
|
131
|
+
<%= raw status.call(s[:auto_capture][:middleware]) %>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="guide-card-desc">
|
|
134
|
+
Catches all unhandled exceptions in the web request cycle. Runs after
|
|
135
|
+
<code>ActionDispatch::DebugExceptions</code> so it only fires for real 5xx errors,
|
|
136
|
+
not routing errors or 404s. The main way web exceptions are captured.
|
|
137
|
+
</div>
|
|
138
|
+
<div class="guide-card-code">
|
|
139
|
+
<%= raw code.call("config.install_middleware = true # default\nconfig.install_middleware = false # disable") %>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div class="guide-card">
|
|
144
|
+
<div class="guide-card-head">
|
|
145
|
+
<div class="guide-card-title"><span class="guide-card-icon">⚙️</span>ActiveJob</div>
|
|
146
|
+
<%= raw status.call(s[:auto_capture][:active_job]) %>
|
|
147
|
+
</div>
|
|
148
|
+
<div class="guide-card-desc">
|
|
149
|
+
Wraps every ActiveJob subclass via <code>ActiveSupport.on_load(:active_job)</code>.
|
|
150
|
+
Works with any queue adapter — Sidekiq, Solid Queue, Delayed Job, async, etc.
|
|
151
|
+
Disable if you only use Sidekiq and want to avoid double-counting.
|
|
152
|
+
</div>
|
|
153
|
+
<div class="guide-card-code">
|
|
154
|
+
<%= raw code.call("config.install_active_job = true # default\nconfig.install_active_job = false # disable") %>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<div class="guide-card">
|
|
159
|
+
<div class="guide-card-head">
|
|
160
|
+
<div class="guide-card-title"><span class="guide-card-icon">🔄</span>Sidekiq</div>
|
|
161
|
+
<%= raw status.call(s[:auto_capture][:sidekiq]) %>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="guide-card-desc">
|
|
164
|
+
Installs a Sidekiq server middleware that captures every failed job including
|
|
165
|
+
retries. Auto-detected — only activates if Sidekiq is loaded.
|
|
166
|
+
<% unless defined?(::Sidekiq) %>
|
|
167
|
+
<strong>Sidekiq not detected in this process.</strong>
|
|
168
|
+
<% end %>
|
|
169
|
+
</div>
|
|
170
|
+
<div class="guide-card-code">
|
|
171
|
+
<%= raw code.call("config.install_sidekiq = true # default\nconfig.install_sidekiq = false # disable") %>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<div class="guide-card">
|
|
176
|
+
<div class="guide-card-head">
|
|
177
|
+
<div class="guide-card-title"><span class="guide-card-icon">🔧</span>Rake tasks</div>
|
|
178
|
+
<%= raw status.call(s[:auto_capture][:rake]) %>
|
|
179
|
+
</div>
|
|
180
|
+
<div class="guide-card-desc">
|
|
181
|
+
Monkey-patches <code>Rake::Task#execute</code> so any unhandled exception in
|
|
182
|
+
any rake task is automatically captured and re-raised. Useful for cron jobs
|
|
183
|
+
and one-off maintenance tasks.
|
|
184
|
+
</div>
|
|
185
|
+
<div class="guide-card-code">
|
|
186
|
+
<%= raw code.call("config.install_rake = true # default\nconfig.install_rake = false # disable") %>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<div class="guide-card">
|
|
191
|
+
<div class="guide-card-head">
|
|
192
|
+
<div class="guide-card-title"><span class="guide-card-icon">📋</span>RailsAdmin</div>
|
|
193
|
+
<%= raw status.call(s[:auto_capture][:rails_admin]) %>
|
|
194
|
+
</div>
|
|
195
|
+
<div class="guide-card-desc">
|
|
196
|
+
Registers an ErrorLog board in RailsAdmin with custom resolve/ignore actions.
|
|
197
|
+
Auto-detected — only activates if RailsAdmin is loaded.
|
|
198
|
+
<% unless defined?(::RailsAdmin) %>
|
|
199
|
+
<strong>RailsAdmin not detected.</strong>
|
|
200
|
+
<% end %>
|
|
201
|
+
</div>
|
|
202
|
+
<div class="guide-card-code">
|
|
203
|
+
<%= raw code.call("config.install_rails_admin = true # default\nconfig.install_rails_admin = false # disable") %>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<div class="guide-card">
|
|
208
|
+
<div class="guide-card-head">
|
|
209
|
+
<div class="guide-card-title"><span class="guide-card-icon">🚫</span>Ignored exceptions</div>
|
|
210
|
+
</div>
|
|
211
|
+
<div class="guide-card-desc">
|
|
212
|
+
Exception classes the middleware will NOT log. Defaults cover expected
|
|
213
|
+
client errors: routing errors, 404s, bad requests, auth failures.
|
|
214
|
+
Add your own to keep the dashboard free of noise.
|
|
215
|
+
</div>
|
|
216
|
+
<div class="guide-card-code">
|
|
217
|
+
<%= raw code.call("config.ignored_exceptions += [\n 'MyApp::ExpectedError',\n 'Pundit::NotAuthorizedError'\n]") %>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<%# ── Notifications ── %>
|
|
225
|
+
<div class="guide-section" id="notifications">
|
|
226
|
+
<h2>🔔 Notifications <span style="font-size:12px;font-weight:400;color:#6b7280">— alerts when errors occur</span></h2>
|
|
227
|
+
<div class="guide-note">
|
|
228
|
+
Current rule: <strong><code>notify_on: [<%= s[:notifications][:notify_on].presence || 'not set' %>]</code></strong>.
|
|
229
|
+
Add channels below, then choose when to fire with <code>notify_on</code>.
|
|
230
|
+
</div>
|
|
231
|
+
<div class="guide-cards">
|
|
232
|
+
|
|
233
|
+
<div class="guide-card">
|
|
234
|
+
<div class="guide-card-head">
|
|
235
|
+
<div class="guide-card-title"><span class="guide-card-icon">⏰</span>notify_on rules</div>
|
|
236
|
+
</div>
|
|
237
|
+
<div class="guide-card-desc">
|
|
238
|
+
Controls <em>when</em> alerts fire. Mix and match:
|
|
239
|
+
<br>• <code>:new_error</code> — first time a fingerprint is seen (no throttle)
|
|
240
|
+
<br>• <code>:critical</code> — any critical severity, throttled 1×/hour
|
|
241
|
+
<br>• <code>:all</code> — every hit, throttled 1×/hour per fingerprint
|
|
242
|
+
<br>• <code>:spike</code> — N hits in M minutes (see Spike Alerts below)
|
|
243
|
+
</div>
|
|
244
|
+
<div class="guide-card-code">
|
|
245
|
+
<%= raw code.call("config.notify_on = [:new_error]\n# or\nconfig.notify_on = [:new_error, :spike]\n# or\nconfig.notify_on = [:new_error, :critical]") %>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<div class="guide-card">
|
|
250
|
+
<div class="guide-card-head">
|
|
251
|
+
<div class="guide-card-title"><span class="guide-card-icon">💬</span>Slack</div>
|
|
252
|
+
<%= raw status.call(s[:notifications][:slack]) %>
|
|
253
|
+
</div>
|
|
254
|
+
<div class="guide-card-desc">
|
|
255
|
+
Sends a Block Kit message with error details and a "View in Error Radar" button.
|
|
256
|
+
Create an incoming webhook at api.slack.com/messaging/webhooks.
|
|
257
|
+
</div>
|
|
258
|
+
<div class="guide-card-code">
|
|
259
|
+
<%= raw code.call("config.slack_webhook_url = ENV['SLACK_WEBHOOK_URL']\nconfig.slack_channel = '#errors' # optional override") %>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
<div class="guide-card">
|
|
264
|
+
<div class="guide-card-head">
|
|
265
|
+
<div class="guide-card-title"><span class="guide-card-icon">🎮</span>Discord</div>
|
|
266
|
+
<%= raw status.call(s[:notifications][:discord]) %>
|
|
267
|
+
</div>
|
|
268
|
+
<div class="guide-card-desc">
|
|
269
|
+
Sends a rich embed to any Discord channel via an incoming webhook.
|
|
270
|
+
Create at: Server Settings → Integrations → Webhooks.
|
|
271
|
+
</div>
|
|
272
|
+
<div class="guide-card-code">
|
|
273
|
+
<%= raw code.call("config.discord_webhook_url = ENV['DISCORD_WEBHOOK_URL']") %>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
<div class="guide-card">
|
|
278
|
+
<div class="guide-card-head">
|
|
279
|
+
<div class="guide-card-title"><span class="guide-card-icon">📧</span>Email</div>
|
|
280
|
+
<%= raw status.call(s[:notifications][:email]) %>
|
|
281
|
+
</div>
|
|
282
|
+
<div class="guide-card-desc">
|
|
283
|
+
Delivers an HTML + plain-text email via ActionMailer. Uses
|
|
284
|
+
<code>deliver_later</code> so it doesn't block the request.
|
|
285
|
+
Requires ActionMailer to be configured in the host app.
|
|
286
|
+
</div>
|
|
287
|
+
<div class="guide-card-code">
|
|
288
|
+
<%= raw code.call("config.email_recipients = ['dev@myapp.com']\nconfig.email_from = 'errors@myapp.com'") %>
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
<div class="guide-card">
|
|
293
|
+
<div class="guide-card-head">
|
|
294
|
+
<div class="guide-card-title"><span class="guide-card-icon">🔗</span>Generic webhooks</div>
|
|
295
|
+
<%= raw status.call(s[:notifications][:webhooks]) %>
|
|
296
|
+
</div>
|
|
297
|
+
<div class="guide-card-desc">
|
|
298
|
+
POSTs a JSON payload to any URL on each alert. Use for PagerDuty, OpsGenie,
|
|
299
|
+
n8n, Zapier, or any custom script. Multiple URLs supported.
|
|
300
|
+
</div>
|
|
301
|
+
<div class="guide-card-code">
|
|
302
|
+
<%= raw code.call("config.webhook_urls = [\n ENV['PAGERDUTY_WEBHOOK_URL'],\n 'https://myapp.com/hooks/errors'\n]") %>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<div class="guide-card">
|
|
307
|
+
<div class="guide-card-head">
|
|
308
|
+
<div class="guide-card-title"><span class="guide-card-icon">⚡</span>Spike alerts</div>
|
|
309
|
+
<%= raw status.call(s[:notifications][:spike_alerts]) %>
|
|
310
|
+
</div>
|
|
311
|
+
<div class="guide-card-desc">
|
|
312
|
+
Fires when a single error exceeds the threshold within the window.
|
|
313
|
+
Uses the occurrences DB (if track_occurrences is on) or an in-memory
|
|
314
|
+
ring buffer. Re-alerts at most once per window.
|
|
315
|
+
</div>
|
|
316
|
+
<div class="guide-card-code">
|
|
317
|
+
<%= raw code.call("config.notify_on = [:new_error, :spike]\nconfig.spike_threshold = 10 # occurrences...\nconfig.spike_window_minutes = 5 # ...in this many minutes") %>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
<div class="guide-card">
|
|
322
|
+
<div class="guide-card-head">
|
|
323
|
+
<div class="guide-card-title"><span class="guide-card-icon">🔧</span>Custom callbacks</div>
|
|
324
|
+
<%= raw status.call(s[:notifications][:callbacks]) %>
|
|
325
|
+
</div>
|
|
326
|
+
<div class="guide-card-desc">
|
|
327
|
+
Run arbitrary Ruby after all built-in channels. Use for integrations
|
|
328
|
+
not covered above (Telegram, PagerDuty SDK, SMS, custom logging, etc.).
|
|
329
|
+
</div>
|
|
330
|
+
<div class="guide-card-code">
|
|
331
|
+
<%= raw code.call("config.on_error do |error_log|\n PagerDuty.create_incident(\n title: error_log.error_class,\n severity: error_log.severity\n )\nend") %>
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
|
|
335
|
+
<div class="guide-card">
|
|
336
|
+
<div class="guide-card-head">
|
|
337
|
+
<div class="guide-card-title"><span class="guide-card-icon">🌍</span>Deep links in alerts</div>
|
|
338
|
+
<%= raw status.call(cfg.app_host.present?) %>
|
|
339
|
+
</div>
|
|
340
|
+
<div class="guide-card-desc">
|
|
341
|
+
Set <code>app_host</code> to include a clickable link to the error detail
|
|
342
|
+
page in every Slack/Discord/email/webhook notification.
|
|
343
|
+
</div>
|
|
344
|
+
<div class="guide-card-code">
|
|
345
|
+
<%= raw code.call("config.app_host = 'https://myapp.com'\nconfig.app_name = 'MyApp' # shown in notification title") %>
|
|
346
|
+
</div>
|
|
347
|
+
</div>
|
|
348
|
+
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
|
|
352
|
+
<%# ── API & GitHub ── %>
|
|
353
|
+
<div class="guide-section" id="api">
|
|
354
|
+
<h2>🔌 API & GitHub</h2>
|
|
355
|
+
<div class="guide-cards">
|
|
356
|
+
|
|
357
|
+
<div class="guide-card">
|
|
358
|
+
<div class="guide-card-head">
|
|
359
|
+
<div class="guide-card-title"><span class="guide-card-icon">📡</span>REST API</div>
|
|
360
|
+
<%= raw badge.call('ALWAYS ON', '#2563eb') %>
|
|
361
|
+
</div>
|
|
362
|
+
<div class="guide-card-desc">
|
|
363
|
+
JSON endpoints for CI/CD scripts, external dashboards, and automation.
|
|
364
|
+
Secured via Bearer token (set <code>api_token</code>). Leave nil for open access (dev only).
|
|
365
|
+
<br><br>
|
|
366
|
+
<code>GET /api/errors</code> — paginated list with filters<br>
|
|
367
|
+
<code>GET /api/errors/:id</code> — full detail + occurrences<br>
|
|
368
|
+
<code>PATCH /api/errors/:id</code> — update status<br>
|
|
369
|
+
<code>GET /api/stats</code> — summary counts
|
|
370
|
+
</div>
|
|
371
|
+
<div class="guide-card-code">
|
|
372
|
+
<%= raw code.call("config.api_token = ENV['ERROR_RADAR_API_TOKEN']\n\n# Usage:\n# curl -H 'Authorization: Bearer $TOKEN' \\\n# https://myapp.com/error_radar/api/stats") %>
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
|
|
376
|
+
<div class="guide-card">
|
|
377
|
+
<div class="guide-card-head">
|
|
378
|
+
<div class="guide-card-title"><span class="guide-card-icon">🐙</span>GitHub Issues</div>
|
|
379
|
+
<%= raw status.call(s[:api][:github]) %>
|
|
380
|
+
</div>
|
|
381
|
+
<div class="guide-card-desc">
|
|
382
|
+
Create a pre-filled GitHub issue from any error detail page.
|
|
383
|
+
Includes error class, source, message, backtrace and a deep-link back.
|
|
384
|
+
Requires the upgrade_v050 migration for URL persistence.
|
|
385
|
+
<% if s[:api][:github_repo].present? %>
|
|
386
|
+
<br><strong>Repo:</strong> <code><%= s[:api][:github_repo] %></code>
|
|
387
|
+
<% end %>
|
|
388
|
+
</div>
|
|
389
|
+
<div class="guide-card-code">
|
|
390
|
+
<%= raw code.call("config.github_token = ENV['GITHUB_TOKEN'] # PAT with repo scope\nconfig.github_repo = 'myorg/myapp'\n\n# Then run:\n# rails generate error_radar:upgrade_v050\n# rails db:migrate") %>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
|
|
397
|
+
<%# ── Performance ── %>
|
|
398
|
+
<div class="guide-section" id="performance">
|
|
399
|
+
<h2>⚙️ Performance & Scale</h2>
|
|
400
|
+
<div class="guide-cards">
|
|
401
|
+
|
|
402
|
+
<div class="guide-card">
|
|
403
|
+
<div class="guide-card-head">
|
|
404
|
+
<div class="guide-card-title"><span class="guide-card-icon">🚀</span>Async capture</div>
|
|
405
|
+
<%= raw status.call(s[:performance][:async_capture]) %>
|
|
406
|
+
</div>
|
|
407
|
+
<div class="guide-card-desc">
|
|
408
|
+
Enqueues the DB write via ActiveJob so exceptions don't block the
|
|
409
|
+
hot request/job path. Falls back to synchronous if ActiveJob is absent.
|
|
410
|
+
Requires a queue adapter (Sidekiq, Solid Queue, etc.).
|
|
411
|
+
</div>
|
|
412
|
+
<div class="guide-card-code">
|
|
413
|
+
<%= raw code.call("config.async_capture = true\nconfig.capture_job_queue = :default # queue name") %>
|
|
414
|
+
</div>
|
|
415
|
+
</div>
|
|
416
|
+
|
|
417
|
+
<div class="guide-card">
|
|
418
|
+
<div class="guide-card-head">
|
|
419
|
+
<div class="guide-card-title"><span class="guide-card-icon">🔍</span>Occurrence history</div>
|
|
420
|
+
<%= raw status.call(s[:performance][:occurrence_tracking]) %>
|
|
421
|
+
</div>
|
|
422
|
+
<div class="guide-card-desc">
|
|
423
|
+
Stores each individual hit in <code>error_radar_occurrences</code> with
|
|
424
|
+
context, backtrace, HTTP status and request URL. Shown as a paginated
|
|
425
|
+
timeline on the error detail page. Also powers accurate spike detection.
|
|
426
|
+
</div>
|
|
427
|
+
<div class="guide-card-code">
|
|
428
|
+
<%= raw code.call("# 1. Run migration:\n# rails generate error_radar:upgrade_v060\n# rails db:migrate\n\n# 2. Enable:\nconfig.track_occurrences = true\nconfig.max_occurrences_per_error = 200 # keep last N per error") %>
|
|
429
|
+
</div>
|
|
430
|
+
</div>
|
|
431
|
+
|
|
432
|
+
<div class="guide-card">
|
|
433
|
+
<div class="guide-card-head">
|
|
434
|
+
<div class="guide-card-title"><span class="guide-card-icon">🧹</span>Retention / cleanup</div>
|
|
435
|
+
<%
|
|
436
|
+
ret_on = s[:performance][:retention_days].present? || s[:performance][:max_records].present?
|
|
437
|
+
%>
|
|
438
|
+
<%= raw status.call(ret_on) %>
|
|
439
|
+
</div>
|
|
440
|
+
<div class="guide-card-desc">
|
|
441
|
+
Auto-prune resolved/ignored records to keep the table lean.
|
|
442
|
+
Only deletes <code>resolved</code> and <code>ignored</code> rows — open errors are never touched.
|
|
443
|
+
<% if s[:performance][:retention_days] %>
|
|
444
|
+
<br>Current: <strong><%= s[:performance][:retention_days] %> days</strong> retention.
|
|
445
|
+
<% end %>
|
|
446
|
+
<% if s[:performance][:max_records] %>
|
|
447
|
+
<br>Cap: <strong><%= number_with_delimiter(s[:performance][:max_records]) %> records</strong>.
|
|
448
|
+
<% end %>
|
|
449
|
+
</div>
|
|
450
|
+
<div class="guide-card-code">
|
|
451
|
+
<%= raw code.call("config.retention_days = 90 # delete records older than 90 days\nconfig.max_records = 50000 # hard cap on total rows\n\n# Schedule via cron:\n# rake error_radar:cleanup\n# rake error_radar:cleanup:dry_run") %>
|
|
452
|
+
</div>
|
|
453
|
+
</div>
|
|
454
|
+
|
|
455
|
+
<div class="guide-card">
|
|
456
|
+
<div class="guide-card-head">
|
|
457
|
+
<div class="guide-card-title"><span class="guide-card-icon">🛠</span>Rake tasks</div>
|
|
458
|
+
<%= raw badge.call('AVAILABLE', '#2563eb') %>
|
|
459
|
+
</div>
|
|
460
|
+
<div class="guide-card-desc">
|
|
461
|
+
All built-in rake tasks for maintenance and reporting.
|
|
462
|
+
</div>
|
|
463
|
+
<div class="guide-card-code">
|
|
464
|
+
<%= raw code.call("rake error_radar:stats # print table summary\nrake error_radar:cleanup # prune per config\nrake error_radar:cleanup:dry_run # preview without deleting\nrake error_radar:cleanup:older_than # DAYS=30 rake ...\nrake error_radar:digest # send daily digest\nrake error_radar:digest:weekly # send weekly digest") %>
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
|
|
468
|
+
</div>
|
|
469
|
+
</div>
|
|
470
|
+
|
|
471
|
+
<%# ── Team workflow ── %>
|
|
472
|
+
<div class="guide-section" id="team">
|
|
473
|
+
<h2>👥 Team workflow</h2>
|
|
474
|
+
<div class="guide-cards">
|
|
475
|
+
|
|
476
|
+
<div class="guide-card">
|
|
477
|
+
<div class="guide-card-head">
|
|
478
|
+
<div class="guide-card-title"><span class="guide-card-icon">📋</span>Assignment & Comments</div>
|
|
479
|
+
<%= raw status.call(s[:team][:v100_migrated]) %>
|
|
480
|
+
</div>
|
|
481
|
+
<div class="guide-card-desc">
|
|
482
|
+
Assign errors to team members, add comment threads, and track every action
|
|
483
|
+
in an activity log. One migration adds all three features.
|
|
484
|
+
<% unless s[:team][:v100_migrated] %>
|
|
485
|
+
<br><strong>Migration not yet run.</strong>
|
|
486
|
+
<% end %>
|
|
487
|
+
</div>
|
|
488
|
+
<div class="guide-card-code">
|
|
489
|
+
<%= raw code.call("# Run once:\nbin/rails generate error_radar:upgrade_v100\nbin/rails db:migrate\n\n# No config needed — use the UI on any error detail page.") %>
|
|
490
|
+
</div>
|
|
491
|
+
</div>
|
|
492
|
+
|
|
493
|
+
<div class="guide-card">
|
|
494
|
+
<div class="guide-card-head">
|
|
495
|
+
<div class="guide-card-title"><span class="guide-card-icon">📊</span>Digest email</div>
|
|
496
|
+
<%= raw status.call(s[:team][:digest]) %>
|
|
497
|
+
</div>
|
|
498
|
+
<div class="guide-card-desc">
|
|
499
|
+
Periodic HTML summary email: new errors, top unresolved, resolved this period,
|
|
500
|
+
severity breakdown, category breakdown. Schedule via cron or Heroku Scheduler.
|
|
501
|
+
Separate recipient list from real-time alerts.
|
|
502
|
+
</div>
|
|
503
|
+
<div class="guide-card-code">
|
|
504
|
+
<%= raw code.call("config.digest_enabled = true\nconfig.digest_recipients = ['team@myapp.com']\n\n# Heroku Scheduler / cron:\n# 0 8 * * * rake error_radar:digest\n# 0 8 * * 1 rake error_radar:digest:weekly") %>
|
|
505
|
+
</div>
|
|
506
|
+
</div>
|
|
507
|
+
|
|
508
|
+
<div class="guide-card">
|
|
509
|
+
<div class="guide-card-head">
|
|
510
|
+
<div class="guide-card-title"><span class="guide-card-icon">🏷</span>Custom categories</div>
|
|
511
|
+
<%= raw badge.call('EXTENSIBLE', '#7c3aed') %>
|
|
512
|
+
</div>
|
|
513
|
+
<div class="guide-card-desc">
|
|
514
|
+
Built-in: <code>application</code>, <code>external_api</code>, <code>background_job</code>,
|
|
515
|
+
<code>syntax</code>, <code>database</code>, <code>network</code>.
|
|
516
|
+
Add app-specific categories — stored integers must stay stable once data exists.
|
|
517
|
+
</div>
|
|
518
|
+
<div class="guide-card-code">
|
|
519
|
+
<%= raw code.call("config.register_category(:payments_api, 6)\nconfig.register_category(:instagram_api, 7)\n\n# Or custom classification rules:\nconfig.categorize do |e|\n :payments_api if e.is_a?(Stripe::StripeError)\nend") %>
|
|
520
|
+
</div>
|
|
521
|
+
</div>
|
|
522
|
+
|
|
523
|
+
<div class="guide-card">
|
|
524
|
+
<div class="guide-card-head">
|
|
525
|
+
<div class="guide-card-title"><span class="guide-card-icon">🔐</span>Sensitive param scrubbing</div>
|
|
526
|
+
<%= raw badge.call('ALWAYS ON', '#2563eb') %>
|
|
527
|
+
</div>
|
|
528
|
+
<div class="guide-card-desc">
|
|
529
|
+
Request params matching the sensitive list are never stored in context.
|
|
530
|
+
Defaults cover common secrets. Extend for your app's specific fields.
|
|
531
|
+
</div>
|
|
532
|
+
<div class="guide-card-code">
|
|
533
|
+
<%= raw code.call("# Defaults: password, token, access_token,\n# authorization, secret, ...\nconfig.sensitive_params += [\n 'credit_card', 'ssn', 'api_key'\n]") %>
|
|
534
|
+
</div>
|
|
535
|
+
</div>
|
|
536
|
+
|
|
537
|
+
</div>
|
|
538
|
+
</div>
|
|
539
|
+
|
|
540
|
+
<%# ── Generators ── %>
|
|
541
|
+
<div class="guide-section" id="generators">
|
|
542
|
+
<h2>🛠 Upgrade generators</h2>
|
|
543
|
+
<div class="guide-note">Run these in order as you enable new features.</div>
|
|
544
|
+
<table class="cfg-table">
|
|
545
|
+
<thead>
|
|
546
|
+
<tr>
|
|
547
|
+
<th>Generator</th>
|
|
548
|
+
<th>What it creates</th>
|
|
549
|
+
<th>Required for</th>
|
|
550
|
+
</tr>
|
|
551
|
+
</thead>
|
|
552
|
+
<tbody>
|
|
553
|
+
<tr>
|
|
554
|
+
<td><code>error_radar:install</code></td>
|
|
555
|
+
<td>Base migration + initializer</td>
|
|
556
|
+
<td>Everything — run first</td>
|
|
557
|
+
</tr>
|
|
558
|
+
<tr>
|
|
559
|
+
<td><code>error_radar:upgrade_v050</code></td>
|
|
560
|
+
<td><code>github_issue_url</code> column</td>
|
|
561
|
+
<td>GitHub Issue URL persistence</td>
|
|
562
|
+
</tr>
|
|
563
|
+
<tr>
|
|
564
|
+
<td><code>error_radar:upgrade_v060</code></td>
|
|
565
|
+
<td><code>error_radar_occurrences</code> table</td>
|
|
566
|
+
<td><code>track_occurrences</code>, spike detection (DB mode)</td>
|
|
567
|
+
</tr>
|
|
568
|
+
<tr>
|
|
569
|
+
<td><code>error_radar:upgrade_v100</code></td>
|
|
570
|
+
<td><code>assigned_to</code> column + comments + activities tables</td>
|
|
571
|
+
<td>Assignment, comments, activity log</td>
|
|
572
|
+
</tr>
|
|
573
|
+
</tbody>
|
|
574
|
+
</table>
|
|
575
|
+
</div>
|
|
576
|
+
|
|
577
|
+
<%# ── Config reference ── %>
|
|
578
|
+
<div class="guide-section" id="config-ref">
|
|
579
|
+
<h2>🗂 Configuration reference <span style="font-size:12px;font-weight:400;color:#6b7280">— current values</span></h2>
|
|
580
|
+
<table class="cfg-table">
|
|
581
|
+
<thead>
|
|
582
|
+
<tr>
|
|
583
|
+
<th style="width:220px">Option</th>
|
|
584
|
+
<th style="width:130px">Current value</th>
|
|
585
|
+
<th>Description</th>
|
|
586
|
+
</tr>
|
|
587
|
+
</thead>
|
|
588
|
+
<tbody>
|
|
589
|
+
<%
|
|
590
|
+
rows = [
|
|
591
|
+
['enabled', cfg.enabled, 'Master switch. Set false in test env.'],
|
|
592
|
+
['backtrace_lines', cfg.backtrace_lines, 'How many backtrace lines to store per error.'],
|
|
593
|
+
['max_message_length', cfg.max_message_length, 'Truncate stored messages to this many chars.'],
|
|
594
|
+
['install_middleware', cfg.install_middleware, 'Auto-capture web exceptions via Rack.'],
|
|
595
|
+
['install_active_job', cfg.install_active_job, 'Auto-capture ActiveJob failures.'],
|
|
596
|
+
['install_sidekiq', cfg.install_sidekiq, 'Auto-capture Sidekiq job failures.'],
|
|
597
|
+
['install_rake', cfg.install_rake, 'Auto-capture Rake task failures.'],
|
|
598
|
+
['install_rails_admin', cfg.install_rails_admin, 'Register ErrorLog board in RailsAdmin.'],
|
|
599
|
+
['notify_on', Array(cfg.notify_on).join(', '), 'When to fire alerts (:new_error/:critical/:all/:spike).'],
|
|
600
|
+
['spike_threshold', cfg.spike_threshold, 'Occurrence count that triggers a spike alert.'],
|
|
601
|
+
['spike_window_minutes', cfg.spike_window_minutes, 'Rolling window (minutes) for spike detection.'],
|
|
602
|
+
['slack_webhook_url', cfg.slack_webhook_url.present? ? '*** (set)' : nil, 'Slack incoming webhook URL.'],
|
|
603
|
+
['slack_channel', cfg.slack_channel, 'Override Slack target channel (optional).'],
|
|
604
|
+
['discord_webhook_url', cfg.discord_webhook_url.present? ? '*** (set)' : nil, 'Discord incoming webhook URL.'],
|
|
605
|
+
['email_recipients', cfg.email_recipients.any? ? cfg.email_recipients.join(', ') : nil, 'Email addresses for alert emails.'],
|
|
606
|
+
['email_from', cfg.email_from, 'From address for notification emails.'],
|
|
607
|
+
['webhook_urls', cfg.webhook_urls.any? ? "#{cfg.webhook_urls.size} URL(s)" : nil, 'Generic webhook URLs (POST JSON on each alert).'],
|
|
608
|
+
['app_name', cfg.app_name, 'App name shown in notifications.'],
|
|
609
|
+
['app_host', cfg.app_host, 'Base URL for deep-links in notifications.'],
|
|
610
|
+
['api_token', cfg.api_token.present? ? '*** (set)' : nil, 'Bearer token for /api/* endpoints.'],
|
|
611
|
+
['github_token', cfg.github_token.present? ? '*** (set)' : nil, 'GitHub personal access token (repo scope).'],
|
|
612
|
+
['github_repo', cfg.github_repo, '"owner/repo" for GitHub Issue creation.'],
|
|
613
|
+
['async_capture', cfg.async_capture, 'Write ErrorLog via ActiveJob (non-blocking).'],
|
|
614
|
+
['capture_job_queue', cfg.capture_job_queue, 'Queue name for CaptureJob.'],
|
|
615
|
+
['track_occurrences', cfg.track_occurrences, 'Store each hit in error_radar_occurrences.'],
|
|
616
|
+
['max_occurrences_per_error',cfg.max_occurrences_per_error, 'Keep last N occurrences per error (oldest pruned).'],
|
|
617
|
+
['retention_days', cfg.retention_days, 'Delete resolved/ignored records older than N days.'],
|
|
618
|
+
['max_records', cfg.max_records, 'Hard cap on total records (oldest pruned first).'],
|
|
619
|
+
['digest_enabled', cfg.digest_enabled, 'Enable digest email delivery.'],
|
|
620
|
+
['digest_recipients', cfg.digest_recipients.any? ? cfg.digest_recipients.join(', ') : nil, 'Digest email recipients (falls back to email_recipients).'],
|
|
621
|
+
]
|
|
622
|
+
%>
|
|
623
|
+
<% rows.each do |key, val, desc| %>
|
|
624
|
+
<tr>
|
|
625
|
+
<td><code>config.<%= key %></code></td>
|
|
626
|
+
<td>
|
|
627
|
+
<% if val.nil? || val == false || (val.respond_to?(:empty?) && val.empty?) %>
|
|
628
|
+
<span style="color:#9ca3af"><%= val.nil? ? 'nil' : val.to_s.empty? ? '(empty)' : val.inspect %></span>
|
|
629
|
+
<% elsif val == true %>
|
|
630
|
+
<span style="color:#16a34a;font-weight:600">true</span>
|
|
631
|
+
<% else %>
|
|
632
|
+
<span style="color:#1f2937"><%= val %></span>
|
|
633
|
+
<% end %>
|
|
634
|
+
</td>
|
|
635
|
+
<td style="color:#6b7280"><%= desc %></td>
|
|
636
|
+
</tr>
|
|
637
|
+
<% end %>
|
|
638
|
+
</tbody>
|
|
639
|
+
</table>
|
|
640
|
+
</div>
|
|
641
|
+
|
|
642
|
+
</div><%# end main %>
|
|
643
|
+
</div><%# end guide-wrap %>
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
<a href="<%= root_path %>" class="nav-logo">🛰 Error Radar</a>
|
|
57
57
|
<a href="<%= root_path %>" class="nav-link <%= 'active' if controller_name == 'dashboard' %>">Dashboard</a>
|
|
58
58
|
<a href="<%= errors_path %>" class="nav-link <%= 'active' if controller_name == 'errors' %>">All Errors</a>
|
|
59
|
+
<a href="<%= guide_path %>" class="nav-link <%= 'active' if controller_name == 'guide' %>">Guide</a>
|
|
59
60
|
</div>
|
|
60
61
|
</nav>
|
|
61
62
|
<div class="wrap"><%= yield %></div>
|
data/config/routes.rb
CHANGED
data/lib/error_radar/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: error_radar
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- chienbn9x
|
|
@@ -63,6 +63,7 @@ files:
|
|
|
63
63
|
- app/controllers/error_radar/application_controller.rb
|
|
64
64
|
- app/controllers/error_radar/dashboard_controller.rb
|
|
65
65
|
- app/controllers/error_radar/errors_controller.rb
|
|
66
|
+
- app/controllers/error_radar/guide_controller.rb
|
|
66
67
|
- app/mailers/error_radar/digest_mailer.rb
|
|
67
68
|
- app/mailers/error_radar/error_mailer.rb
|
|
68
69
|
- app/models/error_radar/application_record.rb
|
|
@@ -78,6 +79,7 @@ files:
|
|
|
78
79
|
- app/views/error_radar/error_mailer/new_error.text.erb
|
|
79
80
|
- app/views/error_radar/errors/index.html.erb
|
|
80
81
|
- app/views/error_radar/errors/show.html.erb
|
|
82
|
+
- app/views/error_radar/guide/index.html.erb
|
|
81
83
|
- app/views/layouts/error_radar/application.html.erb
|
|
82
84
|
- config/routes.rb
|
|
83
85
|
- lib/error_radar.rb
|