Explorar el Código

правки в пользователях

Stas hace 1 mes
padre
commit
6bca52560a

BIN
.DS_Store


+ 61 - 1
app/Http/Controllers/Admin/UserAdminController.php

@@ -6,6 +6,8 @@ namespace App\Http\Controllers\Admin;
  * UserAdminController — управление пользователями и правами в административной панели.
  * UserAdminController — управление пользователями и правами в административной панели.
  *
  *
  * index()       — список всех пользователей (только для users.view)
  * index()       — список всех пользователей (только для users.view)
+ * create()      — форма создания нового пользователя с назначением роли и прав
+ * store()       — сохранение нового пользователя
  * edit()        — форма редактирования роли и прав конкретного пользователя
  * edit()        — форма редактирования роли и прав конкретного пользователя
  * update()      — сохранение роли и индивидуальных override-прав
  * update()      — сохранение роли и индивидуальных override-прав
  * destroy()     — удаление пользователя (superadmin удалить нельзя)
  * destroy()     — удаление пользователя (superadmin удалить нельзя)
@@ -34,6 +36,64 @@ class UserAdminController extends Controller
         return view('admin.users.index', compact('users', 'roleLabels'));
         return view('admin.users.index', compact('users', 'roleLabels'));
     }
     }
 
 
+    public function create(): View
+    {
+        $this->authorize('users.manage');
+
+        $permissions = PermissionService::all();
+        $roleLabels = PermissionService::roleLabels();
+
+        return view('admin.users.create', compact('permissions', 'roleLabels'));
+    }
+
+    public function store(Request $request): RedirectResponse
+    {
+        $this->authorize('users.manage');
+
+        $request->validate([
+            'name' => 'required|string|max:255',
+            'email' => 'required|email|max:255|unique:users,email',
+            'password' => 'required|string|min:8|confirmed',
+            'role' => 'nullable|in:superadmin,admin,editor,viewer',
+            'permissions' => 'nullable|array',
+            'permissions.*' => 'nullable|in:allow,deny',
+        ]);
+
+        $newRole = $request->input('role');
+
+        // Только superadmin может назначать роль superadmin или admin
+        if (in_array($newRole, ['superadmin', 'admin']) && Auth::user()->role !== 'superadmin') {
+            abort(403, 'Только суперадминистратор может назначать эту роль.');
+        }
+
+        $user = User::create([
+            'name' => $request->input('name'),
+            'email' => $request->input('email'),
+            'password' => $request->input('password'),
+            'role' => $newRole ?: null,
+            'is_admin' => ! empty($newRole),
+        ]);
+
+        $allKeys = PermissionService::keys();
+        foreach ($request->input('permissions', []) as $key => $action) {
+            if (! in_array($key, $allKeys, true)) {
+                continue;
+            }
+            if (! in_array($action, ['allow', 'deny'], true)) {
+                continue;
+            }
+
+            UserPermission::create([
+                'user_id' => $user->id,
+                'permission' => $key,
+                'action' => $action,
+            ]);
+        }
+
+        return redirect()->route('admin.users.index')
+            ->with('success', 'Пользователь «'.$user->name.'» создан.');
+    }
+
     public function edit(User $user): View
     public function edit(User $user): View
     {
     {
         $this->authorize('users.manage');
         $this->authorize('users.manage');
@@ -56,7 +116,7 @@ class UserAdminController extends Controller
         $request->validate([
         $request->validate([
             'role' => 'nullable|in:superadmin,admin,editor,viewer',
             'role' => 'nullable|in:superadmin,admin,editor,viewer',
             'permissions' => 'nullable|array',
             'permissions' => 'nullable|array',
-            'permissions.*' => 'in:allow,deny',
+            'permissions.*' => 'nullable|in:allow,deny',
         ]);
         ]);
 
 
         // superadmin не может снять роль superadmin сам у себя, если он единственный
         // superadmin не может снять роль superadmin сам у себя, если он единственный

+ 191 - 0
resources/views/admin/users/create.blade.php

@@ -0,0 +1,191 @@
+{{-- Вьюха: Создание нового пользователя с назначением роли и прав.
+     Контроллер: Admin\UserAdminController@create / store
+     Переменные: $permissions (все права по группам), $roleLabels (метки ролей)
+--}}
+@extends('admin.layout')
+
+@section('title', 'Новый пользователь')
+
+@section('content_header')
+    <div class="d-flex justify-content-between align-items-center">
+        <h1 class="m-0">Новый пользователь</h1>
+        <a href="{{ route('admin.users.index') }}" class="btn btn-default btn-sm">
+            <i class="fas fa-arrow-left"></i> Назад
+        </a>
+    </div>
+@stop
+
+@section('breadcrumb')
+    <li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}">Главная</a></li>
+    <li class="breadcrumb-item"><a href="{{ route('admin.users.index') }}">Пользователи</a></li>
+    <li class="breadcrumb-item active">Новый пользователь</li>
+@stop
+
+@section('content')
+    @if($errors->any())
+        <div class="alert alert-danger alert-dismissible">
+            <button type="button" class="close" data-dismiss="alert">&times;</button>
+            <ul class="mb-0 pl-3">
+                @foreach($errors->all() as $error)
+                    <li>{{ $error }}</li>
+                @endforeach
+            </ul>
+        </div>
+    @endif
+
+    <form action="{{ route('admin.users.store') }}" method="POST">
+        @csrf
+
+        <div class="row">
+            {{-- Левая колонка: данные + роль --}}
+            <div class="col-md-4">
+
+                {{-- Данные пользователя --}}
+                <div class="card card-primary card-outline">
+                    <div class="card-header"><h3 class="card-title">Учётные данные</h3></div>
+                    <div class="card-body">
+                        <div class="form-group">
+                            <label for="name">Имя <span class="text-danger">*</span></label>
+                            <input type="text" id="name" name="name" class="form-control @error('name') is-invalid @enderror"
+                                   value="{{ old('name') }}" placeholder="Иван Иванов" required>
+                            @error('name')
+                                <div class="invalid-feedback">{{ $message }}</div>
+                            @enderror
+                        </div>
+                        <div class="form-group">
+                            <label for="email">Email <span class="text-danger">*</span></label>
+                            <input type="email" id="email" name="email" class="form-control @error('email') is-invalid @enderror"
+                                   value="{{ old('email') }}" placeholder="user@example.com" required>
+                            @error('email')
+                                <div class="invalid-feedback">{{ $message }}</div>
+                            @enderror
+                        </div>
+                        <div class="form-group">
+                            <label for="password">Пароль <span class="text-danger">*</span></label>
+                            <input type="password" id="password" name="password"
+                                   class="form-control @error('password') is-invalid @enderror"
+                                   placeholder="Минимум 8 символов" required>
+                            @error('password')
+                                <div class="invalid-feedback">{{ $message }}</div>
+                            @enderror
+                        </div>
+                        <div class="form-group mb-0">
+                            <label for="password_confirmation">Подтверждение пароля <span class="text-danger">*</span></label>
+                            <input type="password" id="password_confirmation" name="password_confirmation"
+                                   class="form-control" placeholder="Повторите пароль" required>
+                        </div>
+                    </div>
+                </div>
+
+                {{-- Роль --}}
+                <div class="card card-primary card-outline">
+                    <div class="card-header"><h3 class="card-title">Роль</h3></div>
+                    <div class="card-body">
+                        <p class="text-muted" style="font-size:13px">
+                            Роль задаёт базовый набор прав. Индивидуальные overrides (справа) могут дополнить или ограничить их.
+                        </p>
+                        <div class="form-group mb-0">
+                            @foreach($roleLabels as $roleKey => $roleInfo)
+                                {{-- superadmin может назначить только суперадмин --}}
+                                @if($roleKey === 'superadmin' && auth()->user()->role !== 'superadmin')
+                                    @continue
+                                @endif
+                                {{-- admin может назначить только суперадмин --}}
+                                @if($roleKey === 'admin' && auth()->user()->role !== 'superadmin')
+                                    @continue
+                                @endif
+                                <div class="custom-control custom-radio mb-2">
+                                    <input type="radio" class="custom-control-input" id="role_{{ $roleKey }}"
+                                           name="role" value="{{ $roleKey }}" {{ old('role') === $roleKey ? 'checked' : '' }}>
+                                    <label class="custom-control-label" for="role_{{ $roleKey }}">
+                                        <span class="badge badge-{{ $roleInfo['color'] }}">{{ $roleInfo['label'] }}</span>
+                                        <div style="font-size:11px;color:var(--muted,#666);margin-top:3px">
+                                            @switch($roleKey)
+                                                @case('superadmin') Все права, управляет всеми @break
+                                                @case('admin') Всё кроме управления admins @break
+                                                @case('editor') Автомобили + контент сайта @break
+                                                @case('viewer') Только просмотр @break
+                                            @endswitch
+                                        </div>
+                                    </label>
+                                </div>
+                            @endforeach
+                            <div class="custom-control custom-radio mt-2">
+                                <input type="radio" class="custom-control-input" id="role_none"
+                                       name="role" value="" {{ ! old('role') ? 'checked' : '' }}>
+                                <label class="custom-control-label" for="role_none">
+                                    <span class="badge badge-secondary">Без роли</span>
+                                    <div style="font-size:11px;color:var(--muted,#666);margin-top:3px">Нет доступа в панель</div>
+                                </label>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="card-footer">
+                        <button type="submit" class="btn btn-success btn-block">
+                            <i class="fas fa-user-plus"></i> Создать пользователя
+                        </button>
+                        <a href="{{ route('admin.users.index') }}" class="btn btn-secondary btn-block mt-1">Отмена</a>
+                    </div>
+                </div>
+            </div>
+
+            {{-- Правая колонка: индивидуальные overrides --}}
+            <div class="col-md-8">
+                <div class="card card-secondary card-outline">
+                    <div class="card-header">
+                        <h3 class="card-title">Индивидуальные overrides</h3>
+                        <div class="card-tools">
+                            <small class="text-muted">
+                                <span class="badge badge-success">Allow</span> — разрешить поверх роли &nbsp;
+                                <span class="badge badge-danger">Deny</span> — запретить поверх роли &nbsp;
+                                <span class="badge badge-secondary">Роль</span> — без override
+                            </small>
+                        </div>
+                    </div>
+                    <div class="card-body">
+                        @foreach($permissions as $groupName => $groupPerms)
+                            <h6 class="text-uppercase text-muted mb-2" style="font-size:11px;letter-spacing:.1em">{{ $groupName }}</h6>
+                            <table class="table table-sm table-bordered mb-3">
+                                <thead>
+                                    <tr>
+                                        <th>Право</th>
+                                        <th style="width:260px" class="text-center">Override</th>
+                                    </tr>
+                                </thead>
+                                <tbody>
+                                    @foreach($groupPerms as $key => $label)
+                                        <tr>
+                                            <td>
+                                                {{ $label }}
+                                                <div><code style="font-size:10px">{{ $key }}</code></div>
+                                            </td>
+                                            <td class="text-center">
+                                                <div class="d-flex justify-content-center" style="gap:6px">
+                                                    <label class="mb-0">
+                                                        <input type="radio" name="permissions[{{ $key }}]" value="allow"
+                                                               {{ old('permissions.'.$key) === 'allow' ? 'checked' : '' }}>
+                                                        <span class="badge badge-success ml-1">Allow</span>
+                                                    </label>
+                                                    <label class="mb-0">
+                                                        <input type="radio" name="permissions[{{ $key }}]" value="deny"
+                                                               {{ old('permissions.'.$key) === 'deny' ? 'checked' : '' }}>
+                                                        <span class="badge badge-danger ml-1">Deny</span>
+                                                    </label>
+                                                    <label class="mb-0">
+                                                        <input type="radio" name="permissions[{{ $key }}]" value=""
+                                                               {{ ! old('permissions.'.$key) ? 'checked' : '' }}>
+                                                        <span class="badge badge-secondary ml-1">Роль</span>
+                                                    </label>
+                                                </div>
+                                            </td>
+                                        </tr>
+                                    @endforeach
+                                </tbody>
+                            </table>
+                        @endforeach
+                    </div>
+                </div>
+            </div>
+        </div>
+    </form>
+@stop

+ 8 - 1
resources/views/admin/users/index.blade.php

@@ -4,7 +4,14 @@
 @section('title', 'Пользователи')
 @section('title', 'Пользователи')
 
 
 @section('content_header')
 @section('content_header')
-    <h1 class="m-0">Пользователи</h1>
+    <div class="d-flex justify-content-between align-items-center">
+        <h1 class="m-0">Пользователи</h1>
+        @can('users.manage')
+            <a href="{{ route('admin.users.create') }}" class="btn btn-success btn-sm">
+                <i class="fas fa-user-plus"></i> Создать пользователя
+            </a>
+        @endcan
+    </div>
 @stop
 @stop
 
 
 @section('breadcrumb')
 @section('breadcrumb')

+ 2 - 0
routes/web.php

@@ -90,6 +90,8 @@ Route::prefix('admin')->name('admin.')->group(function () {
 
 
         // Пользователи и права
         // Пользователи и права
         Route::get('users', [UserAdminController::class, 'index'])->name('users.index');
         Route::get('users', [UserAdminController::class, 'index'])->name('users.index');
+        Route::get('users/create', [UserAdminController::class, 'create'])->name('users.create');
+        Route::post('users', [UserAdminController::class, 'store'])->name('users.store');
         Route::get('users/{user}/edit', [UserAdminController::class, 'edit'])->name('users.edit');
         Route::get('users/{user}/edit', [UserAdminController::class, 'edit'])->name('users.edit');
         Route::put('users/{user}', [UserAdminController::class, 'update'])->name('users.update');
         Route::put('users/{user}', [UserAdminController::class, 'update'])->name('users.update');
         Route::delete('users/{user}', [UserAdminController::class, 'destroy'])->name('users.destroy');
         Route::delete('users/{user}', [UserAdminController::class, 'destroy'])->name('users.destroy');