diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index ba1a5366f..0faa54124 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -75,8 +75,8 @@ class UsersController extends Controller 'users.autoassign_licenses', 'users.website', - ])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy',) - ->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count'); + ])->with('manager', 'groups', 'userloc', 'company', 'department', 'assets', 'licenses', 'accessories', 'consumables', 'createdBy') + ->withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'managesUsers as manages_users_count', 'managedLocations as manages_locations_count'); if ($request->filled('activated')) { @@ -187,6 +187,14 @@ class UsersController extends Controller $users->has('accessories', '=', $request->input('accessories_count')); } + if ($request->filled('manages_users_count')) { + $users->has('manages_users_count', '=', $request->input('manages_users_count')); + } + + if ($request->filled('manages_locations_count')) { + $users->has('manages_locations_count', '=', $request->input('manages_locations_count')); + } + if ($request->filled('autoassign_licenses')) { $users->where('autoassign_licenses', '=', $request->input('autoassign_licenses')); } @@ -244,6 +252,8 @@ class UsersController extends Controller 'licenses_count', 'consumables_count', 'accessories_count', + 'manages_user_count', + 'manages_locations_count', 'phone', 'address', 'city', @@ -405,11 +415,15 @@ class UsersController extends Controller { $this->authorize('view', User::class); - $user = User::withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count')->findOrFail($id); - $user = Company::scopeCompanyables($user)->find($id); - $this->authorize('update', $user); + $user = User::withCount('assets as assets_count', 'licenses as licenses_count', 'accessories as accessories_count', 'consumables as consumables_count', 'managesUsers as manages_users_count', 'managedLocations as manages_locations_count'); + + if ($user = Company::scopeCompanyables($user)->find($id)) { + $this->authorize('view', $user); + return (new UsersTransformer)->transformUser($user); + } + + return response()->json(Helper::formatStandardApiResponse('error', null, trans('admin/users/message.user_not_found', compact('id')))); - return (new UsersTransformer)->transformUser($user); } @@ -470,7 +484,6 @@ class UsersController extends Controller } - // Update the location of any assets checked out to this user Asset::where('assigned_type', User::class) ->where('assigned_to', $user->id)->update(['location_id' => $request->input('location_id', null)]); @@ -480,12 +493,6 @@ class UsersController extends Controller if ($user->save()) { - // Sync group memberships: - // This was changed in Snipe-IT v4.6.x to 4.7, since we upgraded to Laravel 5.5 - // which changes the behavior of has vs filled. - // The $request->has method will now return true even if the input value is an empty string or null. - // A new $request->filled method has was added that provides the previous behavior of the has method. - // Check if the request has groups passed and has a value if ($request->filled('groups')) { diff --git a/app/Http/Transformers/UsersTransformer.php b/app/Http/Transformers/UsersTransformer.php index 0ebaca269..64752d044 100644 --- a/app/Http/Transformers/UsersTransformer.php +++ b/app/Http/Transformers/UsersTransformer.php @@ -21,6 +21,7 @@ class UsersTransformer public function transformUser(User $user) { + $array = [ 'id' => (int) $user->id, 'avatar' => e($user->present()->gravatar), @@ -64,6 +65,8 @@ class UsersTransformer 'licenses_count' => (int) $user->licenses_count, 'accessories_count' => (int) $user->accessories_count, 'consumables_count' => (int) $user->consumables_count, + 'manages_users_count' => (int) $user->manages_users_count, + 'manages_locations_count' => (int) $user->manages_locations_count, 'company' => ($user->company) ? ['id' => (int) $user->company->id, 'name'=> e($user->company->name)] : null, 'created_by' => ($user->createdBy) ? [ 'id' => (int) $user->createdBy->id, diff --git a/app/Models/User.php b/app/Models/User.php index e535fa0fd..e30136703 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -214,10 +214,12 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo public function isDeletable() { return Gate::allows('delete', $this) - && ($this->assets()->count() === 0) - && ($this->licenses()->count() === 0) - && ($this->consumables()->count() === 0) - && ($this->accessories()->count() === 0) + && ($this->assets->count() === 0) + && ($this->licenses->count() === 0) + && ($this->consumables->count() === 0) + && ($this->accessories->count() === 0) + && ($this->managedLocations->count() === 0) + && ($this->managesUsers->count() === 0) && ($this->deleted_at == ''); } @@ -410,6 +412,19 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo return $this->belongsTo(self::class, 'manager_id')->withTrashed(); } + /** + * Establishes the user -> managed users relationship + * + * @author A. Gianotto + * @since [v6.4.1] + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function managesUsers() + { + return $this->hasMany(\App\Models\User::class, 'manager_id'); + } + + /** * Establishes the user -> managed locations relationship * diff --git a/app/Presenters/UserPresenter.php b/app/Presenters/UserPresenter.php index 4726205c7..a5b99adb1 100644 --- a/app/Presenters/UserPresenter.php +++ b/app/Presenters/UserPresenter.php @@ -221,7 +221,7 @@ class UserPresenter extends Presenter 'switchable' => true, 'escape' => true, 'class' => 'css-barcode', - 'title' => 'Assets', + 'title' => trans('general.assets'), 'visible' => true, ], [ @@ -230,7 +230,7 @@ class UserPresenter extends Presenter 'sortable' => true, 'switchable' => true, 'class' => 'css-license', - 'title' => 'License', + 'title' => trans('general.licenses'), 'visible' => true, ], [ @@ -239,7 +239,7 @@ class UserPresenter extends Presenter 'sortable' => true, 'switchable' => true, 'class' => 'css-consumable', - 'title' => 'Consumables', + 'title' => trans('general.consumables'), 'visible' => true, ], [ @@ -248,7 +248,25 @@ class UserPresenter extends Presenter 'sortable' => true, 'switchable' => true, 'class' => 'css-accessory', - 'title' => 'Accessories', + 'title' => trans('general.accessories'), + 'visible' => true, + ], + [ + 'field' => 'manages_users_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'class' => 'css-users', + 'title' => trans('admin/users/table.managed_users'), + 'visible' => true, + ], + [ + 'field' => 'manages_locations_count', + 'searchable' => false, + 'sortable' => true, + 'switchable' => true, + 'class' => 'css-location', + 'title' => trans('admin/users/table.managed_locations'), 'visible' => true, ], [ diff --git a/public/css/build/app.css b/public/css/build/app.css index e527c35aa..e48198e10 100644 --- a/public/css/build/app.css +++ b/public/css/build/app.css @@ -881,6 +881,8 @@ th.css-barcode > .th-inner, th.css-license > .th-inner, th.css-consumable > .th-inner, th.css-envelope > .th-inner, +th.css-users > .th-inner, +th.css-location > .th-inner, th.css-accessory > .th-inner { font-size: 0px; line-height: 0.75 !important; @@ -894,6 +896,8 @@ th.css-barcode > .th-inner::before, th.css-license > .th-inner::before, th.css-consumable > .th-inner::before, th.css-envelope > .th-inner::before, +th.css-users > .th-inner::before, +th.css-location > .th-inner::before, th.css-accessory > .th-inner::before { display: inline-block; font-size: 20px; @@ -908,6 +912,7 @@ th.css-padlock > .th-inner::before { font-size: 12px; } /** +BEGIN ICON TABLE HEADERS Set the font-weight css property as 900 (For Solid), 400 (Regular or Brands), 300 (Light for pro icons). **/ th.css-barcode > .th-inner::before { @@ -935,6 +940,17 @@ th.css-accessory > .th-inner::before { font-family: "Font Awesome 5 Free"; font-weight: 400; } +th.css-users > .th-inner::before { + content: "\f0c0"; + font-family: "Font Awesome 5 Free"; + font-size: 15px; +} +th.css-location > .th-inner::before { + content: "\f3c5"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} .small-box .inner { padding-left: 15px; padding-right: 15px; diff --git a/public/css/build/overrides.css b/public/css/build/overrides.css index fe8cb67e8..402329d6f 100644 --- a/public/css/build/overrides.css +++ b/public/css/build/overrides.css @@ -514,6 +514,8 @@ th.css-barcode > .th-inner, th.css-license > .th-inner, th.css-consumable > .th-inner, th.css-envelope > .th-inner, +th.css-users > .th-inner, +th.css-location > .th-inner, th.css-accessory > .th-inner { font-size: 0px; line-height: 0.75 !important; @@ -527,6 +529,8 @@ th.css-barcode > .th-inner::before, th.css-license > .th-inner::before, th.css-consumable > .th-inner::before, th.css-envelope > .th-inner::before, +th.css-users > .th-inner::before, +th.css-location > .th-inner::before, th.css-accessory > .th-inner::before { display: inline-block; font-size: 20px; @@ -541,6 +545,7 @@ th.css-padlock > .th-inner::before { font-size: 12px; } /** +BEGIN ICON TABLE HEADERS Set the font-weight css property as 900 (For Solid), 400 (Regular or Brands), 300 (Light for pro icons). **/ th.css-barcode > .th-inner::before { @@ -568,6 +573,17 @@ th.css-accessory > .th-inner::before { font-family: "Font Awesome 5 Free"; font-weight: 400; } +th.css-users > .th-inner::before { + content: "\f0c0"; + font-family: "Font Awesome 5 Free"; + font-size: 15px; +} +th.css-location > .th-inner::before { + content: "\f3c5"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} .small-box .inner { padding-left: 15px; padding-right: 15px; diff --git a/public/css/dist/all.css b/public/css/dist/all.css index ac39c8698..a8bd7025d 100644 --- a/public/css/dist/all.css +++ b/public/css/dist/all.css @@ -22408,6 +22408,8 @@ th.css-barcode > .th-inner, th.css-license > .th-inner, th.css-consumable > .th-inner, th.css-envelope > .th-inner, +th.css-users > .th-inner, +th.css-location > .th-inner, th.css-accessory > .th-inner { font-size: 0px; line-height: 0.75 !important; @@ -22421,6 +22423,8 @@ th.css-barcode > .th-inner::before, th.css-license > .th-inner::before, th.css-consumable > .th-inner::before, th.css-envelope > .th-inner::before, +th.css-users > .th-inner::before, +th.css-location > .th-inner::before, th.css-accessory > .th-inner::before { display: inline-block; font-size: 20px; @@ -22435,6 +22439,7 @@ th.css-padlock > .th-inner::before { font-size: 12px; } /** +BEGIN ICON TABLE HEADERS Set the font-weight css property as 900 (For Solid), 400 (Regular or Brands), 300 (Light for pro icons). **/ th.css-barcode > .th-inner::before { @@ -22462,6 +22467,17 @@ th.css-accessory > .th-inner::before { font-family: "Font Awesome 5 Free"; font-weight: 400; } +th.css-users > .th-inner::before { + content: "\f0c0"; + font-family: "Font Awesome 5 Free"; + font-size: 15px; +} +th.css-location > .th-inner::before { + content: "\f3c5"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} .small-box .inner { padding-left: 15px; padding-right: 15px; @@ -23686,6 +23702,8 @@ th.css-barcode > .th-inner, th.css-license > .th-inner, th.css-consumable > .th-inner, th.css-envelope > .th-inner, +th.css-users > .th-inner, +th.css-location > .th-inner, th.css-accessory > .th-inner { font-size: 0px; line-height: 0.75 !important; @@ -23699,6 +23717,8 @@ th.css-barcode > .th-inner::before, th.css-license > .th-inner::before, th.css-consumable > .th-inner::before, th.css-envelope > .th-inner::before, +th.css-users > .th-inner::before, +th.css-location > .th-inner::before, th.css-accessory > .th-inner::before { display: inline-block; font-size: 20px; @@ -23713,6 +23733,7 @@ th.css-padlock > .th-inner::before { font-size: 12px; } /** +BEGIN ICON TABLE HEADERS Set the font-weight css property as 900 (For Solid), 400 (Regular or Brands), 300 (Light for pro icons). **/ th.css-barcode > .th-inner::before { @@ -23740,6 +23761,17 @@ th.css-accessory > .th-inner::before { font-family: "Font Awesome 5 Free"; font-weight: 400; } +th.css-users > .th-inner::before { + content: "\f0c0"; + font-family: "Font Awesome 5 Free"; + font-size: 15px; +} +th.css-location > .th-inner::before { + content: "\f3c5"; + font-family: "Font Awesome 5 Free"; + font-size: 19px; + margin-bottom: 0px; +} .small-box .inner { padding-left: 15px; padding-right: 15px; diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 2798a8307..39d83f16e 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,8 +1,8 @@ { "/js/build/app.js": "/js/build/app.js?id=ea5f3edebafdb29b616d23fa89106080", "/css/dist/skins/skin-blue.css": "/css/dist/skins/skin-blue.css?id=f677207c6cf9678eb539abecb408c374", - "/css/build/overrides.css": "/css/build/overrides.css?id=be3c0a217bc6c0e0744f75faed784887", - "/css/build/app.css": "/css/build/app.css?id=a168b0a799aa800ee926bffa1b1a434a", + "/css/build/overrides.css": "/css/build/overrides.css?id=3d1aa807fc9395794b76f4cdab99c984", + "/css/build/app.css": "/css/build/app.css?id=40e80d931c21cde71b27be4c8eaaea62", "/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=dc383f8560a8d4adb51d44fb4043e03b", "/css/dist/skins/skin-orange.css": "/css/dist/skins/skin-orange.css?id=6f0563e726c2fe4fab4026daaa5bfdf2", "/css/dist/skins/skin-orange-dark.css": "/css/dist/skins/skin-orange-dark.css?id=620b684d9dd9d3bb5fdda00a3a2467c3", @@ -18,7 +18,7 @@ "/css/dist/skins/skin-green.css": "/css/dist/skins/skin-green.css?id=0a82a6ae6bb4e58fe62d162c4fb50397", "/css/dist/skins/skin-contrast.css": "/css/dist/skins/skin-contrast.css?id=da6c7997d9de2f8329142399f0ce50da", "/css/dist/skins/skin-red.css": "/css/dist/skins/skin-red.css?id=44bf834f2110504a793dadec132a5898", - "/css/dist/all.css": "/css/dist/all.css?id=6e9aa08b535262053b95aee495caa7df", + "/css/dist/all.css": "/css/dist/all.css?id=b51ba67a606c04c8f12fa30adcf33fd0", "/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7", "/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7", "/css/webfonts/fa-brands-400.ttf": "/css/webfonts/fa-brands-400.ttf?id=0141634c24336be626e05c8b77d1fa27", diff --git a/resources/assets/less/overrides.less b/resources/assets/less/overrides.less index 31e85ca26..12bce00bb 100644 --- a/resources/assets/less/overrides.less +++ b/resources/assets/less/overrides.less @@ -586,6 +586,8 @@ th.css-barcode > .th-inner, th.css-license > .th-inner, th.css-consumable > .th-inner, th.css-envelope > .th-inner, +th.css-users > .th-inner, +th.css-location > .th-inner, th.css-accessory > .th-inner { font-size: 0px; @@ -602,6 +604,8 @@ th.css-barcode > .th-inner::before, th.css-license > .th-inner::before, th.css-consumable > .th-inner::before, th.css-envelope > .th-inner::before, +th.css-users > .th-inner::before, +th.css-location > .th-inner::before, th.css-accessory > .th-inner::before { @@ -621,6 +625,7 @@ th.css-padlock > .th-inner::before } /** +BEGIN ICON TABLE HEADERS Set the font-weight css property as 900 (For Solid), 400 (Regular or Brands), 300 (Light for pro icons). **/ th.css-barcode > .th-inner::before @@ -643,12 +648,20 @@ th.css-envelope > .th-inner::before content: "\f0e0"; font-family: "Font Awesome 5 Free"; font-weight: 400; } - th.css-accessory > .th-inner::before { content: "\f11c"; font-family: "Font Awesome 5 Free"; font-weight: 400; } +th.css-users > .th-inner::before { + content: "\f0c0"; font-family: "Font Awesome 5 Free"; font-size: 15px; +} + +th.css-location > .th-inner::before { + content: "\f3c5"; font-family: "Font Awesome 5 Free"; font-size: 19px; margin-bottom: 0px; +} + + .small-box .inner { padding-left: 15px; padding-right: 15px; diff --git a/resources/lang/en-US/admin/users/table.php b/resources/lang/en-US/admin/users/table.php index b8b919bf2..7c5fb2cad 100644 --- a/resources/lang/en-US/admin/users/table.php +++ b/resources/lang/en-US/admin/users/table.php @@ -20,6 +20,7 @@ return array( 'lock_passwords' => 'Login details cannot be changed on this installation.', 'manager' => 'Manager', 'managed_locations' => 'Managed Locations', + 'managed_users' => 'Managed Users', 'name' => 'Name', 'nogroup' => 'No groups have been created yet. To add one, visit: ', 'notes' => 'Notes', diff --git a/resources/views/users/view.blade.php b/resources/views/users/view.blade.php index 3dbd9ff53..f79270d18 100755 --- a/resources/views/users/view.blade.php +++ b/resources/views/users/view.blade.php @@ -89,9 +89,9 @@ - @if ($user->managedLocations()->count() >= 0 ) + @if ($user->managedLocations->count() >= 0 )
  • - +
  • @endif - @can('update', $user) + @if ($user->managesUsers->count() >= 0 ) +
  • + + + +
  • + @endif + + + @can('update', $user)