lato_users 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +67 -0
- data/Rakefile +8 -0
- data/app/assets/config/lato_users_manifest.js +2 -0
- data/app/assets/javascripts/lato_users/application.js +1 -0
- data/app/assets/javascripts/lato_users/controllers/lato_users_form_controller.js +27 -0
- data/app/assets/stylesheets/lato_users/application.scss +15 -0
- data/app/controllers/lato_users/application_controller.rb +20 -0
- data/app/controllers/lato_users/invitations_controller.rb +39 -0
- data/app/controllers/lato_users/users_controller.rb +107 -0
- data/app/helpers/lato_users/application_helper.rb +4 -0
- data/app/helpers/lato_users/invitations_helper.rb +25 -0
- data/app/helpers/lato_users/users_helper.rb +15 -0
- data/app/jobs/lato_users/application_job.rb +4 -0
- data/app/mailers/lato_users/application_mailer.rb +6 -0
- data/app/views/lato_users/invitations/_form.html.erb +23 -0
- data/app/views/lato_users/invitations/index.html.erb +20 -0
- data/app/views/lato_users/invitations/new.html.erb +14 -0
- data/app/views/lato_users/users/_card_account_informations.html.erb +29 -0
- data/app/views/lato_users/users/_card_email_verification.html.erb +37 -0
- data/app/views/lato_users/users/_card_privacy_and_terms.html.erb +70 -0
- data/app/views/lato_users/users/_card_security.html.erb +162 -0
- data/app/views/lato_users/users/_form.html.erb +98 -0
- data/app/views/lato_users/users/edit.html.erb +13 -0
- data/app/views/lato_users/users/index.html.erb +24 -0
- data/app/views/lato_users/users/new.html.erb +13 -0
- data/app/views/lato_users/users/show.html.erb +12 -0
- data/config/importmap.rb +2 -0
- data/config/locales/en.yml +61 -0
- data/config/locales/it.yml +60 -0
- data/config/routes.rb +14 -0
- data/db/migrate/20250328072343_add_lato_users_admin_to_lato_user.rb +5 -0
- data/lib/lato_users/config.rb +10 -0
- data/lib/lato_users/engine.rb +13 -0
- data/lib/lato_users/version.rb +3 -0
- data/lib/lato_users.rb +15 -0
- data/lib/tasks/lato_users_tasks.rake +11 -0
- metadata +110 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b87e863ea43eda9714f7f1322e2765405ea8dce78ef8782aadff894dbb459d77
|
|
4
|
+
data.tar.gz: 2fe7688ff2e8672ccb38e78bb69d69cf73b97f9edb4b33783ea2ae38cb7d5a11
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 699e2b36913d0904525a54fb7e424b594a8924e8db9c462b1d6e11ea7ce3cca8fc09d85f4139825584a2838740fb42bd0731f6d1bcaa154be3f156e73e084211
|
|
7
|
+
data.tar.gz: 2dca85f420d6ad80e0f5528471c044cd9760ff12e8e11caef54842fd7a2f08eb8769c24669307c1cc0e63d4f93f76393c603fa98f9a309026ccf38fa2afe5d57
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright Gregorio Galante
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Lato Users
|
|
2
|
+
|
|
3
|
+
Manage application users on Lato projects.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
Add required dependencies to your application's Gemfile:
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
# Use lato as application panel
|
|
10
|
+
gem "lato"
|
|
11
|
+
gem "lato_users"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Install gem and run required tasks:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
$ bundle
|
|
18
|
+
$ rails lato_users:install:application
|
|
19
|
+
$ rails lato_users:install:migrations
|
|
20
|
+
$ rails db:migrate
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Mount lato users routes on the **config/routes.rb** file:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
Rails.application.routes.draw do
|
|
27
|
+
mount LatoUsers::Engine => "/lato-users"
|
|
28
|
+
# ....
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Import Lato Scss on **app/assets/stylesheets/application.scss** file:
|
|
33
|
+
```scss
|
|
34
|
+
@import 'lato_users/application';
|
|
35
|
+
|
|
36
|
+
// ....
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Import Lato Users Js on **app/javascript/application.js** file:
|
|
40
|
+
```js
|
|
41
|
+
import "lato_users/application";
|
|
42
|
+
|
|
43
|
+
// ....
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Development
|
|
47
|
+
|
|
48
|
+
Clone repository, install dependencies, run migrations and start:
|
|
49
|
+
|
|
50
|
+
```shell
|
|
51
|
+
$ git clone https://github.com/Lato-GAM/lato_users
|
|
52
|
+
$ cd lato_users
|
|
53
|
+
$ bundle
|
|
54
|
+
$ rails db:migrate
|
|
55
|
+
$ rails db:seed
|
|
56
|
+
$ foreman start -f Procfile.dev
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Publish
|
|
60
|
+
|
|
61
|
+
```shell
|
|
62
|
+
$ ruby ./bin/publish.rb
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
67
|
+
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
console.log("Lato Users JS loaded");
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = [
|
|
5
|
+
"password",
|
|
6
|
+
"passwordConfirmation",
|
|
7
|
+
"generatedPassword"
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
connect() {
|
|
11
|
+
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
generatePassword(event) {
|
|
15
|
+
event.preventDefault()
|
|
16
|
+
const length = 12
|
|
17
|
+
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
|
|
18
|
+
let retVal = ""
|
|
19
|
+
for (let i = 0, n = charset.length; i < length; ++i) {
|
|
20
|
+
retVal += charset.charAt(Math.floor(Math.random() * n))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.passwordTarget.value = retVal
|
|
24
|
+
this.passwordConfirmationTarget.value = retVal
|
|
25
|
+
this.generatedPasswordTarget.value = retVal
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module LatoUsers
|
|
2
|
+
class ApplicationController < Lato::ApplicationController
|
|
3
|
+
layout 'lato/application'
|
|
4
|
+
before_action :authenticate_session
|
|
5
|
+
before_action :authenticate_lato_users_admin
|
|
6
|
+
before_action { active_sidebar(:lato_users); active_navbar(:lato_users) }
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
redirect_to lato_users.users_path
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
protected
|
|
13
|
+
|
|
14
|
+
def authenticate_lato_users_admin
|
|
15
|
+
return true if @session.user&.lato_users_admin
|
|
16
|
+
|
|
17
|
+
redirect_to lato.root_path, alert: 'You have not access to this section.'
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module LatoUsers
|
|
2
|
+
class InvitationsController < ApplicationController
|
|
3
|
+
def index
|
|
4
|
+
@invitations = lato_index_collection(
|
|
5
|
+
Lato::Invitation.all,
|
|
6
|
+
columns: %i[email accepted_at created_at actions],
|
|
7
|
+
searchable_columns: %i[email],
|
|
8
|
+
sortable_columns: %i[email accepted_at created_at],
|
|
9
|
+
pagination: true,
|
|
10
|
+
default_sort_by: "created_at|desc"
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def new
|
|
15
|
+
@invitation = Lato::Invitation.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create
|
|
19
|
+
@invitation = Lato::Invitation.new(invitation_params.merge(inviter_lato_user_id: @session.user_id))
|
|
20
|
+
if @invitation.save
|
|
21
|
+
redirect_to invitations_path, notice: I18n.t('lato_users.invitation_created')
|
|
22
|
+
else
|
|
23
|
+
render 'new'
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def destroy
|
|
28
|
+
@invitation = Lato::Invitation.find(params[:id])
|
|
29
|
+
@invitation.destroy
|
|
30
|
+
redirect_to invitations_path, notice: I18n.t('lato_users.invitation_destroyed')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def invitation_params
|
|
36
|
+
params.require(:invitation).permit(:email)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
module LatoUsers
|
|
2
|
+
class UsersController < ApplicationController
|
|
3
|
+
before_action :load_available_admin_permissions
|
|
4
|
+
|
|
5
|
+
def index
|
|
6
|
+
@users = lato_index_collection(
|
|
7
|
+
Lato::User.all,
|
|
8
|
+
columns: %i[id email first_name last_name locale actions],
|
|
9
|
+
searchable_columns: %i[email first_name last_name],
|
|
10
|
+
sortable_columns: %i[id email first_name last_name],
|
|
11
|
+
pagination: true,
|
|
12
|
+
default_sort_by: "id|asc"
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def show
|
|
17
|
+
@user = Lato::User.find(params[:id])
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def new
|
|
21
|
+
@user = Lato::User.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create
|
|
25
|
+
@user = Lato::User.new(user_params.merge(
|
|
26
|
+
accepted_privacy_policy_version: Lato.config.legal_privacy_policy_version,
|
|
27
|
+
accepted_terms_and_conditions_version: Lato.config.legal_terms_and_conditions_version
|
|
28
|
+
))
|
|
29
|
+
if @user.save
|
|
30
|
+
redirect_to users_path
|
|
31
|
+
else
|
|
32
|
+
render 'new'
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def edit
|
|
37
|
+
@user = Lato::User.find(params[:id])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def update
|
|
41
|
+
@user = Lato::User.find(params[:id])
|
|
42
|
+
if @user.update(user_params)
|
|
43
|
+
redirect_to(params[:redirect_to] || users_path)
|
|
44
|
+
else
|
|
45
|
+
render 'edit'
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def destroy
|
|
50
|
+
if params[:id]&.to_i == @session.user_id
|
|
51
|
+
redirect_to users_path, alert: I18n.t('lato_users.cannot_delete_self')
|
|
52
|
+
return
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
@user = Lato::User.find(params[:id])
|
|
56
|
+
@user.destroy
|
|
57
|
+
redirect_to users_path
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def resend_verification_email
|
|
61
|
+
@user = Lato::User.find(params[:id])
|
|
62
|
+
if @user.request_verify_email
|
|
63
|
+
redirect_to user_path(@user), notice: I18n.t('lato_users.verification_email_sent')
|
|
64
|
+
else
|
|
65
|
+
redirect_to user_path(@user), alert: @user.errors.full_messages.join(', ')
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def manual_verify_email
|
|
70
|
+
@user = Lato::User.find(params[:id])
|
|
71
|
+
if @user.update(email_verified_at: Time.current)
|
|
72
|
+
redirect_to user_path(@user), notice: I18n.t('lato_users.email_manually_verified')
|
|
73
|
+
else
|
|
74
|
+
redirect_to user_path(@user), alert: @user.errors.full_messages.join(', ')
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def revoke_email_verification
|
|
79
|
+
@user = Lato::User.find(params[:id])
|
|
80
|
+
if @user.update(email_verified_at: nil)
|
|
81
|
+
redirect_to user_path(@user), notice: I18n.t('lato_users.email_verification_revoked')
|
|
82
|
+
else
|
|
83
|
+
redirect_to user_path(@user), alert: @user.errors.full_messages.join(', ')
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def force_update_password
|
|
88
|
+
@user = Lato::User.find(params[:id])
|
|
89
|
+
new_password = SecureRandom.hex(8)
|
|
90
|
+
if @user.update(password: new_password, password_confirmation: new_password)
|
|
91
|
+
redirect_to user_path(@user), notice: I18n.t('lato_users.password_updated', password: new_password)
|
|
92
|
+
else
|
|
93
|
+
redirect_to user_path(@user), alert: I18n.t('lato_users.password_update_failed')
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def user_params
|
|
100
|
+
params.require(:user).permit(:email, :first_name, :last_name, :password, :password_confirmation, @available_admin_permissions)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def load_available_admin_permissions
|
|
104
|
+
@available_admin_permissions ||= Lato::User.columns.map(&:name).select { |c| c.start_with?('lato_') && c.end_with?('_admin') }
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module LatoUsers
|
|
2
|
+
module InvitationsHelper
|
|
3
|
+
def lato_invitation_created_at(invitation)
|
|
4
|
+
I18n.l(invitation.created_at, format: :short)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def lato_invitation_accepted_at(invitation)
|
|
8
|
+
if invitation.accepted_at.present?
|
|
9
|
+
I18n.l(invitation.accepted_at, format: :short)
|
|
10
|
+
else
|
|
11
|
+
' - '
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def lato_invitation_actions(invitation)
|
|
16
|
+
content_tag(:div, class: 'btn-group btn-group-sm') do
|
|
17
|
+
if invitation.accepted_at.nil?
|
|
18
|
+
concat link_to(I18n.t('lato_users.cta_delete'), lato_users.invitation_path(invitation), class: 'btn btn-danger', data: { turbo_confirm: I18n.t('lato_users.cta_delete_confirm'), turbo_method: :delete, turbo_frame: '_top' })
|
|
19
|
+
else
|
|
20
|
+
concat content_tag(:button, I18n.t('lato_users.cta_delete'), class: 'btn btn-danger', disabled: true, title: I18n.t('lato_users.cannot_delete_accepted_invitation'))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module LatoUsers
|
|
2
|
+
module UsersHelper
|
|
3
|
+
def lato_user_actions(user, show: true)
|
|
4
|
+
content_tag(:div, class: 'btn-group btn-group-sm') do
|
|
5
|
+
concat link_to(I18n.t('lato_users.cta_show'), lato_users.user_path(user), class: 'btn btn-secondary', data: { turbo_frame: '_top' }) if show
|
|
6
|
+
concat link_to(I18n.t('lato_users.cta_edit'), lato_users.edit_user_path(user, redirect_to: request.fullpath), class: 'btn btn-primary', data: { lato_action_target: 'trigger', turbo_frame: dom_id(user, 'form'), action_title: I18n.t('lato_users.edit_user') })
|
|
7
|
+
if user.id == @session.user_id
|
|
8
|
+
concat content_tag(:button, I18n.t('lato_users.cta_delete'), class: 'btn btn-danger', disabled: true, title: I18n.t('lato_users.cannot_delete_self'))
|
|
9
|
+
else
|
|
10
|
+
concat link_to(I18n.t('lato_users.cta_delete'), lato_users.user_path(user), class: 'btn btn-danger', data: { turbo_confirm: I18n.t('lato_users.cta_delete_confirm'), turbo_method: :delete, turbo_frame: '_top' })
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<%
|
|
2
|
+
invitation ||= Lato::Invitation.new
|
|
3
|
+
%>
|
|
4
|
+
|
|
5
|
+
<%= turbo_frame_tag dom_id(invitation, 'form') do %>
|
|
6
|
+
<%= form_with model: invitation, url: invitation.persisted? ? lato_users.invitation_path(invitation) : lato_users.invitations_path, data: { turbo_frame: '_self', controller: 'lato-form' } do |form| %>
|
|
7
|
+
<%= lato_form_notices class: %w[mb-3] %>
|
|
8
|
+
<%= lato_form_errors invitation, class: %w[mb-3] %>
|
|
9
|
+
|
|
10
|
+
<div class="row">
|
|
11
|
+
<div class="col col-12 mb-3">
|
|
12
|
+
<%= lato_form_item_label form, :email %>
|
|
13
|
+
<%= lato_form_item_input_email form, :email, required: true %>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="row">
|
|
18
|
+
<div class="col-12 text-end">
|
|
19
|
+
<%= form.submit I18n.t('lato_users.cta_confirm'), class: 'btn btn-primary' %>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
<% end %>
|
|
23
|
+
<% end %>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<%= lato_page_head I18n.t('lato_users.invitations'), [
|
|
2
|
+
{ label: I18n.t('lato_users.users'), path: lato_users.users_path },
|
|
3
|
+
{ label: I18n.t('lato_users.invitations') }
|
|
4
|
+
] %>
|
|
5
|
+
|
|
6
|
+
<div class="card mb-4">
|
|
7
|
+
<div class="card-body">
|
|
8
|
+
<%= lato_index @invitations,
|
|
9
|
+
custom_actions: {
|
|
10
|
+
create: {
|
|
11
|
+
path: lato_users.new_invitation_path,
|
|
12
|
+
icon: 'bi bi-plus',
|
|
13
|
+
aria_label: I18n.t('lato_users.add_invitation'),
|
|
14
|
+
title: I18n.t('lato_users.add_invitation'),
|
|
15
|
+
data: { lato_action_target: 'trigger', turbo_frame: dom_id(Lato::Invitation.new, 'form'), action_title: I18n.t('lato_users.add_invitation'), controller: 'lato-tooltip' },
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
%>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<%= lato_page_head I18n.t('lato_users.add_invitation'), [
|
|
2
|
+
{ label: I18n.t('lato_users.users'), path: lato_users.users_path },
|
|
3
|
+
{ label: I18n.t('lato_users.invitations'), path: lato_users.invitations_path },
|
|
4
|
+
{ label: I18n.t('lato_users.add_invitation') }
|
|
5
|
+
] %>
|
|
6
|
+
|
|
7
|
+
<div class="card mb-4">
|
|
8
|
+
<div class="card-header">
|
|
9
|
+
<h2 class="fs-4 mb-0"><%= I18n.t('lato_users.add_invitation') %></h2>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="card-body">
|
|
12
|
+
<%= render 'form', invitation: @invitation %>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<div class="card mb-4">
|
|
2
|
+
<div class="card-header d-flex align-items-center justify-content-between">
|
|
3
|
+
<h2 class="fs-4 mb-0"><%= I18n.t('lato_users.account_informations') %></h2>
|
|
4
|
+
<%= lato_user_actions(@user, show: false) %>
|
|
5
|
+
</div>
|
|
6
|
+
<div class="card-body">
|
|
7
|
+
<div class="row">
|
|
8
|
+
<div class="col col-12 col-md-6 col-lg-3 mb-3">
|
|
9
|
+
<label class="form-label" for="first_name"><%= I18n.t('activerecord.attributes.lato/user.first_name') %></label>
|
|
10
|
+
<input type="text" id="first_name" class="form-control" value="<%= @user.first_name %>" disabled>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="col col-12 col-md-6 col-lg-3 mb-3">
|
|
14
|
+
<label class="form-label" for="last_name"><%= I18n.t('activerecord.attributes.lato/user.last_name') %></label>
|
|
15
|
+
<input type="text" id="last_name" class="form-control" value="<%= @user.last_name %>" disabled>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="col col-12 col-md-6 col-lg-3 mb-3">
|
|
19
|
+
<label class="form-label" for="email"><%= I18n.t('activerecord.attributes.lato/user.email') %></label>
|
|
20
|
+
<input type="text" id="email" class="form-control" value="<%= @user.email %>" disabled>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="col col-12 col-md-6 col-lg-3 mb-3">
|
|
24
|
+
<label class="form-label" for="locale"><%= I18n.t('activerecord.attributes.lato/user.locale') %></label>
|
|
25
|
+
<input type="text" id="locale" class="form-control" value="<%= @user.locale %>" disabled>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<div class="card mb-4">
|
|
2
|
+
<div class="card-header">
|
|
3
|
+
<h2 class="fs-4 mb-0"><%= I18n.t('lato_users.email_verification') %></h2>
|
|
4
|
+
</div>
|
|
5
|
+
<div class="card-body">
|
|
6
|
+
<% if @user.email_verified_at %>
|
|
7
|
+
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between p-3 bg-success bg-opacity-10 border border-success rounded">
|
|
8
|
+
<div class="d-flex align-items-center mb-3 mb-md-0">
|
|
9
|
+
<div class="me-3 text-success">
|
|
10
|
+
<i class="bi bi-check-circle-fill fs-4"></i>
|
|
11
|
+
</div>
|
|
12
|
+
<div>
|
|
13
|
+
<h5 class="fs-6 fw-bold text-success mb-0"><%= I18n.t('lato_users.email_verified') %></h5>
|
|
14
|
+
<small class="text-muted"><%= I18n.t('lato_users.email_verified_at', date: l(@user.email_verified_at, format: :long)) %></small>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
<%= button_to I18n.t('lato_users.revoke_verification'), revoke_email_verification_user_path(@user), method: :patch, class: "btn btn-danger btn-sm", form: { data: { turbo_confirm: I18n.t('lato_users.are_you_sure') } } %>
|
|
18
|
+
</div>
|
|
19
|
+
<% else %>
|
|
20
|
+
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between p-3 bg-warning bg-opacity-10 border border-warning rounded">
|
|
21
|
+
<div class="d-flex align-items-center mb-3 mb-md-0">
|
|
22
|
+
<div class="me-3 text-warning">
|
|
23
|
+
<i class="bi bi-exclamation-triangle-fill fs-4"></i>
|
|
24
|
+
</div>
|
|
25
|
+
<div>
|
|
26
|
+
<h5 class="fs-6 fw-bold text-warning mb-0"><%= I18n.t('lato_users.email_not_verified') %></h5>
|
|
27
|
+
<small class="text-muted"><%= I18n.t('lato_users.email_verification_required') %></small>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="d-flex gap-2">
|
|
31
|
+
<%= button_to I18n.t('lato_users.resend_verification_email'), resend_verification_email_user_path(@user), method: :post, class: "btn btn-primary btn-sm" %>
|
|
32
|
+
<%= button_to I18n.t('lato_users.manual_verify_email'), manual_verify_email_user_path(@user), method: :patch, class: "btn btn-success btn-sm", form: { data: { turbo_confirm: I18n.t('lato_users.are_you_sure') } } %>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<% end %>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<div class="card mb-4">
|
|
2
|
+
<div class="card-header">
|
|
3
|
+
<h2 class="fs-4 mb-0"><%= I18n.t('lato_users.privacy_and_terms') %></h2>
|
|
4
|
+
</div>
|
|
5
|
+
<div class="card-body">
|
|
6
|
+
<div class="row">
|
|
7
|
+
<div class="col col-12 col-lg-6 mb-3 mb-lg-0">
|
|
8
|
+
<h5 class="fs-6 fw-bold mb-3"><%= I18n.t('lato_users.privacy_policy') %></h5>
|
|
9
|
+
<p class="mb-1">
|
|
10
|
+
<%= I18n.t('lato_users.accepted_version') %>:
|
|
11
|
+
<span class="fw-bold"><%= @user.accepted_privacy_policy_version %></span>
|
|
12
|
+
</p>
|
|
13
|
+
<p class="mb-3">
|
|
14
|
+
<%= I18n.t('lato_users.current_version') %>:
|
|
15
|
+
<span class="fw-bold"><%= Lato.config.legal_privacy_policy_version %></span>
|
|
16
|
+
</p>
|
|
17
|
+
<% if @user.accepted_privacy_policy_version < Lato.config.legal_privacy_policy_version %>
|
|
18
|
+
<div class="d-flex align-items-center p-3 bg-warning bg-opacity-10 border border-warning rounded">
|
|
19
|
+
<div class="me-3 text-warning">
|
|
20
|
+
<i class="bi bi-exclamation-triangle-fill fs-4"></i>
|
|
21
|
+
</div>
|
|
22
|
+
<div>
|
|
23
|
+
<h5 class="fs-6 fw-bold text-warning mb-0"><%= I18n.t('lato_users.version_outdated') %></h5>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
<% else %>
|
|
27
|
+
<div class="d-flex align-items-center p-3 bg-success bg-opacity-10 border border-success rounded">
|
|
28
|
+
<div class="me-3 text-success">
|
|
29
|
+
<i class="bi bi-check-circle-fill fs-4"></i>
|
|
30
|
+
</div>
|
|
31
|
+
<div>
|
|
32
|
+
<h5 class="fs-6 fw-bold text-success mb-0"><%= I18n.t('lato_users.version_up_to_date') %></h5>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<% end %>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="col col-12 col-lg-6">
|
|
39
|
+
<h5 class="fs-6 fw-bold mb-3"><%= I18n.t('lato_users.terms_and_conditions') %></h5>
|
|
40
|
+
<p class="mb-1">
|
|
41
|
+
<%= I18n.t('lato_users.accepted_version') %>:
|
|
42
|
+
<span class="fw-bold"><%= @user.accepted_terms_and_conditions_version %></span>
|
|
43
|
+
</p>
|
|
44
|
+
<p class="mb-3">
|
|
45
|
+
<%= I18n.t('lato_users.current_version') %>:
|
|
46
|
+
<span class="fw-bold"><%= Lato.config.legal_terms_and_conditions_version %></span>
|
|
47
|
+
</p>
|
|
48
|
+
<% if @user.accepted_terms_and_conditions_version < Lato.config.legal_terms_and_conditions_version %>
|
|
49
|
+
<div class="d-flex align-items-center p-3 bg-warning bg-opacity-10 border border-warning rounded">
|
|
50
|
+
<div class="me-3 text-warning">
|
|
51
|
+
<i class="bi bi-exclamation-triangle-fill fs-4"></i>
|
|
52
|
+
</div>
|
|
53
|
+
<div>
|
|
54
|
+
<h5 class="fs-6 fw-bold text-warning mb-0"><%= I18n.t('lato_users.version_outdated') %></h5>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
<% else %>
|
|
58
|
+
<div class="d-flex align-items-center p-3 bg-success bg-opacity-10 border border-success rounded">
|
|
59
|
+
<div class="me-3 text-success">
|
|
60
|
+
<i class="bi bi-check-circle-fill fs-4"></i>
|
|
61
|
+
</div>
|
|
62
|
+
<div>
|
|
63
|
+
<h5 class="fs-6 fw-bold text-success mb-0"><%= I18n.t('lato_users.version_up_to_date') %></h5>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
<% end %>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<div class="card mb-4">
|
|
2
|
+
<div class="card-header">
|
|
3
|
+
<h2 class="fs-4 mb-0"><%= I18n.t('lato_users.security') %></h2>
|
|
4
|
+
</div>
|
|
5
|
+
<div class="card-body">
|
|
6
|
+
<% if Lato.config.authenticator_connection || Lato.config.webauthn_connection %>
|
|
7
|
+
<div class="row mb-4">
|
|
8
|
+
<div class="col-12">
|
|
9
|
+
<div class="list-group">
|
|
10
|
+
<% if Lato.config.authenticator_connection %>
|
|
11
|
+
<div class="list-group-item p-3">
|
|
12
|
+
<div class="row align-items-center">
|
|
13
|
+
<div class="col">
|
|
14
|
+
<span class="fw-bold"><%= I18n.t('lato_users.authenticator_connection') %></span>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="col-auto">
|
|
17
|
+
<% if @user.authenticator_secret.present? %>
|
|
18
|
+
<span class="badge bg-success"><%= I18n.t('lato_users.connected') %></span>
|
|
19
|
+
<% else %>
|
|
20
|
+
<span class="badge bg-secondary"><%= I18n.t('lato_users.not_connected') %></span>
|
|
21
|
+
<% end %>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
<% end %>
|
|
26
|
+
|
|
27
|
+
<% if Lato.config.webauthn_connection %>
|
|
28
|
+
<div class="list-group-item p-3">
|
|
29
|
+
<div class="row align-items-center">
|
|
30
|
+
<div class="col">
|
|
31
|
+
<span class="fw-bold"><%= I18n.t('lato_users.webauthn_connection') %></span>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="col-auto">
|
|
34
|
+
<span class="badge bg-primary">
|
|
35
|
+
<%= I18n.t('lato_users.devices_count') %>: <%= @user.webauthn_public_key.present? ? 1 : 0 %>
|
|
36
|
+
</span>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
<% end %>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
<% end %>
|
|
45
|
+
|
|
46
|
+
<div class="row mb-4">
|
|
47
|
+
<div class="col-12">
|
|
48
|
+
<h5 class="fs-6 fw-bold mb-3"><%= I18n.t('lato_users.signup_logs') %></h5>
|
|
49
|
+
<% if @user.lato_log_user_signups.any? %>
|
|
50
|
+
<div class="list-group">
|
|
51
|
+
<% @user.lato_log_user_signups.order(created_at: :desc).limit(5).each do |log| %>
|
|
52
|
+
<div class="list-group-item p-3">
|
|
53
|
+
<div class="row align-items-center gy-2">
|
|
54
|
+
<div class="col-12 col-md-3 text-muted small">
|
|
55
|
+
<i class="bi bi-calendar-event me-2"></i>
|
|
56
|
+
<%= l(log.created_at, format: :long) %>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="col-12 col-md-3">
|
|
59
|
+
<div class="d-flex align-items-center">
|
|
60
|
+
<i class="bi bi-globe me-2 text-muted d-md-none"></i>
|
|
61
|
+
<code class="text-dark"><%= log.ip_address %></code>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="col-12 col-md-6 text-muted small">
|
|
65
|
+
<div class="d-flex align-items-center">
|
|
66
|
+
<i class="bi bi-laptop me-2 d-md-none"></i>
|
|
67
|
+
<span class="text-truncate d-block" style="max-width: 100%;" title="<%= log.user_agent %>" data-bs-toggle="tooltip">
|
|
68
|
+
<%= log.user_agent %>
|
|
69
|
+
</span>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
<% end %>
|
|
75
|
+
</div>
|
|
76
|
+
<% else %>
|
|
77
|
+
<div class="alert alert-light border text-center text-muted">
|
|
78
|
+
<i class="bi bi-info-circle me-2"></i><%= I18n.t('lato_users.no_logs_found') %>
|
|
79
|
+
</div>
|
|
80
|
+
<% end %>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div class="row mb-4">
|
|
85
|
+
<div class="col-12">
|
|
86
|
+
<h5 class="fs-6 fw-bold mb-3"><%= I18n.t('lato_users.login_logs') %></h5>
|
|
87
|
+
<% if @user.lato_log_user_signins.any? %>
|
|
88
|
+
<div class="list-group">
|
|
89
|
+
<% @user.lato_log_user_signins.order(created_at: :desc).limit(5).each do |log| %>
|
|
90
|
+
<div class="list-group-item p-3">
|
|
91
|
+
<div class="row align-items-center gy-2">
|
|
92
|
+
<div class="col-12 col-md-3 text-muted small">
|
|
93
|
+
<i class="bi bi-calendar-event me-2"></i>
|
|
94
|
+
<%= l(log.created_at, format: :long) %>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="col-12 col-md-3">
|
|
97
|
+
<div class="d-flex align-items-center">
|
|
98
|
+
<i class="bi bi-globe me-2 text-muted d-md-none"></i>
|
|
99
|
+
<code class="text-dark"><%= log.ip_address %></code>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
<div class="col-12 col-md-6 text-muted small">
|
|
103
|
+
<div class="d-flex align-items-center">
|
|
104
|
+
<i class="bi bi-laptop me-2 d-md-none"></i>
|
|
105
|
+
<span class="text-truncate d-block" style="max-width: 100%;" title="<%= log.user_agent %>" data-bs-toggle="tooltip">
|
|
106
|
+
<%= log.user_agent %>
|
|
107
|
+
</span>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
<% end %>
|
|
113
|
+
</div>
|
|
114
|
+
<% else %>
|
|
115
|
+
<div class="alert alert-light border text-center text-muted">
|
|
116
|
+
<i class="bi bi-info-circle me-2"></i><%= I18n.t('lato_users.no_logs_found') %>
|
|
117
|
+
</div>
|
|
118
|
+
<% end %>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<div class="row">
|
|
123
|
+
<div class="col-12">
|
|
124
|
+
<h5 class="fs-6 fw-bold mb-3"><%= I18n.t('lato_users.password_management') %></h5>
|
|
125
|
+
|
|
126
|
+
<% if @user.password_digest.present? %>
|
|
127
|
+
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between p-3 bg-success bg-opacity-10 border border-success rounded">
|
|
128
|
+
<div class="d-flex align-items-center mb-3 mb-md-0">
|
|
129
|
+
<div class="me-3 text-success">
|
|
130
|
+
<i class="bi bi-shield-lock-fill fs-4"></i>
|
|
131
|
+
</div>
|
|
132
|
+
<div>
|
|
133
|
+
<h5 class="fs-6 fw-bold text-success mb-0"><%= I18n.t('lato_users.password_set') %></h5>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
<div class="d-flex gap-2">
|
|
137
|
+
<%= button_to force_update_password_user_path(@user), method: :patch, class: "btn btn-danger btn-sm d-flex align-items-center", form: { data: { turbo_confirm: I18n.t('lato_users.are_you_sure') } } do %>
|
|
138
|
+
<%= I18n.t('lato_users.force_password_update') %>
|
|
139
|
+
<% end %>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
<% else %>
|
|
143
|
+
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between p-3 bg-danger bg-opacity-10 border border-danger rounded">
|
|
144
|
+
<div class="d-flex align-items-center mb-3 mb-md-0">
|
|
145
|
+
<div class="me-3 text-danger">
|
|
146
|
+
<i class="bi bi-shield-exclamation fs-4"></i>
|
|
147
|
+
</div>
|
|
148
|
+
<div>
|
|
149
|
+
<h5 class="fs-6 fw-bold text-danger mb-0"><%= I18n.t('lato_users.password_not_set') %></h5>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
<div class="d-flex gap-2">
|
|
153
|
+
<%= button_to force_update_password_user_path(@user), method: :patch, class: "btn btn-danger btn-sm d-flex align-items-center", form: { data: { turbo_confirm: I18n.t('lato_users.are_you_sure') } } do %>
|
|
154
|
+
<%= I18n.t('lato_users.force_password_update') %>
|
|
155
|
+
<% end %>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
<% end %>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<%
|
|
2
|
+
|
|
3
|
+
user ||= Lato::User.new
|
|
4
|
+
|
|
5
|
+
%>
|
|
6
|
+
|
|
7
|
+
<%= turbo_frame_tag dom_id(user, 'form') do %>
|
|
8
|
+
<%= form_with model: user, url: user.persisted? ? lato_users.user_path(user) : lato_users.users_path, data: { turbo_frame: '_self', controller: 'lato-form lato-users-form' } do |form| %>
|
|
9
|
+
<%= lato_form_notices class: %w[mb-3] %>
|
|
10
|
+
<%= lato_form_errors user, class: %w[mb-3] %>
|
|
11
|
+
|
|
12
|
+
<% unless params[:redirect_to].blank? %>
|
|
13
|
+
<%= hidden_field_tag :redirect_to, params[:redirect_to] %>
|
|
14
|
+
<% end %>
|
|
15
|
+
|
|
16
|
+
<div class="row">
|
|
17
|
+
<div class="col col-12 mb-3">
|
|
18
|
+
<%= lato_form_item_label form, :email %>
|
|
19
|
+
<%= lato_form_item_input_email form, :email, required: true %>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="col col-12 col-md-6 mb-3">
|
|
23
|
+
<%= lato_form_item_label form, :first_name %>
|
|
24
|
+
<%= lato_form_item_input_text form, :first_name, required: true %>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="col col-12 col-md-6 mb-3">
|
|
28
|
+
<%= lato_form_item_label form, :last_name %>
|
|
29
|
+
<%= lato_form_item_input_text form, :last_name, required: true %>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<% unless user.persisted?%>
|
|
34
|
+
<div class="row">
|
|
35
|
+
<div class="col col-12 col-md-6 mb-3">
|
|
36
|
+
<%= lato_form_item_label form, :password %>
|
|
37
|
+
<%= lato_form_item_input_password form, :password, required: true, data: { lato_users_form_target: 'password' } %>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="col col-12 col-md-6 mb-3">
|
|
41
|
+
<%= lato_form_item_label form, :password_confirmation %>
|
|
42
|
+
<%= lato_form_item_input_password form, :password_confirmation, required: true, data: { lato_users_form_target: 'passwordConfirmation' } %>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="row mb-3">
|
|
47
|
+
<div class="col-12">
|
|
48
|
+
<div class="card bg-light">
|
|
49
|
+
<div class="card-body">
|
|
50
|
+
<div class="row align-items-center">
|
|
51
|
+
<div class="col-auto">
|
|
52
|
+
<button class="btn btn-primary" data-action="click->lato-users-form#generatePassword">
|
|
53
|
+
<i class="bi bi-shuffle me-2"></i>
|
|
54
|
+
<%= I18n.t('lato_users.generate_password') %>
|
|
55
|
+
</button>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="col">
|
|
58
|
+
<div class="input-group">
|
|
59
|
+
<span class="input-group-text"><i class="bi bi-key"></i></span>
|
|
60
|
+
<input type="text" class="form-control font-monospace" readonly data-lato-users-form-target="generatedPassword" placeholder="...">
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
<% end %>
|
|
69
|
+
|
|
70
|
+
<% if @available_admin_permissions.any? %>
|
|
71
|
+
<div class="accordion mb-3" id="permissionsAccordion">
|
|
72
|
+
<div class="accordion-item">
|
|
73
|
+
<h2 class="accordion-header" id="headingPermissions">
|
|
74
|
+
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapsePermissions" aria-expanded="false" aria-controls="collapsePermissions">
|
|
75
|
+
<%= I18n.t('lato_users.permissions') %>
|
|
76
|
+
</button>
|
|
77
|
+
</h2>
|
|
78
|
+
<div id="collapsePermissions" class="accordion-collapse collapse" aria-labelledby="headingPermissions" data-bs-parent="#permissionsAccordion">
|
|
79
|
+
<div class="accordion-body">
|
|
80
|
+
<div class="row">
|
|
81
|
+
<% @available_admin_permissions.each_with_index do |permission, index| %>
|
|
82
|
+
<div class="col-12 col-md-6 <%= 'mb-3' unless index == @available_admin_permissions.size - 1 %>">
|
|
83
|
+
<%= lato_form_item_input_check form, permission.to_sym, permission.humanize %>
|
|
84
|
+
</div>
|
|
85
|
+
<% end %>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
<% end %>
|
|
92
|
+
|
|
93
|
+
<div class="d-flex justify-content-end">
|
|
94
|
+
<%= lato_form_submit form, user.persisted? ? I18n.t('lato_users.cta_update') : I18n.t('lato_users.cta_confirm'), class: %w[btn-success] %>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<% end %>
|
|
98
|
+
<% end %>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%= lato_page_head I18n.t('lato_users.edit_user'), [
|
|
2
|
+
{ label: I18n.t('lato_users.users'), path: lato_users.users_path },
|
|
3
|
+
{ label: I18n.t('lato_users.edit_user') }
|
|
4
|
+
] %>
|
|
5
|
+
|
|
6
|
+
<div class="card mb-4">
|
|
7
|
+
<div class="card-header">
|
|
8
|
+
<h2 class="fs-4 mb-0"><%= I18n.t('lato_users.edit_user') %></h2>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="card-body">
|
|
11
|
+
<%= render 'lato_users/users/form', user: @user %>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<%= lato_page_head I18n.t('lato_users.users') %>
|
|
2
|
+
|
|
3
|
+
<div class="card mb-4">
|
|
4
|
+
<div class="card-body">
|
|
5
|
+
<%= lato_index @users,
|
|
6
|
+
custom_actions: {
|
|
7
|
+
invitations: {
|
|
8
|
+
path: lato_users.invitations_path,
|
|
9
|
+
icon: 'bi bi-envelope',
|
|
10
|
+
aria_label: I18n.t('lato_users.invitations'),
|
|
11
|
+
title: I18n.t('lato_users.invitations'),
|
|
12
|
+
data: { controller: 'lato-tooltip' },
|
|
13
|
+
},
|
|
14
|
+
create: {
|
|
15
|
+
path: lato_users.new_user_path,
|
|
16
|
+
icon: 'bi bi-plus',
|
|
17
|
+
aria_label: I18n.t('lato_users.add_user'),
|
|
18
|
+
title: I18n.t('lato_users.add_user'),
|
|
19
|
+
data: { lato_action_target: 'trigger', turbo_frame: dom_id(Lato::User.new, 'form'), action_title: I18n.t('lato_users.add_user'), controller: 'lato-tooltip' },
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
%>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%= lato_page_head I18n.t('lato_users.add_user'), [
|
|
2
|
+
{ label: I18n.t('lato_users.users'), path: lato_users.users_path },
|
|
3
|
+
{ label: I18n.t('lato_users.add_user') }
|
|
4
|
+
] %>
|
|
5
|
+
|
|
6
|
+
<div class="card mb-4">
|
|
7
|
+
<div class="card-header">
|
|
8
|
+
<h2 class="fs-4 mb-0"><%= I18n.t('lato_users.add_user') %></h2>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="card-body">
|
|
11
|
+
<%= render 'lato_users/users/form', user: @user %>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<%= lato_page_head I18n.t('lato_users.show_user'), [
|
|
2
|
+
{ label: I18n.t('lato_users.users'), path: lato_users.users_path },
|
|
3
|
+
{ label: I18n.t('lato_users.show_user') }
|
|
4
|
+
] %>
|
|
5
|
+
|
|
6
|
+
<%= render 'card_account_informations' %>
|
|
7
|
+
|
|
8
|
+
<%= render 'card_email_verification' %>
|
|
9
|
+
|
|
10
|
+
<%= render 'card_security' %>
|
|
11
|
+
|
|
12
|
+
<%= render 'card_privacy_and_terms' %>
|
data/config/importmap.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
en:
|
|
2
|
+
lato_users:
|
|
3
|
+
users: Users
|
|
4
|
+
add_user: Add User
|
|
5
|
+
edit_user: Edit User
|
|
6
|
+
show_user: User details
|
|
7
|
+
cta_show: Show
|
|
8
|
+
cta_edit: Edit
|
|
9
|
+
cta_update: Update
|
|
10
|
+
cta_delete: Delete
|
|
11
|
+
cta_confirm: Confirm
|
|
12
|
+
cta_delete_confirm: Are you sure you want to delete this user?
|
|
13
|
+
cannot_delete_self: You cannot delete your own user account.
|
|
14
|
+
account_informations: Account Informations
|
|
15
|
+
email_verification: "Email Verification"
|
|
16
|
+
email_verified: "Email Verified"
|
|
17
|
+
email_verified_at: "Verified on %{date}"
|
|
18
|
+
email_verification_required: "Verification required"
|
|
19
|
+
revoke_verification: "Revoke verification"
|
|
20
|
+
are_you_sure: "Are you sure?"
|
|
21
|
+
email_not_verified: "Email not verified"
|
|
22
|
+
resend_verification_email: "Resend verification email"
|
|
23
|
+
manual_verify_email: "Manually verify"
|
|
24
|
+
verification_email_sent: "Verification email sent"
|
|
25
|
+
email_manually_verified: "Email manually verified"
|
|
26
|
+
email_verification_revoked: "Email verification revoked"
|
|
27
|
+
privacy_and_terms: "Privacy Policy and Terms"
|
|
28
|
+
privacy_policy: "Privacy Policy"
|
|
29
|
+
terms_and_conditions: "Terms and Conditions"
|
|
30
|
+
accepted_version: "Accepted version"
|
|
31
|
+
current_version: "Current version"
|
|
32
|
+
version_outdated: "Version outdated"
|
|
33
|
+
version_up_to_date: "Version up to date"
|
|
34
|
+
security: "Security"
|
|
35
|
+
signup_logs: "Signup Logs"
|
|
36
|
+
date: "Date"
|
|
37
|
+
ip_address: "IP Address"
|
|
38
|
+
user_agent: "User Agent"
|
|
39
|
+
no_logs_found: "No logs found"
|
|
40
|
+
login_logs: "Login Logs"
|
|
41
|
+
password_management: "Password Management"
|
|
42
|
+
password_set: "Password set"
|
|
43
|
+
password_not_set: "Password not set"
|
|
44
|
+
send_reset_password_email: "Send reset password email"
|
|
45
|
+
force_password_update: "Force password update"
|
|
46
|
+
recover_password_email_sent: "Recovery password email sent"
|
|
47
|
+
recover_password_email_failed: "Failed to send recovery password email"
|
|
48
|
+
password_updated: "Password updated: %{password}"
|
|
49
|
+
password_update_failed: "Failed to update password"
|
|
50
|
+
authenticator_connection: "Authenticator Connection"
|
|
51
|
+
invitations: "Invitations"
|
|
52
|
+
add_invitation: "Add Invitation"
|
|
53
|
+
invitation_created: "Invitation created"
|
|
54
|
+
invitation_destroyed: "Invitation destroyed"
|
|
55
|
+
webauthn_connection: "WebAuthn Connection"
|
|
56
|
+
connected: "Connected"
|
|
57
|
+
not_connected: "Not Connected"
|
|
58
|
+
devices_count: "Connected Devices"
|
|
59
|
+
invitations: "Invitations"
|
|
60
|
+
generate_password: 'Generate Password'
|
|
61
|
+
permissions: "Permissions"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
it:
|
|
2
|
+
lato_users:
|
|
3
|
+
users: Utenti
|
|
4
|
+
add_user: Aggiungi Utente
|
|
5
|
+
edit_user: Modifica Utente
|
|
6
|
+
show_user: Dettagli Utente
|
|
7
|
+
cta_show: Visualizza
|
|
8
|
+
cta_edit: Modifica
|
|
9
|
+
cta_update: Aggiorna
|
|
10
|
+
cta_delete: Elimina
|
|
11
|
+
cta_confirm: Conferma
|
|
12
|
+
cta_delete_confirm: Sei sicuro di voler eliminare questo utente?
|
|
13
|
+
cannot_delete_self: Non puoi eliminare il tuo account utente.
|
|
14
|
+
account_informations: Informazioni Account
|
|
15
|
+
email_verification: "Verifica Email"
|
|
16
|
+
email_verified: "Email Verificata"
|
|
17
|
+
email_verified_at: "Verificata il %{date}"
|
|
18
|
+
email_verification_required: "Verifica richiesta"
|
|
19
|
+
revoke_verification: "Revoca verifica"
|
|
20
|
+
are_you_sure: "Sei sicuro?"
|
|
21
|
+
email_not_verified: "Email non verificata"
|
|
22
|
+
resend_verification_email: "Invia nuovamente email di verifica"
|
|
23
|
+
manual_verify_email: "Verifica manualmente"
|
|
24
|
+
verification_email_sent: "Email di verifica inviata"
|
|
25
|
+
email_manually_verified: "Email verificata manualmente"
|
|
26
|
+
email_verification_revoked: "Verifica email revocata"
|
|
27
|
+
privacy_and_terms: "Privacy Policy e Termini"
|
|
28
|
+
privacy_policy: "Privacy Policy"
|
|
29
|
+
terms_and_conditions: "Termini e Condizioni"
|
|
30
|
+
accepted_version: "Versione accettata"
|
|
31
|
+
current_version: "Versione corrente"
|
|
32
|
+
version_outdated: "Versione obsoleta"
|
|
33
|
+
version_up_to_date: "Versione aggiornata"
|
|
34
|
+
security: "Sicurezza"
|
|
35
|
+
signup_logs: "Log di Registrazione"
|
|
36
|
+
date: "Data"
|
|
37
|
+
ip_address: "Indirizzo IP"
|
|
38
|
+
user_agent: "User Agent"
|
|
39
|
+
no_logs_found: "Nessun log trovato"
|
|
40
|
+
login_logs: "Log di Accesso"
|
|
41
|
+
password_management: "Gestione Password"
|
|
42
|
+
password_set: "Password impostata"
|
|
43
|
+
password_not_set: "Password non impostata"
|
|
44
|
+
send_reset_password_email: "Invia email di reset password"
|
|
45
|
+
force_password_update: "Forza aggiornamento password"
|
|
46
|
+
recover_password_email_sent: "Email di recupero password inviata"
|
|
47
|
+
recover_password_email_failed: "Impossibile inviare email di recupero password"
|
|
48
|
+
password_updated: "Password aggiornata: %{password}"
|
|
49
|
+
password_update_failed: "Impossibile aggiornare la password"
|
|
50
|
+
authenticator_connection: "Connessione Authenticator"
|
|
51
|
+
invitations: "Inviti"
|
|
52
|
+
add_invitation: "Aggiungi Invito"
|
|
53
|
+
invitation_created: "Invito creato"
|
|
54
|
+
invitation_destroyed: "Invito eliminato"
|
|
55
|
+
webauthn_connection: "Connessione WebAuthn"
|
|
56
|
+
connected: "Connesso"
|
|
57
|
+
not_connected: "Non Connesso"
|
|
58
|
+
devices_count: "Dispositivi Connessi"
|
|
59
|
+
generate_password: 'Genera Password'
|
|
60
|
+
permissions: "Permessi"
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LatoUsers::Engine.routes.draw do
|
|
2
|
+
root 'application#index'
|
|
3
|
+
|
|
4
|
+
resources :users do
|
|
5
|
+
member do
|
|
6
|
+
post :resend_verification_email
|
|
7
|
+
patch :manual_verify_email
|
|
8
|
+
patch :revoke_email_verification
|
|
9
|
+
patch :force_update_password
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
resources :invitations, except: [:show, :edit, :update]
|
|
14
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module LatoUsers
|
|
2
|
+
class Engine < ::Rails::Engine
|
|
3
|
+
isolate_namespace LatoUsers
|
|
4
|
+
|
|
5
|
+
initializer 'lato_users.importmap', before: 'importmap' do |app|
|
|
6
|
+
app.config.importmap.paths << root.join('config/importmap.rb')
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
initializer "lato_users.precompile" do |app|
|
|
10
|
+
app.config.assets.precompile << "lato_users_manifest.js"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/lato_users.rb
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
namespace :lato_users do
|
|
4
|
+
namespace :install do
|
|
5
|
+
desc 'Install Lato users engine and run tasks on main rails application'
|
|
6
|
+
# Usage: rails lato_users:install:application
|
|
7
|
+
task :application do
|
|
8
|
+
# Free for future use...
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lato_users
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 3.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Gregorio Galante
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-12-20 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 7.1.1
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 7.1.1
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: lato
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
description: A Rails engine to manage application users on Lato projects!
|
|
42
|
+
email:
|
|
43
|
+
- me@gregoriogalante.com
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- MIT-LICENSE
|
|
49
|
+
- README.md
|
|
50
|
+
- Rakefile
|
|
51
|
+
- app/assets/config/lato_users_manifest.js
|
|
52
|
+
- app/assets/javascripts/lato_users/application.js
|
|
53
|
+
- app/assets/javascripts/lato_users/controllers/lato_users_form_controller.js
|
|
54
|
+
- app/assets/stylesheets/lato_users/application.scss
|
|
55
|
+
- app/controllers/lato_users/application_controller.rb
|
|
56
|
+
- app/controllers/lato_users/invitations_controller.rb
|
|
57
|
+
- app/controllers/lato_users/users_controller.rb
|
|
58
|
+
- app/helpers/lato_users/application_helper.rb
|
|
59
|
+
- app/helpers/lato_users/invitations_helper.rb
|
|
60
|
+
- app/helpers/lato_users/users_helper.rb
|
|
61
|
+
- app/jobs/lato_users/application_job.rb
|
|
62
|
+
- app/mailers/lato_users/application_mailer.rb
|
|
63
|
+
- app/views/lato_users/invitations/_form.html.erb
|
|
64
|
+
- app/views/lato_users/invitations/index.html.erb
|
|
65
|
+
- app/views/lato_users/invitations/new.html.erb
|
|
66
|
+
- app/views/lato_users/users/_card_account_informations.html.erb
|
|
67
|
+
- app/views/lato_users/users/_card_email_verification.html.erb
|
|
68
|
+
- app/views/lato_users/users/_card_privacy_and_terms.html.erb
|
|
69
|
+
- app/views/lato_users/users/_card_security.html.erb
|
|
70
|
+
- app/views/lato_users/users/_form.html.erb
|
|
71
|
+
- app/views/lato_users/users/edit.html.erb
|
|
72
|
+
- app/views/lato_users/users/index.html.erb
|
|
73
|
+
- app/views/lato_users/users/new.html.erb
|
|
74
|
+
- app/views/lato_users/users/show.html.erb
|
|
75
|
+
- config/importmap.rb
|
|
76
|
+
- config/locales/en.yml
|
|
77
|
+
- config/locales/it.yml
|
|
78
|
+
- config/routes.rb
|
|
79
|
+
- db/migrate/20250328072343_add_lato_users_admin_to_lato_user.rb
|
|
80
|
+
- lib/lato_users.rb
|
|
81
|
+
- lib/lato_users/config.rb
|
|
82
|
+
- lib/lato_users/engine.rb
|
|
83
|
+
- lib/lato_users/version.rb
|
|
84
|
+
- lib/tasks/lato_users_tasks.rake
|
|
85
|
+
homepage: https://github.com/Lato-org/lato_users
|
|
86
|
+
licenses:
|
|
87
|
+
- MIT
|
|
88
|
+
metadata:
|
|
89
|
+
homepage_uri: https://github.com/Lato-org/lato_users
|
|
90
|
+
source_code_uri: https://github.com/Lato-org/lato_users
|
|
91
|
+
post_install_message:
|
|
92
|
+
rdoc_options: []
|
|
93
|
+
require_paths:
|
|
94
|
+
- lib
|
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
96
|
+
requirements:
|
|
97
|
+
- - ">="
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
version: '0'
|
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: '0'
|
|
105
|
+
requirements: []
|
|
106
|
+
rubygems_version: 3.4.1
|
|
107
|
+
signing_key:
|
|
108
|
+
specification_version: 4
|
|
109
|
+
summary: Another engine for Lato projects
|
|
110
|
+
test_files: []
|