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.
- checksums.yaml +4 -4
- data/Rakefile +6 -1
- data/app/assets/stylesheets/lesli_audit/application.css +366 -35
- data/app/controllers/lesli_audit/accounts_controller.rb +7 -3
- data/app/controllers/lesli_audit/logs_controller.rb +3 -8
- data/app/controllers/lesli_audit/visitors_controller.rb +1 -1
- data/app/interfaces/lesli_audit/logger_interface.rb +56 -59
- data/app/jobs/lesli_audit/retention_cleanup_job.rb +9 -0
- data/app/models/concerns/lesli_audit/append_only.rb +12 -0
- data/app/models/lesli_audit/account.rb +26 -10
- data/app/models/lesli_audit/account_device.rb +3 -1
- data/app/models/lesli_audit/account_log.rb +12 -7
- data/app/models/lesli_audit/account_request.rb +3 -1
- data/app/models/lesli_audit/user_journal.rb +16 -3
- data/app/models/lesli_audit/user_log.rb +12 -3
- data/app/models/lesli_audit/user_request.rb +7 -3
- data/app/services/lesli_audit/activity_audit.rb +130 -0
- data/app/services/lesli_audit/request_service.rb +2 -35
- data/app/services/lesli_audit/retention_cleanup.rb +146 -0
- data/app/services/lesli_audit/user_agent_parser.rb +51 -0
- data/app/services/lesli_audit/user_service.rb +18 -16
- data/app/services/lesli_audit/visitor_service.rb +22 -49
- data/app/views/lesli_audit/logs/show.html.erb +14 -8
- data/app/views/lesli_audit/partials/_navigation.html.erb +1 -1
- data/app/views/lesli_audit/users/index.html.erb +0 -3
- data/app/views/lesli_audit/visitors/index.html.erb +12 -12
- data/config/routes.rb +3 -2
- data/db/migrate/v1.0/0501000110_create_lesli_audit_accounts.rb +14 -1
- data/db/migrate/v1.0/0501110110_create_lesli_audit_account_logs.rb +10 -6
- data/db/migrate/v1.0/0501110210_create_lesli_audit_account_devices.rb +10 -7
- data/db/migrate/v1.0/0501110310_create_lesli_audit_account_requests.rb +10 -7
- data/db/migrate/v1.0/0501120110_create_lesli_audit_user_logs.rb +11 -7
- data/db/migrate/v1.0/0501120210_create_lesli_audit_user_journals.rb +19 -8
- data/db/migrate/v1.0/0501120310_create_lesli_audit_user_requests.rb +13 -10
- data/db/seed/devices.rb +1 -1
- data/db/seed/requests.rb +3 -3
- data/lib/lesli_audit/engine.rb +1 -12
- data/lib/lesli_audit/version.rb +2 -2
- data/lib/tasks/lesli_audit_tasks.rake +12 -4
- data/readme.md +18 -5
- metadata +14 -6
- data/app/assets/stylesheets/lesli_audit/application.tailwind.css +0 -2
- 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
|
|
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
|
-
|
|
56
|
-
|
|
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',
|
|
61
|
+
group_by = "strftime('%Y-%m-%d', date)"
|
|
65
62
|
end
|
|
66
63
|
|
|
67
|
-
requests = current_user.account.audit.account_requests
|
|
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, :
|
|
135
|
+
# .group(:agent_browser, :date)
|
|
142
136
|
# .select(
|
|
143
|
-
# '
|
|
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
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1
|
+
<%= render(LesliView::Layout::Container.new("audit-log")) do %>
|
|
2
|
+
<%= render(LesliView::Components::Header.new("Audit event")) %>
|
|
2
3
|
|
|
3
|
-
|
|
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
|
-
|
|
7
|
-
|
|
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.
|
|
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'); %>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
<%
|
|
3
|
-
|
|
4
|
-
top_days =
|
|
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="
|
|
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
|
-
|
|
40
|
-
|
|
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="
|
|
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: "
|
|
49
|
+
{ label: "Requests", align: :center }
|
|
50
50
|
],
|
|
51
51
|
class_name: "!min-w-0",
|
|
52
52
|
compact: true,
|
|
53
|
-
caption: "
|
|
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: "
|
|
84
|
+
{ label: "Requests", align: :center }
|
|
85
85
|
],
|
|
86
86
|
class_name: "!min-w-0",
|
|
87
87
|
compact: true,
|
|
88
|
-
caption: "
|
|
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: "
|
|
119
|
+
{ label: "Requests", align: :center }
|
|
120
120
|
],
|
|
121
121
|
class_name: "!min-w-0",
|
|
122
122
|
compact: true,
|
|
123
|
-
caption: "
|
|
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
|
-
#
|
|
46
|
+
# Requests by date
|
|
47
47
|
# Trends by date
|
|
48
48
|
# Most active users
|
|
49
49
|
# Most active controllers
|
|
50
|
-
|
|
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[
|
|
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[
|
|
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.
|
|
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:
|
|
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
|
|
37
|
-
t.string
|
|
38
|
-
t.string
|
|
39
|
-
t.
|
|
40
|
-
t.date
|
|
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
|
-
|
|
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[
|
|
33
|
+
class CreateLesliAuditAccountRequests < ActiveRecord::Migration[8.1]
|
|
34
34
|
def change
|
|
35
35
|
create_table :lesli_audit_account_requests do |t|
|
|
36
|
-
t.string
|
|
37
|
-
t.string
|
|
38
|
-
t.
|
|
39
|
-
t.date
|
|
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
|
-
|
|
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[
|
|
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.
|
|
42
|
-
t.
|
|
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[
|
|
33
|
+
class CreateLesliAuditUserJournals < ActiveRecord::Migration[8.1]
|
|
34
34
|
def change
|
|
35
35
|
create_table :lesli_audit_user_journals do |t|
|
|
36
|
-
t.string
|
|
37
|
-
t.string
|
|
38
|
-
t.
|
|
39
|
-
t.
|
|
40
|
-
t.
|
|
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[
|
|
33
|
+
class CreateLesliAuditUserRequests < ActiveRecord::Migration[8.1]
|
|
34
34
|
def change
|
|
35
35
|
create_table :lesli_audit_user_requests do |t|
|
|
36
|
-
t.string
|
|
37
|
-
t.string
|
|
38
|
-
t.
|
|
39
|
-
t.date
|
|
40
|
-
t.
|
|
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
|
-
|
|
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
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
|
-
:
|
|
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
|
-
:
|
|
70
|
-
:
|
|
69
|
+
:date => date,
|
|
70
|
+
:session_id => session&.id,
|
|
71
71
|
:user => current_user
|
|
72
72
|
)
|
|
73
73
|
end
|
data/lib/lesli_audit/engine.rb
CHANGED
|
@@ -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
|
|
data/lib/lesli_audit/version.rb
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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-
|
|
12
|
-
<img alt="LesliAudit test status" src="https://img.shields.io/github/actions/workflow/status/LesliTech/LesliAudit/lesli-ci-
|
|
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
|
-
-
|
|
51
|
-
-
|
|
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
|
-
-
|
|
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)
|