source_monitor 0.10.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/agent-memory/vbw-vbw-debugger/MEMORY.md +15 -0
  3. data/.claude/skills/sm-configuration-setting/reference/settings-catalog.md +3 -3
  4. data/.claude/skills/sm-configure/reference/configuration-reference.md +3 -3
  5. data/.claude/skills/sm-domain-model/SKILL.md +2 -2
  6. data/.claude/skills/sm-domain-model/reference/table-structure.md +3 -1
  7. data/.claude/skills/sm-engine-migration/SKILL.md +1 -1
  8. data/.claude/skills/sm-engine-migration/reference/migration-conventions.md +1 -1
  9. data/.claude/skills/sm-health-rule/SKILL.md +18 -21
  10. data/.claude/skills/sm-health-rule/reference/health-system.md +1 -1
  11. data/.claude/skills/sm-host-setup/reference/initializer-template.md +2 -2
  12. data/.claude/skills/sm-upgrade/reference/version-history.md +17 -12
  13. data/CHANGELOG.md +42 -0
  14. data/CLAUDE.md +2 -2
  15. data/Gemfile +1 -0
  16. data/Gemfile.lock +4 -1
  17. data/README.md +3 -3
  18. data/VERSION +1 -1
  19. data/app/assets/builds/source_monitor/application.css +132 -12
  20. data/app/assets/builds/source_monitor/application.js +25 -1
  21. data/app/assets/builds/source_monitor/application.js.map +2 -2
  22. data/app/assets/javascripts/source_monitor/controllers/modal_controller.js +8 -0
  23. data/app/assets/javascripts/source_monitor/controllers/select_all_controller.js +22 -2
  24. data/app/assets/stylesheets/source_monitor/application.tailwind.css +1 -1
  25. data/app/controllers/source_monitor/bulk_scrape_enablements_controller.rb +57 -0
  26. data/app/controllers/source_monitor/dashboard_controller.rb +10 -1
  27. data/app/controllers/source_monitor/import_history_dismissals_controller.rb +20 -0
  28. data/app/controllers/source_monitor/source_retries_controller.rb +10 -2
  29. data/app/controllers/source_monitor/source_scrape_tests_controller.rb +73 -0
  30. data/app/controllers/source_monitor/sources_controller.rb +51 -9
  31. data/app/helpers/source_monitor/application_helper.rb +24 -0
  32. data/app/helpers/source_monitor/health_badge_helper.rb +7 -20
  33. data/app/jobs/source_monitor/fetch_feed_job.rb +32 -3
  34. data/app/jobs/source_monitor/source_health_check_job.rb +1 -1
  35. data/app/models/source_monitor/fetch_log.rb +4 -0
  36. data/app/models/source_monitor/import_history.rb +2 -0
  37. data/app/models/source_monitor/source.rb +47 -2
  38. data/app/views/source_monitor/dashboard/_fetch_schedule.html.erb +94 -68
  39. data/app/views/source_monitor/dashboard/_scrape_recommendations.html.erb +17 -0
  40. data/app/views/source_monitor/dashboard/_stats.html.erb +19 -0
  41. data/app/views/source_monitor/dashboard/index.html.erb +7 -1
  42. data/app/views/source_monitor/import_sessions/health_check/_row.html.erb +2 -2
  43. data/app/views/source_monitor/shared/_pagination.html.erb +74 -0
  44. data/app/views/source_monitor/source_scrape_tests/_result.html.erb +81 -0
  45. data/app/views/source_monitor/source_scrape_tests/show.html.erb +60 -0
  46. data/app/views/source_monitor/sources/_bulk_scrape_enable_modal.html.erb +29 -0
  47. data/app/views/source_monitor/sources/_details.html.erb +19 -1
  48. data/app/views/source_monitor/sources/_empty_state_row.html.erb +1 -1
  49. data/app/views/source_monitor/sources/_import_history_panel.html.erb +12 -5
  50. data/app/views/source_monitor/sources/_row.html.erb +34 -6
  51. data/app/views/source_monitor/sources/index.html.erb +184 -132
  52. data/config/brakeman.ignore +11 -1
  53. data/config/routes.rb +5 -0
  54. data/db/migrate/20260305120000_add_dismissed_at_to_import_histories.rb +7 -0
  55. data/db/migrate/20260306233004_add_error_category_to_fetch_logs.rb +8 -0
  56. data/db/migrate/20260307120000_add_consecutive_fetch_failures_to_sources.rb +11 -0
  57. data/db/migrate/20260312120000_simplify_health_status_values.rb +20 -0
  58. data/docs/configuration.md +9 -1
  59. data/docs/troubleshooting.md +9 -0
  60. data/docs/upgrade.md +31 -0
  61. data/lib/generators/source_monitor/install/templates/source_monitor.rb.tt +2 -3
  62. data/lib/source_monitor/analytics/scrape_recommendations.rb +27 -0
  63. data/lib/source_monitor/configuration/health_settings.rb +0 -2
  64. data/lib/source_monitor/configuration/scraping_settings.rb +8 -1
  65. data/lib/source_monitor/dashboard/queries/stats_query.rb +12 -1
  66. data/lib/source_monitor/dashboard/queries.rb +6 -3
  67. data/lib/source_monitor/dashboard/recent_activity_presenter.rb +6 -5
  68. data/lib/source_monitor/dashboard/upcoming_fetch_schedule.rb +40 -54
  69. data/lib/source_monitor/favicons/discoverer.rb +16 -0
  70. data/lib/source_monitor/favicons/svg_converter.rb +60 -0
  71. data/lib/source_monitor/fetching/cloudflare_bypass.rb +79 -0
  72. data/lib/source_monitor/fetching/feed_fetcher/source_updater.rb +82 -2
  73. data/lib/source_monitor/fetching/feed_fetcher.rb +55 -1
  74. data/lib/source_monitor/fetching/fetch_error.rb +27 -0
  75. data/lib/source_monitor/fetching/fetch_runner.rb +4 -0
  76. data/lib/source_monitor/fetching/retry_policy.rb +4 -0
  77. data/lib/source_monitor/health/import_source_health_check.rb +3 -3
  78. data/lib/source_monitor/health/source_health_monitor.rb +9 -14
  79. data/lib/source_monitor/health/source_health_reset.rb +1 -1
  80. data/lib/source_monitor/pagination/paginator.rb +18 -1
  81. data/lib/source_monitor/version.rb +1 -1
  82. data/lib/source_monitor.rb +3 -0
  83. metadata +17 -1
@@ -562,12 +562,12 @@ video {
562
562
  color: rgb(15 23 42 / var(--tw-text-opacity, 1));
563
563
  }
564
564
 
565
- .fm-admin a {
565
+ .fm-admin a:not([class*="bg-"]) {
566
566
  --tw-text-opacity: 1;
567
567
  color: rgb(37 99 235 / var(--tw-text-opacity, 1));
568
568
  }
569
569
 
570
- .fm-admin a:hover {
570
+ .fm-admin a:not([class*="bg-"]):hover {
571
571
  --tw-text-opacity: 1;
572
572
  color: rgb(59 130 246 / var(--tw-text-opacity, 1));
573
573
  }
@@ -642,6 +642,10 @@ video {
642
642
  position: relative;
643
643
  }
644
644
 
645
+ .fm-admin .sticky {
646
+ position: sticky;
647
+ }
648
+
645
649
  .fm-admin .inset-0 {
646
650
  inset: 0px;
647
651
  }
@@ -659,6 +663,10 @@ video {
659
663
  right: -0.25rem;
660
664
  }
661
665
 
666
+ .fm-admin .bottom-0 {
667
+ bottom: 0px;
668
+ }
669
+
662
670
  .fm-admin .left-4 {
663
671
  left: 1rem;
664
672
  }
@@ -817,10 +825,18 @@ video {
817
825
  min-height: 100vh;
818
826
  }
819
827
 
828
+ .fm-admin .w-10 {
829
+ width: 2.5rem;
830
+ }
831
+
820
832
  .fm-admin .w-12 {
821
833
  width: 3rem;
822
834
  }
823
835
 
836
+ .fm-admin .w-14 {
837
+ width: 3.5rem;
838
+ }
839
+
824
840
  .fm-admin .w-2 {
825
841
  width: 0.5rem;
826
842
  }
@@ -889,6 +905,10 @@ video {
889
905
  max-width: 42rem;
890
906
  }
891
907
 
908
+ .fm-admin .max-w-3xl {
909
+ max-width: 48rem;
910
+ }
911
+
892
912
  .fm-admin .max-w-6xl {
893
913
  max-width: 72rem;
894
914
  }
@@ -897,6 +917,10 @@ video {
897
917
  max-width: 60%;
898
918
  }
899
919
 
920
+ .fm-admin .max-w-lg {
921
+ max-width: 32rem;
922
+ }
923
+
900
924
  .fm-admin .max-w-md {
901
925
  max-width: 28rem;
902
926
  }
@@ -977,10 +1001,18 @@ video {
977
1001
  user-select: all;
978
1002
  }
979
1003
 
1004
+ .fm-admin .resize {
1005
+ resize: both;
1006
+ }
1007
+
980
1008
  .fm-admin .list-disc {
981
1009
  list-style-type: disc;
982
1010
  }
983
1011
 
1012
+ .fm-admin .grid-cols-2 {
1013
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1014
+ }
1015
+
984
1016
  .fm-admin .flex-col {
985
1017
  flex-direction: column;
986
1018
  }
@@ -1156,6 +1188,10 @@ video {
1156
1188
  border-radius: 0.375rem;
1157
1189
  }
1158
1190
 
1191
+ .fm-admin .rounded-xl {
1192
+ border-radius: 0.75rem;
1193
+ }
1194
+
1159
1195
  .fm-admin .rounded-l-md {
1160
1196
  border-top-left-radius: 0.375rem;
1161
1197
  border-bottom-left-radius: 0.375rem;
@@ -1255,6 +1291,11 @@ video {
1255
1291
  border-color: transparent;
1256
1292
  }
1257
1293
 
1294
+ .fm-admin .border-violet-200 {
1295
+ --tw-border-opacity: 1;
1296
+ border-color: rgb(221 214 254 / var(--tw-border-opacity, 1));
1297
+ }
1298
+
1258
1299
  .fm-admin .bg-amber-100 {
1259
1300
  --tw-bg-opacity: 1;
1260
1301
  background-color: rgb(254 243 199 / var(--tw-bg-opacity, 1));
@@ -1274,6 +1315,10 @@ video {
1274
1315
  background-color: rgb(217 119 6 / 0.1);
1275
1316
  }
1276
1317
 
1318
+ .fm-admin .bg-black\/50 {
1319
+ background-color: rgb(0 0 0 / 0.5);
1320
+ }
1321
+
1277
1322
  .fm-admin .bg-blue-100 {
1278
1323
  --tw-bg-opacity: 1;
1279
1324
  background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1));
@@ -1328,11 +1373,6 @@ video {
1328
1373
  background-color: rgb(34 197 94 / var(--tw-bg-opacity, 1));
1329
1374
  }
1330
1375
 
1331
- .fm-admin .bg-orange-100 {
1332
- --tw-bg-opacity: 1;
1333
- background-color: rgb(255 237 213 / var(--tw-bg-opacity, 1));
1334
- }
1335
-
1336
1376
  .fm-admin .bg-red-100 {
1337
1377
  --tw-bg-opacity: 1;
1338
1378
  background-color: rgb(254 226 226 / var(--tw-bg-opacity, 1));
@@ -1411,11 +1451,31 @@ video {
1411
1451
  background-color: rgb(15 23 42 / 0.4);
1412
1452
  }
1413
1453
 
1454
+ .fm-admin .bg-violet-100 {
1455
+ --tw-bg-opacity: 1;
1456
+ background-color: rgb(237 233 254 / var(--tw-bg-opacity, 1));
1457
+ }
1458
+
1459
+ .fm-admin .bg-violet-50 {
1460
+ --tw-bg-opacity: 1;
1461
+ background-color: rgb(245 243 255 / var(--tw-bg-opacity, 1));
1462
+ }
1463
+
1464
+ .fm-admin .bg-violet-600 {
1465
+ --tw-bg-opacity: 1;
1466
+ background-color: rgb(124 58 237 / var(--tw-bg-opacity, 1));
1467
+ }
1468
+
1414
1469
  .fm-admin .bg-white {
1415
1470
  --tw-bg-opacity: 1;
1416
1471
  background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
1417
1472
  }
1418
1473
 
1474
+ .fm-admin .bg-yellow-100 {
1475
+ --tw-bg-opacity: 1;
1476
+ background-color: rgb(254 249 195 / var(--tw-bg-opacity, 1));
1477
+ }
1478
+
1419
1479
  .fm-admin .object-contain {
1420
1480
  -o-object-fit: contain;
1421
1481
  object-fit: contain;
@@ -1425,6 +1485,10 @@ video {
1425
1485
  padding: 0.125rem;
1426
1486
  }
1427
1487
 
1488
+ .fm-admin .p-1 {
1489
+ padding: 0.25rem;
1490
+ }
1491
+
1428
1492
  .fm-admin .p-3 {
1429
1493
  padding: 0.75rem;
1430
1494
  }
@@ -1437,6 +1501,11 @@ video {
1437
1501
  padding: 1.25rem;
1438
1502
  }
1439
1503
 
1504
+ .fm-admin .px-1 {
1505
+ padding-left: 0.25rem;
1506
+ padding-right: 0.25rem;
1507
+ }
1508
+
1440
1509
  .fm-admin .px-2 {
1441
1510
  padding-left: 0.5rem;
1442
1511
  padding-right: 0.5rem;
@@ -1659,6 +1728,11 @@ video {
1659
1728
  color: rgb(120 53 15 / var(--tw-text-opacity, 1));
1660
1729
  }
1661
1730
 
1731
+ .fm-admin .text-blue-400 {
1732
+ --tw-text-opacity: 1;
1733
+ color: rgb(96 165 250 / var(--tw-text-opacity, 1));
1734
+ }
1735
+
1662
1736
  .fm-admin .text-blue-500 {
1663
1737
  --tw-text-opacity: 1;
1664
1738
  color: rgb(59 130 246 / var(--tw-text-opacity, 1));
@@ -1693,6 +1767,11 @@ video {
1693
1767
  color: rgb(6 95 70 / var(--tw-text-opacity, 1));
1694
1768
  }
1695
1769
 
1770
+ .fm-admin .text-green-600 {
1771
+ --tw-text-opacity: 1;
1772
+ color: rgb(22 163 74 / var(--tw-text-opacity, 1));
1773
+ }
1774
+
1696
1775
  .fm-admin .text-green-700 {
1697
1776
  --tw-text-opacity: 1;
1698
1777
  color: rgb(21 128 61 / var(--tw-text-opacity, 1));
@@ -1703,11 +1782,6 @@ video {
1703
1782
  color: rgb(22 101 52 / var(--tw-text-opacity, 1));
1704
1783
  }
1705
1784
 
1706
- .fm-admin .text-orange-700 {
1707
- --tw-text-opacity: 1;
1708
- color: rgb(194 65 12 / var(--tw-text-opacity, 1));
1709
- }
1710
-
1711
1785
  .fm-admin .text-red-700 {
1712
1786
  --tw-text-opacity: 1;
1713
1787
  color: rgb(185 28 28 / var(--tw-text-opacity, 1));
@@ -1783,11 +1857,26 @@ video {
1783
1857
  color: rgb(15 23 42 / var(--tw-text-opacity, 1));
1784
1858
  }
1785
1859
 
1860
+ .fm-admin .text-violet-600 {
1861
+ --tw-text-opacity: 1;
1862
+ color: rgb(124 58 237 / var(--tw-text-opacity, 1));
1863
+ }
1864
+
1865
+ .fm-admin .text-violet-700 {
1866
+ --tw-text-opacity: 1;
1867
+ color: rgb(109 40 217 / var(--tw-text-opacity, 1));
1868
+ }
1869
+
1786
1870
  .fm-admin .text-white {
1787
1871
  --tw-text-opacity: 1;
1788
1872
  color: rgb(255 255 255 / var(--tw-text-opacity, 1));
1789
1873
  }
1790
1874
 
1875
+ .fm-admin .text-yellow-700 {
1876
+ --tw-text-opacity: 1;
1877
+ color: rgb(161 98 7 / var(--tw-text-opacity, 1));
1878
+ }
1879
+
1791
1880
  .fm-admin .underline {
1792
1881
  text-decoration-line: underline;
1793
1882
  }
@@ -1826,6 +1915,12 @@ video {
1826
1915
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
1827
1916
  }
1828
1917
 
1918
+ .fm-admin .shadow-md {
1919
+ --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
1920
+ --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
1921
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
1922
+ }
1923
+
1829
1924
  .fm-admin .shadow-sm {
1830
1925
  --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
1831
1926
  --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
@@ -1855,6 +1950,11 @@ video {
1855
1950
  --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1));
1856
1951
  }
1857
1952
 
1953
+ .fm-admin .ring-slate-300 {
1954
+ --tw-ring-opacity: 1;
1955
+ --tw-ring-color: rgb(203 213 225 / var(--tw-ring-opacity, 1));
1956
+ }
1957
+
1858
1958
  .fm-admin .ring-transparent {
1859
1959
  --tw-ring-color: transparent;
1860
1960
  }
@@ -1894,6 +1994,11 @@ video {
1894
1994
  background-color: rgb(245 158 11 / var(--tw-bg-opacity, 1));
1895
1995
  }
1896
1996
 
1997
+ .fm-admin .hover\:bg-blue-100:hover {
1998
+ --tw-bg-opacity: 1;
1999
+ background-color: rgb(219 234 254 / var(--tw-bg-opacity, 1));
2000
+ }
2001
+
1897
2002
  .fm-admin .hover\:bg-blue-500:hover {
1898
2003
  --tw-bg-opacity: 1;
1899
2004
  background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1));
@@ -1924,6 +2029,16 @@ video {
1924
2029
  background-color: rgb(51 65 85 / var(--tw-bg-opacity, 1));
1925
2030
  }
1926
2031
 
2032
+ .fm-admin .hover\:bg-violet-100:hover {
2033
+ --tw-bg-opacity: 1;
2034
+ background-color: rgb(237 233 254 / var(--tw-bg-opacity, 1));
2035
+ }
2036
+
2037
+ .fm-admin .hover\:bg-violet-500:hover {
2038
+ --tw-bg-opacity: 1;
2039
+ background-color: rgb(139 92 246 / var(--tw-bg-opacity, 1));
2040
+ }
2041
+
1927
2042
  .fm-admin .hover\:text-blue-500:hover {
1928
2043
  --tw-text-opacity: 1;
1929
2044
  color: rgb(59 130 246 / var(--tw-text-opacity, 1));
@@ -2020,6 +2135,11 @@ video {
2020
2135
  --tw-ring-color: rgb(226 232 240 / var(--tw-ring-opacity, 1));
2021
2136
  }
2022
2137
 
2138
+ .fm-admin .focus\:ring-violet-500:focus {
2139
+ --tw-ring-opacity: 1;
2140
+ --tw-ring-color: rgb(139 92 246 / var(--tw-ring-opacity, 1));
2141
+ }
2142
+
2023
2143
  .fm-admin .focus\:ring-offset-2:focus {
2024
2144
  --tw-ring-offset-width: 2px;
2025
2145
  }
@@ -2743,8 +2743,12 @@ var dropdown_controller_default = class extends Controller {
2743
2743
  var modal_controller_default = class extends Controller {
2744
2744
  static targets = ["panel"];
2745
2745
  static classes = ["open"];
2746
+ static values = { autoOpen: Boolean, removeOnClose: Boolean };
2746
2747
  connect() {
2747
2748
  this.handleEscape = this.handleEscape.bind(this);
2749
+ if (this.autoOpenValue) {
2750
+ this.open();
2751
+ }
2748
2752
  }
2749
2753
  disconnect() {
2750
2754
  this.teardown();
@@ -2767,6 +2771,9 @@ var modal_controller_default = class extends Controller {
2767
2771
  this.panelTarget.classList.remove(this.openClass);
2768
2772
  }
2769
2773
  this.teardown();
2774
+ if (this.removeOnCloseValue) {
2775
+ this.element.remove();
2776
+ }
2770
2777
  }
2771
2778
  backdrop(event) {
2772
2779
  if (event.target === event.currentTarget) {
@@ -2826,15 +2833,18 @@ var confirm_navigation_controller_default = class extends Controller {
2826
2833
 
2827
2834
  // app/assets/javascripts/source_monitor/controllers/select_all_controller.js
2828
2835
  var select_all_controller_default = class extends Controller {
2829
- static targets = ["master", "item"];
2836
+ static targets = ["master", "item", "actionBar", "count"];
2830
2837
  connect() {
2831
2838
  this.syncMaster();
2839
+ this.updateActionBar();
2832
2840
  }
2833
2841
  itemTargetConnected() {
2834
2842
  this.syncMaster();
2843
+ this.updateActionBar();
2835
2844
  }
2836
2845
  itemTargetDisconnected() {
2837
2846
  this.syncMaster();
2847
+ this.updateActionBar();
2838
2848
  }
2839
2849
  toggleAll(event) {
2840
2850
  const checked = event.target.checked;
@@ -2842,9 +2852,11 @@ var select_all_controller_default = class extends Controller {
2842
2852
  if (checkbox.disabled) return;
2843
2853
  checkbox.checked = checked;
2844
2854
  });
2855
+ this.updateActionBar();
2845
2856
  }
2846
2857
  toggleItem() {
2847
2858
  this.syncMaster();
2859
+ this.updateActionBar();
2848
2860
  }
2849
2861
  syncMaster() {
2850
2862
  if (!this.hasMasterTarget) return;
@@ -2852,6 +2864,18 @@ var select_all_controller_default = class extends Controller {
2852
2864
  const allChecked = selectable.length > 0 && selectable.every((checkbox) => checkbox.checked);
2853
2865
  this.masterTarget.checked = allChecked;
2854
2866
  }
2867
+ updateActionBar() {
2868
+ if (!this.hasActionBarTarget) return;
2869
+ const checkedCount = this.itemTargets.filter((cb) => cb.checked).length;
2870
+ if (this.hasCountTarget) {
2871
+ this.countTarget.textContent = checkedCount;
2872
+ }
2873
+ if (checkedCount > 0) {
2874
+ this.actionBarTarget.classList.remove("hidden");
2875
+ } else {
2876
+ this.actionBarTarget.classList.add("hidden");
2877
+ }
2878
+ }
2855
2879
  };
2856
2880
 
2857
2881
  // app/assets/javascripts/source_monitor/turbo_actions.js