Tenant 패널 (Level 2)
📝 초안 (Draft)
이 문서는 검토 중입니다. 내용이 변경될 수 있습니다.
Tenant Admin 전용 패널 기능을 설명합니다.
개요
Tenant 패널은 **Level 2 (Tenant Admin)**을 위한 관리 패널입니다. 테넌트(고객사) 내의 조직 구조, 사용자, Guardian 관계 등을 관리합니다.
접근 권한
| 항목 | 값 |
|---|---|
| 권한 레벨 | Level 2 (Tenant Admin) |
| 기본 경로 | /tenant |
| 환경변수 | PANEL_PATH_TENANT |
| 접근 가능자 | Level 0, 1, 2 |
담당 범위
Tenant Admin은 자신의 테넌트 내에서만 권한을 가집니다:
Tenant A (담당)
├── Organization 1 ✅ 관리 가능
│ ├── Workspace 1 ✅
│ └── Workspace 2 ✅
├── Organization 2 ✅ 관리 가능
│ └── Workspace 3 ✅
└── 모든 사용자 ✅ 관리 가능
Tenant B (타 테넌트)
└── 모든 데이터 ❌ 접근 불가 (RLS 격리)
범용 UX 기능
| 기능 | 설명 |
|---|---|
| brandName | 좌상단에 테넌트 이름 표시 (logo_url 등록 시 로고 표시) |
| tenantMenu | 테넌트 선택 드롭다운 숨김 (URL로 결정) |
| 패널간 네비게이션 | 우상단 메뉴에서 Platform/SaaS 패널로 이동 (L0/L1만) |
| SafeDeleteAction | 모든 삭제에 "DELETE {대상명}" 입력 확인 |
| homeUrl | 좌상단 로고 클릭 시 현재 테넌트 대시보드로 이동 |
주요 기능
1. 조직(Organization) 관리
테넌트 내의 조직 구조를 관리합니다. 조직은 계층 구조를 가질 수 있습니다.
OrganizationResource 주요 필드:
| 필드 | 설명 | 예시 |
|---|---|---|
name | 조직명 | "영업부" |
code | 조직 코드 | "SALES-001" |
type | 조직 유형 | headquarters, division, department, team |
parent_id | 상위 조직 | null (최상위) 또는 조직 ID |
org_level | 조직 레벨 | 1, 2, 3... |
is_active | 활성화 여부 | true/false |
조직 유형:
| 유형 | 설명 | 색상 |
|---|---|---|
headquarters | 본사 | 빨강 |
division | 사업부 | 주황 |
department | 부서 | 파랑 |
team | 팀 | 초록 |
2. Guardian 관계 관리
Guardian 확장이 활성화된 경우, 보호자-피보호자 관계를 관리합니다.
GuardianRelationshipResource 기능:
- 관계 생성 (학부모-학생, 담당교사-학생 등)
- 승인 대기 목록 확인
- 승인/거부 액션
| 액션 | 설명 |
|---|---|
| ApproveAction | 관계 요청 승인 (pending → approved) |
| RejectAction | 관계 요청 거부 (pending → rejected) |
// GuardianRelationshipResource.php
use App\Core\Extensions\Guardian\Filament\Actions\ApproveAction;
use App\Core\Extensions\Guardian\Filament\Actions\RejectAction;
->recordActions([
ApproveAction::make(),
RejectAction::make(),
EditAction::make(),
])
3. 사용자 관리
테넌트 내 모든 레벨의 사용자를 관리합니다.
| 관리 대상 | 레벨 | 설명 |
|---|---|---|
| Organization Admin | Level 3 | 조직 관리자 배정 |
| Workspace Admin | Level 4 | 워크스페이스 관리자 배정 |
| Group Leader | Level 5 | 그룹 리더 배정 |
| Member | Level 6 | 일반 회원 관리 |
4. 감사 로그
테넌트 내의 감사 로그를 조회합니다. (플랫폼 전체가 아닌 테넌트 범위)
// TenantAuditLogResource.php
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->where('tenant_id', auth()->user()->tenant_id);
}
파일 구조
app/Filament/Tenant/
├── Resources/
│ ├── OrganizationResource.php
│ │ └── Pages/
│ │ ├── ListOrganizations.php
│ │ ├── CreateOrganization.php
│ │ └── EditOrganization.php
│ │
│ ├── GuardianRelationshipResource.php
│ │ └── Pages/
│ │ ├── ListGuardianRelationships.php
│ │ ├── CreateGuardianRelationship.php
│ │ └── EditGuardianRelationship.php
│ │
│ └── AuditLogResource.php
│ └── Pages/
│ ├── ListAuditLogs.php
│ └── ViewAuditLog.php
│
├── Pages/
│ └── Dashboard.php
│
└── Widgets/
├── OrganizationTreeWidget.php
└── GuardianPendingWidget.php
PanelProvider 설정
// app/Providers/Filament/TenantPanelProvider.php
class TenantPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->id('tenant')
->path(env('PANEL_PATH_TENANT', 'tenant'))
->login()
->colors(['primary' => Color::Blue])
->tenant(Tenant::class) // 멀티테넌트 활성화
->discoverResources(
in: app_path('Filament/Tenant/Resources'),
for: 'App\Filament\Tenant\Resources'
)
->authMiddleware([
Authenticate::class,
EnsureUserLevel::class.':0,1,2', // Level 0-2 접근
]);
}
}
RLS 데이터 격리
Tenant 패널의 모든 리소스는 **RLS(Row Level Security)**로 자동 격리됩니다.
HasTenantBasedAuthorization Trait
// OrganizationResource.php
use App\Core\Base\Permission\Filament\Traits\HasTenantBasedAuthorization;
class OrganizationResource extends Resource
{
use HasTenantBasedAuthorization;
// 자동으로 tenant_id 필터링 적용
}
자동 필터링
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()
->where('tenant_id', auth()->user()->tenant_id);
}
RLS 보장
PostgreSQL RLS 정책으로 DB 레벨에서도 격리가 보장됩니다. SQL Injection 공격으로도 다른 테넌트 데이터에 접근할 수 없습니다.
Guardian 확장 통합
Guardian 확장이 활성화되면 Tenant 패널에 Guardian 관련 기능이 추가됩니다.
활성화 확인
// config/core.php
'extensions' => [
'guardian' => env('CORE_GUARDIAN_ENABLED', false),
],
조건부 네비게이션
// GuardianRelationshipResource.php
public static function shouldRegisterNavigation(): bool
{
return config('core.extensions.guardian', false);
}