client_manager 0.1.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/README.md +128 -0
- data/Rakefile +37 -0
- data/app/assets/config/client_manager_manifest.js +2 -0
- data/app/assets/javascripts/client_manager/application.js +16 -0
- data/app/assets/javascripts/client_manager/clipboard.min.js +7 -0
- data/app/assets/javascripts/client_manager/oneui.min.js +5 -0
- data/app/assets/javascripts/client_manager/passwords.js +2 -0
- data/app/assets/javascripts/client_manager/sessions.js +2 -0
- data/app/assets/javascripts/client_manager/users.js +2 -0
- data/app/assets/stylesheets/client_manager/application.css +17 -0
- data/app/assets/stylesheets/client_manager/bootstrap.min.css +5 -0
- data/app/assets/stylesheets/client_manager/custom.css +23 -0
- data/app/assets/stylesheets/client_manager/oneui.min.css +4 -0
- data/app/assets/stylesheets/client_manager/passwords.css +4 -0
- data/app/assets/stylesheets/client_manager/sessions.css +4 -0
- data/app/assets/stylesheets/client_manager/users.css +4 -0
- data/app/controllers/client_manager/application_controller.rb +31 -0
- data/app/controllers/client_manager/clients_controller.rb +46 -0
- data/app/controllers/client_manager/concerns/set_client_by_token.rb +40 -0
- data/app/controllers/client_manager/passwords_controller.rb +26 -0
- data/app/controllers/client_manager/sessions_controller.rb +38 -0
- data/app/controllers/client_manager/users_controller.rb +121 -0
- data/app/helpers/client_manager/application_helper.rb +5 -0
- data/app/helpers/client_manager/passwords_helper.rb +4 -0
- data/app/helpers/client_manager/sessions_helper.rb +4 -0
- data/app/helpers/client_manager/users_helper.rb +4 -0
- data/app/jobs/client_manager/application_job.rb +4 -0
- data/app/mailers/client_manager/application_mailer.rb +6 -0
- data/app/mailers/client_manager/registration_mailer.rb +12 -0
- data/app/models/client_manager/application_record.rb +5 -0
- data/app/models/client_manager/client.rb +33 -0
- data/app/models/client_manager/user.rb +42 -0
- data/app/views/client_manager/clients/index.html.erb +37 -0
- data/app/views/client_manager/clients/new.html.erb +27 -0
- data/app/views/client_manager/clients/show.html.erb +41 -0
- data/app/views/client_manager/passwords/change.html.erb +46 -0
- data/app/views/client_manager/registration_mailer/registration_email.html.erb +90 -0
- data/app/views/client_manager/sessions/login.html.erb +53 -0
- data/app/views/client_manager/users/edit.html.erb +55 -0
- data/app/views/client_manager/users/index.html.erb +53 -0
- data/app/views/client_manager/users/new.html.erb +39 -0
- data/app/views/layouts/client_manager/application.html.erb +20 -0
- data/app/views/layouts/client_manager/none.html.erb +1 -0
- data/app/views/layouts/client_manager/partials/_header.html.erb +40 -0
- data/app/views/layouts/client_manager/partials/_modal.html.erb +15 -0
- data/config/locales/devise.en.yml +62 -0
- data/config/routes.rb +10 -0
- data/db/migrate/20160816183756_create_client_manager_users.rb +11 -0
- data/db/migrate/20160822112804_create_client_manager_clients.rb +11 -0
- data/db/migrate/20160905184226_add_password_digest_to_user.rb +5 -0
- data/db/migrate/20160930152731_add_password_changed_flag_to_users.rb +5 -0
- data/db/migrate/20161007235114_add_super_admin_flag_to_users.rb +5 -0
- data/lib/client_manager.rb +5 -0
- data/lib/client_manager/engine.rb +32 -0
- data/lib/client_manager/version.rb +3 -0
- data/lib/generators/client_manager/install_generator.rb +107 -0
- data/lib/generators/client_manager/templates/client_manager.rb +3 -0
- data/lib/tasks/client_manager_tasks.rake +12 -0
- metadata +217 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
require_dependency "client_manager/application_controller"
|
2
|
+
|
3
|
+
module ClientManager
|
4
|
+
class SessionsController < ApplicationController
|
5
|
+
|
6
|
+
def login
|
7
|
+
redirect_to after_login_path if client_manager_current_user
|
8
|
+
end
|
9
|
+
|
10
|
+
def logout
|
11
|
+
session.delete(:client_manager_current_user_id)
|
12
|
+
redirect_to login_path
|
13
|
+
end
|
14
|
+
|
15
|
+
def login_attempt
|
16
|
+
authorized_user = ClientManager::User.find_by(email: params[:email]).try(:authenticate, params[:password])
|
17
|
+
|
18
|
+
if !authorized_user
|
19
|
+
flash[:error] = "Invalid Email or Password"
|
20
|
+
redirect_to login_path
|
21
|
+
else
|
22
|
+
session[:client_manager_current_user_id] = authorized_user.id
|
23
|
+
redirect_to after_login_path
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def after_login_path
|
30
|
+
if client_manager_current_user.superadmin
|
31
|
+
return users_path
|
32
|
+
else
|
33
|
+
return clients_path
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require_dependency "client_manager/application_controller"
|
2
|
+
|
3
|
+
|
4
|
+
module ClientManager
|
5
|
+
class UsersController < ApplicationController
|
6
|
+
before_action :set_user, only: [:destroy, :update, :edit]
|
7
|
+
before_action :authenticate_superadmin, except: [:edit, :update]
|
8
|
+
before_action :authenticate_editor, only: [:edit, :update]
|
9
|
+
before_action :set_edit_view_variables, only: [:edit]
|
10
|
+
layout "client_manager/none", only: [:new, :edit]
|
11
|
+
|
12
|
+
def new
|
13
|
+
@user = ClientManager::User.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def index
|
17
|
+
@users = ClientManager::User.where.not(id: client_manager_current_user.id, superadmin: true)
|
18
|
+
end
|
19
|
+
|
20
|
+
def edit
|
21
|
+
session[:return_to] ||= request.referer
|
22
|
+
end
|
23
|
+
|
24
|
+
def update
|
25
|
+
@user.assign_attributes(user_update_params)
|
26
|
+
return if password_change_attempted? && !handle_password_update
|
27
|
+
return if max_clients_is_too_low?
|
28
|
+
if @user.save
|
29
|
+
flash[:success] = "User successfully updated"
|
30
|
+
else
|
31
|
+
flash[:error] = @user.errors.empty? ? "Error" : @user.errors.full_messages.uniq.to_sentence
|
32
|
+
end
|
33
|
+
|
34
|
+
redirect_to session.delete(:return_to)
|
35
|
+
end
|
36
|
+
|
37
|
+
def create
|
38
|
+
@user = ClientManager::User.new(user_params)
|
39
|
+
if @user.save
|
40
|
+
flash[:success] = "User successfully created"
|
41
|
+
else
|
42
|
+
flash[:error] = @user.errors.empty? ? "Error" : @user.errors.full_messages.uniq.to_sentence
|
43
|
+
end
|
44
|
+
redirect_to users_path
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def destroy
|
49
|
+
@user.destroy
|
50
|
+
flash[:success] = "User successfully deleted"
|
51
|
+
redirect_to users_path
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
|
58
|
+
def authenticate_editor
|
59
|
+
if !(client_manager_current_user == @user)
|
60
|
+
authenticate_superadmin
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def set_edit_view_variables
|
65
|
+
@modal_title = "MY PROFILE" if client_manager_current_user == @user
|
66
|
+
@client_manager_current_user_is_superadmin = @user.superadmin && @user == client_manager_current_user
|
67
|
+
|
68
|
+
if @client_manager_current_user_is_superadmin
|
69
|
+
@max_clients = "Infinity"
|
70
|
+
@input_type = "text"
|
71
|
+
else
|
72
|
+
@max_clients = @user.maximum_number_of_clients
|
73
|
+
@input_type = "number"
|
74
|
+
end
|
75
|
+
|
76
|
+
if client_manager_current_user == @user
|
77
|
+
@button_text = "Save Profile"
|
78
|
+
else
|
79
|
+
@button_text = "Save User"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def max_clients_is_too_low?
|
84
|
+
if !(params[:user][:maximum_number_of_clients].blank?) && (params[:user][:maximum_number_of_clients].to_i <= @user.clients.count)
|
85
|
+
flash[:error] = "User already has #{@user.clients.count} clients. Max. number of clients cannot be lower."
|
86
|
+
redirect_to session.delete(:return_to)
|
87
|
+
return true
|
88
|
+
end
|
89
|
+
return false
|
90
|
+
end
|
91
|
+
|
92
|
+
def handle_password_update
|
93
|
+
if @new_password != @new_password_confirmation
|
94
|
+
flash[:error] = "Confirmation password mismatch. Password not changed. Nothing was updated."
|
95
|
+
redirect_to session.delete(:return_to)
|
96
|
+
return false
|
97
|
+
else
|
98
|
+
@user.assign_attributes(password: @new_password)
|
99
|
+
flash[:success] = "Password changed successfully"
|
100
|
+
return true
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def password_change_attempted?
|
105
|
+
@new_password = params[:user][:new_password]; @new_password_confirmation = params[:user][:new_password_confirmation]
|
106
|
+
return (!@new_password.blank? || !@new_password_confirmation.blank?) && client_manager_current_user == @user
|
107
|
+
end
|
108
|
+
|
109
|
+
def set_user
|
110
|
+
@user = ClientManager::User.find(params[:id])
|
111
|
+
end
|
112
|
+
|
113
|
+
def user_params
|
114
|
+
params.require(:user).permit(:name, :email, :maximum_number_of_clients)
|
115
|
+
end
|
116
|
+
|
117
|
+
def user_update_params
|
118
|
+
params.require(:user).permit(:name, :maximum_number_of_clients)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ClientManager
|
2
|
+
class RegistrationMailer < ApplicationMailer
|
3
|
+
|
4
|
+
|
5
|
+
def registration_email(user)
|
6
|
+
@user = user
|
7
|
+
@application_name = ::Rails.application.class.parent.name
|
8
|
+
@url = ClientManager::Engine.routes.url_helpers.login_path
|
9
|
+
mail(to: @user.email, subject: "You have been added to #{@application_name}'s ClientManager'")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module ClientManager
|
2
|
+
class Client < ApplicationRecord
|
3
|
+
belongs_to :user
|
4
|
+
after_create :generate_token, unless: :token_exists?
|
5
|
+
validates_presence_of :name, :user_id
|
6
|
+
validate :number_of_users_clients
|
7
|
+
|
8
|
+
|
9
|
+
def token_exists?
|
10
|
+
!self.token.blank?
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def generate_token
|
17
|
+
if ClientManager.token_secret.blank?
|
18
|
+
raise "config.token_secret is missing. Please set it in the client_manager.rb initializer."
|
19
|
+
end
|
20
|
+
payload = {client_id: id}
|
21
|
+
puts payload
|
22
|
+
token = JWT.encode payload, ClientManager.token_secret, 'HS256'
|
23
|
+
self.update(token: token)
|
24
|
+
end
|
25
|
+
|
26
|
+
def number_of_users_clients
|
27
|
+
if !self.user.superadmin && self.user.maximum_number_of_clients <= self.user.clients.count
|
28
|
+
self.errors[:base] << "Maximum number of clients reached. You cannot create more than #{user.maximum_number_of_clients} clients."
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module ClientManager
|
4
|
+
class User < ApplicationRecord
|
5
|
+
has_secure_password
|
6
|
+
|
7
|
+
default_scope { order('created_at DESC') }
|
8
|
+
validates :name, :email, presence: true
|
9
|
+
validates :email, presence: true, uniqueness: true
|
10
|
+
validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
|
11
|
+
validates_numericality_of :maximum_number_of_clients, allow_nil: true
|
12
|
+
|
13
|
+
before_validation :set_temporary_password, unless: :password_exists?
|
14
|
+
after_create :send_registration_email
|
15
|
+
has_many :clients, dependent: :destroy
|
16
|
+
|
17
|
+
def client_count
|
18
|
+
clients.count
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.create_superadmin(name, email, password)
|
22
|
+
ClientManager::User.create(name: name, email: email, password: password, superadmin: true, maximum_number_of_clients: nil, password_changed: true)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def password_exists?
|
28
|
+
!self.password_digest.blank?
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_temporary_password
|
32
|
+
self.password = SecureRandom.hex(6)
|
33
|
+
end
|
34
|
+
|
35
|
+
def send_registration_email
|
36
|
+
if !self.superadmin
|
37
|
+
RegistrationMailer.registration_email(self).deliver
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
<%= render 'layouts/client_manager/partials/header', path: new_client_path, path_title: 'Add New Client' %>
|
2
|
+
<main id="main-container">
|
3
|
+
<div class="content bg-gray-lighter">
|
4
|
+
<div class="row items-push">
|
5
|
+
<div class="col-sm-7">
|
6
|
+
<h1 class="page-heading">
|
7
|
+
Clients
|
8
|
+
</h1>
|
9
|
+
</div>
|
10
|
+
</div>
|
11
|
+
</div>
|
12
|
+
<div class="content">
|
13
|
+
<div class="content-grid push-50">
|
14
|
+
<div class="row">
|
15
|
+
|
16
|
+
<% @clients.each do |client| %>
|
17
|
+
<div class="col-lg-4">
|
18
|
+
<div class="block block-link-hover3">
|
19
|
+
<div class="block-content block-content-full clearfix">
|
20
|
+
<a href="<%= client_path(client) %>" data-method="delete" data-confirm="Are you sure you want to delete this client?" class="icon-btn">
|
21
|
+
<span class="sr-only">Delete Client <%= client.name %></span>
|
22
|
+
<i class="fa fa-trash fa-2x text-danger text-muted" aria-hidden="true"></i>
|
23
|
+
</a>
|
24
|
+
<a class="icon-btn" data-toggle="modal" data-target="#modal" href="<%= client_path(client) %>">
|
25
|
+
<span class="sr-only">View Details for Client <%= client.name %></span>
|
26
|
+
<i class="fa fa-eye fa-2x text-primary text-muted" aria-hidden="true"></i>
|
27
|
+
</a>
|
28
|
+
<h2 class="h4 font-w700"><%= client.name %></h2>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
<% end %>
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
</main>
|
37
|
+
</div>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<%= form_for @client, html: {class: "form-horizontal push-5-t"} do |user_form| %>
|
2
|
+
<div class="block block-themed block-transparent remove-margin-b">
|
3
|
+
<div class="block-header bg-primary-dark">
|
4
|
+
<ul class="block-options">
|
5
|
+
<li>
|
6
|
+
<button data-dismiss="modal" type="button">
|
7
|
+
<span class="sr-only">Close Modal</span>
|
8
|
+
<i class="fa fa-close" aria-hidden="true"></i>
|
9
|
+
</button>
|
10
|
+
</li>
|
11
|
+
</ul>
|
12
|
+
<h3 class="block-title">New Client</h3>
|
13
|
+
</div>
|
14
|
+
<div class="block-content">
|
15
|
+
<div class="form-group">
|
16
|
+
<%= user_form.label :name, class: "col-xs-12", required: true %>
|
17
|
+
<div class="col-xs-12">
|
18
|
+
<%= user_form.text_field :name, class: "form-control", required: true, placeholder: "Example - 'Android Staging', 'Angular Prod'" %>
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
<div class="modal-footer">
|
22
|
+
<button class="btn btn-sm btn-success" type="submit"><i class="fa fa-check" aria-hidden="true"></i> Save client</button>
|
23
|
+
<button class="btn btn-sm btn-danger" type="button" data-dismiss="modal">Close</button>
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
</div>
|
27
|
+
<% end %>
|
@@ -0,0 +1,41 @@
|
|
1
|
+
<div class="form-horizontal push-5-t">
|
2
|
+
<div class="block block-themed block-transparent remove-margin-b">
|
3
|
+
<div class="block-header bg-primary-dark">
|
4
|
+
<ul class="block-options">
|
5
|
+
<li>
|
6
|
+
<button data-dismiss="modal" type="button">
|
7
|
+
<span class="sr-only">Close Modal</span>
|
8
|
+
<i class="fa fa-close" aria-hidden="true"></i>
|
9
|
+
</button>
|
10
|
+
</li>
|
11
|
+
</ul>
|
12
|
+
<h3 class="block-title"><%= @client.name %></h3>
|
13
|
+
</div>
|
14
|
+
<div class="block-content">
|
15
|
+
<div class="form-group">
|
16
|
+
<label class="col-xs-12">Token</label>
|
17
|
+
<div class="col-xs-12">
|
18
|
+
<input class="form-control" id="token" type="text" value="<%= @client.token %>" disabled="true">
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
<div class="modal-footer">
|
22
|
+
<a id="copy" class="btn btn-sm btn-default" type="button" data-clipboard-text="<%= @client.token %>">
|
23
|
+
<span class="sr-only">Copy Token</span>
|
24
|
+
<i class="fa fa-clipboard" aria-hidden="true"></i>
|
25
|
+
</a>
|
26
|
+
<a class="btn btn-sm btn-danger" type="button" data-dismiss="modal">Close</a>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
|
32
|
+
<script>
|
33
|
+
$(document).ready(function () {
|
34
|
+
var clipboard = new Clipboard('#copy');
|
35
|
+
|
36
|
+
clipboard.on('success', function (e) {
|
37
|
+
var token = document.getElementById('token')
|
38
|
+
token.setSelectionRange(0, token.value.length)
|
39
|
+
});
|
40
|
+
})
|
41
|
+
</script>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
<!-- Login Content -->
|
2
|
+
<div class="bg-white pulldown">
|
3
|
+
<div class="content content-boxed overflow-hidden">
|
4
|
+
<div class="row">
|
5
|
+
<div class="col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-4 col-lg-offset-4">
|
6
|
+
<div class="push-30-t push-50 animated fadeIn">
|
7
|
+
<!-- Login Title -->
|
8
|
+
<div class="text-center">
|
9
|
+
<i class="fa fa-2x fa-circle-o-notch text-primary"></i>
|
10
|
+
<h1 class="text-muted push-15-t">Hey first timer, <br> You have to change your password to proceed.</h1>
|
11
|
+
</div>
|
12
|
+
<!-- END Login Title -->
|
13
|
+
|
14
|
+
<!-- Login Form -->
|
15
|
+
<!-- jQuery Validation (.js-validation-login class is initialized in js/pages/base_pages_login.js) -->
|
16
|
+
<!-- For more examples you can check out https://github.com/jzaefferer/jquery-validation -->
|
17
|
+
<%= form_tag({action: :change_password_attempt}, class: "js-validation-login form-horizontal push-30-t") do %>
|
18
|
+
<div class="form-group">
|
19
|
+
<div class="col-xs-12">
|
20
|
+
<div class="form-material form-material-primary floating">
|
21
|
+
<%= password_field_tag(:new_password, "", class: "form-control", id: "login-password") %>
|
22
|
+
<label for="login-password">New password</label>
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
<div class="form-group">
|
27
|
+
<div class="col-xs-12">
|
28
|
+
<div class="form-material form-material-primary floating">
|
29
|
+
<%= password_field_tag(:confirm_new_password, "", class: "form-control", id: "login-confirm-password") %>
|
30
|
+
<label for="login-confirm-password">Confirm new password</label>
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
<div class="form-group push-30-t">
|
35
|
+
<div class="col-xs-12 col-sm-6 col-sm-offset-3 col-md-4 col-md-offset-4">
|
36
|
+
<button class="btn btn-sm btn-block btn-primary" type="submit">Change</button>
|
37
|
+
</div>
|
38
|
+
</div>
|
39
|
+
<% end %>
|
40
|
+
<!-- END Login Form -->
|
41
|
+
</div>
|
42
|
+
</div>
|
43
|
+
</div>
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
<!-- END Login Content -->
|