source_monitor 0.11.1 → 0.12.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/.claude/commands/rails-audit.md +77 -0
- data/CHANGELOG.md +50 -0
- data/CLAUDE.md +2 -2
- data/Gemfile.lock +7 -20
- data/RAILS_AUDIT.md +424 -0
- data/VERSION +1 -1
- data/app/assets/builds/source_monitor/application.css +4 -24
- data/app/assets/builds/source_monitor/application.js +57 -89
- data/app/assets/builds/source_monitor/application.js.map +4 -4
- data/app/assets/javascripts/source_monitor/application.js +3 -6
- data/app/assets/javascripts/source_monitor/controllers/dropdown_controller.js +6 -86
- data/app/assets/javascripts/source_monitor/controllers/filter_submit_controller.js +13 -0
- data/app/assets/javascripts/source_monitor/controllers/modal_controller.js +56 -0
- data/app/assets/javascripts/source_monitor/controllers/notification_controller.js +3 -13
- data/app/components/source_monitor/application_component.rb +10 -0
- data/app/components/source_monitor/filter_dropdown_component.rb +62 -0
- data/app/components/source_monitor/icon_component.rb +140 -0
- data/app/components/source_monitor/status_badge_component.html.erb +8 -0
- data/app/components/source_monitor/status_badge_component.rb +96 -0
- data/app/controllers/concerns/source_monitor/sanitizes_search_params.rb +4 -0
- data/app/controllers/concerns/source_monitor/set_source.rb +13 -0
- data/app/controllers/source_monitor/application_controller.rb +17 -0
- data/app/controllers/source_monitor/bulk_scrape_enablements_controller.rb +6 -10
- data/app/controllers/source_monitor/dashboard_controller.rb +5 -1
- data/app/controllers/source_monitor/import_history_dismissals_controller.rb +1 -1
- data/app/controllers/source_monitor/import_sessions_controller.rb +30 -9
- data/app/controllers/source_monitor/item_scrapes_controller.rb +70 -0
- data/app/controllers/source_monitor/items_controller.rb +2 -69
- data/app/controllers/source_monitor/source_bulk_scrapes_controller.rb +1 -4
- data/app/controllers/source_monitor/source_favicon_fetches_controller.rb +2 -12
- data/app/controllers/source_monitor/source_fetches_controller.rb +1 -6
- data/app/controllers/source_monitor/source_health_checks_controller.rb +9 -16
- data/app/controllers/source_monitor/source_health_resets_controller.rb +1 -6
- data/app/controllers/source_monitor/source_retries_controller.rb +1 -6
- data/app/controllers/source_monitor/source_scrape_tests_controller.rb +2 -4
- data/app/controllers/source_monitor/source_turbo_responses.rb +1 -3
- data/app/controllers/source_monitor/sources_controller.rb +15 -20
- data/app/helpers/source_monitor/application_helper.rb +15 -31
- data/app/helpers/source_monitor/health_badge_helper.rb +8 -0
- data/app/jobs/source_monitor/download_content_images_job.rb +1 -59
- data/app/jobs/source_monitor/favicon_fetch_job.rb +1 -58
- data/app/jobs/source_monitor/fetch_feed_job.rb +2 -52
- data/app/jobs/source_monitor/import_opml_job.rb +6 -145
- data/app/jobs/source_monitor/import_session_health_check_job.rb +15 -76
- data/app/jobs/source_monitor/item_cleanup_job.rb +5 -0
- data/app/jobs/source_monitor/log_cleanup_job.rb +13 -2
- data/app/jobs/source_monitor/schedule_fetches_job.rb +8 -0
- data/app/jobs/source_monitor/scrape_item_job.rb +6 -52
- data/app/jobs/source_monitor/source_health_check_job.rb +1 -72
- data/app/models/concerns/source_monitor/loggable.rb +12 -0
- data/app/models/source_monitor/fetch_log.rb +0 -8
- data/app/models/source_monitor/health_check_log.rb +0 -8
- data/app/models/source_monitor/import_history.rb +14 -0
- data/app/models/source_monitor/import_session.rb +2 -0
- data/app/models/source_monitor/item.rb +15 -0
- data/app/models/source_monitor/item_content.rb +4 -3
- data/app/models/source_monitor/scrape_log.rb +4 -6
- data/app/models/source_monitor/source.rb +28 -19
- data/app/presenters/source_monitor/base_presenter.rb +19 -0
- data/app/presenters/source_monitor/source_details_presenter.rb +61 -0
- data/app/presenters/source_monitor/sources_filter_presenter.rb +61 -0
- data/app/views/source_monitor/dashboard/_recent_activity.html.erb +3 -3
- data/app/views/source_monitor/dashboard/_stat_card.html.erb +2 -1
- data/app/views/source_monitor/dashboard/_stats.html.erb +5 -7
- data/app/views/source_monitor/items/_details.html.erb +11 -14
- data/app/views/source_monitor/items/index.html.erb +10 -35
- data/app/views/source_monitor/logs/index.html.erb +20 -41
- data/app/views/source_monitor/shared/_form_errors.html.erb +14 -0
- data/app/views/source_monitor/source_scrape_tests/_result.html.erb +1 -29
- data/app/views/source_monitor/source_scrape_tests/_result_content.html.erb +33 -0
- data/app/views/source_monitor/source_scrape_tests/show.html.erb +1 -29
- data/app/views/source_monitor/sources/_bulk_scrape_enable_modal.html.erb +2 -2
- data/app/views/source_monitor/sources/_bulk_scrape_modal.html.erb +7 -5
- data/app/views/source_monitor/sources/_details.html.erb +24 -52
- data/app/views/source_monitor/sources/_health_status_badge.html.erb +4 -6
- data/app/views/source_monitor/sources/_row.html.erb +7 -18
- data/app/views/source_monitor/sources/edit.html.erb +1 -10
- data/app/views/source_monitor/sources/index.html.erb +26 -46
- data/app/views/source_monitor/sources/new.html.erb +1 -10
- data/config/routes.rb +1 -1
- data/db/migrate/20260313120000_add_composite_indexes_to_log_tables.rb +14 -0
- data/db/migrate/20260314120000_align_health_status_default.rb +11 -0
- data/lib/source_monitor/analytics/sources_index_metrics.rb +15 -0
- data/lib/source_monitor/dashboard/queries/recent_activity_query.rb +10 -4
- data/lib/source_monitor/dashboard/turbo_broadcaster.rb +21 -5
- data/lib/source_monitor/favicons/fetcher.rb +86 -0
- data/lib/source_monitor/fetching/cloudflare_bypass.rb +14 -5
- data/lib/source_monitor/fetching/completion/event_publisher.rb +12 -0
- data/lib/source_monitor/fetching/completion/follow_up_handler.rb +15 -2
- data/lib/source_monitor/fetching/completion/retention_handler.rb +11 -3
- data/lib/source_monitor/fetching/feed_fetcher.rb +2 -21
- data/lib/source_monitor/fetching/fetch_runner.rb +12 -3
- data/lib/source_monitor/fetching/retry_orchestrator.rb +102 -0
- data/lib/source_monitor/fetching/stalled_fetch_reconciler.rb +9 -0
- data/lib/source_monitor/health/source_health_check_orchestrator.rb +95 -0
- data/lib/source_monitor/health.rb +1 -0
- data/lib/source_monitor/images/downloader.rb +6 -7
- data/lib/source_monitor/images/processor.rb +98 -0
- data/lib/source_monitor/import_sessions/health_check_updater.rb +95 -0
- data/lib/source_monitor/import_sessions/opml_importer.rb +163 -0
- data/lib/source_monitor/items/item_creator.rb +0 -21
- data/lib/source_monitor/logs/query.rb +20 -0
- data/lib/source_monitor/queries/scrape_candidates_query.rb +30 -0
- data/lib/source_monitor/queries.rb +7 -0
- data/lib/source_monitor/scheduler.rb +5 -0
- data/lib/source_monitor/scraping/bulk_result_presenter.rb +11 -8
- data/lib/source_monitor/scraping/runner.rb +52 -0
- data/lib/source_monitor/scraping/scheduler.rb +5 -0
- data/lib/source_monitor/scraping/state.rb +4 -2
- data/lib/source_monitor/security/parameter_sanitizer.rb +7 -0
- data/lib/source_monitor/version.rb +1 -1
- data/lib/source_monitor.rb +7 -0
- data/source_monitor.gemspec +1 -0
- metadata +47 -1
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
<% base_params = @filter_params.except(:page) %>
|
|
10
10
|
|
|
11
|
+
<%= turbo_frame_tag "source_monitor_logs" do %>
|
|
11
12
|
<div class="flex flex-wrap items-center gap-3">
|
|
12
13
|
<div class="flex overflow-hidden rounded-md border border-slate-200">
|
|
13
14
|
<% status_options = [
|
|
@@ -24,7 +25,8 @@
|
|
|
24
25
|
"px-4 py-2 text-sm font-medium border-slate-200",
|
|
25
26
|
active ? "bg-slate-800 text-white" : "bg-white text-slate-600 hover:bg-slate-50",
|
|
26
27
|
value.present? ? "border-l" : ""
|
|
27
|
-
].join(" ")
|
|
28
|
+
].join(" "),
|
|
29
|
+
data: { turbo_frame: "source_monitor_logs" } %>
|
|
28
30
|
<% end %>
|
|
29
31
|
</div>
|
|
30
32
|
|
|
@@ -44,12 +46,13 @@
|
|
|
44
46
|
"px-4 py-2 text-sm font-medium border-slate-200",
|
|
45
47
|
active ? "bg-slate-800 text-white" : "bg-white text-slate-600 hover:bg-slate-50",
|
|
46
48
|
value.present? ? "border-l" : ""
|
|
47
|
-
].join(" ")
|
|
49
|
+
].join(" "),
|
|
50
|
+
data: { turbo_frame: "source_monitor_logs" } %>
|
|
48
51
|
<% end %>
|
|
49
52
|
</div>
|
|
50
53
|
</div>
|
|
51
54
|
|
|
52
|
-
<%= form_with url: source_monitor.logs_path, method: :get,
|
|
55
|
+
<%= form_with url: source_monitor.logs_path, method: :get, html: { class: "rounded-lg border border-slate-200 bg-white p-4 shadow-sm", data: { turbo_frame: "source_monitor_logs" } } do |form| %>
|
|
53
56
|
<%= form.hidden_field :status, value: @filter_set.status %>
|
|
54
57
|
<%= form.hidden_field :log_type, value: @filter_set.log_type %>
|
|
55
58
|
|
|
@@ -62,12 +65,10 @@
|
|
|
62
65
|
class: "mt-1 rounded-md border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-slate-500 focus:outline-none focus:ring-2 focus:ring-slate-200" %>
|
|
63
66
|
</div>
|
|
64
67
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
{}, class: "mt-1 rounded-md border border-slate-300 px-3 py-2 text-sm shadow-sm focus:border-slate-500 focus:outline-none focus:ring-2 focus:ring-slate-200" %>
|
|
70
|
-
</div>
|
|
68
|
+
<%= render SourceMonitor::FilterDropdownComponent.new(
|
|
69
|
+
label: "Timeframe", param_name: :timeframe,
|
|
70
|
+
options: [["All time", ""], ["Last 24 hours", "24h"], ["Last 7 days", "7d"], ["Last 30 days", "30d"]],
|
|
71
|
+
selected_value: @filter_set.timeframe, form: form) %>
|
|
71
72
|
|
|
72
73
|
<div class="flex flex-col">
|
|
73
74
|
<%= form.label :started_after, "Started after", class: "text-xs font-semibold uppercase tracking-wide text-slate-500" %>
|
|
@@ -100,7 +101,7 @@
|
|
|
100
101
|
|
|
101
102
|
<div class="mt-4 flex flex-wrap items-center gap-3">
|
|
102
103
|
<%= form.submit "Search", class: "inline-flex items-center rounded-md bg-slate-800 px-4 py-2 text-sm font-medium text-white hover:bg-slate-700" %>
|
|
103
|
-
<%= link_to "Clear", source_monitor.logs_path, class: "text-sm font-medium text-blue-600 hover:text-blue-500" %>
|
|
104
|
+
<%= link_to "Clear", source_monitor.logs_path, class: "text-sm font-medium text-blue-600 hover:text-blue-500", data: { turbo_frame: "source_monitor_logs" } %>
|
|
104
105
|
</div>
|
|
105
106
|
<% end %>
|
|
106
107
|
|
|
@@ -155,8 +156,9 @@
|
|
|
155
156
|
<span class="font-medium"><%= row.http_summary %></span>
|
|
156
157
|
</td>
|
|
157
158
|
<td class="px-6 py-4 text-sm">
|
|
158
|
-
|
|
159
|
-
|
|
159
|
+
<%= render SourceMonitor::StatusBadgeComponent.new(
|
|
160
|
+
status: row.success? ? "success" : "failed",
|
|
161
|
+
label: row.status_label) %>
|
|
160
162
|
<% if row.error_message.present? %>
|
|
161
163
|
<p class="mt-2 text-xs text-slate-500"><%= row.error_message %></p>
|
|
162
164
|
<% end %>
|
|
@@ -185,33 +187,10 @@
|
|
|
185
187
|
</table>
|
|
186
188
|
</div>
|
|
187
189
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
<% next_page = @query_result.has_next_page ? @query_result.page + 1 : @query_result.page %>
|
|
195
|
-
<% prev_params = @filter_params.merge(page: prev_page).compact %>
|
|
196
|
-
<% next_params = @filter_params.merge(page: next_page).compact %>
|
|
197
|
-
<%= link_to "Previous",
|
|
198
|
-
source_monitor.logs_path(prev_params),
|
|
199
|
-
class: [
|
|
200
|
-
"inline-flex items-center rounded-md border px-3 py-1 text-sm font-medium",
|
|
201
|
-
@query_result.has_previous_page ? "border-slate-300 text-slate-700 hover:bg-slate-50" : "border-slate-200 text-slate-300 cursor-not-allowed"
|
|
202
|
-
].join(" "),
|
|
203
|
-
aria: { disabled: !@query_result.has_previous_page } %>
|
|
204
|
-
<%= link_to "Next",
|
|
205
|
-
source_monitor.logs_path(next_params),
|
|
206
|
-
class: [
|
|
207
|
-
"inline-flex items-center rounded-md border px-3 py-1 text-sm font-medium",
|
|
208
|
-
@query_result.has_next_page ? "border-slate-300 text-slate-700 hover:bg-slate-50" : "border-slate-200 text-slate-300 cursor-not-allowed"
|
|
209
|
-
].join(" "),
|
|
210
|
-
aria: { disabled: !@query_result.has_next_page } %>
|
|
211
|
-
</div>
|
|
212
|
-
</div>
|
|
213
|
-
|
|
214
|
-
<p class="text-xs text-slate-500">
|
|
215
|
-
Showing up to <%= @query_result.per_page %> logs per page.
|
|
216
|
-
</p>
|
|
190
|
+
<%= render "source_monitor/shared/pagination",
|
|
191
|
+
paginator_result: @query_result,
|
|
192
|
+
base_path: source_monitor.logs_path,
|
|
193
|
+
extra_params: @filter_params.except(:page),
|
|
194
|
+
turbo_frame: "source_monitor_logs" %>
|
|
195
|
+
<% end %>
|
|
217
196
|
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<%# Shared form validation error display
|
|
2
|
+
Locals:
|
|
3
|
+
record - ActiveRecord model instance with potential errors
|
|
4
|
+
%>
|
|
5
|
+
<% if record.errors.any? %>
|
|
6
|
+
<div class="mt-4 rounded border border-red-300 bg-red-50 p-4">
|
|
7
|
+
<h2 class="font-medium text-red-700">Please fix the following:</h2>
|
|
8
|
+
<ul class="mt-2 list-disc space-y-1 pl-5 text-red-700">
|
|
9
|
+
<% record.errors.full_messages.each do |message| %>
|
|
10
|
+
<li><%= message %></li>
|
|
11
|
+
<% end %>
|
|
12
|
+
</ul>
|
|
13
|
+
</div>
|
|
14
|
+
<% end %>
|
|
@@ -28,35 +28,7 @@
|
|
|
28
28
|
</div>
|
|
29
29
|
|
|
30
30
|
<div class="px-6 py-5">
|
|
31
|
-
|
|
32
|
-
<div>
|
|
33
|
-
<dt class="text-xs font-medium uppercase tracking-wide text-slate-500">Feed Word Count</dt>
|
|
34
|
-
<dd class="mt-1 text-2xl font-semibold text-slate-900"><%= test_result[:feed_word_count] || "N/A" %></dd>
|
|
35
|
-
</div>
|
|
36
|
-
<div>
|
|
37
|
-
<dt class="text-xs font-medium uppercase tracking-wide text-slate-500">Scraped Word Count</dt>
|
|
38
|
-
<dd class="mt-1 text-2xl font-semibold text-slate-900"><%= test_result[:scraped_word_count] || "N/A" %></dd>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
|
|
42
|
-
<% if test_result[:improvement] && test_result[:improvement] != 0 %>
|
|
43
|
-
<div class="mt-4">
|
|
44
|
-
<% color = test_result[:improvement] > 0 ? "text-green-600" : "text-amber-600" %>
|
|
45
|
-
<span class="text-sm font-medium <%= color %>">
|
|
46
|
-
<%= test_result[:improvement] > 0 ? "+" : "" %><%= test_result[:improvement] %>% word count change
|
|
47
|
-
</span>
|
|
48
|
-
</div>
|
|
49
|
-
<% end %>
|
|
50
|
-
|
|
51
|
-
<% if test_result[:scrape_result]&.success? %>
|
|
52
|
-
<div class="mt-4 rounded-md bg-green-50 px-3 py-2 text-sm text-green-700">
|
|
53
|
-
Scrape successful. Enabling scraping for this source would capture more content.
|
|
54
|
-
</div>
|
|
55
|
-
<% else %>
|
|
56
|
-
<div class="mt-4 rounded-md bg-amber-50 px-3 py-2 text-sm text-amber-700">
|
|
57
|
-
Scrape had issues: <%= test_result[:scrape_result]&.message || "Unknown error" %>
|
|
58
|
-
</div>
|
|
59
|
-
<% end %>
|
|
31
|
+
<%= render "source_monitor/source_scrape_tests/result_content", test_result: test_result %>
|
|
60
32
|
</div>
|
|
61
33
|
|
|
62
34
|
<div class="flex items-center justify-end gap-3 border-t border-slate-200 px-6 py-4">
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<%# Shared scrape test result content used by both show.html.erb and _result.html.erb
|
|
2
|
+
Locals:
|
|
3
|
+
test_result - Hash with :feed_word_count, :scraped_word_count, :improvement, :scrape_result
|
|
4
|
+
%>
|
|
5
|
+
<div class="grid grid-cols-2 gap-6">
|
|
6
|
+
<div>
|
|
7
|
+
<dt class="text-xs font-medium uppercase tracking-wide text-slate-500">Feed Word Count</dt>
|
|
8
|
+
<dd class="mt-1 text-2xl font-semibold text-slate-900"><%= test_result[:feed_word_count] || "N/A" %></dd>
|
|
9
|
+
</div>
|
|
10
|
+
<div>
|
|
11
|
+
<dt class="text-xs font-medium uppercase tracking-wide text-slate-500">Scraped Word Count</dt>
|
|
12
|
+
<dd class="mt-1 text-2xl font-semibold text-slate-900"><%= test_result[:scraped_word_count] || "N/A" %></dd>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<% if test_result[:improvement] && test_result[:improvement] != 0 %>
|
|
17
|
+
<div class="mt-4">
|
|
18
|
+
<% color = test_result[:improvement] > 0 ? "text-green-600" : "text-amber-600" %>
|
|
19
|
+
<span class="text-sm font-medium <%= color %>">
|
|
20
|
+
<%= test_result[:improvement] > 0 ? "+" : "" %><%= test_result[:improvement] %>% word count change
|
|
21
|
+
</span>
|
|
22
|
+
</div>
|
|
23
|
+
<% end %>
|
|
24
|
+
|
|
25
|
+
<% if test_result[:scrape_result]&.success? %>
|
|
26
|
+
<div class="mt-4 rounded-md bg-green-50 px-3 py-2 text-sm text-green-700">
|
|
27
|
+
Scrape successful. Enabling scraping for this source would capture more content.
|
|
28
|
+
</div>
|
|
29
|
+
<% else %>
|
|
30
|
+
<div class="mt-4 rounded-md bg-amber-50 px-3 py-2 text-sm text-amber-700">
|
|
31
|
+
Scrape had issues: <%= test_result[:scrape_result]&.message || "Unknown error" %>
|
|
32
|
+
</div>
|
|
33
|
+
<% end %>
|
|
@@ -13,35 +13,7 @@
|
|
|
13
13
|
</p>
|
|
14
14
|
</div>
|
|
15
15
|
<div class="px-6 py-5">
|
|
16
|
-
|
|
17
|
-
<div>
|
|
18
|
-
<dt class="text-xs font-medium uppercase tracking-wide text-slate-500">Feed Word Count</dt>
|
|
19
|
-
<dd class="mt-1 text-2xl font-semibold text-slate-900"><%= @test_result[:feed_word_count] || "N/A" %></dd>
|
|
20
|
-
</div>
|
|
21
|
-
<div>
|
|
22
|
-
<dt class="text-xs font-medium uppercase tracking-wide text-slate-500">Scraped Word Count</dt>
|
|
23
|
-
<dd class="mt-1 text-2xl font-semibold text-slate-900"><%= @test_result[:scraped_word_count] || "N/A" %></dd>
|
|
24
|
-
</div>
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
<% if @test_result[:improvement] && @test_result[:improvement] != 0 %>
|
|
28
|
-
<div class="mt-4">
|
|
29
|
-
<% color = @test_result[:improvement] > 0 ? "text-green-600" : "text-amber-600" %>
|
|
30
|
-
<span class="text-sm font-medium <%= color %>">
|
|
31
|
-
<%= @test_result[:improvement] > 0 ? "+" : "" %><%= @test_result[:improvement] %>% word count change
|
|
32
|
-
</span>
|
|
33
|
-
</div>
|
|
34
|
-
<% end %>
|
|
35
|
-
|
|
36
|
-
<% if @test_result[:scrape_result]&.success? %>
|
|
37
|
-
<div class="mt-4 rounded-md bg-green-50 px-3 py-2 text-sm text-green-700">
|
|
38
|
-
Scrape successful. Enabling scraping for this source would capture more content.
|
|
39
|
-
</div>
|
|
40
|
-
<% else %>
|
|
41
|
-
<div class="mt-4 rounded-md bg-amber-50 px-3 py-2 text-sm text-amber-700">
|
|
42
|
-
Scrape had issues: <%= @test_result[:scrape_result]&.message || "Unknown error" %>
|
|
43
|
-
</div>
|
|
44
|
-
<% end %>
|
|
16
|
+
<%= render "source_monitor/source_scrape_tests/result_content", test_result: @test_result %>
|
|
45
17
|
</div>
|
|
46
18
|
|
|
47
19
|
<% unless @source.scraping_enabled? %>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<div data-controller="modal" class="relative">
|
|
2
|
-
<div data-modal-target="panel" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50" data-action="click->modal#backdrop">
|
|
2
|
+
<div data-modal-target="panel" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50" data-action="click->modal#backdrop" role="dialog" aria-modal="true" aria-labelledby="bulk-scrape-enable-modal-heading">
|
|
3
3
|
<div class="w-full max-w-md rounded-lg bg-white shadow-xl" data-action="click->modal#stop">
|
|
4
4
|
<div class="border-b border-slate-200 px-6 py-4">
|
|
5
|
-
<h3 class="text-lg font-semibold text-slate-900">Enable Scraping</h3>
|
|
5
|
+
<h3 id="bulk-scrape-enable-modal-heading" class="text-lg font-semibold text-slate-900">Enable Scraping</h3>
|
|
6
6
|
</div>
|
|
7
7
|
<div class="px-6 py-4">
|
|
8
8
|
<p class="text-sm text-slate-700">
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
<% selected = (local_assigns[:selected] || :current).to_sym %>
|
|
2
2
|
<% preview_limit = local_assigns[:preview_limit] || SourceMonitor::Scraping::BulkSourceScraper::DEFAULT_PREVIEW_LIMIT %>
|
|
3
|
-
<%
|
|
3
|
+
<% preview_items = local_assigns[:preview_items] || @items&.to_a || SourceMonitor::Item.where(source_id: source.id)
|
|
4
4
|
.order(Arel.sql("published_at DESC NULLS LAST, created_at DESC"))
|
|
5
|
-
.limit(preview_limit) %>
|
|
6
|
-
<%
|
|
7
|
-
<% counts = SourceMonitor::Scraping::BulkSourceScraper.selection_counts(
|
|
5
|
+
.limit(preview_limit).to_a %>
|
|
6
|
+
<% counts = local_assigns[:counts] || SourceMonitor::Scraping::BulkSourceScraper.selection_counts(
|
|
8
7
|
source: source,
|
|
9
8
|
preview_items: preview_items,
|
|
10
9
|
preview_limit: preview_limit
|
|
@@ -24,12 +23,15 @@
|
|
|
24
23
|
data-modal-target="panel"
|
|
25
24
|
class="hidden fixed inset-0 z-50 items-center justify-center"
|
|
26
25
|
data-testid="bulk-scrape-modal"
|
|
26
|
+
role="dialog"
|
|
27
|
+
aria-modal="true"
|
|
28
|
+
aria-labelledby="bulk-scrape-modal-heading"
|
|
27
29
|
>
|
|
28
30
|
<div class="absolute inset-0 bg-slate-900/40" data-action="click->modal#close"></div>
|
|
29
31
|
<div class="relative z-10 w-full max-w-xl overflow-hidden rounded-lg bg-white shadow-xl">
|
|
30
32
|
<div class="flex items-start justify-between border-b border-slate-200 px-6 py-4">
|
|
31
33
|
<div>
|
|
32
|
-
<h2 class="text-lg font-semibold text-slate-900">Bulk Scrape Items</h2>
|
|
34
|
+
<h2 id="bulk-scrape-modal-heading" class="text-lg font-semibold text-slate-900">Bulk Scrape Items</h2>
|
|
33
35
|
<p class="mt-1 text-xs text-slate-500">Queue scraping jobs for this source without leaving the page.</p>
|
|
34
36
|
</div>
|
|
35
37
|
<button
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<% source = local_assigns.fetch(:source) %>
|
|
2
|
-
<%
|
|
3
|
-
<%
|
|
2
|
+
<% presenter = SourceMonitor::SourceDetailsPresenter.new(source) %>
|
|
3
|
+
<% recent_fetch_logs = @recent_fetch_logs || source.fetch_logs.order(started_at: :desc).limit(5) %>
|
|
4
|
+
<% recent_scrape_logs = @recent_scrape_logs || source.scrape_logs.order(started_at: :desc).limit(5) %>
|
|
4
5
|
<% preview_limit = SourceMonitor::Scraping::BulkSourceScraper::DEFAULT_PREVIEW_LIMIT %>
|
|
5
|
-
<% items = source.items.recent.includes(:item_content).limit(preview_limit) %>
|
|
6
|
-
<% fetch_status = async_status_badge(source.fetch_status) %>
|
|
6
|
+
<% items = @items || source.items.recent.includes(:item_content).limit(preview_limit) %>
|
|
7
7
|
<% health_status_override = local_assigns[:health_status_override] %>
|
|
8
8
|
|
|
9
9
|
<div class="space-y-8">
|
|
@@ -28,15 +28,12 @@
|
|
|
28
28
|
action: "turbo:submit-start->async-submit#start turbo:submit-end->async-submit#finish"
|
|
29
29
|
}
|
|
30
30
|
} do %>
|
|
31
|
-
|
|
32
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
|
33
|
-
</svg>
|
|
31
|
+
<%= render SourceMonitor::IconComponent.new(:refresh, size: :sm, css_class: "text-slate-500") %>
|
|
34
32
|
<% end %>
|
|
35
33
|
<% end %>
|
|
36
34
|
</div>
|
|
37
|
-
<span data-testid="fetch-status-badge"
|
|
38
|
-
<%=
|
|
39
|
-
<%= fetch_status[:label] %>
|
|
35
|
+
<span class="mt-2" data-testid="fetch-status-badge">
|
|
36
|
+
<%= render SourceMonitor::StatusBadgeComponent.new(status: source.fetch_status) %>
|
|
40
37
|
</span>
|
|
41
38
|
</div>
|
|
42
39
|
<div>
|
|
@@ -174,34 +171,11 @@
|
|
|
174
171
|
<h2 class="text-lg font-medium">Source Details</h2>
|
|
175
172
|
</div>
|
|
176
173
|
<dl class="divide-y divide-slate-100">
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
else
|
|
183
|
-
"Closed"
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
details = {
|
|
187
|
-
"Website" => (source.website_url.present? ? external_link_to(source.website_url, source.website_url, class: "text-slate-900 hover:text-blue-500") : "\u2014"),
|
|
188
|
-
"Fetch interval" => "#{source.fetch_interval_minutes} minutes (~#{interval_hours} hours)",
|
|
189
|
-
"Adaptive interval" => source.adaptive_fetching_enabled? ? "Auto" : "Fixed",
|
|
190
|
-
"Scraper" => source.scraper_adapter,
|
|
191
|
-
"Feed content" => source.feed_content_readability_enabled? ? "Readability" : "Raw",
|
|
192
|
-
"Active" => source.active? ? "Yes" : "No",
|
|
193
|
-
"Scraping" => source.scraping_enabled? ? "Enabled" : "Disabled",
|
|
194
|
-
"Auto scrape" => source.auto_scrape? ? "Enabled" : "Disabled",
|
|
195
|
-
"Requires JS" => source.requires_javascript? ? "Yes" : "No",
|
|
196
|
-
"Failure count" => source.failure_count,
|
|
197
|
-
"Retry attempt" => source.fetch_retry_attempt,
|
|
198
|
-
"Circuit state" => circuit_state,
|
|
199
|
-
"Last error" => source.last_error.presence || "None",
|
|
200
|
-
"Items count" => source.items_count,
|
|
201
|
-
"Retention days" => source.items_retention_days || "—",
|
|
202
|
-
"Max items" => source.max_items || "—"
|
|
203
|
-
} %>
|
|
204
|
-
<% details.each do |label, value| %>
|
|
174
|
+
<div class="flex items-center justify-between px-5 py-3">
|
|
175
|
+
<dt class="text-sm font-medium text-slate-600">Website</dt>
|
|
176
|
+
<dd class="text-sm text-slate-900"><%= source.website_url.present? ? external_link_to(source.website_url, source.website_url, class: "text-slate-900 hover:text-blue-500") : "\u2014" %></dd>
|
|
177
|
+
</div>
|
|
178
|
+
<% presenter.details_hash.each do |label, value| %>
|
|
205
179
|
<div class="flex items-center justify-between px-5 py-3">
|
|
206
180
|
<dt class="text-sm font-medium text-slate-600"><%= label %></dt>
|
|
207
181
|
<dd class="text-sm text-slate-900"><%= value %></dd>
|
|
@@ -246,9 +220,10 @@
|
|
|
246
220
|
<div class="px-5 py-3 text-sm text-slate-700">
|
|
247
221
|
<div class="flex items-center justify-between">
|
|
248
222
|
<span>Started <%= log.started_at&.strftime("%b %d, %H:%M") || "Unknown" %></span>
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
223
|
+
<%= render SourceMonitor::StatusBadgeComponent.new(
|
|
224
|
+
status: log.success? ? "success" : "failed",
|
|
225
|
+
label: log.success? ? "Success" : "Failure",
|
|
226
|
+
size: :sm) %>
|
|
252
227
|
</div>
|
|
253
228
|
<p class="mt-1 text-xs text-slate-500"><%= log.items_created %> created · <%= log.items_updated %> updated · <%= log.items_failed %> failed</p>
|
|
254
229
|
</div>
|
|
@@ -269,9 +244,10 @@
|
|
|
269
244
|
<div class="px-5 py-3 text-sm text-slate-700">
|
|
270
245
|
<div class="flex items-center justify-between">
|
|
271
246
|
<span>Started <%= log.started_at&.strftime("%b %d, %H:%M") || "Unknown" %></span>
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
247
|
+
<%= render SourceMonitor::StatusBadgeComponent.new(
|
|
248
|
+
status: log.success? ? "success" : "failed",
|
|
249
|
+
label: log.success? ? "Success" : "Failure",
|
|
250
|
+
size: :sm) %>
|
|
275
251
|
</div>
|
|
276
252
|
<p class="mt-1 text-xs text-slate-500"><%= log.scraper_adapter || "—" %></p>
|
|
277
253
|
</div>
|
|
@@ -327,14 +303,10 @@
|
|
|
327
303
|
</td>
|
|
328
304
|
<td class="px-5 py-4 text-xs">
|
|
329
305
|
<% scrape_badge = item_scrape_status_badge(item: item, source: source) %>
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
>
|
|
335
|
-
<%= loading_spinner_svg(css_class: "h-3.5 w-3.5 animate-spin text-blue-500") if scrape_badge[:show_spinner] %>
|
|
336
|
-
<%= scrape_badge[:label] %>
|
|
337
|
-
</span>
|
|
306
|
+
<%= render SourceMonitor::StatusBadgeComponent.new(
|
|
307
|
+
status: scrape_badge[:status],
|
|
308
|
+
label: scrape_badge[:label],
|
|
309
|
+
data: { testid: "item-scrape-status-badge" }) %>
|
|
338
310
|
</td>
|
|
339
311
|
<td class="px-5 py-4 text-xs text-slate-500">
|
|
340
312
|
<%= item.item_content&.feed_word_count || "\u2014" %>
|
|
@@ -5,18 +5,16 @@
|
|
|
5
5
|
<% interactive = interactive_health_status?(source, override: override) && actions.any? %>
|
|
6
6
|
|
|
7
7
|
<% if interactive %>
|
|
8
|
-
<div data-controller="dropdown" class="relative inline-block text-left" data-testid="source-health-menu">
|
|
8
|
+
<div data-controller="dropdown" class="relative inline-block text-left" data-testid="source-health-menu-<%= dom_id(source) %>">
|
|
9
9
|
<button type="button"
|
|
10
10
|
class="inline-flex w-full items-center justify-center rounded-full px-3 py-1 text-xs font-semibold <%= badge[:classes] %>"
|
|
11
|
-
data-action="dropdown#toggle
|
|
12
|
-
data-testid="source-health-menu-toggle">
|
|
11
|
+
data-action="dropdown#toggle"
|
|
12
|
+
data-testid="source-health-menu-toggle-<%= dom_id(source) %>">
|
|
13
13
|
<% if badge[:show_spinner] %>
|
|
14
14
|
<%= loading_spinner_svg(css_class: "mr-1 h-3.5 w-3.5 animate-spin text-blue-500") %>
|
|
15
15
|
<% end %>
|
|
16
16
|
<span><%= badge[:label] %></span>
|
|
17
|
-
|
|
18
|
-
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 0 1 1.06.02L10 11.168l3.71-3.938a.75.75 0 1 1 1.08 1.04l-4.25 4.5a.75.75 0 0 1-1.08 0l-4.25-4.5a.75.75 0 0 1 .02-1.06z" clip-rule="evenodd" />
|
|
19
|
-
</svg>
|
|
17
|
+
<%= render SourceMonitor::IconComponent.new(:chevron_down, size: :sm, css_class: "ml-2 text-slate-500") %>
|
|
20
18
|
</button>
|
|
21
19
|
<div data-dropdown-target="menu" class="absolute z-20 mt-2 w-80 origin-top-left rounded-md border border-slate-200 bg-white shadow-lg hidden">
|
|
22
20
|
<div class="py-2">
|
|
@@ -4,12 +4,7 @@
|
|
|
4
4
|
<% scrape_candidates = local_assigns[:scrape_candidate_ids] || Set.new %>
|
|
5
5
|
<% activity_rate = rate_map.fetch(source.id, 0.0) %>
|
|
6
6
|
<% health_status_override = local_assigns[:health_status_override] %>
|
|
7
|
-
<% health_status =
|
|
8
|
-
{ label: "Paused", classes: "bg-amber-100 text-amber-700", show_spinner: false }
|
|
9
|
-
else
|
|
10
|
-
source_health_badge(source, override: health_status_override)
|
|
11
|
-
end %>
|
|
12
|
-
<% fetch_status = async_status_badge(source.fetch_status) %>
|
|
7
|
+
<% health_status = source_health_badge(source, override: health_status_override) unless !source.active? %>
|
|
13
8
|
<% search_params = local_assigns[:search_params] || {} %>
|
|
14
9
|
<% delete_query =
|
|
15
10
|
if search_params.respond_to?(:to_unsafe_h)
|
|
@@ -21,7 +16,7 @@
|
|
|
21
16
|
else
|
|
22
17
|
{}
|
|
23
18
|
end %>
|
|
24
|
-
<% delete_query =
|
|
19
|
+
<% delete_query = compact_blank_hash(delete_query) if delete_query.present? %>
|
|
25
20
|
<% delete_path = delete_query.present? ? source_monitor.source_path(source, q: delete_query) : source_monitor.source_path(source) %>
|
|
26
21
|
|
|
27
22
|
<tr id="<%= dom_id(source, :row) %>" class="hover:bg-slate-50">
|
|
@@ -72,7 +67,7 @@
|
|
|
72
67
|
source: source,
|
|
73
68
|
health_status_override: health_status_override %>
|
|
74
69
|
<% else %>
|
|
75
|
-
|
|
70
|
+
<%= render SourceMonitor::StatusBadgeComponent.new(status: "paused") %>
|
|
76
71
|
<% end %>
|
|
77
72
|
<% if source.rolling_success_rate.present? %>
|
|
78
73
|
<span class="text-[11px] text-slate-500">Success Rate: <%= number_to_percentage(source.rolling_success_rate * 100, precision: 0) %></span>
|
|
@@ -81,13 +76,10 @@
|
|
|
81
76
|
</td>
|
|
82
77
|
<td class="px-6 py-4">
|
|
83
78
|
<div class="flex flex-col gap-1 text-xs">
|
|
84
|
-
|
|
85
|
-
<%= loading_spinner_svg(css_class: "mr-1 h-3.5 w-3.5 animate-spin text-blue-500") if fetch_status[:show_spinner] %>
|
|
86
|
-
<%= fetch_status[:label] %>
|
|
79
|
+
<%= render SourceMonitor::StatusBadgeComponent.new(status: source.fetch_status) %>
|
|
87
80
|
<% if source.fetch_status == "fetching" && source.last_fetch_started_at.present? %>
|
|
88
81
|
<span class="ml-2 font-normal text-[10px] text-slate-500">(since <%= source.last_fetch_started_at.strftime("%H:%M:%S") %>)</span>
|
|
89
82
|
<% end %>
|
|
90
|
-
</span>
|
|
91
83
|
<span class="text-[11px] text-slate-500">(<%= number_with_precision(1440.0 / source.fetch_interval_minutes, precision: 1) %>x / day)</span>
|
|
92
84
|
</div>
|
|
93
85
|
</td>
|
|
@@ -106,15 +98,12 @@
|
|
|
106
98
|
<%= source.last_fetched_at ? source.last_fetched_at.strftime("%b %d, %H:%M") : "Never" %>
|
|
107
99
|
</td>
|
|
108
100
|
<td class="px-6 py-4 text-right text-sm">
|
|
109
|
-
<div data-controller="dropdown" class="relative inline-block text-left">
|
|
101
|
+
<div data-controller="dropdown" class="relative inline-block text-left" data-testid="source-actions-<%= dom_id(source) %>">
|
|
110
102
|
<button type="button"
|
|
111
103
|
class="inline-flex items-center rounded-md border border-slate-200 bg-white px-2.5 py-1.5 text-slate-600 shadow-sm hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
|
112
|
-
data-action="dropdown#toggle
|
|
104
|
+
data-action="dropdown#toggle"
|
|
113
105
|
aria-label="Source actions">
|
|
114
|
-
|
|
115
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94a.75.75 0 0 0-1.093-.332l-.822.548a2.25 2.25 0 0 1-2.287.014l-.856-.506a.75.75 0 0 0-1.087.63l.03.988a2.25 2.25 0 0 1-.639 1.668l-.715.715a.75.75 0 0 0 0 1.06l.715.715a2.25 2.25 0 0 1 .639 1.668l-.03.988a.75.75 0 0 0 1.087.63l.856-.506a2.25 2.25 0 0 1 2.287.014l.822.548a.75.75 0 0 0 1.093-.332l.38-.926a2.25 2.25 0 0 1 1.451-1.297l.964-.258a.75.75 0 0 0 .534-.72v-.946a.75.75 0 0 0-.534-.72l-.964-.258a2.25 2.25 0 0 1-1.45-1.297l-.381-.926Z" />
|
|
116
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M12 10a2 2 0 1 1-4 0 2 2 0 0 1 4 0Z" />
|
|
117
|
-
</svg>
|
|
106
|
+
<%= render SourceMonitor::IconComponent.new(:menu_dots) %>
|
|
118
107
|
</button>
|
|
119
108
|
<div data-dropdown-target="menu" class="absolute right-0 z-10 mt-2 w-36 origin-top-right rounded-md border border-slate-200 bg-white shadow-lg transition hidden">
|
|
120
109
|
<div class="py-1 text-sm text-slate-700">
|
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
<div class="mx-auto max-w-2xl py-10">
|
|
2
2
|
<h1 class="text-3xl font-semibold">Edit Source</h1>
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
<div class="mt-4 rounded border border-red-300 bg-red-50 p-4">
|
|
6
|
-
<h2 class="font-medium text-red-700">Please fix the following:</h2>
|
|
7
|
-
<ul class="mt-2 list-disc space-y-1 pl-5 text-red-700">
|
|
8
|
-
<% @source.errors.full_messages.each do |message| %>
|
|
9
|
-
<li><%= message %></li>
|
|
10
|
-
<% end %>
|
|
11
|
-
</ul>
|
|
12
|
-
</div>
|
|
13
|
-
<% end %>
|
|
4
|
+
<%= render "source_monitor/shared/form_errors", record: @source %>
|
|
14
5
|
|
|
15
6
|
<div class="mt-6">
|
|
16
7
|
<%= render "form", source: @source %>
|
|
@@ -28,45 +28,37 @@
|
|
|
28
28
|
</div>
|
|
29
29
|
</div>
|
|
30
30
|
<div class="flex flex-wrap items-end gap-2">
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
</div>
|
|
31
|
+
<%= render SourceMonitor::FilterDropdownComponent.new(
|
|
32
|
+
label: "Status", param_name: :active_eq,
|
|
33
|
+
options: [["All Statuses", ""], ["Active", "true"], ["Paused", "false"]],
|
|
34
|
+
selected_value: @search_params["active_eq"].to_s, form: form) %>
|
|
35
|
+
<%= render SourceMonitor::FilterDropdownComponent.new(
|
|
36
|
+
label: "Health", param_name: :health_status_eq,
|
|
37
|
+
options: [["All Health", ""], ["Working", "working"], ["Declining", "declining"], ["Improving", "improving"], ["Failing", "failing"]],
|
|
38
|
+
selected_value: @search_params["health_status_eq"].to_s, form: form) %>
|
|
39
|
+
<%= render SourceMonitor::FilterDropdownComponent.new(
|
|
40
|
+
label: "Format", param_name: :feed_format_eq,
|
|
41
|
+
options: [["All Formats", ""], ["RSS", "rss"], ["Atom", "atom"], ["JSON", "json"]],
|
|
42
|
+
selected_value: @search_params["feed_format_eq"].to_s, form: form) %>
|
|
43
|
+
<%= render SourceMonitor::FilterDropdownComponent.new(
|
|
44
|
+
label: "Adapter", param_name: :scraper_adapter_eq,
|
|
45
|
+
options: [["All Adapters", ""]] + @filter_presenter.adapter_options.map { |a| [a.titleize, a] },
|
|
46
|
+
selected_value: @search_params["scraper_adapter_eq"].to_s, form: form) %>
|
|
47
|
+
<%= render SourceMonitor::FilterDropdownComponent.new(
|
|
48
|
+
label: "Scrape", param_name: :scraping_enabled_eq,
|
|
49
|
+
options: [["All Sources", ""], ["Scraping Enabled", "true"], ["Scraping Disabled", "false"], ["Recommendations", "recommend"]],
|
|
50
|
+
selected_value: @search_params["scraping_enabled_eq"].to_s, form: form) %>
|
|
52
51
|
</div>
|
|
53
52
|
<% end %>
|
|
54
53
|
|
|
55
54
|
<div class="overflow-hidden rounded-lg border border-slate-200 bg-white shadow-sm">
|
|
56
55
|
<%= turbo_frame_tag "source_monitor_sources_table" do %>
|
|
57
|
-
<%
|
|
58
|
-
<% active_dropdown_filters = dropdown_filter_keys.select { |k| @search_params[k].present? } %>
|
|
59
|
-
<% has_any_filter = @search_term.present? || @fetch_interval_filter.present? || active_dropdown_filters.any? %>
|
|
60
|
-
<% if has_any_filter %>
|
|
56
|
+
<% if @filter_presenter.has_any_filter? %>
|
|
61
57
|
<div class="rounded-t-lg border-b border-blue-100 bg-blue-50 px-4 py-3 text-xs text-blue-700">
|
|
62
58
|
<% if @search_term.present? %>
|
|
63
59
|
<% clear_search_query = @search_params.dup %>
|
|
64
60
|
<% clear_search_query.delete("name_or_feed_url_or_website_url_cont") %>
|
|
65
|
-
<% clear_search_query =
|
|
66
|
-
clear_search_query.compact_blank
|
|
67
|
-
else
|
|
68
|
-
clear_search_query.reject { |_key, value| value.respond_to?(:blank?) ? value.blank? : value.nil? }
|
|
69
|
-
end %>
|
|
61
|
+
<% clear_search_query = compact_blank_hash(clear_search_query) %>
|
|
70
62
|
<% clear_search_path = clear_search_query.empty? ? source_monitor.sources_path : source_monitor.sources_path(q: clear_search_query) %>
|
|
71
63
|
<div>
|
|
72
64
|
Showing results for "<%= @search_term %>".
|
|
@@ -84,28 +76,16 @@
|
|
|
84
76
|
</div>
|
|
85
77
|
<% end %>
|
|
86
78
|
|
|
87
|
-
<% if
|
|
79
|
+
<% if @filter_presenter.active_filter_keys.any? %>
|
|
88
80
|
<div class="mt-1 flex flex-wrap items-center gap-2">
|
|
89
81
|
<span>Filtered by</span>
|
|
90
|
-
<%
|
|
91
|
-
"active_eq" => @search_params["active_eq"] == "true" ? "Status: Active" : "Status: Paused",
|
|
92
|
-
"health_status_eq" => "Health: #{@search_params['health_status_eq']&.titleize}",
|
|
93
|
-
"feed_format_eq" => "Format: #{@search_params['feed_format_eq']&.upcase}",
|
|
94
|
-
"scraper_adapter_eq" => "Adapter: #{@search_params['scraper_adapter_eq']&.titleize}",
|
|
95
|
-
"scraping_enabled_eq" => @search_params["scraping_enabled_eq"] == "true" ? "Scraping: Enabled" : "Scraping: Disabled",
|
|
96
|
-
"avg_feed_words_lt" => "Avg Feed Words: < #{@search_params['avg_feed_words_lt']}"
|
|
97
|
-
} %>
|
|
98
|
-
<% active_dropdown_filters.each do |filter_key| %>
|
|
82
|
+
<% @filter_presenter.active_filter_keys.each do |filter_key| %>
|
|
99
83
|
<% clear_query = @search_params.dup %>
|
|
100
84
|
<% clear_query.delete(filter_key) %>
|
|
101
|
-
<% clear_query =
|
|
102
|
-
clear_query.compact_blank
|
|
103
|
-
else
|
|
104
|
-
clear_query.reject { |_k, v| v.respond_to?(:blank?) ? v.blank? : v.nil? }
|
|
105
|
-
end %>
|
|
85
|
+
<% clear_query = compact_blank_hash(clear_query) %>
|
|
106
86
|
<% clear_path = clear_query.empty? ? source_monitor.sources_path : source_monitor.sources_path(q: clear_query) %>
|
|
107
87
|
<span class="inline-flex items-center gap-1 rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700">
|
|
108
|
-
<%= filter_labels[filter_key] %>
|
|
88
|
+
<%= @filter_presenter.filter_labels[filter_key] %>
|
|
109
89
|
<%= link_to "×", clear_path, class: "ml-0.5 font-bold text-blue-500 hover:text-blue-700", data: { turbo_frame: "source_monitor_sources_table" } %>
|
|
110
90
|
</span>
|
|
111
91
|
<% end %>
|