trackguard 0.16.1 → 0.19.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b4c12f652065f915f2b4b906869a232738d8cf96632e6676f7107e84c442ea9d
4
- data.tar.gz: a50ce6042ea670cb43486972f7a47a8187273aebd2947bd42f2ccfd4aba67225
3
+ metadata.gz: 90c710c8d2149047c75e5b99151b708e9741e29385d17de7c4f101ad9db697cf
4
+ data.tar.gz: 49aaf485a932c241d9c18986120f639305485f4c316653923e9ad9a018566e01
5
5
  SHA512:
6
- metadata.gz: 4eb185b4afe7cd71d3347afac5d203625a3348ff812647fc3ea6b04cff52bb2ded026d2601d546945900bd5f9a05bb0154fcb78a590ae6f5ec3249f283254b04
7
- data.tar.gz: 18a0b707b1c085cab5c43a7249c4a6ed8bf753ba30a3b1e4e5bee00283381de4f2f511af06651009b587e9eeada7d69a860d4664f914adf92dfd2307e16d84f2
6
+ metadata.gz: b1a17b99009baaacee17c4473bf33b811870df411541cee1c055807f2b8981b4ab09ba0c28eeee15adc0a8ab87081f6b463b494435618cafafb2aea424b95226
7
+ data.tar.gz: 8da17d592514268ced0ab53cb0c2331671eea10702deaac46264f90874f2af33e60f952a1c89b7c1f920b6ad35ad93297bfe6ab786c796816c3704d35a17424d
@@ -7,7 +7,7 @@
7
7
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Inter", sans-serif;
8
8
  font-size: 16px;
9
9
  line-height: 1.5;
10
- background-color: #07101f;
10
+ background-color: #0f0909;
11
11
  color: #e0e0e0;
12
12
  -webkit-font-smoothing: antialiased;
13
13
  min-height: 100vh;
@@ -22,8 +22,8 @@
22
22
 
23
23
  /* ── Header ────────────────────────────────────────────────────────── */
24
24
  .tg-header {
25
- background: #0d1829;
26
- border-bottom: 1px solid #1c2d4a;
25
+ background: #190e0e;
26
+ border-bottom: 1px solid #2e1515;
27
27
  padding: 0.875rem 0;
28
28
  }
29
29
 
@@ -40,16 +40,16 @@
40
40
  font-size: 0.9375rem;
41
41
  font-weight: 600;
42
42
  letter-spacing: 0.03em;
43
- color: #60a5fa;
43
+ color: #f87171;
44
44
  text-decoration: none;
45
45
  flex: 1;
46
46
  }
47
47
 
48
48
  .tg-brand__logo {
49
49
  width: 20px;
50
- height: 22px;
50
+ height: auto;
51
51
  flex-shrink: 0;
52
- filter: drop-shadow(0 0 4px rgba(59, 130, 246, 0.35));
52
+ filter: drop-shadow(0 0 4px rgba(239, 68, 68, 0.4));
53
53
  }
54
54
 
55
55
  /* ── Back link ─────────────────────────────────────────────────────── */
@@ -76,8 +76,8 @@
76
76
 
77
77
  /* ── Nav ───────────────────────────────────────────────────────────── */
78
78
  .tg-nav {
79
- background: #0d1829;
80
- border-bottom: 1px solid #1c2d4a;
79
+ background: #190e0e;
80
+ border-bottom: 1px solid #2e1515;
81
81
  }
82
82
 
83
83
  .tg-nav > .tg-container {
@@ -97,8 +97,8 @@
97
97
  .tg-nav__link:hover { color: #e0e0e0; }
98
98
 
99
99
  .tg-nav__link--active {
100
- color: #60a5fa;
101
- border-bottom-color: #60a5fa;
100
+ color: #f87171;
101
+ border-bottom-color: #e53e3e;
102
102
  }
103
103
 
104
104
  /* ── Main ──────────────────────────────────────────────────────────── */
@@ -128,8 +128,8 @@
128
128
  }
129
129
 
130
130
  .tg-stat {
131
- background: #0d1829;
132
- border: 1px solid #1c2d4a;
131
+ background: #190e0e;
132
+ border: 1px solid #2e1515;
133
133
  border-radius: 8px;
134
134
  padding: 1.25rem;
135
135
  }
@@ -167,8 +167,8 @@
167
167
  }
168
168
 
169
169
  .tg-panel {
170
- background: #0d1829;
171
- border: 1px solid #1c2d4a;
170
+ background: #190e0e;
171
+ border: 1px solid #2e1515;
172
172
  border-radius: 8px;
173
173
  padding: 1rem;
174
174
  margin-bottom: 1rem;
@@ -211,14 +211,14 @@
211
211
  font-size: 0.8125rem;
212
212
  color: #e0e0e0;
213
213
  text-decoration: none;
214
- background: #1c2d4a;
214
+ background: #2e1515;
215
215
  }
216
216
 
217
- .tg-pagination__link:hover { background: #243a5e; }
217
+ .tg-pagination__link:hover { background: #3d1c1c; }
218
218
 
219
219
  .tg-pagination__link--active {
220
- background: #60a5fa;
221
- color: #07101f;
220
+ background: #e53e3e;
221
+ color: #ffffff;
222
222
  font-weight: 600;
223
223
  }
224
224
 
@@ -249,14 +249,14 @@
249
249
  text-transform: uppercase;
250
250
  letter-spacing: 0.08em;
251
251
  color: #666666;
252
- border-bottom: 1px solid #1c2d4a;
252
+ border-bottom: 1px solid #2e1515;
253
253
  }
254
254
 
255
255
  .tg-th--right { text-align: right; }
256
256
 
257
257
  .tg-td {
258
258
  padding: 0.75rem 0.875rem;
259
- border-bottom: 1px solid #1c2d4a;
259
+ border-bottom: 1px solid #2e1515;
260
260
  color: #f0f0f0;
261
261
  font-weight: 500;
262
262
  }
@@ -271,7 +271,7 @@
271
271
 
272
272
  .tg-td--bare {
273
273
  padding: 0;
274
- border-bottom: 1px solid #1c2d4a;
274
+ border-bottom: 1px solid #2e1515;
275
275
  }
276
276
 
277
277
  /* ── Empty state ───────────────────────────────────────────────────── */
@@ -361,7 +361,7 @@
361
361
  /* ── Detail panel ──────────────────────────────────────────────────── */
362
362
  .tg-detail {
363
363
  padding: 0 1rem 1rem 1rem;
364
- background: #0d1829;
364
+ background: #190e0e;
365
365
  }
366
366
 
367
367
  .tg-detail__grid {
@@ -423,7 +423,7 @@
423
423
  align-items: center;
424
424
  gap: 0.5rem;
425
425
  padding: 0.75rem 0 0;
426
- border-top: 1px solid #1c2d4a;
426
+ border-top: 1px solid #2e1515;
427
427
  margin-top: 0.75rem;
428
428
  }
429
429
 
@@ -436,8 +436,8 @@
436
436
 
437
437
  .tg-input {
438
438
  flex: 1;
439
- background: #07101f;
440
- border: 1px solid #1c2d4a;
439
+ background: #0f0909;
440
+ border: 1px solid #2e1515;
441
441
  border-radius: 4px;
442
442
  color: #e0e0e0;
443
443
  font-size: 0.8125rem;
@@ -445,7 +445,7 @@
445
445
  outline: none;
446
446
  }
447
447
 
448
- .tg-input:focus { border-color: #60a5fa; }
448
+ .tg-input:focus { border-color: #e53e3e; }
449
449
  .tg-input::placeholder { color: #444; }
450
450
 
451
451
  .tg-btn {
@@ -461,8 +461,8 @@
461
461
  .tg-btn--danger { background: #7f1d1d; color: #fca5a5; }
462
462
  .tg-btn--danger:hover { background: #991b1b; }
463
463
 
464
- .tg-btn--ghost { background: #1c2d4a; color: #e0e0e0; }
465
- .tg-btn--ghost:hover { background: #243a5e; }
464
+ .tg-btn--ghost { background: #2e1515; color: #e0e0e0; }
465
+ .tg-btn--ghost:hover { background: #3d1c1c; }
466
466
 
467
467
  .tg-btn--whitelist { background: #064e3b; color: #6ee7b7; }
468
468
  .tg-btn--whitelist:hover { background: #065f46; }
@@ -476,4 +476,4 @@
476
476
  .tg-stats { grid-template-columns: 1fr; }
477
477
  .tg-detail__grid { grid-template-columns: 1fr; }
478
478
  .tg-summary__ip { display: none; }
479
- }
479
+ }
@@ -11,11 +11,13 @@ module Trackguard
11
11
  end
12
12
  end
13
13
 
14
+ # rubocop:disable Metrics/AbcSize
14
15
  def flag
15
16
  if @visitor.update(
16
17
  flagged_at: Time.current,
17
18
  flag_reason: params[:flag_reason].presence,
18
- flagged_by: params[:flagged_by].presence || Visitor::FLAGGED_BY.first
19
+ flagged_by: params[:flagged_by].presence || Visitor::FLAGGED_BY.first,
20
+ name: params[:name].presence || BlockedUserAgent.matching_pattern(@visitor.user_agent)
19
21
  )
20
22
  respond_to do |format|
21
23
  format.html { redirect_back_or_to dashboard_path }
@@ -28,6 +30,7 @@ module Trackguard
28
30
  end
29
31
  end
30
32
  end
33
+ # rubocop:enable Metrics/AbcSize
31
34
 
32
35
  def unflag
33
36
  @visitor.update!(flagged_at: nil, flag_reason: nil, flagged_by: nil)
@@ -43,13 +43,15 @@ module Trackguard
43
43
  return if count.zero?
44
44
  return if visitor.whitelisted_ip&.active?
45
45
 
46
+ name = name_from_ua(visitor.user_agent)
47
+
46
48
  if count >= HARD_FLAG_THRESHOLD
47
- flag!(visitor, "#{count} page views in 24h (hard flag threshold)")
49
+ flag!(visitor, "#{count} page views in 24h (hard flag threshold)", name: name)
48
50
  return
49
51
  end
50
52
 
51
53
  if (reason = ua_flag_reason(visitor.user_agent))
52
- flag!(visitor, reason)
54
+ flag!(visitor, reason, name: name)
53
55
  return
54
56
  end
55
57
 
@@ -58,7 +60,7 @@ module Trackguard
58
60
  return if count < MIN_VIEWS
59
61
 
60
62
  if views.all? { |pv| pv.session_id.nil? && pv.referer.nil? && pv.path == "/" }
61
- flag!(visitor, "no session, no referrer, single root hit")
63
+ flag!(visitor, "no session, no referrer, single root hit", name: name)
62
64
  return
63
65
  end
64
66
 
@@ -85,7 +87,7 @@ module Trackguard
85
87
 
86
88
  return if score < FLAG_SCORE_THRESHOLD
87
89
 
88
- flag!(visitor, reasons.join("; "))
90
+ flag!(visitor, reasons.join("; "), name: name)
89
91
  end
90
92
  # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
91
93
 
@@ -107,7 +109,8 @@ module Trackguard
107
109
  .where("wi.id IS NULL OR wi.expires_at <= ?", Time.current)
108
110
  .distinct
109
111
  .each do |visitor|
110
- flag!(visitor, "trace_id shared across multiple visitors (cross-visitor bot detected)")
112
+ flag!(visitor, "trace_id shared across multiple visitors (cross-visitor bot detected)",
113
+ name: name_from_ua(visitor.user_agent))
111
114
  end
112
115
  end
113
116
 
@@ -115,8 +118,12 @@ module Trackguard
115
118
  "blank or minimal user-agent" if user_agent.blank? || user_agent.to_s.length < 10
116
119
  end
117
120
 
118
- def flag!(visitor, reason)
119
- visitor.update!(flagged_at: Time.current, flag_reason: reason, flagged_by: "claw:auto")
121
+ def flag!(visitor, reason, name: nil)
122
+ visitor.update!(flagged_at: Time.current, flag_reason: reason, flagged_by: "claw:auto", name: name)
123
+ end
124
+
125
+ def name_from_ua(user_agent)
126
+ BlockedUserAgent.matching_pattern(user_agent)
120
127
  end
121
128
 
122
129
  def blank_ratio(views, attr)
@@ -12,5 +12,12 @@ module Trackguard
12
12
  end
13
13
  patterns.any? { |p| user_agent.to_s.downcase.include?(p.downcase) }
14
14
  end
15
+
16
+ def self.matching_pattern(user_agent)
17
+ patterns = Rails.cache.fetch(CACHE_KEY, expires_in: 10.minutes) do
18
+ pluck(:pattern)
19
+ end
20
+ patterns.find { |p| user_agent.to_s.downcase.include?(p.downcase) }
21
+ end
15
22
  end
16
23
  end
@@ -1,13 +1,5 @@
1
1
  module Trackguard
2
2
  class PageViewRecorder < ApplicationService
3
- BOT_REGEX = /
4
- Googlebot|Bingbot|Slurp|DuckDuckBot|Baidu|YandexBot|
5
- facebookexternalhit|Twitterbot|LinkedInBot|
6
- curl|wget|python-requests|python-urllib|
7
- Go-http-client|libwww|Java|Ruby|
8
- bot|crawl|spider
9
- /ix
10
-
11
3
  def initialize(path:, ip:, user_agent:, referer:, session_id:, trace_id:, source: nil, initial: false,
12
4
  http_method: nil)
13
5
  @path = path.to_s
@@ -22,11 +14,11 @@ module Trackguard
22
14
  end
23
15
 
24
16
  def call
25
- return if BOT_REGEX.match?(@user_agent)
26
- return if BlockedUserAgent.blocked?(@user_agent)
17
+ adapter = Trackguard.adapter
18
+ return if adapter.blocked_user_agent?(@user_agent)
27
19
  return if @path.blank? || @path.start_with?("/admin")
28
20
 
29
- TrackPageViewJob.perform_later(
21
+ adapter.track_page_view(
30
22
  path: @path,
31
23
  ip: @ip,
32
24
  user_agent: @user_agent,
@@ -11,27 +11,7 @@
11
11
  <div class="tg-container">
12
12
  <div class="tg-header__inner">
13
13
  <span class="tg-brand">
14
- <svg class="tg-brand__logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 22" aria-hidden="true">
15
- <defs>
16
- <linearGradient id="tg-shield-grad" x1="0" y1="0" x2="0" y2="1">
17
- <stop offset="0%" stop-color="#1e3a5f"/>
18
- <stop offset="100%" stop-color="#0d1f3c"/>
19
- </linearGradient>
20
- </defs>
21
- <!-- Shield -->
22
- <path d="M10 1L19 4.5V11.5C19 16.4 15 20.3 10 21.5C5 20.3 1 16.4 1 11.5V4.5Z"
23
- fill="url(#tg-shield-grad)" stroke="#3b82f6" stroke-width="1.25" stroke-linejoin="round"/>
24
- <!-- Left rail -->
25
- <line x1="7.5" y1="6" x2="7.5" y2="17" stroke="#60a5fa" stroke-width="1.6" stroke-linecap="round"/>
26
- <!-- Right rail -->
27
- <line x1="12.5" y1="6" x2="12.5" y2="17" stroke="#60a5fa" stroke-width="1.6" stroke-linecap="round"/>
28
- <!-- Cross-tie 1 -->
29
- <line x1="6.25" y1="8" x2="13.75" y2="8" stroke="#3b82f6" stroke-width="1.25" stroke-linecap="round"/>
30
- <!-- Cross-tie 2 -->
31
- <line x1="6.25" y1="11.5" x2="13.75" y2="11.5" stroke="#3b82f6" stroke-width="1.25" stroke-linecap="round"/>
32
- <!-- Cross-tie 3 -->
33
- <line x1="6.25" y1="15" x2="13.75" y2="15" stroke="#3b82f6" stroke-width="1.25" stroke-linecap="round"/>
34
- </svg>
14
+ <%= image_tag "trackguard/logo.png", class: "tg-brand__logo", alt: "Trackguard" %>
35
15
  Trackguard
36
16
  </span>
37
17
 
@@ -151,6 +151,7 @@
151
151
  <%= hidden_field_tag :id, visitor.id %>
152
152
  <%= f.submit "Flag", class: "tg-btn tg-btn--danger" %>
153
153
  <%= f.text_field :flag_reason, placeholder: "Flag reason (optional)", class: "tg-input", autocomplete: "off" %>
154
+ <%= f.text_field :name, placeholder: "Name (optional, auto-detected if blank)", class: "tg-input", autocomplete: "off" %>
154
155
  <% end %>
155
156
  <% end %>
156
157
  </div>
@@ -175,6 +176,10 @@
175
176
  <span class="tg-dl__term">User agent</span>
176
177
  <span class="tg-dl__def tg-dl__def--break"><%= visitor&.user_agent.presence || "—" %></span>
177
178
  </div>
179
+ <div class="tg-dl__row">
180
+ <span class="tg-dl__term">Name</span>
181
+ <span class="tg-dl__def"><%= visitor&.name.presence || "—" %></span>
182
+ </div>
178
183
  <div class="tg-dl__row">
179
184
  <span class="tg-dl__term">Flag status</span>
180
185
  <% if flagged %>
@@ -63,6 +63,7 @@
63
63
  <%= hidden_field_tag :id, visitor.id %>
64
64
  <%= f.submit "Flag", class: "tg-btn tg-btn--danger" %>
65
65
  <%= f.text_field :flag_reason, placeholder: "Flag reason (optional)", class: "tg-input", autocomplete: "off" %>
66
+ <%= f.text_field :name, placeholder: "Name (optional, auto-detected if blank)", class: "tg-input", autocomplete: "off" %>
66
67
  <% end %>
67
68
  <% end %>
68
69
  </div>
@@ -87,6 +88,10 @@
87
88
  <span class="tg-dl__term">User agent</span>
88
89
  <span class="tg-dl__def tg-dl__def--break"><%= visitor&.user_agent.presence || "—" %></span>
89
90
  </div>
91
+ <div class="tg-dl__row">
92
+ <span class="tg-dl__term">Name</span>
93
+ <span class="tg-dl__def"><%= visitor&.name.presence || "—" %></span>
94
+ </div>
90
95
  <div class="tg-dl__row">
91
96
  <span class="tg-dl__term">Flag status</span>
92
97
  <% if flagged %>
@@ -0,0 +1,5 @@
1
+ class AddNameToTrackguardVisitors < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_column :trackguard_visitors, :name, :string
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddVisitorName < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
4
+ def change
5
+ add_column :trackguard_visitors, :name, :string
6
+ end
7
+ end
@@ -15,6 +15,10 @@ module Trackguard
15
15
  migration_template "add_trackguard_visits.rb", "db/migrate/add_trackguard_visits.rb"
16
16
  end
17
17
 
18
+ def create_visitor_name_migration_file
19
+ migration_template "add_visitor_name.rb", "db/migrate/add_visitor_name.rb"
20
+ end
21
+
18
22
  def print_next_steps
19
23
  say "\nNext steps:", :green
20
24
  say " 1. rails db:migrate"
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trackguard
4
+ module Adapters
5
+ class Base
6
+ def blocked_user_agent?(user_agent) = raise NotImplementedError, "#{self.class}#blocked_user_agent?"
7
+ def whitelisted_ip?(ip) = raise NotImplementedError, "#{self.class}#whitelisted_ip?"
8
+ def flagged_visitor?(ip) = raise NotImplementedError, "#{self.class}#flagged_visitor?"
9
+
10
+ def track_page_view(path:, ip:, user_agent:, referer:, session_id:, trace_id:, source:, initial:, http_method:)
11
+ raise NotImplementedError, "#{self.class}#track_page_view"
12
+ end
13
+
14
+ def track_blocked_request(ip:, user_agent:, path:, http_method:, block_reason:)
15
+ raise NotImplementedError, "#{self.class}#track_blocked_request"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trackguard
4
+ module Adapters
5
+ class Local < Base
6
+ def blocked_user_agent?(user_agent)
7
+ BlockedUserAgent.blocked?(user_agent)
8
+ end
9
+
10
+ def whitelisted_ip?(ip)
11
+ WhitelistedIp.whitelisted?(ip)
12
+ end
13
+
14
+ def flagged_visitor?(ip)
15
+ Visitor.flagged?(ip)
16
+ end
17
+
18
+ def track_page_view(path:, ip:, user_agent:, referer:, session_id:, trace_id:, source:, initial:, http_method:)
19
+ TrackPageViewJob.perform_later(
20
+ path: path,
21
+ ip: ip,
22
+ user_agent: user_agent,
23
+ referer: referer,
24
+ session_id: session_id,
25
+ trace_id: trace_id,
26
+ source: source,
27
+ initial: initial,
28
+ http_method: http_method
29
+ )
30
+ end
31
+
32
+ def track_blocked_request(ip:, user_agent:, path:, http_method:, block_reason:)
33
+ TrackBlockedRequestJob.perform_later(
34
+ ip: ip,
35
+ user_agent: user_agent,
36
+ path: path,
37
+ http_method: http_method,
38
+ block_reason: block_reason
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
@@ -5,20 +5,22 @@ require "rack/attack"
5
5
  module Trackguard
6
6
  module RackAttack
7
7
  def self.configure
8
+ adapter = Trackguard.adapter
9
+
8
10
  ::Rack::Attack.safelist("trackguard/allow local") do |req|
9
11
  [ "127.0.0.1", "::1" ].include?(req.ip)
10
12
  end
11
13
 
12
14
  ::Rack::Attack.safelist("trackguard/allow whitelisted ips") do |req|
13
- Trackguard::WhitelistedIp.whitelisted?(req.ip)
15
+ adapter.whitelisted_ip?(req.ip)
14
16
  end
15
17
 
16
18
  ::Rack::Attack.blocklist("trackguard/block known scanners") do |req|
17
- Trackguard::BlockedUserAgent.blocked?(req.user_agent)
19
+ adapter.blocked_user_agent?(req.user_agent)
18
20
  end
19
21
 
20
22
  ::Rack::Attack.blocklist("trackguard/flagged visitors") do |req|
21
- Trackguard::Visitor.flagged?(req.ip)
23
+ adapter.flagged_visitor?(req.ip)
22
24
  end
23
25
 
24
26
  ::Rack::Attack.throttle(
@@ -27,15 +29,15 @@ module Trackguard
27
29
  period: Trackguard.throttle_period, &:ip
28
30
  )
29
31
 
30
- subscribe_to_blocked_requests
32
+ subscribe_to_blocked_requests(adapter)
31
33
  end
32
34
 
33
- def self.subscribe_to_blocked_requests
35
+ def self.subscribe_to_blocked_requests(adapter)
34
36
  @subscribe_to_blocked_requests ||= ActiveSupport::Notifications.subscribe("rack.attack") do |*, payload|
35
37
  req = payload[:request]
36
38
  next unless req.env["rack.attack.match_type"] == :blocklist
37
39
 
38
- Trackguard::TrackBlockedRequestJob.perform_later(
40
+ adapter.track_blocked_request(
39
41
  ip: req.ip,
40
42
  user_agent: req.user_agent.to_s,
41
43
  path: req.path,
@@ -1,3 +1,3 @@
1
1
  module Trackguard
2
- VERSION = "0.16.1".freeze
2
+ VERSION = "0.19.0".freeze
3
3
  end
data/lib/trackguard.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require "trackguard/version"
4
4
  require "trackguard/engine"
5
5
  require "trackguard/rack_attack"
6
+ require "trackguard/adapters/base"
7
+ require "trackguard/adapters/local"
6
8
 
7
9
  module Trackguard
8
10
  class << self
@@ -36,5 +38,23 @@ module Trackguard
36
38
  def throttle_period
37
39
  @throttle_period ||= 60
38
40
  end
41
+
42
+ def adapter
43
+ @adapter ||= Trackguard::Adapters::Local.new
44
+ end
45
+
46
+ def adapter=(value)
47
+ @adapter = resolve_adapter(value)
48
+ end
49
+
50
+ private
51
+
52
+ def resolve_adapter(value)
53
+ case value
54
+ when Symbol then Trackguard::Adapters.const_get(value.to_s.camelize).new
55
+ when Class then value.new
56
+ else value
57
+ end
58
+ end
39
59
  end
40
60
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trackguard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.1
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Krzysztof Rygielski
@@ -43,6 +43,7 @@ executables: []
43
43
  extensions: []
44
44
  extra_rdoc_files: []
45
45
  files:
46
+ - app/assets/images/trackguard/logo.png
46
47
  - app/assets/javascripts/controllers/page_tracker_controller.js
47
48
  - app/assets/stylesheets/trackguard/admin.css
48
49
  - app/controllers/concerns/trackguard/page_tracker.rb
@@ -72,12 +73,16 @@ files:
72
73
  - app/views/trackguard/admin/visits/index.html.erb
73
74
  - config/importmap.rb
74
75
  - config/routes.rb
76
+ - db/migrate/20260505191009_add_name_to_trackguard_visitors.rb
75
77
  - lib/generators/trackguard/install_generator.rb
76
78
  - lib/generators/trackguard/templates/add_trackguard_visits.rb
79
+ - lib/generators/trackguard/templates/add_visitor_name.rb
77
80
  - lib/generators/trackguard/templates/create_trackguard_tables.rb
78
81
  - lib/generators/trackguard/upgrade_generator.rb
79
82
  - lib/tasks/trackguard.rake
80
83
  - lib/trackguard.rb
84
+ - lib/trackguard/adapters/base.rb
85
+ - lib/trackguard/adapters/local.rb
81
86
  - lib/trackguard/engine.rb
82
87
  - lib/trackguard/rack_attack.rb
83
88
  - lib/trackguard/version.rb