lesli_audit 1.1.1 → 1.1.2

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +6 -1
  3. data/app/assets/stylesheets/lesli_audit/application.css +366 -35
  4. data/app/controllers/lesli_audit/accounts_controller.rb +7 -3
  5. data/app/controllers/lesli_audit/logs_controller.rb +3 -8
  6. data/app/controllers/lesli_audit/visitors_controller.rb +1 -1
  7. data/app/interfaces/lesli_audit/logger_interface.rb +56 -59
  8. data/app/jobs/lesli_audit/retention_cleanup_job.rb +9 -0
  9. data/app/models/concerns/lesli_audit/append_only.rb +12 -0
  10. data/app/models/lesli_audit/account.rb +26 -10
  11. data/app/models/lesli_audit/account_device.rb +3 -1
  12. data/app/models/lesli_audit/account_log.rb +12 -7
  13. data/app/models/lesli_audit/account_request.rb +3 -1
  14. data/app/models/lesli_audit/user_journal.rb +16 -3
  15. data/app/models/lesli_audit/user_log.rb +12 -3
  16. data/app/models/lesli_audit/user_request.rb +7 -3
  17. data/app/services/lesli_audit/activity_audit.rb +130 -0
  18. data/app/services/lesli_audit/request_service.rb +2 -35
  19. data/app/services/lesli_audit/retention_cleanup.rb +146 -0
  20. data/app/services/lesli_audit/user_agent_parser.rb +51 -0
  21. data/app/services/lesli_audit/user_service.rb +18 -16
  22. data/app/services/lesli_audit/visitor_service.rb +22 -49
  23. data/app/views/lesli_audit/logs/show.html.erb +14 -8
  24. data/app/views/lesli_audit/partials/_navigation.html.erb +1 -1
  25. data/app/views/lesli_audit/users/index.html.erb +0 -3
  26. data/app/views/lesli_audit/visitors/index.html.erb +12 -12
  27. data/config/routes.rb +3 -2
  28. data/db/migrate/v1.0/0501000110_create_lesli_audit_accounts.rb +14 -1
  29. data/db/migrate/v1.0/0501110110_create_lesli_audit_account_logs.rb +10 -6
  30. data/db/migrate/v1.0/0501110210_create_lesli_audit_account_devices.rb +10 -7
  31. data/db/migrate/v1.0/0501110310_create_lesli_audit_account_requests.rb +10 -7
  32. data/db/migrate/v1.0/0501120110_create_lesli_audit_user_logs.rb +11 -7
  33. data/db/migrate/v1.0/0501120210_create_lesli_audit_user_journals.rb +19 -8
  34. data/db/migrate/v1.0/0501120310_create_lesli_audit_user_requests.rb +13 -10
  35. data/db/seed/devices.rb +1 -1
  36. data/db/seed/requests.rb +3 -3
  37. data/lib/lesli_audit/engine.rb +1 -12
  38. data/lib/lesli_audit/version.rb +2 -2
  39. data/lib/tasks/lesli_audit_tasks.rake +12 -4
  40. data/readme.md +18 -5
  41. metadata +14 -6
  42. data/app/assets/stylesheets/lesli_audit/application.tailwind.css +0 -2
  43. data/app/models/concerns/lesli_audit/user_associations.rb +0 -11
@@ -34,7 +34,7 @@ module LesliAudit
34
34
  class VisitorService < Lesli::ApplicationLesliService
35
35
 
36
36
  def visits
37
- requests = current_user.account.audit.account_requests
37
+ requests = apply_time_filter(current_user.account.audit.account_requests)
38
38
 
39
39
  controllers, total_requests = requests.pick(
40
40
  Arel.sql("COUNT(DISTINCT request_controller)"),
@@ -47,26 +47,22 @@ module LesliAudit
47
47
  }
48
48
  end
49
49
 
50
- def visitors
50
+ def traffic
51
51
  #Rails.cache.fetch(cache_key_for_account(__method__), expires_in: 1.hour) do
52
52
  group = 'day'
53
53
  #group = params[:bygroup] if ['month','week','day'].include?(params[:bygroup])
54
54
 
55
- # only the users of the account
56
- usrs = current_user.account.users
57
-
58
- group_by = "DATE_TRUNC('month', created_at)" if group == 'month'
59
- group_by = "DATE_TRUNC('week', created_at)" if group == 'week'
60
- group_by = "DATE_TRUNC('day', created_at)" if group == 'day'
55
+ group_by = "DATE_TRUNC('month', date)" if group == 'month'
56
+ group_by = "DATE_TRUNC('week', date)" if group == 'week'
57
+ group_by = "DATE_TRUNC('day', date)" if group == 'day'
61
58
 
62
59
  # compatibility for SQLite
63
60
  if ActiveRecord::Base.connection.adapter_name == "SQLite"
64
- group_by = "strftime('%Y-%m-%d', created_at)"
61
+ group_by = "strftime('%Y-%m-%d', date)"
65
62
  end
66
63
 
67
- requests = current_user.account.audit.account_requests.group(group_by)
68
-
69
- requests = apply_filters(requests, query)
64
+ requests = apply_time_filter(current_user.account.audit.account_requests)
65
+ .group(group_by)
70
66
 
71
67
  requests.limit(10).order("xaxiskey desc").select(
72
68
  "sum(request_count) yaxiskey",
@@ -76,11 +72,9 @@ module LesliAudit
76
72
  end
77
73
 
78
74
  def requests
79
- requests = current_user.account.audit.account_requests
75
+ requests = apply_time_filter(current_user.account.audit.account_requests)
80
76
  .group("request_controller")
81
77
 
82
- requests = apply_filters(requests, query)
83
-
84
78
  requests = requests.limit(LIMIT).order("requests DESC").select(
85
79
  :request_controller,
86
80
  "sum(request_count) requests"
@@ -88,7 +82,7 @@ module LesliAudit
88
82
  end
89
83
 
90
84
  def os
91
- device_counts = current_user.account.audit.account_devices
85
+ device_counts = apply_time_filter(current_user.account.audit.account_devices)
92
86
  .group(:agent_platform)
93
87
  .sum(:agent_count)
94
88
 
@@ -105,7 +99,7 @@ module LesliAudit
105
99
  end
106
100
 
107
101
  def devices
108
- device_counts = current_user.account.audit.account_devices
102
+ device_counts = apply_time_filter(current_user.account.audit.account_devices)
109
103
  .group(:agent_device)
110
104
  .sum(:agent_count)
111
105
 
@@ -122,7 +116,7 @@ module LesliAudit
122
116
  end
123
117
 
124
118
  def browsers
125
- browser_counts = current_user.account.audit.account_devices
119
+ browser_counts = apply_time_filter(current_user.account.audit.account_devices)
126
120
  .group(:agent_browser)
127
121
  .sum(:agent_count)
128
122
 
@@ -138,9 +132,9 @@ module LesliAudit
138
132
  end
139
133
  #Rails.cache.fetch(cache_key_for_account(__method__), expires_in: 4.hour) do
140
134
  # current_user.account.audit.account_devices
141
- # .group(:agent_browser, :created_at)
135
+ # .group(:agent_browser, :date)
142
136
  # .select(
143
- # 'created_at as xaxiskey',
137
+ # 'date as xaxiskey',
144
138
  # 'agent_browser as dataname',
145
139
  # 'sum(agent_count) as yaxiskey'
146
140
  # ).as_json
@@ -203,37 +197,16 @@ module LesliAudit
203
197
  DEVICE_ICON_CLASSES.fetch(device.to_s, DEVICE_ICON_CLASSES["unknown"])
204
198
  end
205
199
 
206
- def apply_filters requests, params
207
-
208
- return requests
209
-
210
- # filter by search string
211
- unless params[:search].blank?
212
- requests = requests.where(
213
- "lower(request_agent) like :search_string or lower(request_controller) like :search_string or lower(request_action) like :search_string",
214
- {
215
- search_string: "%#{params[:search].downcase.gsub(" ","%")}%"
216
- }
217
- )
200
+ def apply_time_filter(relation)
201
+ start_date = case query[:bytime]
202
+ when "day" then Date.current
203
+ when "week" then 6.days.ago.to_date
204
+ when "month" then 29.days.ago.to_date
205
+ when "sixmonth" then 6.months.ago.to_date
206
+ when "year" then 1.year.ago.to_date
218
207
  end
219
208
 
220
- # filter by time
221
- unless params[:bytime].blank? && params[:bytime] != 'all'
222
- requests = requests.where(created_at: (Time.now.beginning_of_day)..) if params[:bytime] == 'day'
223
- requests = requests.where(created_at: (Time.now.beginning_of_day - 7.day)..) if params[:bytime] == 'week'
224
- requests = requests.where(created_at: (Time.now.beginning_of_day - 30.day)..) if params[:bytime] == 'month'
225
- requests = requests.where(created_at: (Time.now.beginning_of_day - 6.month)..) if params[:bytime] == 'sixmonth'
226
- requests = requests.where(created_at: (Time.now.beginning_of_day - 1.year)..) if params[:bytime] == 'year'
227
- end
228
-
229
- # filter by request format
230
- if (!params[:byformat].blank? && ['html', 'json'].include?(params[:byformat]))
231
- requests = requests.where(:request_action => 'show') if params[:byformat] == 'html'
232
- requests = requests.where(:request_format => params[:byformat])
233
- end
234
-
235
- requests
236
-
209
+ start_date ? relation.where(date: start_date..) : relation
237
210
  end
238
211
  end
239
212
  end
@@ -1,10 +1,16 @@
1
- <p style="color: green"><%= notice %></p>
1
+ <%= render(LesliView::Layout::Container.new("audit-log")) do %>
2
+ <%= render(LesliView::Components::Header.new("Audit event")) %>
2
3
 
3
- <%= render @log %>
4
+ <dl class="grid grid-cols-1 gap-4 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm sm:grid-cols-2">
5
+ <% %i[engine source action operation description actor_uid session_id created_at].each do |field| %>
6
+ <div>
7
+ <dt class="text-sm font-medium text-slate-500"><%= field.to_s.humanize %></dt>
8
+ <dd class="mt-1 break-words text-sm text-slate-900"><%= @log.public_send(field) %></dd>
9
+ </div>
10
+ <% end %>
11
+ </dl>
4
12
 
5
- <div>
6
- <%= link_to "Edit this log", edit_log_path(@log) %> |
7
- <%= link_to "Back to logs", logs_path %>
8
-
9
- <%= button_to "Destroy this log", @log, method: :delete %>
10
- </div>
13
+ <div class="mt-6">
14
+ <%= link_to "Back to logs", logs_path, class: "text-sm font-medium text-sky-700 hover:text-sky-900" %>
15
+ </div>
16
+ <% end %>
@@ -33,6 +33,6 @@ Building a better future, one line of code at a time.
33
33
  %>
34
34
 
35
35
  <%= navigation_item(lesli_audit.users_path, 'Users', 'ri-team-line'); %>
36
- <%= navigation_item(lesli_audit.visitors_path, 'Visitors', 'ri-line-chart-fill'); %>
36
+ <%= navigation_item(lesli_audit.analytics_path, 'Traffic', 'ri-line-chart-fill'); %>
37
37
  <%= navigation_item(lesli_audit.requests_path, 'Requests', 'ri-arrow-left-right-fill'); %>
38
38
  <%= navigation_item(lesli_audit.logs_path, 'Logs', 'ri-history-line'); %>
@@ -64,9 +64,6 @@ color_classes = {
64
64
  }, {
65
65
  label: "Last activity",
66
66
  field: "last_activity"
67
- }, {
68
- label: "Resources",
69
- field: "resources"
70
67
  }, {
71
68
  label: "Requests",
72
69
  field: "requests"
@@ -1,7 +1,7 @@
1
1
 
2
2
  <%
3
- visitors = @visitors.reverse
4
- top_days = visitors.last(3)
3
+ traffic = @traffic.reverse
4
+ top_days = traffic.last(3)
5
5
  %>
6
6
 
7
7
  <%= render(LesliView::Layout::Container.new("audit-analytics")) do %>
@@ -9,7 +9,7 @@ top_days = visitors.last(3)
9
9
 
10
10
  <section
11
11
  class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-5"
12
- aria-label="Visitor overview">
12
+ aria-label="Traffic overview">
13
13
  <article class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm">
14
14
  <h2 class="text-sm font-medium text-slate-500">Controllers</h2>
15
15
  <p class="mt-3 text-3xl font-semibold tracking-tight text-slate-900 tabular-nums">
@@ -36,21 +36,21 @@ top_days = visitors.last(3)
36
36
 
37
37
  <section class="mt-6 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm">
38
38
  <%= render(LesliView::Charts::Line.new(
39
- title: "Visitors",
40
- database_to_dataset: visitors
39
+ database_to_dataset: traffic,
40
+ title: "Requests"
41
41
  )) %>
42
42
  </section>
43
43
 
44
- <section class="mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3" aria-label="Visitor breakdown">
44
+ <section class="mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3" aria-label="Request traffic breakdown">
45
45
  <article class="min-w-0">
46
46
  <%= render(LesliView::Elements::Table.new(
47
47
  columns: [
48
48
  { label: "Browsers", align: :left },
49
- { label: "Visitors", align: :center }
49
+ { label: "Requests", align: :center }
50
50
  ],
51
51
  class_name: "!min-w-0",
52
52
  compact: true,
53
- caption: "Visitors by browser"
53
+ caption: "Requests by browser"
54
54
  )) do |table| %>
55
55
  <% @browsers.each do |browser| %>
56
56
  <% percentage = browser[:percentage].to_i.clamp(0, 100) %>
@@ -81,11 +81,11 @@ top_days = visitors.last(3)
81
81
  <%= render(LesliView::Elements::Table.new(
82
82
  columns: [
83
83
  { label: "OS", align: :left },
84
- { label: "Visitors", align: :center }
84
+ { label: "Requests", align: :center }
85
85
  ],
86
86
  class_name: "!min-w-0",
87
87
  compact: true,
88
- caption: "Visitors by operating system"
88
+ caption: "Requests by operating system"
89
89
  )) do |table| %>
90
90
  <% @os.each do |device| %>
91
91
  <% percentage = device[:percentage].to_i.clamp(0, 100) %>
@@ -116,11 +116,11 @@ top_days = visitors.last(3)
116
116
  <%= render(LesliView::Elements::Table.new(
117
117
  columns: [
118
118
  { label: "Device", align: :left },
119
- { label: "Visitors", align: :center }
119
+ { label: "Requests", align: :center }
120
120
  ],
121
121
  class_name: "!min-w-0",
122
122
  compact: true,
123
- caption: "Visitors by device"
123
+ caption: "Requests by device"
124
124
  )) do |table| %>
125
125
  <% @devices.each do |device| %>
126
126
  <% percentage = device[:percentage].to_i.clamp(0, 100) %>
data/config/routes.rb CHANGED
@@ -43,11 +43,12 @@ LesliAudit::Engine.routes.draw do
43
43
  resources :users, only: [:index]
44
44
 
45
45
  # Analytics:
46
- # Visitors by date
46
+ # Requests by date
47
47
  # Trends by date
48
48
  # Most active users
49
49
  # Most active controllers
50
- resources :visitors, only: [:index]
50
+ get "analytics", to: "visitors#index", as: :analytics
51
+ resources :visitors, only: [:index]
51
52
 
52
53
  resources :logs, only: [:index, :show]
53
54
 
@@ -30,8 +30,21 @@ Building a better future, one line of code at a time.
30
30
  // ·
31
31
  =end
32
32
 
33
- class CreateLesliAuditAccounts < ActiveRecord::Migration[6.0]
33
+ class CreateLesliAuditAccounts < ActiveRecord::Migration[8.1]
34
34
  def change
35
35
  create_table_lesli_shared_account_10(:lesli_audit)
36
+
37
+ remove_index :lesli_audit_accounts, :account_id
38
+ add_index :lesli_audit_accounts, :account_id,
39
+ unique: true,
40
+ name: "index_lesli_audit_accounts_on_account_id"
41
+
42
+ # Soft deletion keeps the core row and its audit history. Only a
43
+ # deliberate hard deletion activates this cascade.
44
+ remove_foreign_key :lesli_audit_accounts, :lesli_accounts,
45
+ column: :account_id
46
+ add_foreign_key :lesli_audit_accounts, :lesli_accounts,
47
+ column: :account_id,
48
+ on_delete: :cascade
36
49
  end
37
50
  end
@@ -30,24 +30,28 @@ Building a better future, one line of code at a time.
30
30
  // ·
31
31
  =end
32
32
 
33
- class CreateLesliAuditAccountLogs < ActiveRecord::Migration[6.0]
33
+ class CreateLesliAuditAccountLogs < ActiveRecord::Migration[8.1]
34
34
  def change
35
35
  create_table :lesli_audit_account_logs do |t|
36
36
  t.string :engine
37
+ t.string :source
37
38
  t.string :action
38
39
 
39
40
  # Polymorphic subject
40
41
  t.string :subject_type
41
42
  t.bigint :subject_id
42
43
 
43
- t.string :operation
44
- t.string :description
44
+ t.string :operation, null: false
45
+ t.text :description
45
46
  t.string :session_id
47
+ t.string :actor_uid
46
48
 
47
- t.timestamps
49
+ t.timestamps null: false
48
50
  end
49
51
 
50
- add_reference(:lesli_audit_account_logs, :user, foreign_key: { to_table: :lesli_users }, null: true)
51
- add_reference(:lesli_audit_account_logs, :account, foreign_key: { to_table: :lesli_audit_accounts }, null: true)
52
+ add_reference(:lesli_audit_account_logs, :user, foreign_key: { to_table: :lesli_users, on_delete: :nullify }, null: true)
53
+ add_reference(:lesli_audit_account_logs, :account, foreign_key: { to_table: :lesli_audit_accounts, on_delete: :cascade }, null: false)
54
+ add_index(:lesli_audit_account_logs, %i[account_id created_at], name: "index_lesli_audit_account_logs_on_account_and_created_at")
55
+ add_index(:lesli_audit_account_logs, :created_at, name: "index_lesli_audit_account_logs_on_created_at")
52
56
  end
53
57
  end
@@ -33,14 +33,17 @@ Building a better future, one line of code at a time.
33
33
  class CreateLesliAuditAccountDevices < ActiveRecord::Migration[8.1]
34
34
  def change
35
35
  create_table :lesli_audit_account_devices do |t|
36
- t.string :agent_platform
37
- t.string :agent_browser
38
- t.string :agent_device
39
- t.integer :agent_count
40
- t.date :created_at
36
+ t.string :agent_platform, null: false
37
+ t.string :agent_browser, null: false
38
+ t.string :agent_device, null: false
39
+ t.bigint :agent_count, null: false, default: 0
40
+ t.date :date, null: false
41
+ t.timestamps null: false
41
42
  end
42
43
 
43
- add_reference(:lesli_audit_account_devices, :account, foreign_key: { to_table: :lesli_audit_accounts })
44
- add_index(:lesli_audit_account_devices, %i[agent_platform agent_browser agent_device created_at account_id], unique: true, name: "lesli_audit_devices_index")
44
+ add_reference(:lesli_audit_account_devices, :account, null: false, foreign_key: { to_table: :lesli_audit_accounts, on_delete: :cascade })
45
+ add_check_constraint(:lesli_audit_account_devices, "agent_count >= 0", name: "lesli_audit_account_devices_agent_count_nonnegative")
46
+ add_index(:lesli_audit_account_devices, %i[account_id date agent_platform agent_browser agent_device], unique: true, name: "lesli_audit_devices_index")
47
+ add_index(:lesli_audit_account_devices, :date, name: "index_lesli_audit_account_devices_on_date")
45
48
  end
46
49
  end
@@ -30,15 +30,18 @@ Building a better future, one line of code at a time.
30
30
  // ·
31
31
  =end
32
32
 
33
- class CreateLesliAuditAccountRequests < ActiveRecord::Migration[6.0]
33
+ class CreateLesliAuditAccountRequests < ActiveRecord::Migration[8.1]
34
34
  def change
35
35
  create_table :lesli_audit_account_requests do |t|
36
- t.string :request_controller
37
- t.string :request_action
38
- t.integer :request_count
39
- t.date :created_at
36
+ t.string :request_controller, null: false
37
+ t.string :request_action, null: false
38
+ t.bigint :request_count, null: false, default: 0
39
+ t.date :date, null: false
40
+ t.timestamps null: false
40
41
  end
41
- add_reference(:lesli_audit_account_requests, :account, foreign_key: { to_table: :lesli_audit_accounts })
42
- add_index(:lesli_audit_account_requests, %i[request_controller request_action created_at account_id], unique: true, name: "lesli_audit_account_requests_index")
42
+ add_reference(:lesli_audit_account_requests, :account, null: false, foreign_key: { to_table: :lesli_audit_accounts, on_delete: :cascade })
43
+ add_check_constraint(:lesli_audit_account_requests, "request_count >= 0", name: "lesli_audit_account_requests_request_count_nonnegative")
44
+ add_index(:lesli_audit_account_requests, %i[account_id date request_controller request_action], unique: true, name: "lesli_audit_account_requests_index")
45
+ add_index(:lesli_audit_account_requests, :date, name: "index_lesli_audit_account_requests_on_date")
43
46
  end
44
47
  end
@@ -30,25 +30,29 @@ Building a better future, one line of code at a time.
30
30
  // ·
31
31
  =end
32
32
 
33
- class CreateLesliAuditUserLogs < ActiveRecord::Migration[6.0]
33
+ class CreateLesliAuditUserLogs < ActiveRecord::Migration[8.1]
34
34
  def change
35
35
  create_table :lesli_audit_user_logs do |t|
36
36
  t.string :engine
37
37
  t.string :source
38
38
  t.string :action
39
39
 
40
- t.string :operation
41
- t.string :description
42
- t.bigint :session_id
40
+ t.string :operation, null: false
41
+ t.text :description
42
+ t.string :session_id
43
+ t.string :actor_uid
43
44
 
44
45
  # Polymorphic subject
45
46
  t.string :subject_type
46
47
  t.bigint :subject_id
47
48
 
48
- t.timestamps
49
+ t.timestamps null: false
49
50
  end
50
51
 
51
- add_reference(:lesli_audit_user_logs, :user, foreign_key: { to_table: :lesli_users })
52
- add_reference(:lesli_audit_user_logs, :account, foreign_key: { to_table: :lesli_audit_accounts })
52
+ add_reference(:lesli_audit_user_logs, :user, null: true, foreign_key: { to_table: :lesli_users, on_delete: :nullify })
53
+ add_reference(:lesli_audit_user_logs, :account, null: false, foreign_key: { to_table: :lesli_audit_accounts, on_delete: :cascade })
54
+ add_index(:lesli_audit_user_logs, %i[account_id created_at], name: "index_lesli_audit_user_logs_on_account_and_created_at")
55
+ add_index(:lesli_audit_user_logs, %i[user_id created_at], name: "index_lesli_audit_user_logs_on_user_and_created_at")
56
+ add_index(:lesli_audit_user_logs, :created_at, name: "index_lesli_audit_user_logs_on_created_at")
53
57
  end
54
58
  end
@@ -30,16 +30,27 @@ Building a better future, one line of code at a time.
30
30
  // ·
31
31
  =end
32
32
 
33
- class CreateLesliAuditUserJournals < ActiveRecord::Migration[6.0]
33
+ class CreateLesliAuditUserJournals < ActiveRecord::Migration[8.1]
34
34
  def change
35
35
  create_table :lesli_audit_user_journals do |t|
36
- t.string :request_controller
37
- t.string :request_action
38
- t.date :date, index: true
39
- t.bigint :session_id
40
- t.timestamps
36
+ t.string :event_uid, null: false
37
+ t.string :request_controller
38
+ t.string :request_action
39
+ t.date :date, null: false
40
+ t.string :session_id
41
+ t.string :agent_platform
42
+ t.string :agent_browser
43
+ t.string :agent_device
44
+ t.boolean :analytics_enabled, null: false, default: true
45
+ t.boolean :retain, null: false, default: true
46
+ t.datetime :processed_at
47
+ t.timestamps null: false
41
48
  end
42
- add_reference(:lesli_audit_user_journals, :user, foreign_key: { to_table: :lesli_users })
43
- add_reference(:lesli_audit_user_journals, :account, foreign_key: { to_table: :lesli_audit_accounts })
49
+ add_reference(:lesli_audit_user_journals, :user, null: true, foreign_key: { to_table: :lesli_users, on_delete: :nullify })
50
+ add_reference(:lesli_audit_user_journals, :account, null: false, foreign_key: { to_table: :lesli_audit_accounts, on_delete: :cascade })
51
+ add_index(:lesli_audit_user_journals, %i[account_id date], name: "index_lesli_audit_user_journals_on_account_and_date")
52
+ add_index(:lesli_audit_user_journals, %i[account_id event_uid], unique: true, name: "lesli_audit_user_journals_event_index")
53
+ add_index(:lesli_audit_user_journals, :date, name: "index_lesli_audit_user_journals_on_date")
54
+ add_index(:lesli_audit_user_journals, :processed_at, name: "index_lesli_audit_user_journals_on_processed_at")
44
55
  end
45
56
  end
@@ -30,18 +30,21 @@ Building a better future, one line of code at a time.
30
30
  // ·
31
31
  =end
32
32
 
33
- class CreateLesliAuditUserRequests < ActiveRecord::Migration[6.0]
33
+ class CreateLesliAuditUserRequests < ActiveRecord::Migration[8.1]
34
34
  def change
35
35
  create_table :lesli_audit_user_requests do |t|
36
- t.string :request_controller
37
- t.string :request_action
38
- t.integer :request_count
39
- t.date :date, index: true
40
- t.bigint :session_id
41
- t.timestamps
36
+ t.string :request_controller, null: false
37
+ t.string :request_action, null: false
38
+ t.bigint :request_count, null: false, default: 0
39
+ t.date :date, null: false
40
+ t.string :session_id
41
+ t.timestamps null: false
42
42
  end
43
- add_reference(:lesli_audit_user_requests, :user, foreign_key: { to_table: :lesli_users })
44
- add_reference(:lesli_audit_user_requests, :account, foreign_key: { to_table: :lesli_audit_accounts })
45
- add_index(:lesli_audit_user_requests, %i[request_controller request_action date user_id session_id], unique: true, name: "lesli_audit_user_requests_index")
43
+ add_reference(:lesli_audit_user_requests, :user, null: false, foreign_key: { to_table: :lesli_users, on_delete: :cascade })
44
+ add_reference(:lesli_audit_user_requests, :account, null: false, foreign_key: { to_table: :lesli_audit_accounts, on_delete: :cascade })
45
+ add_check_constraint(:lesli_audit_user_requests, "request_count >= 0", name: "lesli_audit_user_requests_request_count_nonnegative")
46
+ add_index(:lesli_audit_user_requests, %i[account_id user_id session_id date request_controller request_action], unique: true, name: "lesli_audit_user_requests_index")
47
+ add_index(:lesli_audit_user_requests, %i[account_id date], name: "index_lesli_audit_user_requests_on_account_and_date")
48
+ add_index(:lesli_audit_user_requests, :date, name: "index_lesli_audit_user_requests_on_date")
46
49
  end
47
50
  end
data/db/seed/devices.rb CHANGED
@@ -68,7 +68,7 @@ current_user = Lesli::User.first
68
68
  :agent_platform => platform,
69
69
  :agent_browser => browser,
70
70
  :agent_device => device,
71
- :created_at => date,
71
+ :date => date,
72
72
  )
73
73
  end
74
74
  end
data/db/seed/requests.rb CHANGED
@@ -58,7 +58,7 @@ session = current_user.sessions.first
58
58
  ).find_or_create_by(
59
59
  :request_controller => controller,
60
60
  :request_action => "show",
61
- :created_at => date,
61
+ :date => date,
62
62
  )
63
63
 
64
64
  current_user.account.audit.user_requests.create_with(
@@ -66,8 +66,8 @@ session = current_user.sessions.first
66
66
  ).find_or_create_by(
67
67
  :request_controller => controller,
68
68
  :request_action => "show",
69
- :created_at => date,
70
- :session => session,
69
+ :date => date,
70
+ :session_id => session&.id,
71
71
  :user => current_user
72
72
  )
73
73
  end
@@ -1,18 +1,7 @@
1
- require "useragent"
2
1
  module LesliAudit
3
2
  class Engine < ::Rails::Engine
4
3
  isolate_namespace LesliAudit
5
-
6
- config.to_prepare do
7
- unless Lesli::User < LesliAudit::UserAssociations
8
- Lesli::User.include(LesliAudit::UserAssociations)
9
- end
10
- end
11
-
12
- initializer :lesli_admin do |app|
13
-
14
- # register assets manifest
15
- config.assets.precompile += %w[lesli_audit_manifest.js]
4
+ initializer :lesli_audit do |app|
16
5
 
17
6
  app.config.assets.paths << root.join("app", "javascript").to_s
18
7
 
@@ -1,4 +1,4 @@
1
1
  module LesliAudit
2
- VERSION = "1.1.1"
3
- BUILD = "1791607001"
2
+ VERSION = "1.1.2"
3
+ BUILD = "1792716699"
4
4
  end
@@ -1,4 +1,12 @@
1
- # desc "Explaining what the task does"
2
- # task :lesli_audit do
3
- # # Task goes here
4
- # end
1
+ namespace :lesli_audit do
2
+ namespace :retention do
3
+ desc "Delete expired LesliAudit data using the configured retention policy"
4
+ task cleanup: :environment do
5
+ deleted = LesliAudit::RetentionCleanup.call
6
+
7
+ deleted.each do |table, count|
8
+ puts "#{table}: #{count} deleted"
9
+ end
10
+ end
11
+ end
12
+ end
data/readme.md CHANGED
@@ -8,8 +8,8 @@
8
8
  <br />
9
9
 
10
10
  <div align="center">
11
- <a target="_blank" href="https://github.com/LesliTech/LesliAudit/actions/workflows/lesli-ci-tests.yaml">
12
- <img alt="LesliAudit test status" src="https://img.shields.io/github/actions/workflow/status/LesliTech/LesliAudit/lesli-ci-tests.yaml?branch=master&style=for-the-badge&logo=github&label=tests">
11
+ <a target="_blank" href="https://github.com/LesliTech/LesliAudit/actions/workflows/lesli-ci-engine.yaml">
12
+ <img alt="LesliAudit test status" src="https://img.shields.io/github/actions/workflow/status/LesliTech/LesliAudit/lesli-ci-engine.yaml?branch=master&style=for-the-badge&logo=github&label=tests">
13
13
  </a>
14
14
  <a target="_blank" href="https://rubygems.org/gems/lesli_audit">
15
15
  <img alt="Gem Version" src="https://img.shields.io/gem/v/lesli_audit?style=for-the-badge&logo=ruby">
@@ -47,10 +47,16 @@ It records account activity and provides administrators with searchable operatio
47
47
 
48
48
  - Account activity and audit logs
49
49
  - Request and navigation history
50
- - Visitor and authenticated-user analytics
51
- - Device and user-agent information
50
+ - Request, device, and authenticated-user analytics
51
+ - Direct account and authenticated-user request counters
52
+ - Configurable batched data retention
53
+ - Normalized browser, platform, and device categories
52
54
  - Detailed views for investigating recorded activity
53
55
 
56
+ Activity logs are explicit append-only business or security events. They are
57
+ separate from request analytics: user requests are grouped counters, while
58
+ user journals are individual normalized HTTP request events.
59
+
54
60
  <br />
55
61
 
56
62
  ## Try LesliAudit
@@ -64,7 +70,8 @@ It records account activity and provides administrators with searchable operatio
64
70
 
65
71
  ### Requirements
66
72
 
67
- - A Rails application with [Lesli](https://rubygems.org/gems/lesli)
73
+ - Ruby 3.x and Rails `~> 8.1`
74
+ - A Rails application with [Lesli](https://rubygems.org/gems/lesli) `~> 5.1.0`
68
75
  - SQLite by default, or PostgreSQL when preferred by the host application
69
76
 
70
77
  ### Install LesliAudit
@@ -143,6 +150,12 @@ bin/rails test
143
150
 
144
151
  - [Lesli website](https://www.lesli.dev/)
145
152
  - [Documentation](https://www.lesli.dev/engines/audit)
153
+ - [Architecture and data policy](./docs/about/architecture.md)
154
+ - [Configuration](./docs/about/configuration.md)
155
+ - [Request analytics](./docs/analytics/overview.md)
156
+ - [Activity events](./docs/events/overview.md)
157
+ - [Operational tasks](./docs/about/tasks.md)
158
+ - [Database](./docs/about/database.md)
146
159
  - [Frontend development](./docs/about/frontend.md)
147
160
  - [Release notes](https://github.com/LesliTech/LesliAudit/releases)
148
161
  - [Issue tracker](https://github.com/LesliTech/LesliAudit/issues)