- LoginController: 로그인 처리 개선 - UserPermissionController: 권한 관리 기능 개선 - User 모델: 권한 관련 메서드 추가 - AuthService: 인증 서비스 로직 개선 - Middleware 추가 - bootstrap/app.php: 미들웨어 등록 - 권한 관리 뷰 개선 (user-permissions, users)
214 lines
11 KiB
PHP
214 lines
11 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', '개인 권한 관리')
|
|
|
|
@section('content')
|
|
<!-- 페이지 헤더 -->
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h1 class="text-2xl font-bold text-gray-800">개인 권한 관리</h1>
|
|
</div>
|
|
|
|
<!-- 사용자 선택 -->
|
|
<div class="bg-white rounded-lg shadow-sm mb-6">
|
|
<div class="px-6 py-4">
|
|
@if($requireTenant)
|
|
{{-- 전체 테넌트 선택 시: 테넌트 선택 안내 --}}
|
|
<div class="text-center py-8">
|
|
<div class="text-yellow-500 text-4xl mb-3">⚠️</div>
|
|
<p class="text-gray-700 font-medium mb-2">테넌트를 선택해주세요</p>
|
|
<p class="text-gray-500 text-sm">개인 권한을 관리하려면 상단 헤더에서 특정 테넌트를 선택해야 합니다.</p>
|
|
</div>
|
|
@else
|
|
{{-- 특정 테넌트 선택 시: 사용자 목록 표시 --}}
|
|
@if($users->isEmpty())
|
|
<div class="text-center py-8">
|
|
<div class="text-gray-400 text-4xl mb-3">👤</div>
|
|
<p class="text-gray-700 font-medium mb-2">사용자가 없습니다</p>
|
|
<p class="text-gray-500 text-sm">선택한 테넌트에 등록된 사용자가 없습니다.</p>
|
|
</div>
|
|
@else
|
|
<div class="flex flex-wrap items-center gap-3">
|
|
<span class="text-sm font-medium text-gray-700">사용자 선택:</span>
|
|
@php
|
|
$autoSelectId = $selectedUserId ?? ($users->first()->id ?? null);
|
|
@endphp
|
|
@foreach($users as $user)
|
|
<button
|
|
type="button"
|
|
class="user-button px-4 py-2 text-sm font-medium rounded-lg border transition-colors bg-white text-gray-700 border-gray-300 hover:bg-gray-50 inline-flex items-center gap-2"
|
|
data-user-id="{{ $user->id }}"
|
|
data-user-name="{{ $user->name }}"
|
|
data-user-login="{{ $user->user_id }}"
|
|
data-auto-select="{{ $user->id == $autoSelectId ? 'true' : 'false' }}"
|
|
data-context-menu="user"
|
|
data-entity-id="{{ $user->id }}"
|
|
data-entity-name="{{ $user->name }}"
|
|
hx-get="/api/admin/user-permissions/matrix"
|
|
hx-target="#permission-matrix"
|
|
hx-include="[name='guard_name']"
|
|
hx-vals='{"user_id": {{ $user->id }}}'
|
|
onclick="selectUser(this)"
|
|
>
|
|
{{ $user->name }}
|
|
<span class="user-id-badge px-1.5 py-0.5 text-xs bg-gray-200 text-gray-600 rounded"> {{ $user->user_id }} </span>
|
|
@php
|
|
$showWebCount = auth()->user()?->is_super_admin && $user->web_permission_count > 0;
|
|
$showApiCount = $user->api_permission_count > 0;
|
|
@endphp
|
|
@if($showWebCount || $showApiCount)
|
|
<span class="permission-counts inline-flex items-center gap-1 text-[10px]">
|
|
@if($showWebCount)
|
|
<span class="px-1 py-0.5 bg-blue-100 text-blue-600 rounded">web:{{ $user->web_permission_count }}</span>
|
|
@endif
|
|
@if($showApiCount)
|
|
<span class="px-1 py-0.5 bg-green-100 text-green-600 rounded">api:{{ $user->api_permission_count }}</span>
|
|
@endif
|
|
</span>
|
|
@endif
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 액션 버튼 -->
|
|
<div class="bg-white rounded-lg shadow-sm mb-6" id="action-buttons" style="display: none;">
|
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-sm font-medium text-gray-700" id="selected-user-name">선택된 사용자</span>
|
|
<div class="flex items-center gap-2">
|
|
<input type="hidden" name="user_id" id="userIdInput" value="">
|
|
|
|
@if(auth()->user()?->is_super_admin)
|
|
<!-- Guard 선택 (라디오 버튼) - 슈퍼관리자만 표시 -->
|
|
<div class="flex items-center gap-4">
|
|
<label class="inline-flex items-center cursor-pointer">
|
|
<input
|
|
type="radio"
|
|
name="guard_name"
|
|
value="api"
|
|
checked
|
|
class="w-4 h-4 text-green-600 bg-gray-100 border-gray-300 focus:ring-green-500"
|
|
onchange="reloadPermissions()"
|
|
>
|
|
<span class="ml-2 px-2 py-1 text-sm font-medium bg-green-100 text-green-700 rounded">API</span>
|
|
</label>
|
|
<label class="inline-flex items-center cursor-pointer">
|
|
<input
|
|
type="radio"
|
|
name="guard_name"
|
|
value="web"
|
|
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 focus:ring-blue-500"
|
|
onchange="reloadPermissions()"
|
|
>
|
|
<span class="ml-2 px-2 py-1 text-sm font-medium bg-blue-100 text-blue-700 rounded">Web</span>
|
|
</label>
|
|
</div>
|
|
|
|
<!-- 구분선 -->
|
|
<div class="h-8 w-px bg-gray-300 mx-1"></div>
|
|
@else
|
|
<!-- 일반 관리자: API만 사용 -->
|
|
<input type="hidden" name="guard_name" value="api">
|
|
<span class="px-2 py-1 text-sm font-medium bg-green-100 text-green-700 rounded">API 권한 관리</span>
|
|
<div class="h-8 w-px bg-gray-300 mx-1"></div>
|
|
@endif
|
|
|
|
<button
|
|
type="button"
|
|
class="px-4 py-2 bg-green-600 text-white text-sm font-medium rounded-lg hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500"
|
|
hx-post="/api/admin/user-permissions/allow-all"
|
|
hx-target="#permission-matrix"
|
|
hx-include="[name='user_id'],[name='guard_name']"
|
|
>
|
|
전체 허용
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="px-4 py-2 bg-red-600 text-white text-sm font-medium rounded-lg hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500"
|
|
hx-post="/api/admin/user-permissions/deny-all"
|
|
hx-target="#permission-matrix"
|
|
hx-include="[name='user_id'],[name='guard_name']"
|
|
>
|
|
전체 거부
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="px-4 py-2 bg-gray-500 text-white text-sm font-medium rounded-lg hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-400"
|
|
hx-post="/api/admin/user-permissions/reset"
|
|
hx-target="#permission-matrix"
|
|
hx-include="[name='user_id'],[name='guard_name']"
|
|
title="모든 메뉴의 조회(view) 권한만 허용"
|
|
>
|
|
초기화
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 권한 매트릭스 테이블 -->
|
|
<div id="permission-matrix" class="bg-white rounded-lg shadow-sm">
|
|
@include('user-permissions.partials.empty-state')
|
|
</div>
|
|
|
|
<script>
|
|
function selectUser(button) {
|
|
// 모든 버튼의 활성 상태 제거
|
|
document.querySelectorAll('.user-button').forEach(btn => {
|
|
btn.classList.remove('bg-blue-700', 'text-white', 'border-blue-700', 'hover:bg-blue-800');
|
|
btn.classList.add('bg-white', 'text-gray-700', 'border-gray-300', 'hover:bg-gray-50');
|
|
// user-id 뱃지 스타일 복원
|
|
const badge = btn.querySelector('.user-id-badge');
|
|
if (badge) {
|
|
badge.classList.remove('bg-blue-500', 'text-white');
|
|
badge.classList.add('bg-gray-200', 'text-gray-600');
|
|
}
|
|
});
|
|
|
|
// 클릭된 버튼 활성화
|
|
button.classList.remove('bg-white', 'text-gray-700', 'border-gray-300', 'hover:bg-gray-50');
|
|
button.classList.add('bg-blue-700', 'text-white', 'border-blue-700', 'hover:bg-blue-800');
|
|
// user-id 뱃지 스타일 변경
|
|
const activeBadge = button.querySelector('.user-id-badge');
|
|
if (activeBadge) {
|
|
activeBadge.classList.remove('bg-gray-200', 'text-gray-600');
|
|
activeBadge.classList.add('bg-blue-500', 'text-white');
|
|
}
|
|
|
|
// 사용자 정보 저장
|
|
const userId = button.getAttribute('data-user-id');
|
|
const userName = button.getAttribute('data-user-name');
|
|
const userLogin = button.getAttribute('data-user-login');
|
|
|
|
document.getElementById('userIdInput').value = userId;
|
|
document.getElementById('selected-user-name').innerHTML = `${userName} <span class="px-3 py-1 text-sm text-blue-600 border border-blue-400 rounded ml-2"> ${userLogin} </span>`;
|
|
|
|
// 액션 버튼 표시
|
|
document.getElementById('action-buttons').style.display = 'block';
|
|
}
|
|
|
|
// Guard 변경 시 권한 매트릭스 새로고침
|
|
function reloadPermissions() {
|
|
const selectedButton = document.querySelector('.user-button.bg-blue-700');
|
|
if (selectedButton) {
|
|
htmx.trigger(selectedButton, 'click');
|
|
}
|
|
}
|
|
|
|
// 페이지 로드 시 첫 번째 사용자 자동 선택 (특정 테넌트 선택 시에만)
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const autoSelectButton = document.querySelector('.user-button[data-auto-select="true"]');
|
|
if (autoSelectButton) {
|
|
// onclick 핸들러 실행
|
|
selectUser(autoSelectButton);
|
|
// HTMX 이벤트 트리거
|
|
htmx.trigger(autoSelectButton, 'click');
|
|
}
|
|
});
|
|
</script>
|
|
<script src="{{ asset('js/menu-tree.js') }}"></script>
|
|
@endsection
|