forked from baron/baron-sso
테넌트 목록 및 조직 계층 구조 개선
This commit is contained in:
118
docs/UI_DESIGN_POLICY.md
Normal file
118
docs/UI_DESIGN_POLICY.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# UI 버튼 위치 및 정렬 정책 (UI Button Placement Policy)
|
||||
|
||||
본 문서는 Baron SSO 프로젝트 내 모든 프론트엔드 애플리케이션(`userfront`, `devfront`, `adminfront`)에서 일관된 사용자 경험(UX)을 제공하기 위한 UI 버튼 배치 및 정렬 가이드라인을 정의합니다. (관련 이슈: [#308](https://gitea.hmac.kr/baron/baron-sso/issues/308))
|
||||
|
||||
## 1. 버튼 종류별 위치 (Button Placement by Type)
|
||||
|
||||
버튼의 성격에 따라 다음과 같이 배치합니다.
|
||||
|
||||
* **Primary Action (주요 동작)**
|
||||
* **예시**: 저장, 확인, 제출, 생성 등
|
||||
* **위치**: 우측 하단 (Bottom Right) 또는 모달/다이얼로그의 우측 끝에 배치합니다. 사용자의 시선 흐름(좌에서 우, 위에서 아래)에 따라 최종 액션을 우측 하단에서 마무리하도록 유도합니다.
|
||||
* **Secondary Action (보조 동작)**
|
||||
* **예시**: 취소, 닫기, 이전으로 등
|
||||
* **위치**: Primary 버튼의 바로 **좌측**에 배치합니다.
|
||||
* **Destructive Action (파괴적 동작)**
|
||||
* **예시**: 삭제, 초기화, 권한 해제 등
|
||||
* **위치 및 스타일**: 붉은색(Red/Destructive) 스타일을 적용하여 시각적으로 명확히 구분합니다. Primary/Secondary 그룹과 물리적으로 분리하거나 (예: 좌측 끝 배치), Secondary 액션 위치에 두되 색상으로 강력한 경고를 줍니다.
|
||||
|
||||
## 2. 정렬 기준 (Alignment Rules)
|
||||
|
||||
* **폼(Form) 하단 버튼 그룹**
|
||||
* **기본 정렬**: 우측 정렬 (Right-aligned). "취소"는 왼쪽, "저장"은 오른쪽에 위치합니다. `[ 취소 ] [ 저장 ]`
|
||||
* **리스트 아이템 내부 액션 버튼**
|
||||
* **기본 정렬**: 리스트/테이블의 각 행(Row) 우측 끝에 배치합니다.
|
||||
* 버튼 개수가 많을 경우 (3개 이상), 툴팁이나 Dropdown 메뉴(예: 햄버거 버튼 또는 "더보기" 아이콘)로 숨겨 UI 복잡도를 낮춥니다.
|
||||
|
||||
## 3. 반응형 고려 (Responsive Design)
|
||||
|
||||
* **모바일 환경 (Mobile / Small Screens)**
|
||||
* 화면 너비가 좁은 모바일 기기(예: `userfront` 앱 환경, `devfront`/`adminfront`의 모바일 뷰)에서는 버튼 그룹을 **Full Width (화면 가득 채움)**로 변경하여 터치 영역을 확보합니다.
|
||||
* 여러 개의 버튼이 있는 경우 세로로 스택(Stack)하며, **Primary Action을 맨 위**에, Secondary Action을 그 아래에 배치합니다.
|
||||
* *데스크탑*: `[ 취소 ] [ 확인 ]`
|
||||
* *모바일*:
|
||||
```
|
||||
[ 확인 ]
|
||||
[ 취소 ]
|
||||
```
|
||||
|
||||
## 4. 로딩 및 피드백 (Loading & Feedback)
|
||||
|
||||
* **중복 제출 방지**: 폼 전송이나 API 호출을 발생시키는 버튼을 클릭하면 즉각적으로 버튼을 비활성화(Disabled) 상태로 변경하여 다중 클릭을 방지합니다.
|
||||
* **로딩 스피너**: 버튼 내부에 로딩 스피너(Spinner)를 표시하여 사용자에게 진행 상황을 시각적으로 알립니다.
|
||||
* **스켈레톤 로딩(Skeleton Loading)**: 화면 진입 시 전체 데이터를 로딩해야 하는 경우, 무의미한 빈 화면(빈 공간) 대신 스켈레톤 UI를 사용하여 로딩 중임을 직관적으로 알리고 체감 대기 시간을 줄입니다.
|
||||
* **작업 결과 안내**: 성공, 실패 등의 결과는 Toast 메시지 (혹은 스낵바)를 통해 화면 하단/상단에 일시적으로 노출하여 사용자가 흐름을 끊지 않고도 인지할 수 있게 돕습니다.
|
||||
|
||||
## 5. 빈 상태 처리 (Empty State)
|
||||
|
||||
* **빈 목록 안내**: 테이블이나 리스트에 표시할 항목이 없는 경우 단순히 빈 화면으로 두지 않고 중앙 정렬된 아이콘이나 일러스트와 함께 "데이터가 없습니다." 등의 명확한 문구를 표시합니다.
|
||||
* **콜 투 액션(Call to Action)**: 데이터가 비어 있는 경우 생성 버튼(Primary Action)을 빈 상태 안내 영역 아래에 배치하여 사용자가 즉시 데이터를 추가할 수 있도록 유도합니다.
|
||||
|
||||
## 6. 오류 표시 (Error Handling)
|
||||
|
||||
* **인라인(Inline) 오류**: 폼(Form)의 유효성 검사에서 실패한 경우, 각 입력 필드 바로 아래에 붉은색 텍스트로 실패 원인을 명확하게 표시합니다.
|
||||
* **포커스 이동**: 제출 버튼 클릭 시 오류가 있는 첫 번째 입력 필드로 자동 스크롤 하거나 포커스(Focus)를 이동시켜 수정이 용이하게 합니다.
|
||||
|
||||
## 7. 접근성 (Accessibility - a11y)
|
||||
|
||||
* **포커스 링(Focus Ring)**: 키보드를 통해 탐색(Tab)하는 사용자를 위해 버튼, 텍스트 입력창 등에 포커스가 갈 경우 외곽선을 명확히 렌더링(예: 파란색 테두리 등)해야 합니다. `outline: none`을 무분별하게 사용하지 않습니다.
|
||||
* **대체 텍스트**: 텍스트 없이 아이콘만 존재하는 버튼(예: X 형태의 닫기 버튼)의 경우 반드시 `aria-label` 속성(또는 Flutter의 `Semantics`)을 사용하여 스크린 리더 사용자가 해당 버튼의 역할을 알 수 있게 해야 합니다.
|
||||
|
||||
## 8. 프론트엔드 환경별 구현 가이드 (Implementation Guide)
|
||||
|
||||
현재 운영 중인 프론트엔드 환경에 맞춘 구현 가이드라인입니다.
|
||||
|
||||
### 8.1. React 환경 (`devfront`, `adminfront`)
|
||||
Tailwind CSS 기반의 컴포넌트를 사용하여 아래와 같이 구현합니다.
|
||||
|
||||
* **버튼 그룹 우측 정렬 (데스크탑)**: `flex justify-end gap-2`
|
||||
* **반응형 (모바일 세로 배치, 데스크탑 가로 배치)**: `flex flex-col-reverse sm:flex-row sm:justify-end gap-2`
|
||||
*(참고: `flex-col-reverse`를 사용하면 코드 상 먼저 작성된 취소 버튼이 모바일에서는 아래로, 나중에 작성된 확인 버튼이 위로 올라가게 배치할 수 있습니다.)*
|
||||
* **코드 예시**:
|
||||
```tsx
|
||||
<div className="flex flex-col-reverse sm:flex-row sm:justify-end gap-2 mt-4">
|
||||
<Button variant="outline" onClick={onCancel} disabled={isLoading}>취소</Button>
|
||||
<Button variant="default" onClick={onSave} disabled={isLoading}>
|
||||
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
저장
|
||||
</Button>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 8.2. Flutter 환경 (`userfront`)
|
||||
Flutter 프레임워크를 사용하는 환경에서는 화면 너비에 따라 위젯 구성을 동적으로 처리해야 합니다.
|
||||
|
||||
* **폼 하단 정렬**: `Row` 위젯과 `MainAxisAlignment.end` 사용.
|
||||
* **반응형 대응**: 화면 너비(MediaQuery)에 따라 `Row`를 전체 너비를 채우는 `Column`으로 스위칭하거나, `OverflowBar` 위젯 등을 활용할 수 있습니다.
|
||||
* **코드 예시**:
|
||||
```dart
|
||||
// 데스크탑/태블릿용 (우측 정렬)
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(onPressed: isLoading ? null : onCancel, child: const Text('취소')),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton(
|
||||
onPressed: isLoading ? null : onSave,
|
||||
child: isLoading
|
||||
? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
|
||||
: const Text('확인')
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
// 모바일용 (전체 너비 세로 배치)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
onPressed: isLoading ? null : onSave,
|
||||
child: isLoading
|
||||
? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
|
||||
: const Text('확인')
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextButton(onPressed: isLoading ? null : onCancel, child: const Text('취소')),
|
||||
],
|
||||
)
|
||||
```
|
||||
87
docs/keto-rebac-namespaces-diagram.md
Normal file
87
docs/keto-rebac-namespaces-diagram.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Ory Keto (ReBAC) 네임스페이스 및 권한 상속 다이어그램
|
||||
|
||||
이 문서는 `docker/ory/keto/namespaces.ts`에 정의된 Baron SSO 프로젝트의 Ory Keto(ReBAC) 네임스페이스와 각 네임스페이스 간의 권한 상속(Permits) 및 관계(Relations)를 나타내는 Mermaid 다이어그램입니다.
|
||||
|
||||
## 네임스페이스 설계 구조
|
||||
|
||||
Ory Keto는 다음과 같은 4개의 주요 네임스페이스로 구성되어 있습니다:
|
||||
|
||||
1. **`User`**: 권한의 주체가 되는 기본 사용자.
|
||||
2. **`System`**: 시스템 전역 권한 (최고 관리자 및 인증된 사용자).
|
||||
3. **`Tenant`**: 조직/회사/부서 등 모든 형태의 격리 공간. 상위-하위(`parents`) 계층 구조를 가짐.
|
||||
4. **`RelyingParty`**: OIDC 클라이언트(앱/리소스). 특정 `Tenant`에 종속될 수 있음.
|
||||
|
||||
---
|
||||
|
||||
## Mermaid 다이어그램
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class User {
|
||||
<<Namespace>>
|
||||
}
|
||||
|
||||
class System {
|
||||
<<Namespace>>
|
||||
-- Relations --
|
||||
super_admins: User[]
|
||||
authenticated_users: User[]
|
||||
-- Permits --
|
||||
manage_all: super_admins
|
||||
}
|
||||
|
||||
class Tenant {
|
||||
<<Namespace>>
|
||||
-- Relations --
|
||||
owners: User[]
|
||||
admins: User[] | SubjectSet~Tenant, owners~
|
||||
members: User[]
|
||||
parents: Tenant[]
|
||||
-- Permits --
|
||||
view: members OR admins OR parents.view
|
||||
manage: admins OR parents.manage
|
||||
create_subtenant: manage
|
||||
}
|
||||
|
||||
class RelyingParty {
|
||||
<<Namespace>>
|
||||
-- Relations --
|
||||
admins: User[]
|
||||
parents: Tenant[]
|
||||
access: User[] | SubjectSet~Tenant, members~ | SubjectSet~System, authenticated_users~
|
||||
-- Permits --
|
||||
view: admins OR parents.view
|
||||
manage: admins OR parents.manage
|
||||
access: access OR manage
|
||||
}
|
||||
|
||||
%% Relationship lines indicating references (SubjectSets or Direct inclusion)
|
||||
User ..> System : super_admins, authenticated_users
|
||||
User ..> Tenant : owners, admins, members
|
||||
User ..> RelyingParty : admins, access
|
||||
|
||||
Tenant "1" --> "*" Tenant : parents (상위 조직 상속)
|
||||
Tenant ..> RelyingParty : parents (소유권 상속)
|
||||
Tenant ..> RelyingParty : access (members 접근 권한)
|
||||
|
||||
System ..> RelyingParty : access (authenticated_users)
|
||||
|
||||
%% Styling
|
||||
style User fill:#e1f5fe,stroke:#333,stroke-width:2px
|
||||
style System fill:#ffe0b2,stroke:#333,stroke-width:2px
|
||||
style Tenant fill:#fff9c4,stroke:#333,stroke-width:2px
|
||||
style RelyingParty fill:#e1bee7,stroke:#333,stroke-width:2px
|
||||
```
|
||||
|
||||
### 권한 평가(Permit) 상세 로직 설명
|
||||
|
||||
- **Tenant (테넌트/조직):**
|
||||
- `view` (조회): 테넌트의 일반 멤버(`members`), 관리자(`admins`), 그리고 **상위 테넌트(parents)에서 조회 권한을 가진 자**가 조회할 수 있습니다.
|
||||
- `manage` (관리): 테넌트의 관리자(`admins`), 그리고 **상위 테넌트(parents)에서 관리 권한을 가진 자**가 관리할 수 있습니다.
|
||||
- _참고:_ 조직장(`owners`)은 자동으로 `admins` 집합(SubjectSet)에 포함됩니다.
|
||||
|
||||
- **RelyingParty (OIDC 앱):**
|
||||
- `view` (조회): 앱의 직접 관리자(`admins`) 또는 **이 앱을 소유한 테넌트(parents)에서 조회 권한을 가진 자**가 조회할 수 있습니다.
|
||||
- `manage` (관리): 앱의 직접 관리자(`admins`) 또는 **이 앱을 소유한 테넌트(parents)에서 관리 권한을 가진 자**가 관리할 수 있습니다.
|
||||
- `access` (접근/로그인 가능 여부): 이 앱에 직접 접근 권한을 부여받은 유저/그룹(`access`), 또는 앱을 관리할 수 있는 권한(`manage`)을 가진 사람이 접근할 수 있습니다.
|
||||
- _접근 대상(access)은 특정 유저, 특정 테넌트의 전 멤버, 또는 전역 인증된 유저(System:authenticated_users)가 될 수 있습니다._
|
||||
Reference in New Issue
Block a user