lesli_admin 1.0.2 → 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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +6 -1
  3. data/app/assets/stylesheets/lesli_admin/application.css +648 -584
  4. data/app/controllers/lesli_admin/account/currencies_controller.rb +2 -2
  5. data/app/controllers/lesli_admin/account/details_controller.rb +13 -6
  6. data/app/controllers/lesli_admin/account/locations_controller.rb +10 -6
  7. data/app/controllers/lesli_admin/account/settings_controller.rb +2 -2
  8. data/app/controllers/lesli_admin/accounts_controller.rb +41 -27
  9. data/app/controllers/lesli_admin/dashboards_controller.rb +21 -0
  10. data/app/models/lesli_admin/account/currency.rb +4 -2
  11. data/app/models/lesli_admin/account/detail.rb +3 -0
  12. data/app/models/lesli_admin/account/location.rb +12 -2
  13. data/app/models/lesli_admin/account/setting.rb +4 -2
  14. data/app/models/lesli_admin/account.rb +18 -1
  15. data/app/models/lesli_admin/dashboard.rb +8 -1
  16. data/app/services/lesli_admin/account_service.rb +43 -16
  17. data/app/views/lesli_admin/accounts/show.html.erb +104 -7
  18. data/app/views/lesli_admin/dashboards/_component-account-metadata.html.erb +44 -0
  19. data/app/views/lesli_admin/dashboards/_component-installed-engines.html.erb +1 -8
  20. data/app/views/lesli_admin/dashboards/_component-roles.html.erb +1 -0
  21. data/app/views/lesli_admin/dashboards/_component-users.html.erb +1 -0
  22. data/app/views/lesli_admin/settings/_configuration_value.html.erb +52 -0
  23. data/app/views/lesli_admin/settings/_defaults.html.erb +153 -56
  24. data/app/views/lesli_admin/settings/_modules.html.erb +28 -32
  25. data/db/migrate/v1/0101000210_create_lesli_admin_account_details.rb +7 -1
  26. data/db/migrate/v1/0101000410_create_lesli_admin_account_locations.rb +6 -1
  27. data/db/seeds.rb +1 -1
  28. data/lib/lesli_admin/engine.rb +0 -4
  29. data/lib/lesli_admin/version.rb +2 -2
  30. data/readme.md +123 -49
  31. metadata +12 -7
  32. data/app/assets/config/lesli_admin_manifest.js +0 -38
  33. data/lib/scss/application.scss +0 -4
@@ -0,0 +1,52 @@
1
+ <%
2
+ sensitive_key = setting_key.to_s.match?(/password|secret|token|api[_-]?key|private[_-]?key|credential|encryption|salt/i)
3
+ %>
4
+
5
+ <% if sensitive_key %>
6
+ <span class="inline-flex items-center gap-1.5 rounded-full bg-slate-200 px-2.5 py-1 text-xs font-semibold text-slate-600">
7
+ <span class="material-symbols-outlined text-sm" aria-hidden="true">lock</span>
8
+ Filtered
9
+ </span>
10
+ <% elsif value.is_a?(Hash) %>
11
+ <dl class="space-y-2">
12
+ <% value.each do |nested_key, nested_value| %>
13
+ <div class="rounded-lg border border-slate-200 bg-white p-3">
14
+ <dt class="text-xs font-medium text-slate-500"><%= nested_key.to_s.humanize %></dt>
15
+ <dd class="mt-1.5">
16
+ <%= render("configuration_value", setting_key: nested_key, value: nested_value) %>
17
+ </dd>
18
+ </div>
19
+ <% end %>
20
+ </dl>
21
+ <% elsif value.is_a?(Array) %>
22
+ <% if value.empty? %>
23
+ <span class="text-slate-400">Empty list</span>
24
+ <% else %>
25
+ <ul class="flex flex-wrap gap-2">
26
+ <% value.each do |item| %>
27
+ <li class="rounded-lg border border-slate-200 bg-white px-2.5 py-1 text-xs text-slate-700">
28
+ <%= render("configuration_value", setting_key: :item, value: item) %>
29
+ </li>
30
+ <% end %>
31
+ </ul>
32
+ <% end %>
33
+ <% elsif value == true %>
34
+ <span class="inline-flex items-center gap-1.5 rounded-full bg-emerald-100 px-2.5 py-1 text-xs font-semibold text-emerald-700">
35
+ <span class="h-1.5 w-1.5 rounded-full bg-emerald-500" aria-hidden="true"></span>
36
+ Enabled
37
+ </span>
38
+ <% elsif value == false %>
39
+ <span class="inline-flex items-center gap-1.5 rounded-full bg-red-100 px-2.5 py-1 text-xs font-semibold text-red-700">
40
+ <span class="h-1.5 w-1.5 rounded-full bg-red-500" aria-hidden="true"></span>
41
+ Disabled
42
+ </span>
43
+ <% elsif value.nil? %>
44
+ <span class="italic text-slate-400">Not configured</span>
45
+ <% elsif value.to_s.match?(/\A#[0-9a-f]{3,8}\z/i) %>
46
+ <span class="inline-flex items-center gap-2">
47
+ <span class="h-5 w-5 rounded-md border border-slate-300 shadow-sm" style="background-color: <%= value %>" aria-hidden="true"></span>
48
+ <code class="break-all rounded-md bg-slate-200 px-2 py-1 text-xs text-slate-700"><%= value %></code>
49
+ </span>
50
+ <% else %>
51
+ <code class="break-all rounded-md bg-slate-200 px-2 py-1 text-xs leading-5 text-slate-700"><%= value %></code>
52
+ <% end %>
@@ -1,61 +1,158 @@
1
- <%
2
- sections = {
3
- "Instance Info" => {
4
- demo: Lesli.config.demo,
5
- instance: Lesli.config.instance
6
- },
7
- "Company Info" => Lesli.config.company,
8
- "Available Locales" => Lesli.config.locales,
9
- "Datetime Settings" => Lesli.config.datetime,
10
- "Security Settings" => Lesli.config.security,
11
- "Theme" => Lesli.config.theme,
12
- "Layout" => Lesli.config.layout,
13
- "Mailer" => Lesli.config.mailer,
14
- "Paths" => {
15
- path_after_login: Lesli.config.path_after_login
1
+ <%
2
+ sections = [
3
+ {
4
+ title: "Instance",
5
+ icon: "dns",
6
+ description: "Runtime identity and operating mode for this installation.",
7
+ config: {
8
+ demo: Lesli.config.demo,
9
+ instance: Lesli.config.instance
10
+ }
11
+ },
12
+ {
13
+ title: "Company",
14
+ icon: "apartment",
15
+ description: "Organization details displayed throughout the platform.",
16
+ config: Lesli.config.company
17
+ },
18
+ {
19
+ title: "Locales",
20
+ icon: "translate",
21
+ description: "Languages currently available to users.",
22
+ config: Lesli.config.babel&.fetch(:locales, {})
23
+ },
24
+ {
25
+ title: "Date and time",
26
+ icon: "schedule",
27
+ description: "Time zone, week boundaries, and display formats.",
28
+ config: Lesli.config.datetime
29
+ },
30
+ {
31
+ title: "Security",
32
+ icon: "shield_lock",
33
+ description: "Authentication and account-access behavior.",
34
+ config: Lesli.config.security
35
+ },
36
+ {
37
+ title: "Theme",
38
+ icon: "palette",
39
+ description: "Colors used by the shared Lesli interface.",
40
+ config: Lesli.config.theme
41
+ },
42
+ {
43
+ title: "Layout",
44
+ icon: "dashboard_customize",
45
+ description: "Global interface features and visibility controls.",
46
+ config: Lesli.config.layout
47
+ },
48
+ {
49
+ title: "Mailer",
50
+ icon: "mail",
51
+ description: "Email delivery and template configuration.",
52
+ config: Lesli.config.mailer
53
+ },
54
+ {
55
+ title: "Paths",
56
+ icon: "route",
57
+ description: "Application destinations used by navigation and authentication.",
58
+ config: {
59
+ path_after_login: Lesli.config.shield&.fetch(:path_after_login, nil)
60
+ }
16
61
  }
17
- }
62
+ ].map { |section| section.merge(config: section[:config] || {}) }
63
+
64
+ leaf_count = lambda do |value|
65
+ case value
66
+ when Hash
67
+ value.values.sum { |nested_value| leaf_count.call(nested_value) }
68
+ when Array
69
+ value.sum { |nested_value| leaf_count.call(nested_value) }
70
+ else
71
+ 1
72
+ end
73
+ end
74
+
75
+ total_settings = sections.sum { |section| leaf_count.call(section[:config]) }
18
76
  %>
19
- <section class="section pt-0">
20
- <h4 class="title is-size-4 mb-4">Lesli Configuration</h4>
21
-
22
- <% sections.each do |title, config| %>
23
- <div class="box">
24
- <h5 class="subtitle is-size-6 has-text-weight-semibold mb-4"><%= title %></h5>
25
- <div class="columns is-multiline is-mobile">
26
- <%
27
- grid = title.underscore
28
- grid = "is-one-quarter" if title.underscore == "instance info"
29
- grid = "is-half" if title.underscore == "datetime settings"
30
- grid = "is-one-quarter" if title.underscore == "security settings"
31
- grid = "is-one-third" if title.underscore == "theme"
32
-
33
- %>
34
- <% config.each do |key, value| %>
35
- <div class="column <%= grid %>">
36
- <p class="has-text-weight-semibold"><%= key.to_s.humanize %></p>
37
- <div>
38
- <% if value.is_a?(Hash) %>
39
- <ul class="pl-4">
40
- <% value.each do |key, value| %>
41
- <li>
42
- <strong><%= key.to_s.humanize %>:</strong>
43
- <code><%= value %></code>
44
- </li>
45
- <% end %>
46
- </ul>
47
-
48
- <% elsif value == true %>
49
- <span class="tag is-success has-text-white">Enabled</span>
50
- <% elsif value == false %>
51
- <span class="tag is-danger has-text-white">Disabled</span>
52
- <% else %>
53
- <code class="is-size-7"><%= value.to_s %></code>
54
- <% end %>
55
- </div>
56
- </div>
57
- <% end %>
77
+
78
+ <section aria-labelledby="lesli-configuration-title">
79
+
80
+ <div class="rounded-2xl border border-slate-200 bg-white p-5 shadow-sm sm:p-6">
81
+ <div class="flex items-center gap-3">
82
+ <span class="material-symbols-outlined rounded-xl bg-sky-50 p-2.5 text-2xl text-sky-700" aria-hidden="true">settings</span>
83
+ <div>
84
+ <h2 id="lesli-configuration-title" class="text-xl font-semibold tracking-tight text-slate-900">
85
+ Lesli configuration
86
+ </h2>
87
+ <p class="mt-1 text-sm leading-6 text-slate-500">
88
+ Effective read-only values loaded by this application instance.
89
+ </p>
58
90
  </div>
59
91
  </div>
60
- <% end %>
92
+
93
+ <dl class="mt-6 grid grid-cols-2 gap-3 lg:grid-cols-4">
94
+ <div class="rounded-xl bg-slate-50 px-4 py-3">
95
+ <dt class="text-xs font-medium uppercase tracking-wide text-slate-500">Environment</dt>
96
+ <dd class="mt-1 text-sm font-semibold text-slate-900"><%= Rails.env.to_s.humanize %></dd>
97
+ </div>
98
+ <div class="rounded-xl bg-slate-50 px-4 py-3">
99
+ <dt class="text-xs font-medium uppercase tracking-wide text-slate-500">Sections</dt>
100
+ <dd class="mt-1 text-sm font-semibold text-slate-900"><%= sections.size %></dd>
101
+ </div>
102
+ <div class="rounded-xl bg-slate-50 px-4 py-3">
103
+ <dt class="text-xs font-medium uppercase tracking-wide text-slate-500">Values</dt>
104
+ <dd class="mt-1 text-sm font-semibold text-slate-900"><%= total_settings %></dd>
105
+ </div>
106
+ <div class="rounded-xl bg-slate-50 px-4 py-3">
107
+ <dt class="text-xs font-medium uppercase tracking-wide text-slate-500">Demo mode</dt>
108
+ <dd class="mt-1">
109
+ <span class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-semibold <%= Lesli.config.demo ? "bg-amber-100 text-amber-700" : "bg-emerald-100 text-emerald-700" %>">
110
+ <%= Lesli.config.demo ? "Enabled" : "Disabled" %>
111
+ </span>
112
+ </dd>
113
+ </div>
114
+ </dl>
115
+ </div>
116
+
117
+ <div class="mt-5 space-y-4">
118
+ <% sections.each_with_index do |section, index| %>
119
+ <details
120
+ class="group rounded-2xl border border-slate-200 bg-white shadow-sm"
121
+ <%= "open" if index < 2 %>>
122
+ <summary class="flex cursor-pointer list-none items-center gap-4 px-5 py-4 marker:hidden sm:px-6">
123
+ <span class="material-symbols-outlined rounded-xl bg-slate-100 p-2.5 text-2xl text-slate-600" aria-hidden="true"><%= section[:icon] %></span>
124
+ <span class="min-w-0 flex-1">
125
+ <span class="block text-base font-semibold text-slate-900"><%= section[:title] %></span>
126
+ <span class="mt-0.5 block text-sm leading-5 text-slate-500"><%= section[:description] %></span>
127
+ </span>
128
+ <span class="hidden rounded-full bg-slate-100 px-2.5 py-1 text-xs font-semibold text-slate-600 sm:inline-flex">
129
+ <%= pluralize(section[:config].size, "group") %>
130
+ </span>
131
+ <span class="material-symbols-outlined text-slate-400 transition-transform group-open:rotate-180" aria-hidden="true">expand_more</span>
132
+ </summary>
133
+
134
+ <div class="border-t border-slate-100 px-5 py-5 sm:px-6">
135
+ <% if section[:config].empty? %>
136
+ <p class="text-sm text-slate-500">No values are configured for this section.</p>
137
+ <% else %>
138
+ <dl class="grid grid-cols-1 gap-4 lg:grid-cols-2">
139
+ <% section[:config].each do |key, value| %>
140
+ <div class="min-w-0 rounded-xl border border-slate-100 bg-slate-50/60 p-4">
141
+ <dt class="text-xs font-semibold uppercase tracking-wide text-slate-500"><%= key.to_s.humanize %></dt>
142
+ <dd class="mt-2 text-sm text-slate-900">
143
+ <%= render("configuration_value", setting_key: key, value: value) %>
144
+ </dd>
145
+ </div>
146
+ <% end %>
147
+ </dl>
148
+ <% end %>
149
+ </div>
150
+ </details>
151
+ <% end %>
152
+ </div>
153
+
154
+ <p class="mt-5 flex items-start gap-2 text-xs leading-5 text-slate-500">
155
+ <span class="material-symbols-outlined text-base" aria-hidden="true">lock</span>
156
+ Sensitive values are filtered on the server and are never included in this page.
157
+ </p>
61
158
  </section>
@@ -20,42 +20,38 @@
20
20
  }
21
21
  }
22
22
  </script>
23
- <style>
24
- .fixed-grid .partial-engine-spec ul {
25
- display: flex;
26
- }
27
- .fixed-grid .partial-engine-spec ul li {
28
- margin: 0 1rem 0 0;
29
- font-size: 13px;
30
- }
31
- .fixed-grid .partial-engine-spec ul li:last-child {
32
- display: none;
33
- }
34
- .fixed-grid .partial-engine-spec .material-symbols {
35
- font-size: 0px;
36
- }
37
- </style>
23
+
38
24
  <%
39
- @lesli_engines = LesliSystem.engines.map do |engine, engine_info|
40
- engine_info[:summary] = engine_info[:summary]&.sub("for the Lesli Framework","")&.sub("for The Lesli Framework","")
41
- engine_info
42
- end
25
+ lesli_engines = LesliSystem.engines.values.map do |engine_info|
26
+ summary = engine_info[:summary]
27
+ &.sub("for the Lesli Framework", "")
28
+ &.sub("for The Lesli Framework", "")
29
+
30
+ engine_info.merge(summary: summary)
31
+ end
43
32
  %>
44
33
 
45
- <div class="fixed-grid">
46
- <div class="grid" x-data="engineManager()">
47
- <% @lesli_engines.each do |engine| %>
34
+ <section
35
+ class="grid grid-cols-1 gap-5 xl:grid-cols-2"
36
+ aria-label="Modules"
37
+ x-data="engineManager()">
38
+ <% lesli_engines.each do |engine| %>
48
39
  <% next if engine[:name] == "Root" %>
49
- <div class="cell">
50
- <div class="box">
40
+
41
+ <% engine_key = engine[:path].delete("/") %>
42
+
43
+ <article class="module-engine-card flex min-w-0 flex-col rounded-2xl border border-slate-200 bg-white p-5 shadow-sm sm:p-6">
44
+ <div class="min-w-0 flex-1">
51
45
  <%= render(LesliView::Partials::EngineSpec.new(engine)) %>
52
- <button
53
- @click="toggleEngine('<%= engine[:path].gsub("/","") %>')"
54
- :class="data.engines.includes('<%= engine[:path].gsub("/","") %>') ? 'button is-small is-info' : 'button is-small is-danger has-text-white'"
55
- x-text="data.engines.includes('<%= engine[:path].gsub("/","") %>') ? 'Activar módulo' : 'Desactivar módulo'">
56
- </button>
57
46
  </div>
58
- </div>
47
+
48
+ <button
49
+ type="button"
50
+ class="mt-5 inline-flex w-fit items-center justify-center rounded-lg px-3 py-2 text-sm font-semibold text-white shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2"
51
+ @click="toggleEngine(<%= engine_key.to_json %>)"
52
+ :class="data.engines.includes(<%= engine_key.to_json %>) ? 'bg-emerald-600 hover:bg-emerald-700 focus-visible:ring-emerald-500/30' : 'bg-red-600 hover:bg-red-700 focus-visible:ring-red-500/30'"
53
+ x-text="data.engines.includes(<%= engine_key.to_json %>) ? 'Activar módulo' : 'Desactivar módulo'">
54
+ </button>
55
+ </article>
59
56
  <% end %>
60
- </div>
61
- </div>
57
+ </section>
@@ -65,6 +65,12 @@ class CreateLesliAdminAccountDetails < ActiveRecord::Migration[6.0]
65
65
  t.datetime :deleted_at, index: true
66
66
  t.timestamps
67
67
  end
68
- add_reference(:lesli_admin_account_details, :account, foreign_key: { to_table: :lesli_accounts })
68
+ add_reference(
69
+ :lesli_admin_account_details,
70
+ :account,
71
+ null: false,
72
+ index: { unique: true },
73
+ foreign_key: { to_table: :lesli_admin_accounts }
74
+ )
69
75
  end
70
76
  end
@@ -57,7 +57,12 @@ class CreateLesliAdminAccountLocations < ActiveRecord::Migration[6.0]
57
57
  end
58
58
 
59
59
  add_reference(:lesli_admin_account_locations, :parent, foreign_key: { to_table: :lesli_admin_account_locations })
60
- add_reference(:lesli_admin_account_locations, :account, foreign_key: { to_table: :lesli_accounts })
60
+ add_reference(
61
+ :lesli_admin_account_locations,
62
+ :account,
63
+ null: false,
64
+ foreign_key: { to_table: :lesli_admin_accounts }
65
+ )
61
66
  add_index(:lesli_admin_account_locations, %i[account_id name level parent_id], unique: true, name: "location_uniqueness_index")
62
67
  end
63
68
  end
data/db/seeds.rb CHANGED
@@ -31,7 +31,7 @@ Building a better future, one line of code at a time.
31
31
  =end
32
32
 
33
33
 
34
- L2.msg("LesliAdmin", "Version: #{LesliAdmin::VERSION}", "Build: #{LesliAdmin::BUILD}")
34
+ Termline.info("Loading seeds for: LesliAdmin #{LesliAdmin::VERSION} (#{LesliAdmin::BUILD})")
35
35
 
36
36
  if Rails.env.development? || Lesli.config.demo
37
37
  #load(LesliAdmin::Engine.root.join("db", "seed", "#{ Rails.env.downcase }.rb"))
@@ -35,10 +35,6 @@ module LesliAdmin
35
35
  isolate_namespace LesliAdmin
36
36
 
37
37
  initializer :lesli_admin do |app|
38
-
39
- # register assets manifest
40
- config.assets.precompile += %w[lesli_admin_manifest.js]
41
-
42
38
  # register engine migrations path
43
39
  unless app.root.to_s.match root.to_s
44
40
  config.paths["db/migrate"].expanded.each do |expanded_path|
@@ -1,4 +1,4 @@
1
1
  module LesliAdmin
2
- VERSION = "1.0.2"
3
- BUILD = "1771710537"
2
+ VERSION = "1.1.2"
3
+ BUILD = "1791607001"
4
4
  end
data/readme.md CHANGED
@@ -1,14 +1,15 @@
1
1
  <div align="center">
2
- <img width="100" alt="LesliAdmin logo" src="./app/assets/images/lesli_admin/admin-logo.svg" />
3
- <h3 align="center">Administration area for the Lesli Framework.</h3>
2
+ <h1 align="center">
3
+ <img width="100" alt="LesliAdmin" src="./app/assets/images/lesli_admin/admin-logo.svg" />
4
+ </h1>
5
+ <h3 align="center">Account and application administration 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/LesliAdmin/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/LesliAdmin/actions/workflows/lesli-ci-engine.yaml">
12
+ <img alt="LesliAdmin test status" src="https://img.shields.io/github/actions/workflow/status/LesliTech/LesliAdmin/lesli-ci-engine.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_admin">
14
15
  <img alt="Gem Version" src="https://img.shields.io/gem/v/lesli_admin?style=for-the-badge&logo=ruby">
@@ -16,83 +17,159 @@
16
17
  <a target="_blank" href="https://codecov.io/github/LesliTech/LesliAdmin">
17
18
  <img alt="Codecov" src="https://img.shields.io/codecov/c/github/LesliTech/LesliAdmin?style=for-the-badge&logo=codecov">
18
19
  </a>
20
+ <a target="_blank" href="https://sonarcloud.io/project/overview?id=LesliTech_LesliAdmin">
21
+ <img alt="Sonar Quality Gate" src="https://img.shields.io/sonar/quality_gate/LesliTech_LesliAdmin?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="LesliAdmin account administration"
31
+ src="./docs/images/screenshot.png" />
28
32
  </div>
29
33
 
34
+ ---
35
+
36
+ <br />
37
+
38
+ ## Introduction
39
+
40
+ LesliAdmin is the official account and application administration engine for the [Lesli Framework](https://github.com/LesliTech/Lesli).
41
+
42
+ It gives account owners and administrators a central interface for maintaining the core account identity, inspecting application configuration, and understanding the engines installed in a Lesli application.
43
+
30
44
  <br />
31
- <hr/>
45
+
46
+ ## Features
47
+
48
+ - Core account identity, status, and data-region management
49
+ - Company profile, contact, address, and social-link management
50
+ - Read-only framework configuration viewer with server-side secret filtering
51
+ - Account dashboard with user, role, installed-engine, and lifecycle metrics
52
+ - Installed-engine inventory and metadata
53
+ - Account-scoped data structures for details, locations, settings, and currencies
54
+ - Optional dashboard components through LesliDashboard
55
+
56
+ <br />
57
+
58
+ ## Try LesliAdmin
59
+
60
+ - [Try the online demo](https://demo.lesli.dev/)
61
+ - [Run the Docker demo](https://github.com/LesliTech/lesli-docker-demo)
62
+
32
63
  <br />
33
64
 
34
- ### Installation
65
+ ## Quick Start
66
+
67
+ ### Requirements
68
+
69
+ - A Rails application with [Lesli 5.1](https://rubygems.org/gems/lesli)
70
+ - SQLite by default, or PostgreSQL when preferred by the host application
71
+
72
+ ### Install LesliAdmin
73
+
74
+ Add the engine to the host Rails application and prepare its database:
35
75
 
36
76
  ```shell
37
- # Add LesliAdmin engine gem
38
77
  bundle add lesli_admin
78
+ bin/rails lesli:db:prepare
79
+ ```
80
+
81
+ ### Mount the engine
39
82
 
40
- # Setup & initialize the database
41
- rake lesli:db:setup
83
+ Applications using Lesli's standard router mount LesliAdmin automatically at `/admin`:
84
+
85
+ ```ruby
86
+ # config/routes.rb
87
+ Rails.application.routes.draw do
88
+ Lesli::Router.mount(self)
89
+ end
42
90
  ```
43
91
 
92
+ If the application does not use the standard Lesli router, mount the engine directly:
93
+
44
94
  ```ruby
45
- # Load LesliAdmin engine
95
+ # config/routes.rb
46
96
  Rails.application.routes.draw do
47
97
  mount LesliAdmin::Engine => "/admin"
48
98
  end
49
99
  ```
50
100
 
101
+ Start Rails and visit [http://127.0.0.1:3000/admin](http://127.0.0.1:3000/admin):
102
+
103
+ ```shell
104
+ bin/rails routes -g admin
105
+ bin/rails server
106
+ ```
107
+
108
+ Install [LesliDashboard](https://rubygems.org/gems/lesli_dashboard) when the application should persist and render LesliAdmin's account metrics and metadata components.
109
+
51
110
  <br />
52
- <hr/>
53
- <br />
54
111
 
55
- ### Development
112
+ ## Development
113
+
114
+ Clone LesliAdmin into the host application's `engines` directory:
56
115
 
57
116
  ```shell
58
- # clone the lesli repo inside your engine folder: RailsApp/engines
59
- git clone https://github.com/LesliTech/LesliAdmin.git
117
+ cd RailsApp
118
+ mkdir -p engines
119
+ git clone https://github.com/LesliTech/LesliAdmin.git engines/LesliAdmin
120
+ ```
60
121
 
61
- # Load LesliAdmin as a Gem
62
- gem "lesli", path: "engines/LesliAdmin"
122
+ Reference the local engine from the host application's `Gemfile`:
63
123
 
64
- # Install the necessary Gems to run LesliAdmin
124
+ ```ruby
125
+ gem "lesli_admin", path: "engines/LesliAdmin"
126
+ ```
127
+
128
+ Install dependencies, prepare the host database, and start Rails:
129
+
130
+ ```shell
65
131
  bundle install
132
+ bin/rails lesli:db:prepare
133
+ bin/rails server
134
+ ```
66
135
 
67
- # Setup & initialize the database
68
- rake lesli:db:setup
136
+ ### Tests
69
137
 
138
+ From a complete Lesli development workspace, run the engine test suite from the LesliAdmin directory:
139
+
140
+ ```shell
141
+ cd engines/LesliAdmin
142
+ bin/rails test
70
143
  ```
71
144
 
72
145
  <br />
73
- <hr/>
74
- <br />
75
-
76
- ### Demo
77
146
 
78
- * [online demo](https://demo.lesli.dev/)
79
- * [docker demo](https://github.com/LesliTech/lesli-docker-demo)
147
+ ## Documentation
80
148
 
149
+ - [Lesli website](https://www.lesli.dev/)
150
+ - [Documentation](https://www.lesli.dev/engines/admin)
151
+ - [Installation guide](https://www.lesli.dev/engines/admin/about/installation)
152
+ - [Account management](https://www.lesli.dev/engines/admin/account/)
153
+ - [Dashboard components](https://www.lesli.dev/engines/admin/about/dashboards)
154
+ - [Settings and configuration](https://www.lesli.dev/engines/admin/about/settings)
155
+ - [Translations](https://www.lesli.dev/engines/admin/about/translations)
156
+ - [Database](https://www.lesli.dev/engines/admin/about/database)
157
+ - [Release notes](https://github.com/LesliTech/LesliAdmin/releases)
158
+ - [Issue tracker](https://github.com/LesliTech/LesliAdmin/issues)
159
+ - [Source code](https://github.com/LesliTech/LesliAdmin)
81
160
 
82
- ### Documentation
83
- * [website](https://www.lesli.dev/)
84
- * [documentation](https://www.lesli.dev/engines/admin)
161
+ <br />
85
162
 
163
+ ## Community
86
164
 
87
- ### Connect with Lesli
165
+ - [X: @LesliTech](https://x.com/LesliTech)
166
+ - [hello@lesli.tech](mailto:hello@lesli.tech)
167
+ - [https://www.lesli.tech](https://www.lesli.tech)
88
168
 
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)
169
+ <br />
92
170
 
171
+ ## License
93
172
 
94
- ### License
95
- -------
96
173
  Copyright (c) 2026, Lesli Technologies, S. A.
97
174
 
98
175
  This program is free software: you can redistribute it and/or modify
@@ -106,19 +183,16 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
106
183
  GNU General Public License for more details.
107
184
 
108
185
  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/.
186
+ along with this program. If not, see [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/).
110
187
 
111
- <br />
112
- <hr />
113
- <br />
114
- <br />
188
+ The complete license text is available in the [license file](./license).
115
189
 
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>
190
+ ---
122
191
 
123
192
  <br />
124
193
  <br />
194
+
195
+ <div align="center">
196
+ <img width="80" alt="Lesli icon" src="https://cdn.lesli.tech/lesli/brand/app-icon.svg" />
197
+ <h3 align="center">The Open-Source SaaS Development Framework for Ruby on Rails.</h3>
198
+ </div>