This commit is contained in:
Bernd Bestel 2020-08-29 11:51:49 +02:00
parent 61f6dd4bde
commit 4f31627f8e
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
19 changed files with 208 additions and 156 deletions

View File

@ -82,6 +82,11 @@ Setting('DISABLE_BROWSER_BARCODE_CAMERA_SCANNING', false);
# Needs to be a number where Sunday = 0, Monday = 1 and so forth # Needs to be a number where Sunday = 0, Monday = 1 and so forth
Setting('MEAL_PLAN_FIRST_DAY_OF_WEEK', ''); Setting('MEAL_PLAN_FIRST_DAY_OF_WEEK', '');
# Default permissions for new users
# the array needs to contain the technical/constant names
# see the file controllers/Users/User.php for possible values
Setting('DEFAULT_PERMISSIONS', ['ADMIN']);
# Default user settings # Default user settings
# These settings can be changed per user, here the defaults # These settings can be changed per user, here the defaults
@ -172,5 +177,3 @@ Setting('FEATURE_FLAG_CHORES_ASSIGNMENTS', true);
# Feature settings # Feature settings
Setting('FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT', true); // When set to true opened items will be counted as missing from stock when calculating if a product is below its minimum. Setting('FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT', true); // When set to true opened items will be counted as missing from stock when calculating if a product is below its minimum.
Setting('FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA', true); // Enables the torch automaticaly in every camera barcode scanner. Setting('FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA', true); // Enables the torch automaticaly in every camera barcode scanner.
Setting('DEFAULT_PERMISSIONS', ['ADMIN']);

View File

@ -67,7 +67,10 @@ class BaseController
} }
$this->View->set('featureFlags', $constants); $this->View->set('featureFlags', $constants);
$this->View->set('permissions', User::PermissionList()); if (GROCY_AUTHENTICATED)
{
$this->View->set('permissions', User::PermissionList());
}
return $this->View->render($response, $page, $data); return $this->View->render($response, $page, $data);
} }

View File

@ -3,7 +3,6 @@
namespace Grocy\Controllers; namespace Grocy\Controllers;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Slim\Exception\HttpException; use Slim\Exception\HttpException;
@ -44,8 +43,7 @@ class ExceptionController extends BaseApiController
]; ];
if ($displayErrorDetails) { if ($displayErrorDetails) {
$data['error_details'] = [ $data['error_details'] = [
'stack_trace' => $exception->getTrace(), 'stack_trace' => $exception->getTraceAsString(),
'previous' => $exception->getPrevious(),
'file' => $exception->getFile(), 'file' => $exception->getFile(),
'line' => $exception->getLine(), 'line' => $exception->getLine(),
]; ];
@ -68,4 +66,4 @@ class ExceptionController extends BaseApiController
]); ]);
} }
} }

View File

@ -45,7 +45,7 @@ class User
protected function getPermissions(): Result protected function getPermissions(): Result
{ {
return $this->db->permission_check()->where('user_id', GROCY_USER_ID); return $this->db->user_permissions_resolved()->where('user_id', GROCY_USER_ID);
} }
public function hasPermission(string $permission): bool public function hasPermission(string $permission): bool
@ -69,7 +69,7 @@ class User
public function getPermissionList() public function getPermissionList()
{ {
return $this->db->uihelper_permission()->where('user_id', GROCY_USER_ID); return $this->db->uihelper_user_permissions()->where('user_id', GROCY_USER_ID);
} }
public static function hasPermissions(string ...$permissions) public static function hasPermissions(string ...$permissions)
@ -88,7 +88,4 @@ class User
$user = new User(); $user = new User();
return $user->getPermissionList(); return $user->getPermissionList();
} }
} }

View File

@ -38,9 +38,9 @@ class UsersController extends BaseController
public function PermissionList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) public function PermissionList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
User::checkPermission($request, User::PERMISSION_READ_USER); User::checkPermission($request, User::PERMISSION_READ_USER);
return $this->renderPage($response, 'permissions', [ return $this->renderPage($response, 'userpermissions', [
'user' => $this->getDatabase()->users($args['userId']), 'user' => $this->getDatabase()->users($args['userId']),
'permissions' => $this->getDatabase()->uihelper_permission() 'permissions' => $this->getDatabase()->uihelper_user_permissions()
->where('parent IS NULL')->where('user_id', $args['userId']), ->where('parent IS NULL')->where('user_id', $args['userId']),
]); ]);
} }

View File

@ -1850,20 +1850,44 @@ msgstr ""
msgid "Clear filter" msgid "Clear filter"
msgstr "" msgstr ""
msgid "Permissions of %s" msgid "Permissions for user %s"
msgstr "" msgstr ""
msgid "Are you sure you want to stop being an ADMIN?" msgid "Are you sure you want to stop being an ADMIN?"
msgstr "" msgstr ""
msgid "Permissions saved!" msgid "Permissions saved"
msgstr "" msgstr ""
msgid "You are not allowed to view this page!" msgid "You are not allowed to view this page"
msgstr ""
msgid "A server error occured while processing your request. You might found a bug in grocy!"
msgstr "" msgstr ""
msgid "Page not found" msgid "Page not found"
msgstr "" msgstr ""
msgid "Unauthorized"
msgstr ""
msgid "Error source"
msgstr ""
msgid "Error message"
msgstr ""
msgid "Stack trace"
msgstr ""
msgid "This page does not exists"
msgstr ""
msgid "You will be redirected to the default page in %s seconds"
msgstr ""
msgid "Server error"
msgstr ""
msgid "A server error occured while processing your request"
msgstr ""
msgid "If you think this is a bug, please report it"
msgstr ""

View File

@ -1,100 +1,109 @@
CREATE TABLE user_permissions CREATE TABLE user_permissions
( (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
permission_id INTEGER NOT NULL, permission_id INTEGER NOT NULL,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
UNIQUE (user_id, permission_id) UNIQUE (user_id, permission_id)
); );
CREATE TABLE permission_hierarchy CREATE TABLE permission_hierarchy
( (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
name TEXT NOT NULL UNIQUE, name TEXT NOT NULL UNIQUE,
/* if the user has the parent permission, parent INTEGER NULL -- If the user has the parent permission, the user also has the child permission
the user also has the child permission */
parent INTEGER NULL
); );
INSERT INTO permission_hierarchy(name, parent) INSERT INTO permission_hierarchy
VALUES ('ADMIN', NULL); (name, parent)
INSERT INTO user_permissions(permission_id, user_id) VALUES
SELECT (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'), id FROM users; ('ADMIN', NULL);
INSERT INTO user_permissions
(permission_id, user_id)
SELECT (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'), id
FROM users;
DROP VIEW IF EXISTS permission_tree;
CREATE VIEW permission_tree CREATE VIEW permission_tree
AS AS
WITH RECURSIVE perm AS (SELECT id AS root, id AS child, name, parent WITH RECURSIVE perm AS (
FROM permission_hierarchy SELECT id AS root, id AS child, name, parent
UNION FROM permission_hierarchy
SELECT perm.root, ph.id, ph.name, ph.id UNION
FROM permission_hierarchy ph, SELECT perm.root, ph.id, ph.name, ph.id
perm FROM permission_hierarchy ph, perm
WHERE ph.parent = perm.child WHERE ph.parent = perm.child
) )
SELECT root AS id, name AS name SELECT root AS id, name AS name
FROM perm; FROM perm;
DROP VIEW IF EXISTS permission_check; CREATE VIEW user_permissions_resolved
CREATE VIEW permission_check
AS AS
SELECT u.id AS id, -- dummy for LessQL SELECT
u.id AS user_id, u.id AS id, -- Dummy for LessQL
pt.name AS permission_name u.id AS user_id,
FROM permission_tree pt, pt.name AS permission_name
users u FROM permission_tree pt, users u
WHERE pt.id IN (SELECT permission_id FROM user_permissions sub_up WHERE sub_up.user_id = u.id); WHERE pt.id IN (SELECT permission_id FROM user_permissions sub_up WHERE sub_up.user_id = u.id);
CREATE VIEW uihelper_user_permissions
DROP VIEW IF EXISTS uihelper_permission;
CREATE VIEW uihelper_permission
AS AS
SELECT ph.id AS id, SELECT
u.id AS user_id, ph.id AS id,
ph.name AS permission_name, u.id AS user_id,
ph.id AS permission_id, ph.name AS permission_name,
(ph.name IN ph.id AS permission_id,
(SELECT pc.permission_name FROM permission_check pc WHERE pc.user_id = u.id) (ph.name IN (
) AS has_permission, SELECT pc.permission_name
FROM user_permissions_resolved pc
WHERE pc.user_id = u.id
)
) AS has_permission,
ph.parent AS parent ph.parent AS parent
FROM users u, FROM users u, permission_hierarchy ph;
permission_hierarchy ph;
INSERT INTO permission_hierarchy
INSERT INTO permission_hierarchy(name, parent) (name, parent)
VALUES ('CREATE_USER', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'));
INSERT INTO permission_hierarchy(name, parent)
VALUES ('EDIT_USER', last_insert_rowid());
INSERT INTO permission_hierarchy(name, parent)
VALUES ('READ_USER', last_insert_rowid()),
('EDIT_SELF', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'));
INSERT INTO permission_hierarchy(name, parent)
VALUES VALUES
-- Batteries ('CREATE_USER', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'));
('BATTERY_UNDO_TRACK_CHARGE_CYCLE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('BATTERY_TRACK_CHARGE_CYCLE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')), INSERT INTO permission_hierarchy
-- Chores (name, parent)
('CHORE_TRACK', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')), VALUES
('CHORE_TRACK_OTHERS', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')), ('EDIT_USER', last_insert_rowid());
('CHORE_EDIT', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('CHORE_UNDO', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')), INSERT INTO permission_hierarchy
-- Files (name, parent)
('UPLOAD_FILE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')), VALUES
('DELETE_FILE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')), ('READ_USER', last_insert_rowid()),
-- master data ('EDIT_SELF', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'));
('MASTER_DATA_EDIT', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
-- Tasks INSERT INTO permission_hierarchy
('TASKS_UNDO', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')), (name, parent)
('TASKS_MARK_COMPLETED', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')), VALUES
-- Stock / Products -- Batteries
('STOCK_EDIT', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')), ('BATTERY_UNDO_TRACK_CHARGE_CYCLE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('STOCK_TRANSFER', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')), ('BATTERY_TRACK_CHARGE_CYCLE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('STOCK_CORRECTION', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')), -- Chores
('PRODUCT_PURCHASE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')), ('CHORE_TRACK', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('PRODUCT_CONSUME', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')), ('CHORE_TRACK_OTHERS', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('PRODUCT_OPEN', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')), ('CHORE_EDIT', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
-- shopping list ('CHORE_UNDO', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('SHOPPINGLIST_ITEMS_ADD', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')), -- Files
('SHOPPINGLIST_ITEMS_DELETE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')); ('UPLOAD_FILE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('DELETE_FILE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
-- master data
('MASTER_DATA_EDIT', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
-- Tasks
('TASKS_UNDO', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('TASKS_MARK_COMPLETED', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
-- Stock / Products
('STOCK_EDIT', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('STOCK_TRANSFER', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('STOCK_CORRECTION', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('PRODUCT_PURCHASE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('PRODUCT_CONSUME', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('PRODUCT_OPEN', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
-- shopping list
('SHOPPINGLIST_ITEMS_ADD', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN')),
('SHOPPINGLIST_ITEMS_DELETE', (SELECT id FROM permission_hierarchy WHERE name = 'ADMIN'));

View File

@ -663,7 +663,7 @@ $.extend(true, $.fn.dataTable.defaults, {
} }
}); });
$(Grocy.Permissions).each(function (index, item) $(Grocy.UserPermissions).each(function (index, item)
{ {
if(item.has_permission == 0) if(item.has_permission == 0)
{ {

View File

@ -19,17 +19,18 @@ $('#permission-save').click(
}).map(function () { }).map(function () {
return $(this).data('perm-id'); return $(this).data('perm-id');
}).toArray(); }).toArray();
Grocy.Api.Put('users/' + edited_user_id + '/permissions', { Grocy.Api.Put('users/' + Grocy.EditObjectId + '/permissions', {
'permissions': permission_list, 'permissions': permission_list,
}, function (result) { }, function (result) {
toastr.success(__t("Permissions saved!")); toastr.success(__t("Permissions saved"));
}, function (xhr) { }, function (xhr) {
toastr.error(__t(JSON.parse(xhr.response).error_message)); toastr.error(__t(JSON.parse(xhr.response).error_message));
} }
); );
} }
); );
if (edited_user_id == Grocy.UserId) {
if (Grocy.EditObjectId == Grocy.UserId) {
$('input.permission-cb[name=ADMIN]').click(function () { $('input.permission-cb[name=ADMIN]').click(function () {
if (!this.checked) { if (!this.checked) {
if (!confirm(__t('Are you sure you want to stop being an ADMIN?'))) { if (!confirm(__t('Are you sure you want to stop being an ADMIN?'))) {

View File

@ -33,9 +33,7 @@ $app->group('', function(RouteCollectorProxy $group)
// User routes // User routes
$group->get('/users', '\Grocy\Controllers\UsersController:UsersList'); $group->get('/users', '\Grocy\Controllers\UsersController:UsersList');
$group->get('/user/{userId}', '\Grocy\Controllers\UsersController:UserEditForm'); $group->get('/user/{userId}', '\Grocy\Controllers\UsersController:UserEditForm');
$group->get('/user/{userId}/permissions', '\Grocy\Controllers\UsersController:PermissionList');
$group->get('/user/permissions/{userId}', '\Grocy\Controllers\UsersController:PermissionList');
// Stock routes // Stock routes
if (GROCY_FEATURE_FLAG_STOCK) if (GROCY_FEATURE_FLAG_STOCK)
@ -171,7 +169,6 @@ $app->group('/api', function(RouteCollectorProxy $group)
$group->post('/users', '\Grocy\Controllers\UsersApiController:CreateUser'); $group->post('/users', '\Grocy\Controllers\UsersApiController:CreateUser');
$group->put('/users/{userId}', '\Grocy\Controllers\UsersApiController:EditUser'); $group->put('/users/{userId}', '\Grocy\Controllers\UsersApiController:EditUser');
$group->delete('/users/{userId}', '\Grocy\Controllers\UsersApiController:DeleteUser'); $group->delete('/users/{userId}', '\Grocy\Controllers\UsersApiController:DeleteUser');
$group->get('/users/{userId}/permissions', '\Grocy\Controllers\UsersApiController:ListPermissions'); $group->get('/users/{userId}/permissions', '\Grocy\Controllers\UsersApiController:ListPermissions');
$group->post('/users/{userId}/permissions', '\Grocy\Controllers\UsersApiController:AddPermission'); $group->post('/users/{userId}/permissions', '\Grocy\Controllers\UsersApiController:AddPermission');
$group->put('/users/{userId}/permissions', '\Grocy\Controllers\UsersApiController:SetPermissions'); $group->put('/users/{userId}/permissions', '\Grocy\Controllers\UsersApiController:SetPermissions');

View File

@ -4,12 +4,12 @@
</label> </label>
<div id="permission-sub-{{ $perm->permission_name }}"> <div id="permission-sub-{{ $perm->permission_name }}">
<ul> <ul>
@foreach($perm->uihelper_permissionList(array('user_id' => $user->id))->via('parent') as $p) @foreach($perm->uihelper_user_permissionsList(array('user_id' => $user->id))->via('parent') as $p)
<li> <li>
@include('components.permission_select', array( @include('components.userpermission_select', array(
'perm' => $p 'perm' => $p
)) ))
</li> </li>
@endforeach @endforeach
</ul> </ul>
</div> </div>

View File

@ -1,11 +1,11 @@
@extends('errors.base') @extends('errors.base')
@section('title', $__t('You are not allowed to view this page!')) @section('title', $__t('Unauthorized'))
@section('content') @section('content')
<div class="row"> <div class="row">
<div class="col-xs-12 col-md-6 text-center"> <div class="col">
<h2 class="title">@yield('title')</h2> <div class="alert alert-danger">{{ $__t('You are not allowed to view this page') }}</div>
</div> </div>
</div> </div>
@stop @stop

View File

@ -1,14 +1,13 @@
@extends('errors.base') @extends('errors.base')
@section('title', $__t('Page not found')) @section('title', $__t('Page not found'))
@section('content') @section('content')
<meta http-equiv="refresh" content="5;url=/"> <meta http-equiv="refresh" content="5;url={{$U('/')}}">
<div class="row"> <div class="row">
<div class="col-xs-12 col-md-6"> <div class="col">
<h2 class="title">@yield('title')</h2> <div class="alert alert-danger">{{ $__t('This page does not exists') }}</div>
<div> <div>{{ $__t('You will be redirected to the default page in %s seconds', '5') }}</div>
{!! nl2br(e($exception->getTraceAsString())) !!}
</div>
</div> </div>
</div> </div>
@stop @stop

View File

@ -1,3 +1,16 @@
@extends('errors.base') @extends('errors.base')
@section('title', $__t('A server error occured while processing your request. You might found a bug in grocy!')) @section('title', $__t('Server error'))
@section('content')
<div class="row">
<div class="col">
<div class="alert alert-danger">{{ $__t('A server error occured while processing your request') }}</div>
<div class="alert alert-warning">
{{ $__t('If you think this is a bug, please report it') }}<br>
&rarr; <a target="_blank" href="https://github.com/grocy/grocy/issues">https://github.com/grocy/grocy/issues</a>
</div>
</div>
</div>
@parent
@stop

View File

@ -2,10 +2,18 @@
@section('content') @section('content')
<div class="row"> <div class="row">
<div class="col-xs-12 col-md-6"> <div class="col">
<h2 class="title">@yield('title')</h2>
<div> <div>
{!! nl2br(e($exception->getTraceAsString())) !!} <h6>{{ $__t('Error source') }}</h6>
<pre><code>{!! $exception->getFile() !!}:{!! $exception->getLine() !!}</code></pre>
</div>
<div>
<h6>{{ $__t('Error message') }}</h6>
<pre><code>{!! $exception->getMessage() !!}</code></pre>
</div>
<div>
<h6>{{ $__t('Stack trace') }}</h6>
<pre><code>{!! $exception->getTraceAsString() !!}</code></pre>
</div> </div>
</div> </div>
</div> </div>

View File

@ -59,11 +59,10 @@
Grocy.GettextPo = {!! $GettextPo !!}; Grocy.GettextPo = {!! $GettextPo !!};
Grocy.FeatureFlags = {!! json_encode($featureFlags) !!}; Grocy.FeatureFlags = {!! json_encode($featureFlags) !!};
Grocy.Permissions = {!! json_encode($permissions) !!};
@if (GROCY_AUTHENTICATED) @if (GROCY_AUTHENTICATED)
Grocy.UserSettings = {!! json_encode($userSettings) !!}; Grocy.UserSettings = {!! json_encode($userSettings) !!};
Grocy.UserId = {{ GROCY_USER_ID }}; Grocy.UserId = {{ GROCY_USER_ID }};
Grocy.UserPermissions = {!! json_encode($permissions) !!};
@else @else
Grocy.UserSettings = { }; Grocy.UserSettings = { };
Grocy.UserId = -1; Grocy.UserId = -1;

View File

@ -1,33 +0,0 @@
@extends('layout.default')
@section('title', $__t('Permissions of %s', GetUserDisplayName($user)))
@section('activeNav', '')
@section('viewJsName', 'permissions')
@push('pageScripts')
<script>
var edited_user_id = {{ $user->id }};
</script>
@endpush
@section('content')
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
</div>
</div>
<hr>
<div class="row">
<div>
<ul>
@foreach($permissions as $perm)
<li>
@include('components.permission_select', array(
'permission' => $perm
))
</li>
@endforeach
</ul>
<button id="permission-save" class="btn btn-success" type="submit">{{ $__t('Save') }}</button>
</div>
</div>
@endsection

View File

@ -0,0 +1,34 @@
@extends('layout.default')
@section('title', $__t('Permissions for user %s', GetUserDisplayName($user)))
@section('activeNav', '')
@section('viewJsName', 'userpermissions')
@push('pageScripts')
<script>
Grocy.EditObjectId = {{ $user->id }};
</script>
@endpush
@section('content')
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
</div>
</div>
<hr>
<div class="row mt-3">
<div class="col">
<ul>
@foreach($permissions as $perm)
<li>
@include('components.userpermission_select', array(
'permission' => $perm
))
</li>
@endforeach
</ul>
<button id="permission-save" class="btn btn-success" type="submit">{{ $__t('Save') }}</button>
</div>
</div>
@endsection

View File

@ -47,7 +47,7 @@
<a class="btn btn-info btn-sm" href="{{ $U('/user/') }}{{ $user->id }}"> <a class="btn btn-info btn-sm" href="{{ $U('/user/') }}{{ $user->id }}">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</a> </a>
<a class="btn btn-info btn-sm" href="{{ $U('/user/permissions/') }}{{ $user->id }}"> <a class="btn btn-info btn-sm" href="{{ $U('/user/' . $user->id . '/permissions') }}">
<i class="fas fa-lock"></i> <i class="fas fa-lock"></i>
</a> </a>
<a class="btn btn-danger btn-sm user-delete-button @if($user->id == GROCY_USER_ID) disabled @endif" href="#" data-user-id="{{ $user->id }}" data-user-username="{{ $user->username }}"> <a class="btn btn-danger btn-sm user-delete-button @if($user->id == GROCY_USER_ID) disabled @endif" href="#" data-user-id="{{ $user->id }}" data-user-username="{{ $user->username }}">