lesli_audit 1.0.2 → 1.1.1

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/lesli_audit/application.tailwind.css +2 -0
  3. data/app/controllers/lesli_audit/users_controller.rb +1 -2
  4. data/app/controllers/lesli_audit/visitors_controller.rb +14 -0
  5. data/app/interfaces/lesli_audit/logger_interface.rb +22 -29
  6. data/app/models/concerns/lesli_audit/user_associations.rb +11 -0
  7. data/app/services/lesli_audit/user_service.rb +105 -12
  8. data/app/services/lesli_audit/{analytic_service.rb → visitor_service.rb} +121 -24
  9. data/app/views/lesli_audit/partials/_navigation.html.erb +1 -1
  10. data/app/views/lesli_audit/requests/index.html.erb +1 -1
  11. data/app/views/lesli_audit/users/index.html.erb +75 -37
  12. data/app/views/lesli_audit/visitors/index.html.erb +164 -0
  13. data/config/routes.rb +1 -1
  14. data/db/migrate/v1.0/0501110210_create_lesli_audit_account_devices.rb +2 -1
  15. data/db/seed/devices.rb +23 -15
  16. data/db/seed/users.rb +117 -0
  17. data/db/seeds.rb +2 -2
  18. data/lib/lesli_audit/engine.rb +6 -0
  19. data/lib/lesli_audit/version.rb +2 -2
  20. data/readme.md +113 -49
  21. metadata +12 -50
  22. data/app/assets/config/lesli_audit_manifest.js +0 -37
  23. data/app/controllers/lesli_audit/analytics_controller.rb +0 -23
  24. data/app/controllers/lesli_audit/user_journals_controller.rb +0 -60
  25. data/app/helpers/lesli_audit/analytics_helper.rb +0 -4
  26. data/app/views/lesli_audit/analytics/index.html.erb +0 -44
  27. data/app/views/lesli_audit/user_journals/_form.html.erb +0 -17
  28. data/app/views/lesli_audit/user_journals/_user_journal.html.erb +0 -2
  29. data/app/views/lesli_audit/user_journals/edit.html.erb +0 -12
  30. data/app/views/lesli_audit/user_journals/index.html.erb +0 -16
  31. data/app/views/lesli_audit/user_journals/new.html.erb +0 -11
  32. data/app/views/lesli_audit/user_journals/show.html.erb +0 -10
  33. data/app/views/lesli_audit/users/show.html.erb +0 -10
  34. data/lib/scss/application.scss +0 -31
  35. data/lib/vue/application.js +0 -146
  36. data/lib/vue/apps/accounts/activities.vue +0 -48
  37. data/lib/vue/apps/analytics/index.vue +0 -56
  38. data/lib/vue/apps/analytics/trends.vue +0 -148
  39. data/lib/vue/apps/dashboards/components/roles.vue +0 -71
  40. data/lib/vue/apps/dashboards/components/users.vue +0 -71
  41. data/lib/vue/apps/dashboards/show.vue +0 -8
  42. data/lib/vue/apps/requests/index.vue +0 -125
  43. data/lib/vue/apps/security/passwords.vue +0 -52
  44. data/lib/vue/apps/security/sessions.vue +0 -63
  45. data/lib/vue/apps/users/activities.vue +0 -49
  46. data/lib/vue/apps/users/index.vue +0 -89
  47. data/lib/vue/apps/users/logs.vue +0 -49
  48. data/lib/vue/apps/users/registrations.vue +0 -73
  49. data/lib/vue/apps/users/roles.vue +0 -70
  50. data/lib/vue/apps/users/workingHours.vue +0 -52
  51. data/lib/vue/components/requests.vue +0 -63
  52. data/lib/vue/components/visitors.vue +0 -76
  53. data/lib/vue/stores/accounts/activities.js +0 -87
  54. data/lib/vue/stores/analytics.js +0 -127
  55. data/lib/vue/stores/request.js +0 -70
  56. data/lib/vue/stores/security/password.js +0 -75
  57. data/lib/vue/stores/security/session.js +0 -78
  58. data/lib/vue/stores/translations.json +0 -162
  59. data/lib/vue/stores/users/activities.js +0 -76
  60. data/lib/vue/stores/users/logs.js +0 -74
  61. data/lib/vue/stores/users/registrations.js +0 -61
  62. data/lib/vue/stores/users/roles.js +0 -52
  63. data/lib/vue/stores/users/working_hours.js +0 -92
  64. data/lib/vue/stores/users.js +0 -56
@@ -0,0 +1,164 @@
1
+
2
+ <%
3
+ visitors = @visitors.reverse
4
+ top_days = visitors.last(3)
5
+ %>
6
+
7
+ <%= render(LesliView::Layout::Container.new("audit-analytics")) do %>
8
+ <%= render(LesliView::Components::Header.new("Analytics")) %>
9
+
10
+ <section
11
+ class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-5"
12
+ aria-label="Visitor overview">
13
+ <article class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm">
14
+ <h2 class="text-sm font-medium text-slate-500">Controllers</h2>
15
+ <p class="mt-3 text-3xl font-semibold tracking-tight text-slate-900 tabular-nums">
16
+ <%= @visits[:controllers] %>
17
+ </p>
18
+ </article>
19
+
20
+ <article class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm">
21
+ <h2 class="text-sm font-medium text-slate-500">Requests</h2>
22
+ <p class="mt-3 text-3xl font-semibold tracking-tight text-slate-900 tabular-nums">
23
+ <%= @visits[:requests] %>
24
+ </p>
25
+ </article>
26
+
27
+ <% top_days.each do |day| %>
28
+ <article class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm">
29
+ <h2 class="text-sm font-medium text-slate-500"><%= day["xaxiskey"] %></h2>
30
+ <p class="mt-3 text-3xl font-semibold tracking-tight text-slate-900 tabular-nums">
31
+ <%= day["yaxiskey"] %>
32
+ </p>
33
+ </article>
34
+ <% end %>
35
+ </section>
36
+
37
+ <section class="mt-6 rounded-2xl border border-slate-200 bg-white p-5 shadow-sm">
38
+ <%= render(LesliView::Charts::Line.new(
39
+ title: "Visitors",
40
+ database_to_dataset: visitors
41
+ )) %>
42
+ </section>
43
+
44
+ <section class="mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3" aria-label="Visitor breakdown">
45
+ <article class="min-w-0">
46
+ <%= render(LesliView::Elements::Table.new(
47
+ columns: [
48
+ { label: "Browsers", align: :left },
49
+ { label: "Visitors", align: :center }
50
+ ],
51
+ class_name: "!min-w-0",
52
+ compact: true,
53
+ caption: "Visitors by browser"
54
+ )) do |table| %>
55
+ <% @browsers.each do |browser| %>
56
+ <% percentage = browser[:percentage].to_i.clamp(0, 100) %>
57
+ <% table.with_row do |row| %>
58
+ <% row.with_cell(class_name: "!p-0") do %>
59
+ <div class="relative isolate overflow-hidden px-3 py-2.5">
60
+ <span
61
+ class="absolute inset-y-0 left-0 -z-10 bg-slate-100"
62
+ style="width: <%= percentage %>%"
63
+ aria-hidden="true"></span>
64
+ <span class="flex items-center gap-2 text-slate-700">
65
+ <span class="inline-flex h-7 w-7 shrink-0 items-center justify-center text-slate-500">
66
+ <span class="ri-<%= browser[:icon] %> text-lg leading-none" aria-hidden="true"></span>
67
+ </span>
68
+ <span class="truncate"><%= browser[:browser] %></span>
69
+ </span>
70
+ </div>
71
+ <% end %>
72
+ <% row.with_cell(class_name: "font-semibold tabular-nums text-slate-900", align: :center) do %>
73
+ <%= browser[:count] %>
74
+ <% end %>
75
+ <% end %>
76
+ <% end %>
77
+ <% end %>
78
+ </article>
79
+
80
+ <article class="min-w-0">
81
+ <%= render(LesliView::Elements::Table.new(
82
+ columns: [
83
+ { label: "OS", align: :left },
84
+ { label: "Visitors", align: :center }
85
+ ],
86
+ class_name: "!min-w-0",
87
+ compact: true,
88
+ caption: "Visitors by operating system"
89
+ )) do |table| %>
90
+ <% @os.each do |device| %>
91
+ <% percentage = device[:percentage].to_i.clamp(0, 100) %>
92
+ <% table.with_row do |row| %>
93
+ <% row.with_cell(class_name: "!p-0") do %>
94
+ <div class="relative isolate overflow-hidden px-3 py-2.5">
95
+ <span
96
+ class="absolute inset-y-0 left-0 -z-10 bg-slate-100"
97
+ style="width: <%= percentage %>%"
98
+ aria-hidden="true"></span>
99
+ <span class="flex items-center gap-2 text-slate-700">
100
+ <span class="inline-flex h-7 w-7 shrink-0 items-center justify-center text-slate-500">
101
+ <span class="ri-<%= device[:icon].downcase %> text-lg leading-none" aria-hidden="true"></span>
102
+ </span>
103
+ <span class="truncate"><%= device[:device] %></span>
104
+ </span>
105
+ </div>
106
+ <% end %>
107
+ <% row.with_cell(class_name: "font-semibold tabular-nums text-slate-900", align: :center) do %>
108
+ <%= device[:count] %>
109
+ <% end %>
110
+ <% end %>
111
+ <% end %>
112
+ <% end %>
113
+ </article>
114
+
115
+ <article class="min-w-0">
116
+ <%= render(LesliView::Elements::Table.new(
117
+ columns: [
118
+ { label: "Device", align: :left },
119
+ { label: "Visitors", align: :center }
120
+ ],
121
+ class_name: "!min-w-0",
122
+ compact: true,
123
+ caption: "Visitors by device"
124
+ )) do |table| %>
125
+ <% @devices.each do |device| %>
126
+ <% percentage = device[:percentage].to_i.clamp(0, 100) %>
127
+ <% table.with_row do |row| %>
128
+ <% row.with_cell(class_name: "!p-0") do %>
129
+ <div class="relative isolate overflow-hidden px-3 py-2.5">
130
+ <span
131
+ class="absolute inset-y-0 left-0 -z-10 bg-slate-100"
132
+ style="width: <%= percentage %>%"
133
+ aria-hidden="true"></span>
134
+ <span class="flex items-center gap-2 text-slate-700">
135
+ <span class="inline-flex h-7 w-7 shrink-0 items-center justify-center text-slate-500">
136
+ <span class="ri-<%= device[:icon] %> text-lg leading-none" aria-hidden="true"></span>
137
+ </span>
138
+ <span class="truncate"><%= device[:device] %></span>
139
+ </span>
140
+ </div>
141
+ <% end %>
142
+ <% row.with_cell(class_name: "font-semibold tabular-nums text-slate-900", align: :center) do %>
143
+ <%= device[:count] %>
144
+ <% end %>
145
+ <% end %>
146
+ <% end %>
147
+ <% end %>
148
+ </article>
149
+ </section>
150
+
151
+ <section class="mt-6 pb-8" aria-label="Top controllers">
152
+ <%= render(LesliView::Elements::Table.new(
153
+ columns: [{
154
+ label: "Top controllers",
155
+ field: "request_controller"
156
+ }, {
157
+ label: "Requests",
158
+ field: "requests"
159
+ }],
160
+ records: @requests,
161
+ caption: "Requests by controller"
162
+ )) %>
163
+ </section>
164
+ <% end %>
data/config/routes.rb CHANGED
@@ -47,7 +47,7 @@ LesliAudit::Engine.routes.draw do
47
47
  # Trends by date
48
48
  # Most active users
49
49
  # Most active controllers
50
- resources :analytics, only: [:index]
50
+ resources :visitors, only: [:index]
51
51
 
52
52
  resources :logs, only: [:index, :show]
53
53
 
@@ -35,11 +35,12 @@ class CreateLesliAuditAccountDevices < ActiveRecord::Migration[8.1]
35
35
  create_table :lesli_audit_account_devices do |t|
36
36
  t.string :agent_platform
37
37
  t.string :agent_browser
38
+ t.string :agent_device
38
39
  t.integer :agent_count
39
40
  t.date :created_at
40
41
  end
41
42
 
42
43
  add_reference(:lesli_audit_account_devices, :account, foreign_key: { to_table: :lesli_audit_accounts })
43
- add_index(:lesli_audit_account_devices, %i[agent_platform agent_browser created_at account_id], unique: true, name: "lesli_audit_devices_index")
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
45
  end
45
46
  end
data/db/seed/devices.rb CHANGED
@@ -44,25 +44,33 @@ current_user = Lesli::User.first
44
44
 
45
45
  # controllers to seed
46
46
  [
47
- "Macintosh",
47
+ "Mac",
48
48
  "Windows"
49
49
  ].each do |platform|
50
50
 
51
51
  [
52
- "Firefox",
53
- "Chrome",
54
- "Safari"
55
- ].each do |browser|
56
- # Iterate through the dates
57
- (start_date..end_date).each do |date|
58
-
59
- current_user.account.audit.account_devices.create_with(
60
- :agent_count => rand(40..80),
61
- ).find_or_create_by(
62
- :agent_platform => platform,
63
- :agent_browser => browser,
64
- :created_at => date,
65
- )
52
+ "smartphone",
53
+ "tablet",
54
+ "desktop"
55
+ ].each do |device|
56
+
57
+ [
58
+ "Firefox",
59
+ "Chrome",
60
+ "Safari"
61
+ ].each do |browser|
62
+ # Iterate through the dates
63
+ (start_date..end_date).each do |date|
64
+
65
+ current_user.account.audit.account_devices.create_with(
66
+ :agent_count => rand(40..80),
67
+ ).find_or_create_by(
68
+ :agent_platform => platform,
69
+ :agent_browser => browser,
70
+ :agent_device => device,
71
+ :created_at => date,
72
+ )
73
+ end
66
74
  end
67
75
  end
68
76
  end
data/db/seed/users.rb ADDED
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ return unless Rails.env.development?
4
+
5
+ account = Lesli::Account.first!
6
+
7
+ password = if defined?(Devise)
8
+ Devise::Encryptor.digest(Lesli::User, "password123")
9
+ else
10
+ BCrypt::Password.create("password123")
11
+ end
12
+
13
+ now = Time.current
14
+
15
+ # Adjust these numbers freely while building the dashboard.
16
+ TOTAL_USERS = 1_000
17
+ TOTAL_INACTIVE_USERS = 120
18
+ TOTAL_LOCKED_USERS = 45
19
+ TOTAL_UNCONFIRMED_USERS = 80
20
+ TOTAL_USERS_NEVER_SIGNED_IN = 140
21
+ TOTAL_USERS_ACTIVE_LAST_30_DAYS = 520
22
+ TOTAL_NEW_USERS_LAST_30_DAYS = 75
23
+ TOTAL_USERS_WITH_FAILED_ATTEMPTS = 60
24
+ TOTAL_DELETED_USERS = 25
25
+
26
+ users = []
27
+
28
+ TOTAL_USERS.times do |index|
29
+ uid = "DEV-USR-#{index.to_s.rjust(5, "0")}"
30
+
31
+ users << {
32
+ uid: uid,
33
+ account_id: account.id,
34
+
35
+ active: true,
36
+
37
+ first_name: "Demo",
38
+ last_name: "User #{index + 1}",
39
+ email: "demo.user.#{index + 1}@lesli.dev",
40
+ encrypted_password: password,
41
+
42
+ confirmed_at: now - rand(1..180).days,
43
+ confirmation_sent_at: nil,
44
+ confirmation_token: nil,
45
+
46
+ sign_in_count: rand(1..80),
47
+ current_sign_in_at: now - rand(1..90).days,
48
+ last_sign_in_at: now - rand(1..90).days,
49
+ current_sign_in_ip: "127.0.0.1",
50
+ last_sign_in_ip: "127.0.0.1",
51
+
52
+ failed_attempts: 0,
53
+
54
+ locked_at: nil,
55
+ locked_until: nil,
56
+ unlock_token: nil,
57
+
58
+ created_at: now - rand(31..365).days,
59
+ updated_at: now,
60
+ deleted_at: nil
61
+ }
62
+ end
63
+
64
+ # Inactive users
65
+ users.first(TOTAL_INACTIVE_USERS).each do |user|
66
+ user[:active] = false
67
+ end
68
+
69
+ # Locked users
70
+ users.slice(120, TOTAL_LOCKED_USERS).each do |user|
71
+ user[:locked_at] = now - rand(1..15).days
72
+ user[:locked_until] = now + rand(1..15).days
73
+ end
74
+
75
+ # Unconfirmed users
76
+ users.slice(200, TOTAL_UNCONFIRMED_USERS).each do |user|
77
+ user[:confirmed_at] = nil
78
+ user[:confirmation_sent_at] = now - rand(1..10).days
79
+ user[:confirmation_token] = SecureRandom.hex(16)
80
+ end
81
+
82
+ # Never signed in
83
+ users.slice(300, TOTAL_USERS_NEVER_SIGNED_IN).each do |user|
84
+ user[:sign_in_count] = 0
85
+ user[:current_sign_in_at] = nil
86
+ user[:last_sign_in_at] = nil
87
+ user[:current_sign_in_ip] = nil
88
+ user[:last_sign_in_ip] = nil
89
+ end
90
+
91
+ # Active in the last 30 days
92
+ users.slice(450, TOTAL_USERS_ACTIVE_LAST_30_DAYS).each do |user|
93
+ user[:sign_in_count] = rand(1..100)
94
+ user[:last_sign_in_at] = now - rand(0..29).days
95
+ user[:current_sign_in_at] = user[:last_sign_in_at]
96
+ end
97
+
98
+ # New users in the last 30 days
99
+ users.last(TOTAL_NEW_USERS_LAST_30_DAYS).each do |user|
100
+ user[:created_at] = now - rand(0..29).days
101
+ user[:updated_at] = now
102
+ end
103
+
104
+ # Users with failed attempts
105
+ users.slice(700, TOTAL_USERS_WITH_FAILED_ATTEMPTS).each do |user|
106
+ user[:failed_attempts] = rand(1..5)
107
+ end
108
+
109
+ # Soft-deleted users
110
+ users.last(TOTAL_DELETED_USERS).each do |user|
111
+ user[:deleted_at] = now - rand(1..30).days
112
+ end
113
+
114
+ # Remove previous generated users only
115
+ Lesli::User.where("uid LIKE ?", "DEV-USR-%").delete_all
116
+
117
+ Lesli::User.insert_all!(users)
data/db/seeds.rb CHANGED
@@ -30,12 +30,12 @@ Building a better future, one line of code at a time.
30
30
  // ·
31
31
  =end
32
32
 
33
-
34
- L2.msg("LesliAudit", "Version: #{LesliAudit::VERSION}", "Build: #{LesliAudit::BUILD}")
33
+ Termline.info("Loading seeds for: LesliAudit #{LesliAudit::VERSION} (#{LesliAudit::BUILD})")
35
34
 
36
35
 
37
36
  # · load specific environment seeds
38
37
  if Rails.env.development? || Lesli.config.demo
39
38
  load LesliAudit::Engine.root.join("db", "seed", "requests.rb")
40
39
  load LesliAudit::Engine.root.join("db", "seed", "devices.rb")
40
+ load LesliAudit::Engine.root.join("db", "seed", "users.rb")
41
41
  end
@@ -3,6 +3,12 @@ module LesliAudit
3
3
  class Engine < ::Rails::Engine
4
4
  isolate_namespace LesliAudit
5
5
 
6
+ config.to_prepare do
7
+ unless Lesli::User < LesliAudit::UserAssociations
8
+ Lesli::User.include(LesliAudit::UserAssociations)
9
+ end
10
+ end
11
+
6
12
  initializer :lesli_admin do |app|
7
13
 
8
14
  # register assets manifest
@@ -1,4 +1,4 @@
1
1
  module LesliAudit
2
- VERSION = "1.0.2"
3
- BUILD = "1771710537"
2
+ VERSION = "1.1.1"
3
+ BUILD = "1791607001"
4
4
  end
data/readme.md CHANGED
@@ -1,14 +1,15 @@
1
1
  <div align="center">
2
- <img width="100" alt="LesliAudit logo" src="./app/assets/images/lesli_audit/audit-logo.svg" />
3
- <h3 align="center">Activity, logs, security and more... for the Lesli Framework.</h3>
2
+ <h1 align="center">
3
+ <img width="100" alt="LesliAudit" src="./app/assets/images/lesli_audit/audit-logo.svg" />
4
+ </h1>
5
+ <h3 align="center">Audit trails and analytics for the Lesli Framework.</h3>
4
6
  </div>
5
7
 
6
8
  <br />
7
- <hr/>
8
9
 
9
10
  <div align="center">
10
- <a target="_blank" href="https://github.com/LesliTech/LesliAudit/actions">
11
- <img alt="Tests passing" src="https://img.shields.io/badge/Tests-passing-green?style=for-the-badge&logo=github">
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">
12
13
  </a>
13
14
  <a target="_blank" href="https://rubygems.org/gems/lesli_audit">
14
15
  <img alt="Gem Version" src="https://img.shields.io/gem/v/lesli_audit?style=for-the-badge&logo=ruby">
@@ -16,83 +17,149 @@
16
17
  <a target="_blank" href="https://codecov.io/github/LesliTech/LesliAudit">
17
18
  <img alt="Codecov" src="https://img.shields.io/codecov/c/github/LesliTech/LesliAudit?style=for-the-badge&logo=codecov">
18
19
  </a>
20
+ <a target="_blank" href="https://sonarcloud.io/project/overview?id=LesliTech_LesliAudit">
21
+ <img alt="Sonar Quality Gate" src="https://img.shields.io/sonar/quality_gate/LesliTech_LesliAudit?server=https%3A%2F%2Fsonarcloud.io&style=for-the-badge&logo=sonarqubecloud&label=Quality">
22
+ </a>
19
23
  </div>
20
24
 
21
- <hr/>
22
25
  <br />
23
26
 
24
27
  <div align="center">
25
28
  <img
26
29
  style="width:100%;max-width:800px;border-radius:6px;"
27
- alt="Lesli screenshot" src="./docs/images/screenshot.png" />
30
+ alt="LesliAudit activity and request history"
31
+ src="./docs/images/screenshot.png" />
28
32
  </div>
29
33
 
34
+ ---
35
+
36
+ <br />
37
+
38
+ ## Introduction
39
+
40
+ LesliAudit is the official audit trail and analytics engine for the [Lesli Framework](https://github.com/LesliTech/Lesli).
41
+
42
+ It records account activity and provides administrators with searchable operational history and usage insights.
43
+
30
44
  <br />
31
- <hr/>
45
+
46
+ ## Features
47
+
48
+ - Account activity and audit logs
49
+ - Request and navigation history
50
+ - Visitor and authenticated-user analytics
51
+ - Device and user-agent information
52
+ - Detailed views for investigating recorded activity
53
+
54
+ <br />
55
+
56
+ ## Try LesliAudit
57
+
58
+ - [Try the online demo](https://demo.lesli.dev/)
59
+ - [Run the Docker demo](https://github.com/LesliTech/lesli-docker-demo)
60
+
32
61
  <br />
33
62
 
34
- ### Installation
63
+ ## Quick Start
64
+
65
+ ### Requirements
66
+
67
+ - A Rails application with [Lesli](https://rubygems.org/gems/lesli)
68
+ - SQLite by default, or PostgreSQL when preferred by the host application
69
+
70
+ ### Install LesliAudit
71
+
72
+ Add the engine to the host Rails application and prepare its database:
35
73
 
36
74
  ```shell
37
- # Add LesliAudit engine gem
38
75
  bundle add lesli_audit
76
+ bin/rails lesli:db:prepare
77
+ ```
78
+
79
+ ### Mount the engine
39
80
 
40
- # Setup & initialize the database
41
- rake lesli:db:setup
81
+ Applications using Lesli's standard router mount LesliAudit automatically at `/audit`:
82
+
83
+ ```ruby
84
+ # config/routes.rb
85
+ Rails.application.routes.draw do
86
+ Lesli::Router.mount(self)
87
+ end
42
88
  ```
43
89
 
90
+ If the application does not use the standard Lesli router, mount the engine directly:
91
+
44
92
  ```ruby
45
- # Load LesliAudit engine
93
+ # config/routes.rb
46
94
  Rails.application.routes.draw do
47
95
  mount LesliAudit::Engine => "/audit"
48
96
  end
49
97
  ```
50
98
 
51
- <br />
52
- <hr/>
99
+ Start Rails and visit [http://127.0.0.1:3000/audit](http://127.0.0.1:3000/audit):
100
+
101
+ ```shell
102
+ bin/rails server
103
+ ```
104
+
53
105
  <br />
54
106
 
55
- ### Development
107
+ ## Development
108
+
109
+ Clone LesliAudit into the host application's `engines` directory:
56
110
 
57
111
  ```shell
58
- # clone the lesli repo inside your engine folder: RailsApp/engines
59
- git clone https://github.com/LesliTech/LesliAudit.git
112
+ cd RailsApp
113
+ mkdir -p engines
114
+ git clone https://github.com/LesliTech/LesliAudit.git engines/LesliAudit
115
+ ```
60
116
 
61
- # Load LesliAudit as a Gem
62
- gem "lesli", path: "engines/LesliAudit"
117
+ Reference the local engine from the host application's `Gemfile`:
63
118
 
64
- # Install the necessary Gems to run LesliAudit
119
+ ```ruby
120
+ gem "lesli_audit", path: "engines/LesliAudit"
121
+ ```
122
+
123
+ Install dependencies, prepare the host database, and start Rails:
124
+
125
+ ```shell
65
126
  bundle install
127
+ bin/rails lesli:db:prepare
128
+ bin/rails server
129
+ ```
130
+
131
+ ### Tests
66
132
 
67
- # Setup & initialize the database
68
- rake lesli:db:setup
133
+ From a complete Lesli development workspace, run the engine test suite from the LesliAudit directory:
69
134
 
135
+ ```shell
136
+ cd engines/LesliAudit
137
+ bin/rails test
70
138
  ```
71
139
 
72
140
  <br />
73
- <hr/>
74
- <br />
75
-
76
- ### Demo
77
141
 
78
- * [online demo](https://demo.lesli.dev/)
79
- * [docker demo](https://github.com/LesliTech/lesli-docker-demo)
142
+ ## Documentation
80
143
 
144
+ - [Lesli website](https://www.lesli.dev/)
145
+ - [Documentation](https://www.lesli.dev/engines/audit)
146
+ - [Frontend development](./docs/about/frontend.md)
147
+ - [Release notes](https://github.com/LesliTech/LesliAudit/releases)
148
+ - [Issue tracker](https://github.com/LesliTech/LesliAudit/issues)
149
+ - [Source code](https://github.com/LesliTech/LesliAudit)
81
150
 
82
- ### Documentation
83
- * [website](https://www.lesli.dev/)
84
- * [documentation](https://www.lesli.dev/engines/audit)
151
+ <br />
85
152
 
153
+ ## Community
86
154
 
87
- ### Connect with Lesli
155
+ - [X: @LesliTech](https://x.com/LesliTech)
156
+ - [hello@lesli.tech](mailto:hello@lesli.tech)
157
+ - [https://www.lesli.tech](https://www.lesli.tech)
88
158
 
89
- * [X: @LesliTech](https://x.com/LesliTech)
90
- * [Email: hello@lesli.tech](hello@lesli.tech)
91
- * [Website: https://www.lesli.tech](https://www.lesli.tech)
159
+ <br />
92
160
 
161
+ ## License
93
162
 
94
- ### License
95
- -------
96
163
  Copyright (c) 2026, Lesli Technologies, S. A.
97
164
 
98
165
  This program is free software: you can redistribute it and/or modify
@@ -106,19 +173,16 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
106
173
  GNU General Public License for more details.
107
174
 
108
175
  You should have received a copy of the GNU General Public License
109
- along with this program. If not, see http://www.gnu.org/licenses/.
176
+ along with this program. If not, see [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
110
177
 
111
- <br />
112
- <hr />
113
- <br />
114
- <br />
178
+ The complete license text is available in the [license file](./license).
115
179
 
116
- <div align="center" class="has-text-centered">
117
- <img width="200" alt="Lesli logo" src="https://cdn.lesli.tech/lesli/brand/app-logo.svg" />
118
- <h3 align="center" class="mt-0">
119
- The Open-Source SaaS Development Framework for Ruby on Rails.
120
- </h3>
121
- </div>
180
+ ---
122
181
 
123
182
  <br />
124
183
  <br />
184
+
185
+ <div align="center">
186
+ <img width="80" alt="Lesli icon" src="https://cdn.lesli.tech/lesli/brand/app-icon.svg" />
187
+ <h3 align="center">The Open-Source SaaS Development Framework for Ruby on Rails.</h3>
188
+ </div>