feat: 권한 관리 시스템에 Guard 선택 기능 추가

- 부서 권한 아키텍처 재설계 (Role → Department 직접 할당)
- Guard 선택 기능 구현 (API/Web 분리)
- 초기화(Reset) 기능 추가 (view 권한만 허용)
- UI 개선 (Guard 선택기 + 구분선)

부서 권한 관리:
- Department 모델에 HasPermissions trait 추가
- DepartmentPermissionService 완전 재작성 (직접 할당 패턴)
- model_has_permissions 테이블 직접 사용
- guard_name 파라미터 모든 메서드에 추가

역할 권한 관리:
- RolePermissionService에 guard_name 파라미터 추가
- RolePermissionController에 guard_name 처리 구현
- Guard별 독립적인 권한 매트릭스 관리

UI 개선:
- Guard 선택 드롭다운 (Web/API) 추가
- 모든 HTMX 요청에 guard_name 포함
- Guard 변경 시 자동 새로고침 기능
- 시각적 구분선으로 UI 가독성 향상
This commit is contained in:
2025-11-25 20:53:53 +09:00
parent fec284d872
commit 69b04ae041
11 changed files with 517 additions and 73 deletions

View File

@@ -8,7 +8,7 @@
<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">
<div class="flex flex-wrap items-center gap-3">
@@ -22,6 +22,7 @@ class="department-button px-4 py-2 text-sm font-medium rounded-lg border transit
data-department-name="{{ $department->name }}"
hx-get="/api/admin/department-permissions/matrix"
hx-target="#permission-matrix"
hx-include="[name='guard_name']"
hx-vals='{"department_id": {{ $department->id }}}'
onclick="selectDepartment(this)"
>
@@ -39,12 +40,28 @@ class="department-button px-4 py-2 text-sm font-medium rounded-lg border transit
<span class="text-sm font-medium text-gray-700" id="selected-department-name">선택된 부서</span>
<div class="flex items-center gap-2">
<input type="hidden" name="department_id" id="departmentIdInput" value="">
<!-- Guard 선택 -->
<span class="text-sm font-medium text-gray-700">Guard:</span>
<select
id="guardNameSelect"
name="guard_name"
class="px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-primary focus:border-primary"
onchange="reloadPermissions()"
>
<option value="api" selected>API</option>
<option value="web">Web</option>
</select>
<!-- 구분선 -->
<div class="h-8 w-px bg-gray-300 mx-1"></div>
<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/department-permissions/allow-all"
hx-target="#permission-matrix"
hx-include="[name='department_id']"
hx-include="[name='department_id'],[name='guard_name']"
>
전체 허용
</button>
@@ -53,16 +70,17 @@ class="px-4 py-2 bg-green-600 text-white text-sm font-medium rounded-lg hover:bg
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/department-permissions/deny-all"
hx-target="#permission-matrix"
hx-include="[name='department_id']"
hx-include="[name='department_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/department-permissions/deny-all"
hx-post="/api/admin/department-permissions/reset"
hx-target="#permission-matrix"
hx-include="[name='department_id']"
hx-include="[name='department_id'],[name='guard_name']"
title="모든 메뉴의 조회(view) 권한만 허용"
>
초기화
</button>
@@ -99,11 +117,22 @@ function selectDepartment(button) {
document.getElementById('action-buttons').style.display = 'block';
}
// Guard 변경 시 권한 매트릭스 새로고침
function reloadPermissions() {
const selectedButton = document.querySelector('.department-button.bg-blue-700');
if (selectedButton) {
htmx.trigger(selectedButton, 'click');
}
}
// 페이지 로드 시 첫 번째 부서 자동 선택
document.addEventListener('DOMContentLoaded', function() {
const firstButton = document.querySelector('.department-button');
if (firstButton) {
firstButton.click();
// onclick 핸들러 실행
selectDepartment(firstButton);
// HTMX 이벤트 트리거
htmx.trigger(firstButton, 'click');
}
});
</script>

View File

@@ -47,8 +47,9 @@
{{ isset($permissions[$menu->id][$type]) && $permissions[$menu->id][$type] ? 'checked' : '' }}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer"
hx-post="/api/admin/department-permissions/toggle"
hx-trigger="click"
hx-target="#permission-matrix"
hx-include="[name='department_id']"
hx-include="[name='department_id'],[name='guard_name']"
hx-vals='{"menu_id": {{ $menu->id }}, "permission_type": "{{ $type }}"}'
>
</td>

View File

@@ -22,6 +22,7 @@ class="role-button px-4 py-2 text-sm font-medium rounded-lg border transition-co
data-role-name="{{ $role->name }}"
hx-get="/api/admin/role-permissions/matrix"
hx-target="#permission-matrix"
hx-include="[name='guard_name']"
hx-vals='{"role_id": {{ $role->id }}}'
onclick="selectRole(this)"
>
@@ -39,12 +40,28 @@ class="role-button px-4 py-2 text-sm font-medium rounded-lg border transition-co
<span class="text-sm font-medium text-gray-700" id="selected-role-name">선택된 역할</span>
<div class="flex items-center gap-2">
<input type="hidden" name="role_id" id="roleIdInput" value="">
<!-- Guard 선택 -->
<span class="text-sm font-medium text-gray-700">Guard:</span>
<select
id="guardNameSelect"
name="guard_name"
class="px-3 py-1.5 text-sm border border-gray-300 rounded-lg focus:ring-primary focus:border-primary"
onchange="reloadPermissions()"
>
<option value="web" selected>Web</option>
<option value="api">API</option>
</select>
<!-- 구분선 -->
<div class="h-8 w-px bg-gray-300 mx-1"></div>
<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/role-permissions/allow-all"
hx-target="#permission-matrix"
hx-include="[name='role_id']"
hx-include="[name='role_id'],[name='guard_name']"
>
전체 허용
</button>
@@ -53,16 +70,17 @@ class="px-4 py-2 bg-green-600 text-white text-sm font-medium rounded-lg hover:bg
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/role-permissions/deny-all"
hx-target="#permission-matrix"
hx-include="[name='role_id']"
hx-include="[name='role_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/role-permissions/deny-all"
hx-post="/api/admin/role-permissions/reset"
hx-target="#permission-matrix"
hx-include="[name='role_id']"
hx-include="[name='role_id'],[name='guard_name']"
title="모든 메뉴의 조회(view) 권한만 허용"
>
초기화
</button>
@@ -99,11 +117,22 @@ function selectRole(button) {
document.getElementById('action-buttons').style.display = 'block';
}
// Guard 변경 시 권한 매트릭스 새로고침
function reloadPermissions() {
const selectedButton = document.querySelector('.role-button.bg-blue-700');
if (selectedButton) {
htmx.trigger(selectedButton, 'click');
}
}
// 페이지 로드 시 첫 번째 역할 자동 선택
document.addEventListener('DOMContentLoaded', function() {
const firstButton = document.querySelector('.role-button');
if (firstButton) {
firstButton.click();
// onclick 핸들러 실행
selectRole(firstButton);
// HTMX 이벤트 트리거
htmx.trigger(firstButton, 'click');
}
});
</script>

View File

@@ -48,7 +48,7 @@
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer"
hx-post="/api/admin/role-permissions/toggle"
hx-target="#permission-matrix"
hx-include="[name='role_id']"
hx-include="[name='role_id'],[name='guard_name']"
hx-vals='{"menu_id": {{ $menu->id }}, "permission_type": "{{ $type }}"}'
>
</td>