catpm 0.6.0 → 0.6.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/app/assets/images/catpm/favicon-32.png +0 -0
- data/app/assets/images/catpm/favicon-512.png +0 -0
- data/app/assets/images/catpm/favicon.ico +0 -0
- data/app/controllers/catpm/errors_controller.rb +26 -4
- data/app/controllers/catpm/events_controller.rb +14 -3
- data/app/controllers/catpm/samples_controller.rb +3 -1
- data/app/controllers/catpm/status_controller.rb +4 -3
- data/app/models/catpm/error_record.rb +1 -0
- data/app/views/catpm/endpoints/_sample_table.html.erb +7 -0
- data/app/views/catpm/errors/index.html.erb +14 -0
- data/app/views/catpm/errors/show.html.erb +9 -2
- data/app/views/catpm/events/show.html.erb +8 -0
- data/app/views/layouts/catpm/application.html.erb +9 -1
- data/config/routes.rb +6 -1
- data/db/migrate/20250601000001_create_catpm_tables.rb +1 -0
- data/lib/catpm/middleware.rb +1 -0
- data/lib/catpm/version.rb +1 -1
- data/lib/tasks/catpm_tasks.rake +7 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 77d3e9a9e80a64c61ba1cbc9f400f9b53cb774c2eee0cf1fc807952bf1b81c39
|
|
4
|
+
data.tar.gz: 3364e29476e9a6f77494f86c0fb00932c70a84eb90067df86e8455a00608930b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 52c5cb2542feabdb2a493390e1fd0c42bef5e5ea8c8d89bb71115f774b790e88b8c8f253402e47835da8190a4113d3ed7302398f59a2ce2841df2576018a20cb
|
|
7
|
+
data.tar.gz: be688f7c4ce207e04d09fb1ac30b390bc7f2ea8b050120bc46929a952a632d17e63ffc110950907ee37e60aa62f8f7400a3d982acfc568dd2392d01fe9841c91
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -28,7 +28,7 @@ module Catpm
|
|
|
28
28
|
|
|
29
29
|
@total_count = scope.count
|
|
30
30
|
@page = [params[:page].to_i, 1].max
|
|
31
|
-
@errors = scope.order(@sort => @dir).offset((@page - 1) * PER_PAGE).limit(PER_PAGE)
|
|
31
|
+
@errors = scope.order(pinned: :desc, @sort => @dir).offset((@page - 1) * PER_PAGE).limit(PER_PAGE)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def show
|
|
@@ -106,19 +106,41 @@ module Catpm
|
|
|
106
106
|
def resolve
|
|
107
107
|
error = Catpm::ErrorRecord.find(params[:id])
|
|
108
108
|
error.resolve!
|
|
109
|
-
|
|
109
|
+
if request.xhr?
|
|
110
|
+
render json: { resolved: true }
|
|
111
|
+
else
|
|
112
|
+
redirect_to catpm.error_path(error), notice: 'Marked as resolved'
|
|
113
|
+
end
|
|
110
114
|
end
|
|
111
115
|
|
|
112
116
|
def unresolve
|
|
113
117
|
error = Catpm::ErrorRecord.find(params[:id])
|
|
114
118
|
error.unresolve!
|
|
115
|
-
|
|
119
|
+
if request.xhr?
|
|
120
|
+
render json: { resolved: false }
|
|
121
|
+
else
|
|
122
|
+
redirect_to catpm.error_path(error), notice: 'Reopened'
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def toggle_pin
|
|
127
|
+
error = Catpm::ErrorRecord.find(params[:id])
|
|
128
|
+
error.update!(pinned: !error.pinned)
|
|
129
|
+
if request.xhr?
|
|
130
|
+
render json: { pinned: error.pinned }
|
|
131
|
+
else
|
|
132
|
+
redirect_back fallback_location: catpm.error_path(error)
|
|
133
|
+
end
|
|
116
134
|
end
|
|
117
135
|
|
|
118
136
|
def destroy
|
|
119
137
|
error = Catpm::ErrorRecord.find(params[:id])
|
|
120
138
|
error.destroy!
|
|
121
|
-
|
|
139
|
+
if request.xhr?
|
|
140
|
+
render json: { deleted: true }
|
|
141
|
+
else
|
|
142
|
+
redirect_to catpm.errors_path, notice: 'Error deleted'
|
|
143
|
+
end
|
|
122
144
|
end
|
|
123
145
|
|
|
124
146
|
def resolve_all
|
|
@@ -64,9 +64,10 @@ module Catpm
|
|
|
64
64
|
# Sort (pinned always on top)
|
|
65
65
|
@sort = %w[name total_count last_seen].include?(params[:sort]) ? params[:sort] : 'total_count'
|
|
66
66
|
@dir = params[:dir] == 'asc' ? 'asc' : 'desc'
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
sorted = events_list.sort_by { |e| e[@sort.to_sym] || '' }
|
|
68
|
+
sorted = sorted.reverse if @dir == 'desc'
|
|
69
|
+
pinned, unpinned = sorted.partition { |e| e[:pinned] }
|
|
70
|
+
events_list = pinned + unpinned
|
|
70
71
|
|
|
71
72
|
@total_event_names = events_list.size
|
|
72
73
|
|
|
@@ -153,6 +154,16 @@ module Catpm
|
|
|
153
154
|
end
|
|
154
155
|
end
|
|
155
156
|
|
|
157
|
+
def destroy_sample
|
|
158
|
+
sample = Catpm::EventSample.find(params[:sample_id])
|
|
159
|
+
sample.destroy
|
|
160
|
+
if request.xhr?
|
|
161
|
+
render json: { deleted: true }
|
|
162
|
+
else
|
|
163
|
+
redirect_back fallback_location: catpm.events_path, notice: 'Sample deleted'
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
156
167
|
def ignored
|
|
157
168
|
@range, period, _bucket_seconds = helpers.parse_range(remembered_range)
|
|
158
169
|
ignored_prefs = Catpm::EventPref.ignored
|
|
@@ -17,7 +17,9 @@ module Catpm
|
|
|
17
17
|
sample = Catpm::Sample.find(params[:id])
|
|
18
18
|
bucket = sample.bucket
|
|
19
19
|
sample.destroy
|
|
20
|
-
if
|
|
20
|
+
if request.xhr?
|
|
21
|
+
render json: { deleted: true }
|
|
22
|
+
elsif bucket
|
|
21
23
|
redirect_to catpm.endpoint_path(kind: bucket.kind, target: bucket.target, operation: bucket.operation), notice: 'Sample deleted'
|
|
22
24
|
else
|
|
23
25
|
redirect_to catpm.status_index_path, notice: 'Sample deleted'
|
|
@@ -85,9 +85,10 @@ module Catpm
|
|
|
85
85
|
# Server-side sort (pinned always on top)
|
|
86
86
|
@sort = %w[target total_count avg_duration max_duration total_failures last_seen].include?(params[:sort]) ? params[:sort] : 'last_seen'
|
|
87
87
|
@dir = params[:dir] == 'asc' ? 'asc' : 'desc'
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
sorted = endpoints.sort_by { |e| e[@sort.to_sym] || '' }
|
|
89
|
+
sorted = sorted.reverse if @dir == 'desc'
|
|
90
|
+
pinned, unpinned = sorted.partition { |e| e[:pinned] }
|
|
91
|
+
endpoints = pinned + unpinned
|
|
91
92
|
|
|
92
93
|
@total_endpoint_count = endpoints.size
|
|
93
94
|
|
|
@@ -10,6 +10,7 @@ module Catpm
|
|
|
10
10
|
scope :by_kind, ->(kind) { where(kind: kind) }
|
|
11
11
|
scope :unresolved, -> { where(resolved_at: nil) }
|
|
12
12
|
scope :resolved, -> { where.not(resolved_at: nil) }
|
|
13
|
+
scope :pinned, -> { where(pinned: true) }
|
|
13
14
|
scope :recent, ->(period = 24.hours) { where(last_occurred_at: period.ago..) }
|
|
14
15
|
|
|
15
16
|
def resolved?
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<th>Duration</th>
|
|
8
8
|
<th>Segments</th>
|
|
9
9
|
<th>Time</th>
|
|
10
|
+
<th></th>
|
|
10
11
|
</tr>
|
|
11
12
|
</thead>
|
|
12
13
|
<tbody>
|
|
@@ -24,6 +25,12 @@
|
|
|
24
25
|
<% end %>
|
|
25
26
|
</td>
|
|
26
27
|
<td><%= time_with_tooltip(s.recorded_at) %></td>
|
|
28
|
+
<td style="position:relative; z-index:2; width:28px; padding:4px 2px; text-align:center">
|
|
29
|
+
<button class="action-menu-btn">⋮</button>
|
|
30
|
+
<div class="action-menu">
|
|
31
|
+
<button class="menu-danger" data-action="delete" data-url="<%= catpm.sample_path(s) %>">Delete</button>
|
|
32
|
+
</div>
|
|
33
|
+
</td>
|
|
27
34
|
</tr>
|
|
28
35
|
<% end %>
|
|
29
36
|
</tbody>
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
<table id="errors-table">
|
|
28
28
|
<thead>
|
|
29
29
|
<tr>
|
|
30
|
+
<th></th>
|
|
30
31
|
<th>Kind</th>
|
|
31
32
|
<th><%= sort_header("Error Class", "error_class", @sort, @dir, extra_params: extra) %></th>
|
|
32
33
|
<th>Message</th>
|
|
@@ -34,11 +35,13 @@
|
|
|
34
35
|
<th>Trend</th>
|
|
35
36
|
<th><%= sort_header("Last Seen", "last_occurred_at", @sort, @dir, extra_params: extra) %></th>
|
|
36
37
|
<% if @tab == "resolved" %><th>Resolved</th><% end %>
|
|
38
|
+
<th></th>
|
|
37
39
|
</tr>
|
|
38
40
|
</thead>
|
|
39
41
|
<tbody>
|
|
40
42
|
<% @errors.each do |e| %>
|
|
41
43
|
<tr class="linked">
|
|
44
|
+
<td style="position:relative; z-index:1; width:28px; padding:4px 2px; text-align:center"><button class="star-btn<%= ' pinned' if e.pinned %>" data-pin-url="<%= catpm.toggle_pin_error_path(e) %>"><%= e.pinned ? "★".html_safe : "☆".html_safe %></button></td>
|
|
42
45
|
<td><a href="<%= catpm.error_path(e) %>" class="row-link"><%= type_badge(e.kind) %></a></td>
|
|
43
46
|
<td class="mono"><%= e.error_class %></td>
|
|
44
47
|
<td style="color:var(--text-1)"><%= truncate(e.message, length: 60) %></td>
|
|
@@ -46,6 +49,17 @@
|
|
|
46
49
|
<td><%= trend_indicator(e) %></td>
|
|
47
50
|
<td><%= time_with_tooltip(e.last_occurred_at) %></td>
|
|
48
51
|
<% if @tab == "resolved" %><td><%= time_with_tooltip(e.resolved_at) %></td><% end %>
|
|
52
|
+
<td style="position:relative; z-index:2; width:28px; padding:4px 2px; text-align:center">
|
|
53
|
+
<button class="action-menu-btn">⋮</button>
|
|
54
|
+
<div class="action-menu">
|
|
55
|
+
<% if e.resolved? %>
|
|
56
|
+
<button data-action="unresolve" data-url="<%= catpm.unresolve_error_path(e) %>">Reopen</button>
|
|
57
|
+
<% else %>
|
|
58
|
+
<button data-action="resolve" data-url="<%= catpm.resolve_error_path(e) %>">Resolve</button>
|
|
59
|
+
<% end %>
|
|
60
|
+
<button class="menu-danger" data-action="delete" data-url="<%= catpm.error_path(e) %>">Delete</button>
|
|
61
|
+
</div>
|
|
62
|
+
</td>
|
|
49
63
|
</tr>
|
|
50
64
|
<% end %>
|
|
51
65
|
</tbody>
|
|
@@ -106,17 +106,24 @@
|
|
|
106
106
|
<th>Status</th>
|
|
107
107
|
<th>Target</th>
|
|
108
108
|
<th>Segments</th>
|
|
109
|
+
<th></th>
|
|
109
110
|
</tr>
|
|
110
111
|
</thead>
|
|
111
112
|
<tbody>
|
|
112
113
|
<% @samples.each do |sample| %>
|
|
113
114
|
<% ctx = sample.parsed_context %>
|
|
114
|
-
<tr class="
|
|
115
|
-
<td><%= time_with_tooltip(sample.recorded_at) %></td>
|
|
115
|
+
<tr class="linked">
|
|
116
|
+
<td><a href="<%= catpm.sample_path(sample) %>" class="row-link"><%= time_with_tooltip(sample.recorded_at) %></a></td>
|
|
116
117
|
<td class="mono"><%= format_duration(sample.duration) %></td>
|
|
117
118
|
<td><%= status_badge(ctx["status"] || ctx[:status]) %></td>
|
|
118
119
|
<td class="mono"><%= sample.bucket&.target || "—" %></td>
|
|
119
120
|
<td class="mono text-muted"><%= segment_count_summary(ctx["segment_summary"] || ctx[:segment_summary]).presence || "—" %></td>
|
|
121
|
+
<td style="position:relative; z-index:2; width:28px; padding:4px 2px; text-align:center">
|
|
122
|
+
<button class="action-menu-btn">⋮</button>
|
|
123
|
+
<div class="action-menu">
|
|
124
|
+
<button class="menu-danger" data-action="delete" data-url="<%= catpm.sample_path(sample) %>">Delete</button>
|
|
125
|
+
</div>
|
|
126
|
+
</td>
|
|
120
127
|
</tr>
|
|
121
128
|
<% end %>
|
|
122
129
|
</tbody>
|
|
@@ -63,11 +63,13 @@
|
|
|
63
63
|
<colgroup>
|
|
64
64
|
<col style="width:120px">
|
|
65
65
|
<col>
|
|
66
|
+
<col style="width:36px">
|
|
66
67
|
</colgroup>
|
|
67
68
|
<thead>
|
|
68
69
|
<tr>
|
|
69
70
|
<th>Recorded At</th>
|
|
70
71
|
<th>Payload</th>
|
|
72
|
+
<th></th>
|
|
71
73
|
</tr>
|
|
72
74
|
</thead>
|
|
73
75
|
<tbody>
|
|
@@ -87,6 +89,12 @@
|
|
|
87
89
|
<span class="text-muted">—</span>
|
|
88
90
|
<% end %>
|
|
89
91
|
</td>
|
|
92
|
+
<td style="position:relative; z-index:2; width:28px; padding:4px 2px; text-align:center; vertical-align:top">
|
|
93
|
+
<button class="action-menu-btn">⋮</button>
|
|
94
|
+
<div class="action-menu">
|
|
95
|
+
<button class="menu-danger" data-action="delete" data-url="<%= catpm.destroy_sample_events_path(sample_id: sample.id) %>">Delete</button>
|
|
96
|
+
</div>
|
|
97
|
+
</td>
|
|
90
98
|
</tr>
|
|
91
99
|
<% end %>
|
|
92
100
|
</tbody>
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<title>catpm<%= " — #{yield :title}" if content_for?(:title) %></title>
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<link rel="icon" type="image/png" sizes="32x32" href="<%= image_path('catpm/favicon-32.png') %>">
|
|
7
|
+
<link rel="icon" type="image/png" sizes="512x512" href="<%= image_path('catpm/favicon-512.png') %>">
|
|
8
|
+
<link rel="icon" type="image/x-icon" href="<%= image_path('catpm/favicon.ico') %>">
|
|
9
|
+
<link rel="apple-touch-icon" href="<%= image_path('catpm/favicon-512.png') %>">
|
|
6
10
|
<%= csrf_meta_tags %>
|
|
7
11
|
<%= yield :head_extra if content_for?(:head_extra) %>
|
|
8
12
|
<style>
|
|
@@ -541,10 +545,14 @@
|
|
|
541
545
|
if (action === 'ignore') {
|
|
542
546
|
apiRequest(url, 'PATCH').then(function(data) { if (data.ignored && row) fadeOutRow(row); });
|
|
543
547
|
} else if (action === 'delete') {
|
|
544
|
-
if (!confirm('Delete this
|
|
548
|
+
if (!confirm('Delete this and all its data?')) return;
|
|
545
549
|
apiRequest(url, 'DELETE').then(function(data) { if (data.deleted && row) fadeOutRow(row); });
|
|
546
550
|
} else if (action === 'unignore') {
|
|
547
551
|
apiRequest(url, 'PATCH').then(function(data) { if (!data.ignored && row) fadeOutRow(row); });
|
|
552
|
+
} else if (action === 'resolve') {
|
|
553
|
+
apiRequest(url, 'PATCH').then(function(data) { if (data.resolved && row) fadeOutRow(row); });
|
|
554
|
+
} else if (action === 'unresolve') {
|
|
555
|
+
apiRequest(url, 'PATCH').then(function(data) { if (!data.resolved && row) fadeOutRow(row); });
|
|
548
556
|
}
|
|
549
557
|
return;
|
|
550
558
|
}
|
data/config/routes.rb
CHANGED
|
@@ -10,7 +10,11 @@ Catpm::Engine.routes.draw do
|
|
|
10
10
|
patch 'endpoint/ignore', to: 'endpoints#toggle_ignore', as: :endpoint_ignore
|
|
11
11
|
get 'endpoints/ignored', to: 'endpoints#ignored', as: :ignored_endpoints
|
|
12
12
|
resources :samples, only: [:show, :destroy]
|
|
13
|
-
resources :events, only: [:index, :show, :destroy], param: :name
|
|
13
|
+
resources :events, only: [:index, :show, :destroy], param: :name do
|
|
14
|
+
collection do
|
|
15
|
+
delete 'samples/:sample_id', to: 'events#destroy_sample', as: :destroy_sample
|
|
16
|
+
end
|
|
17
|
+
end
|
|
14
18
|
patch 'events/:name/pin', to: 'events#toggle_pin', as: :event_pin
|
|
15
19
|
patch 'events/:name/ignore', to: 'events#toggle_ignore', as: :event_ignore
|
|
16
20
|
get 'events_ignored', to: 'events#ignored', as: :ignored_events
|
|
@@ -21,6 +25,7 @@ Catpm::Engine.routes.draw do
|
|
|
21
25
|
member do
|
|
22
26
|
patch :resolve
|
|
23
27
|
patch :unresolve
|
|
28
|
+
patch :toggle_pin
|
|
24
29
|
end
|
|
25
30
|
end
|
|
26
31
|
end
|
|
@@ -50,6 +50,7 @@ class CreateCatpmTables < ActiveRecord::Migration[8.0]
|
|
|
50
50
|
t.json :contexts
|
|
51
51
|
t.json :occurrence_buckets
|
|
52
52
|
t.datetime :resolved_at
|
|
53
|
+
t.boolean :pinned, null: false, default: false
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
add_index :catpm_errors, :fingerprint, unique: true, name: 'idx_catpm_errors_fingerprint'
|
data/lib/catpm/middleware.rb
CHANGED
data/lib/catpm/version.rb
CHANGED
data/lib/tasks/catpm_tasks.rake
CHANGED
|
@@ -35,6 +35,13 @@ namespace :catpm do
|
|
|
35
35
|
puts '[catpm] catpm_endpoint_prefs table already exists, skipping'
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
unless connection.column_exists?(:catpm_errors, :pinned)
|
|
39
|
+
connection.add_column :catpm_errors, :pinned, :boolean, null: false, default: false
|
|
40
|
+
puts '[catpm] Added pinned column to catpm_errors'
|
|
41
|
+
else
|
|
42
|
+
puts '[catpm] catpm_errors.pinned already exists, skipping'
|
|
43
|
+
end
|
|
44
|
+
|
|
38
45
|
unless connection.table_exists?(:catpm_event_prefs)
|
|
39
46
|
connection.create_table :catpm_event_prefs do |t|
|
|
40
47
|
t.string :name, null: false
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: catpm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ''
|
|
@@ -33,6 +33,9 @@ files:
|
|
|
33
33
|
- MIT-LICENSE
|
|
34
34
|
- README.md
|
|
35
35
|
- Rakefile
|
|
36
|
+
- app/assets/images/catpm/favicon-32.png
|
|
37
|
+
- app/assets/images/catpm/favicon-512.png
|
|
38
|
+
- app/assets/images/catpm/favicon.ico
|
|
36
39
|
- app/assets/stylesheets/catpm/application.css
|
|
37
40
|
- app/controllers/catpm/application_controller.rb
|
|
38
41
|
- app/controllers/catpm/endpoints_controller.rb
|