@things-factory/integration-label-studio 9.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +85 -0
- package/EXTERNAL_DATA_SOURCING.md +484 -0
- package/IMPLEMENTATION_GUIDE.md +469 -0
- package/INTEGRATION.md +279 -0
- package/README.md +1014 -0
- package/SETUP_GUIDE.md +577 -0
- package/TEST_GUIDE.md +387 -0
- package/UI_CUSTOMIZATION.md +395 -0
- package/USER_SYNC_GUIDE.md +514 -0
- package/client/bootstrap.ts +1 -0
- package/client/index.ts +1 -0
- package/client/label-studio-label-page.ts +52 -0
- package/client/label-studio-project-create.ts +216 -0
- package/client/label-studio-project-list.ts +214 -0
- package/client/label-studio-wrapper.ts +294 -0
- package/client/route.ts +15 -0
- package/client/tsconfig.json +13 -0
- package/config/config.development.js +124 -0
- package/config/config.production.js +182 -0
- package/dist-client/bootstrap.d.ts +1 -0
- package/dist-client/bootstrap.js +2 -0
- package/dist-client/bootstrap.js.map +1 -0
- package/dist-client/index.d.ts +1 -0
- package/dist-client/index.js +2 -0
- package/dist-client/index.js.map +1 -0
- package/dist-client/label-studio-label-page.d.ts +8 -0
- package/dist-client/label-studio-label-page.js +54 -0
- package/dist-client/label-studio-label-page.js.map +1 -0
- package/dist-client/label-studio-project-create.d.ts +16 -0
- package/dist-client/label-studio-project-create.js +235 -0
- package/dist-client/label-studio-project-create.js.map +1 -0
- package/dist-client/label-studio-project-list.d.ts +16 -0
- package/dist-client/label-studio-project-list.js +222 -0
- package/dist-client/label-studio-project-list.js.map +1 -0
- package/dist-client/label-studio-wrapper.d.ts +57 -0
- package/dist-client/label-studio-wrapper.js +304 -0
- package/dist-client/label-studio-wrapper.js.map +1 -0
- package/dist-client/route.d.ts +1 -0
- package/dist-client/route.js +14 -0
- package/dist-client/route.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-server/controller/label-studio-role-mapper.d.ts +35 -0
- package/dist-server/controller/label-studio-role-mapper.js +65 -0
- package/dist-server/controller/label-studio-role-mapper.js.map +1 -0
- package/dist-server/controller/user-provisioning-service.d.ts +66 -0
- package/dist-server/controller/user-provisioning-service.js +264 -0
- package/dist-server/controller/user-provisioning-service.js.map +1 -0
- package/dist-server/index.d.ts +7 -0
- package/dist-server/index.js +19 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/route/label-studio-sso.d.ts +2 -0
- package/dist-server/route/label-studio-sso.js +156 -0
- package/dist-server/route/label-studio-sso.js.map +1 -0
- package/dist-server/route/webhook.d.ts +65 -0
- package/dist-server/route/webhook.js +248 -0
- package/dist-server/route/webhook.js.map +1 -0
- package/dist-server/route.d.ts +1 -0
- package/dist-server/route.js +21 -0
- package/dist-server/route.js.map +1 -0
- package/dist-server/service/ai-prediction-service.d.ts +27 -0
- package/dist-server/service/ai-prediction-service.js +222 -0
- package/dist-server/service/ai-prediction-service.js.map +1 -0
- package/dist-server/service/dataset-labeling-integration.d.ts +44 -0
- package/dist-server/service/dataset-labeling-integration.js +512 -0
- package/dist-server/service/dataset-labeling-integration.js.map +1 -0
- package/dist-server/service/external-data-source-service.d.ts +78 -0
- package/dist-server/service/external-data-source-service.js +415 -0
- package/dist-server/service/external-data-source-service.js.map +1 -0
- package/dist-server/service/index.d.ts +12 -0
- package/dist-server/service/index.js +27 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/service/label-studio-sso-service.d.ts +38 -0
- package/dist-server/service/label-studio-sso-service.js +98 -0
- package/dist-server/service/label-studio-sso-service.js.map +1 -0
- package/dist-server/service/ml/ml-backend-service.d.ts +23 -0
- package/dist-server/service/ml/ml-backend-service.js +153 -0
- package/dist-server/service/ml/ml-backend-service.js.map +1 -0
- package/dist-server/service/prediction/prediction-management.d.ts +32 -0
- package/dist-server/service/prediction/prediction-management.js +299 -0
- package/dist-server/service/prediction/prediction-management.js.map +1 -0
- package/dist-server/service/project/project-management.d.ts +36 -0
- package/dist-server/service/project/project-management.js +309 -0
- package/dist-server/service/project/project-management.js.map +1 -0
- package/dist-server/service/task/task-management.d.ts +42 -0
- package/dist-server/service/task/task-management.js +372 -0
- package/dist-server/service/task/task-management.js.map +1 -0
- package/dist-server/service/user-provisioning/user-sync-mutation.d.ts +28 -0
- package/dist-server/service/user-provisioning/user-sync-mutation.js +111 -0
- package/dist-server/service/user-provisioning/user-sync-mutation.js.map +1 -0
- package/dist-server/service/webhook/webhook-management.d.ts +21 -0
- package/dist-server/service/webhook/webhook-management.js +134 -0
- package/dist-server/service/webhook/webhook-management.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/dist-server/types/dataset-labeling-types.d.ts +71 -0
- package/dist-server/types/dataset-labeling-types.js +259 -0
- package/dist-server/types/dataset-labeling-types.js.map +1 -0
- package/dist-server/types/label-studio-types.d.ts +128 -0
- package/dist-server/types/label-studio-types.js +494 -0
- package/dist-server/types/label-studio-types.js.map +1 -0
- package/dist-server/types/prediction-types.d.ts +39 -0
- package/dist-server/types/prediction-types.js +121 -0
- package/dist-server/types/prediction-types.js.map +1 -0
- package/dist-server/utils/annotation-exporter.d.ts +104 -0
- package/dist-server/utils/annotation-exporter.js +261 -0
- package/dist-server/utils/annotation-exporter.js.map +1 -0
- package/dist-server/utils/label-config-builder.d.ts +117 -0
- package/dist-server/utils/label-config-builder.js +286 -0
- package/dist-server/utils/label-config-builder.js.map +1 -0
- package/dist-server/utils/label-studio-api-client.d.ts +180 -0
- package/dist-server/utils/label-studio-api-client.js +401 -0
- package/dist-server/utils/label-studio-api-client.js.map +1 -0
- package/dist-server/utils/media-url-extractor.d.ts +45 -0
- package/dist-server/utils/media-url-extractor.js +152 -0
- package/dist-server/utils/media-url-extractor.js.map +1 -0
- package/dist-server/utils/task-transformer.d.ts +108 -0
- package/dist-server/utils/task-transformer.js +260 -0
- package/dist-server/utils/task-transformer.js.map +1 -0
- package/package.json +47 -0
- package/server/SERVER_STRUCTURE.md +351 -0
- package/server/controller/label-studio-role-mapper.ts +76 -0
- package/server/controller/user-provisioning-service.ts +340 -0
- package/server/index.ts +19 -0
- package/server/route/label-studio-sso.ts +194 -0
- package/server/route/webhook.ts +304 -0
- package/server/route.ts +35 -0
- package/server/service/ai-prediction-service.ts +239 -0
- package/server/service/dataset-labeling-integration.ts +590 -0
- package/server/service/external-data-source-service.ts +438 -0
- package/server/service/index.ts +24 -0
- package/server/service/label-studio-sso-service.ts +108 -0
- package/server/service/labeling-scenario-service.ts.deprecated +566 -0
- package/server/service/ml/ml-backend-service.ts +127 -0
- package/server/service/prediction/prediction-management.ts +281 -0
- package/server/service/project/project-management.ts +284 -0
- package/server/service/task/task-management.ts +363 -0
- package/server/service/user-provisioning/user-sync-mutation.ts +80 -0
- package/server/service/webhook/webhook-management.ts +109 -0
- package/server/tsconfig.json +11 -0
- package/server/types/dataset-labeling-types.ts +181 -0
- package/server/types/global.d.ts +23 -0
- package/server/types/label-studio-types.ts +346 -0
- package/server/types/prediction-types.ts +86 -0
- package/server/types/scenario-types.ts.deprecated +362 -0
- package/server/utils/annotation-exporter.ts +340 -0
- package/server/utils/label-config-builder.ts +340 -0
- package/server/utils/label-studio-api-client.ts +487 -0
- package/server/utils/media-url-extractor.ts +193 -0
- package/server/utils/task-transformer.ts +342 -0
- package/test-ai-prediction.js +268 -0
- package/test-dataset-integration.js +449 -0
- package/test-simple.js +89 -0
- package/things-factory.config.js +12 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
# Label Studio 사용자 동기화 가이드
|
|
2
|
+
|
|
3
|
+
Things-Factory의 사용자를 Label Studio에 동기화하는 방법을 설명합니다.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🔄 SSO vs 사용자 동기화
|
|
8
|
+
|
|
9
|
+
Things-Factory와 Label Studio의 통합은 **두 가지 독립적인 메커니즘**을 사용합니다:
|
|
10
|
+
|
|
11
|
+
### 1. SSO 자동 로그인 (`label-studio-sso` 패키지)
|
|
12
|
+
- **목적**: JWT 토큰을 통한 자동 로그인
|
|
13
|
+
- **방식**: iframe URL에 토큰 포함 → Label Studio에서 자동 인증
|
|
14
|
+
- **사용자 생성**: 선택적 (`JWT_SSO_AUTO_CREATE_USERS` 설정)
|
|
15
|
+
- **권한**: Label Studio 기본 권한 (Annotator)
|
|
16
|
+
|
|
17
|
+
### 2. 배치 사용자 동기화 (이 문서)
|
|
18
|
+
- **목적**: Things-Factory의 권한 체계를 Label Studio에 반영
|
|
19
|
+
- **방식**: Label Studio REST API를 통한 사용자 생성/업데이트
|
|
20
|
+
- **사용자 생성**: 항상 생성 (없으면)
|
|
21
|
+
- **권한**: Things-Factory 권한에 따른 Label Studio 역할 매핑
|
|
22
|
+
|
|
23
|
+
### 권장 구성
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
SSO 자동 로그인: JWT_SSO_AUTO_CREATE_USERS = False
|
|
27
|
+
↓
|
|
28
|
+
배치 사용자 동기화: GraphQL Mutation으로 수동 동기화
|
|
29
|
+
↓
|
|
30
|
+
Things-Factory 권한 → Label Studio 역할 반영
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 🎯 동기화 전략
|
|
36
|
+
|
|
37
|
+
### 핵심 원칙
|
|
38
|
+
|
|
39
|
+
1. **이메일 기반 매칭**: Things-Factory와 Label Studio 간 사용자는 **이메일로 매칭**
|
|
40
|
+
2. **권한 기반 필터링**: **Label Studio 권한이 있는 사용자만** 동기화
|
|
41
|
+
3. **배치 동기화**: 실시간이 아닌 **필요할 때 수동으로** GraphQL Mutation 실행
|
|
42
|
+
4. **3단계 권한 매핑**: Things-Factory Label Studio 권한 → Label Studio 기본 권한
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Things-Factory User (email)
|
|
46
|
+
↓
|
|
47
|
+
Label Studio 권한 확인?
|
|
48
|
+
├─ YES → Label Studio에 동기화 (3단계 권한)
|
|
49
|
+
└─ NO → 동기화 안 함
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 📋 Label Studio 권한 체계
|
|
55
|
+
|
|
56
|
+
Things-Factory 사용자 권한이 Label Studio 권한으로 매핑됩니다:
|
|
57
|
+
|
|
58
|
+
| Things-Factory 권한 | Label Studio 권한 | 설명 |
|
|
59
|
+
|---------------------|------------------|------|
|
|
60
|
+
| `label-studio` 권한 없음 | Inactive | 비활성화 (is_active=false) |
|
|
61
|
+
| `label-studio` 권한 + Owner | Admin | 전체 관리 권한 (is_superuser=true, is_staff=true) |
|
|
62
|
+
| `label-studio` 권한 (일반) | Staff | 라벨링 작업만 (is_superuser=false, is_staff=false) |
|
|
63
|
+
|
|
64
|
+
### 권한별 상세 기능
|
|
65
|
+
|
|
66
|
+
#### 1. Admin (도메인 Owner + label-studio 권한)
|
|
67
|
+
- ✅ 모든 Annotation 생성/수정/삭제
|
|
68
|
+
- ✅ 프로젝트 생성/수정/삭제
|
|
69
|
+
- ✅ 사용자 관리 (추가/삭제/권한 변경)
|
|
70
|
+
- ✅ 조직 설정
|
|
71
|
+
- ✅ Django Admin 페이지 접근
|
|
72
|
+
|
|
73
|
+
#### 2. Staff (label-studio 권한)
|
|
74
|
+
- ✅ 자신의 Annotation 생성/수정/삭제
|
|
75
|
+
- ✅ 다른 사용자의 Annotation 보기
|
|
76
|
+
- ❌ 프로젝트 설정 변경 불가
|
|
77
|
+
- ❌ 사용자 관리 불가
|
|
78
|
+
|
|
79
|
+
#### 3. Inactive (권한 없음)
|
|
80
|
+
- ❌ Label Studio 접근 불가
|
|
81
|
+
- Label Studio에서 비활성화 처리
|
|
82
|
+
|
|
83
|
+
### 권한 부여 방법
|
|
84
|
+
|
|
85
|
+
1. **Things-Factory Admin에서 Role 생성**
|
|
86
|
+
```
|
|
87
|
+
Role 이름: "Label Studio Staff"
|
|
88
|
+
Privileges:
|
|
89
|
+
- label-studio (category: label-studio, privilege: query 또는 mutation)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
2. **사용자에게 Role 부여**
|
|
93
|
+
```
|
|
94
|
+
User → Granted Roles → Add Role → "Label Studio Staff"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
3. **동기화 실행** (GraphQL)
|
|
98
|
+
```graphql
|
|
99
|
+
mutation {
|
|
100
|
+
syncAllUsersToLabelStudio {
|
|
101
|
+
total
|
|
102
|
+
created
|
|
103
|
+
updated
|
|
104
|
+
deactivated
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 🔧 설정
|
|
112
|
+
|
|
113
|
+
### 1. Label Studio API Token 설정
|
|
114
|
+
|
|
115
|
+
Things-Factory에서 사용자 동기화를 위해 Label Studio API Token이 필요합니다.
|
|
116
|
+
|
|
117
|
+
#### Label Studio API Token 생성
|
|
118
|
+
|
|
119
|
+
1. Label Studio 로그인
|
|
120
|
+
2. **Account Settings** → **Access Token** 메뉴 이동
|
|
121
|
+
3. **"Create new token"** 클릭
|
|
122
|
+
4. 생성된 토큰 복사
|
|
123
|
+
|
|
124
|
+
#### Things-Factory 설정
|
|
125
|
+
|
|
126
|
+
**config/label-studio.config.js**:
|
|
127
|
+
```javascript
|
|
128
|
+
module.exports = {
|
|
129
|
+
labelStudio: {
|
|
130
|
+
serverUrl: process.env.LABEL_STUDIO_URL || 'http://localhost:8080',
|
|
131
|
+
apiToken: process.env.LABEL_STUDIO_API_TOKEN || '',
|
|
132
|
+
interfaces: 'panel,controls,annotations:menu'
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**환경 변수 (.env)**:
|
|
138
|
+
```bash
|
|
139
|
+
LABEL_STUDIO_URL=https://label-studio.example.com
|
|
140
|
+
LABEL_STUDIO_API_TOKEN=your-api-token-here
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 🚀 사용자 동기화
|
|
146
|
+
|
|
147
|
+
### Option 1: 개인 동기화 (본인만)
|
|
148
|
+
|
|
149
|
+
**권한 요구사항**: `label-studio` 카테고리에 `staff` 권한 또는 도메인 소유자
|
|
150
|
+
|
|
151
|
+
```graphql
|
|
152
|
+
mutation {
|
|
153
|
+
syncMyUserToLabelStudio {
|
|
154
|
+
success
|
|
155
|
+
email
|
|
156
|
+
action # "created" | "updated" | "deactivated" | "skipped" | "error"
|
|
157
|
+
lsUserId
|
|
158
|
+
lsPermissions # "Admin (Full access)" | "Staff (Labeling only)" | "Inactive"
|
|
159
|
+
error
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**사용 케이스**:
|
|
165
|
+
- 사용자가 처음 Label Studio 권한을 받았을 때
|
|
166
|
+
- Label Studio 메뉴 접근 전 확인용
|
|
167
|
+
|
|
168
|
+
### Option 2: 전체 동기화 (관리자용)
|
|
169
|
+
|
|
170
|
+
**권한 요구사항**: `label-studio` 카테고리에 `admin` 권한 또는 도메인 소유자
|
|
171
|
+
|
|
172
|
+
```graphql
|
|
173
|
+
mutation {
|
|
174
|
+
syncAllUsersToLabelStudio {
|
|
175
|
+
total # 전체 사용자 수
|
|
176
|
+
created # 새로 생성된 수
|
|
177
|
+
updated # 업데이트된 수
|
|
178
|
+
deactivated # 비활성화된 수
|
|
179
|
+
skipped # 건너뛴 수
|
|
180
|
+
errors # 에러 수
|
|
181
|
+
|
|
182
|
+
results {
|
|
183
|
+
success
|
|
184
|
+
email
|
|
185
|
+
action
|
|
186
|
+
lsUserId
|
|
187
|
+
lsPermissions
|
|
188
|
+
error
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**사용 케이스**:
|
|
195
|
+
- 초기 설정 후 모든 사용자 일괄 동기화
|
|
196
|
+
- 권한 변경 후 일괄 반영
|
|
197
|
+
- 주기적인 동기화 (예: 주 1회)
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 📊 동기화 시나리오
|
|
202
|
+
|
|
203
|
+
### 시나리오 1: 신규 사용자 추가 (Staff 권한)
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
1. Things-Factory에 사용자 생성
|
|
207
|
+
User(email: 'john@example.com')
|
|
208
|
+
|
|
209
|
+
2. label-studio 권한이 있는 Role 부여
|
|
210
|
+
Role: "Label Studio Staff"
|
|
211
|
+
→ Privilege: label-studio (query 또는 mutation)
|
|
212
|
+
|
|
213
|
+
3. 동기화 실행
|
|
214
|
+
mutation { syncAllUsersToLabelStudio { ... } }
|
|
215
|
+
|
|
216
|
+
4. 결과
|
|
217
|
+
✅ Label Studio에 사용자 생성
|
|
218
|
+
- email: john@example.com
|
|
219
|
+
- username: john@example.com
|
|
220
|
+
- is_superuser: false
|
|
221
|
+
- is_staff: false
|
|
222
|
+
- is_active: true
|
|
223
|
+
→ lsPermissions: "Staff (Labeling only)"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### 시나리오 2: 권한 업그레이드 (Staff → Admin)
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
1. Things-Factory에서 도메인 Owner로 설정
|
|
230
|
+
User: john@example.com
|
|
231
|
+
→ owner: true
|
|
232
|
+
|
|
233
|
+
2. 동기화 실행
|
|
234
|
+
mutation { syncAllUsersToLabelStudio { ... } }
|
|
235
|
+
|
|
236
|
+
3. 결과
|
|
237
|
+
✅ Label Studio에서 권한 업데이트
|
|
238
|
+
- is_superuser: false → true
|
|
239
|
+
- is_staff: false → true
|
|
240
|
+
→ lsPermissions: "Admin (Full access)"
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 시나리오 3: 권한 제거
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
1. Things-Factory에서 label-studio 권한 제거
|
|
247
|
+
User: john@example.com
|
|
248
|
+
→ label-studio 권한 없음
|
|
249
|
+
|
|
250
|
+
2. 동기화 실행
|
|
251
|
+
mutation { syncAllUsersToLabelStudio { ... } }
|
|
252
|
+
|
|
253
|
+
3. 결과
|
|
254
|
+
✅ Label Studio에서 비활성화
|
|
255
|
+
- is_active: true → false
|
|
256
|
+
→ lsPermissions: "Inactive (No Label Studio access)"
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### 시나리오 4: 이름 변경
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
1. Things-Factory에서 사용자 이름 변경
|
|
263
|
+
User.name: "John Doe" → "Jane Doe"
|
|
264
|
+
|
|
265
|
+
2. 동기화 실행
|
|
266
|
+
mutation { syncAllUsersToLabelStudio { ... } }
|
|
267
|
+
|
|
268
|
+
3. 결과
|
|
269
|
+
✅ Label Studio에서 이름 업데이트
|
|
270
|
+
- first_name: "Jane"
|
|
271
|
+
- last_name: "Doe"
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## 🔍 동기화 로직 상세
|
|
277
|
+
|
|
278
|
+
### 동기화 판단 흐름
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
for each user in Things-Factory domain:
|
|
282
|
+
|
|
283
|
+
1. Label Studio 권한 확인
|
|
284
|
+
hasLabelStudioPrivilege(user)?
|
|
285
|
+
|
|
286
|
+
├─ YES
|
|
287
|
+
│ ↓
|
|
288
|
+
│ 2. Label Studio 역할 매핑
|
|
289
|
+
│ lsRole = mapUserRole(user)
|
|
290
|
+
│
|
|
291
|
+
│ 3. Label Studio API 호출
|
|
292
|
+
│ ├─ 이메일로 사용자 조회
|
|
293
|
+
│ ├─ 존재하면 → UPDATE
|
|
294
|
+
│ └─ 없으면 → CREATE
|
|
295
|
+
│
|
|
296
|
+
│ 4. Organization 멤버십 설정
|
|
297
|
+
│ role = lsRole
|
|
298
|
+
│
|
|
299
|
+
└─ NO
|
|
300
|
+
↓
|
|
301
|
+
5. Label Studio에서 비활성화
|
|
302
|
+
is_active = false
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### 역할 매핑 로직
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
// Things-Factory 사용자의 권한 분석
|
|
309
|
+
const hasLabelStudioPrivilege =
|
|
310
|
+
await User.hasPrivilege('label-studio', 'query', domain, user) ||
|
|
311
|
+
await User.hasPrivilege('label-studio', 'mutation', domain, user)
|
|
312
|
+
|
|
313
|
+
if (!hasLabelStudioPrivilege) {
|
|
314
|
+
// Label Studio 권한 없음 → 비활성화
|
|
315
|
+
return {
|
|
316
|
+
is_superuser: false,
|
|
317
|
+
is_staff: false,
|
|
318
|
+
is_active: false
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (user.owner === true) {
|
|
323
|
+
// 도메인 Owner + Label Studio 권한 → Admin
|
|
324
|
+
return {
|
|
325
|
+
is_superuser: true,
|
|
326
|
+
is_staff: true,
|
|
327
|
+
is_active: true
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Label Studio 권한은 있지만 Owner 아님 → Staff
|
|
332
|
+
return {
|
|
333
|
+
is_superuser: false,
|
|
334
|
+
is_staff: false,
|
|
335
|
+
is_active: true
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## 📈 모니터링 및 로그
|
|
342
|
+
|
|
343
|
+
### 동기화 결과 확인
|
|
344
|
+
|
|
345
|
+
```graphql
|
|
346
|
+
mutation {
|
|
347
|
+
syncAllUsersToLabelStudio {
|
|
348
|
+
total
|
|
349
|
+
created
|
|
350
|
+
updated
|
|
351
|
+
deactivated
|
|
352
|
+
skipped
|
|
353
|
+
errors
|
|
354
|
+
|
|
355
|
+
# 상세 결과
|
|
356
|
+
results {
|
|
357
|
+
email
|
|
358
|
+
action
|
|
359
|
+
error
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### 예상 출력
|
|
366
|
+
|
|
367
|
+
```json
|
|
368
|
+
{
|
|
369
|
+
"data": {
|
|
370
|
+
"syncAllUsersToLabelStudio": {
|
|
371
|
+
"total": 50,
|
|
372
|
+
"created": 5,
|
|
373
|
+
"updated": 40,
|
|
374
|
+
"deactivated": 3,
|
|
375
|
+
"skipped": 0,
|
|
376
|
+
"errors": 2,
|
|
377
|
+
"results": [
|
|
378
|
+
{
|
|
379
|
+
"email": "john@example.com",
|
|
380
|
+
"action": "created",
|
|
381
|
+
"error": null
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
"email": "jane@example.com",
|
|
385
|
+
"action": "updated",
|
|
386
|
+
"error": null
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
"email": "bob@example.com",
|
|
390
|
+
"action": "error",
|
|
391
|
+
"error": "API connection timeout"
|
|
392
|
+
}
|
|
393
|
+
]
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### 서버 로그
|
|
400
|
+
|
|
401
|
+
```
|
|
402
|
+
🔄 Starting batch sync for 50 users...
|
|
403
|
+
✅ Created privilege: label-studio:viewer
|
|
404
|
+
✅ User synced: john@example.com (created)
|
|
405
|
+
✅ User synced: jane@example.com (updated)
|
|
406
|
+
❌ Failed to sync: bob@example.com (API timeout)
|
|
407
|
+
✅ Batch sync completed: {
|
|
408
|
+
total: 50,
|
|
409
|
+
created: 5,
|
|
410
|
+
updated: 40,
|
|
411
|
+
errors: 2
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## 🛠️ 문제 해결
|
|
418
|
+
|
|
419
|
+
### 1. "Label Studio API token is not configured"
|
|
420
|
+
|
|
421
|
+
**원인**: config 파일이나 환경 변수에 API Token이 설정되지 않음
|
|
422
|
+
|
|
423
|
+
**해결**:
|
|
424
|
+
```bash
|
|
425
|
+
# .env 파일에 추가
|
|
426
|
+
LABEL_STUDIO_API_TOKEN=your-api-token-here
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
또는 `config/label-studio.config.js`에 직접 설정:
|
|
430
|
+
```javascript
|
|
431
|
+
module.exports = {
|
|
432
|
+
labelStudio: {
|
|
433
|
+
apiToken: 'your-api-token-here'
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### 2. 사용자가 동기화되지 않음
|
|
439
|
+
|
|
440
|
+
**원인**: `label-studio` 권한이 없음
|
|
441
|
+
|
|
442
|
+
**확인**:
|
|
443
|
+
1. Things-Factory에서 사용자의 Granted Roles 확인
|
|
444
|
+
2. 해당 Role의 Privileges에 `label-studio` 카테고리 권한이 있는지 확인
|
|
445
|
+
|
|
446
|
+
**해결**:
|
|
447
|
+
```
|
|
448
|
+
User → Granted Roles → Add Role → (label-studio 권한이 있는 Role)
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### 3. API 호출 실패
|
|
452
|
+
|
|
453
|
+
**원인**: 네트워크 문제 또는 Label Studio 서버 다운
|
|
454
|
+
|
|
455
|
+
**확인**:
|
|
456
|
+
```bash
|
|
457
|
+
curl -H "Authorization: Token YOUR_TOKEN" \
|
|
458
|
+
https://label-studio.example.com/api/users
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
**해결**:
|
|
462
|
+
- Label Studio 서버 상태 확인
|
|
463
|
+
- 네트워크 연결 확인
|
|
464
|
+
- API Token 유효성 확인
|
|
465
|
+
|
|
466
|
+
### 4. 권한 에러 발생
|
|
467
|
+
|
|
468
|
+
**원인**: GraphQL 뮤테이션 실행 권한 부족
|
|
469
|
+
|
|
470
|
+
**확인**:
|
|
471
|
+
- `syncMyUserToLabelStudio`: `label-studio` 카테고리에 `staff` 권한 필요
|
|
472
|
+
- `syncAllUsersToLabelStudio`: `label-studio` 카테고리에 `admin` 권한 필요
|
|
473
|
+
|
|
474
|
+
**해결**:
|
|
475
|
+
적절한 권한을 가진 사용자로 재로그인하거나 권한 부여
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## ⏰ 권장 동기화 주기
|
|
480
|
+
|
|
481
|
+
### 수동 동기화 (권장)
|
|
482
|
+
|
|
483
|
+
```
|
|
484
|
+
이벤트 기반:
|
|
485
|
+
├─ 초기 설정 후 → 전체 동기화 1회
|
|
486
|
+
├─ 신규 사용자 추가 시 → 개인 동기화
|
|
487
|
+
├─ 권한 변경 시 → 전체 또는 개인 동기화
|
|
488
|
+
└─ 정기 점검 (월 1회) → 전체 동기화
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### 자동화 (선택)
|
|
492
|
+
|
|
493
|
+
Things-Factory의 스케줄러를 사용하여 주기적 동기화 가능:
|
|
494
|
+
|
|
495
|
+
```typescript
|
|
496
|
+
// 매주 일요일 오전 2시 동기화
|
|
497
|
+
schedule('0 2 * * 0', async () => {
|
|
498
|
+
await syncAllUsersToLabelStudio()
|
|
499
|
+
})
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## 📚 관련 문서
|
|
505
|
+
|
|
506
|
+
- [README.md](./README.md) - 모듈 개요
|
|
507
|
+
- [Label Studio User Management](https://labelstud.io/guide/manage_users.html)
|
|
508
|
+
- [Things-Factory Role & Privilege](https://github.com/hatiolab/things-factory)
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
**문서 버전**: 1.0
|
|
513
|
+
**작성일**: 2025-10-01
|
|
514
|
+
**업데이트**: 2025-10-01
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function bootstrap() {}
|
package/client/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './label-studio-wrapper.js'
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import './label-studio-wrapper.js'
|
|
2
|
+
|
|
3
|
+
import { css, html, LitElement } from 'lit'
|
|
4
|
+
import { customElement, state, property } from 'lit/decorators.js'
|
|
5
|
+
import { gql } from 'graphql-tag'
|
|
6
|
+
import { client } from '@operato/graphql'
|
|
7
|
+
import { PageView } from '@things-factory/shell/client'
|
|
8
|
+
|
|
9
|
+
@customElement('label-studio-label-page')
|
|
10
|
+
export class LabelStudioLabelPage extends PageView {
|
|
11
|
+
static styles = css`
|
|
12
|
+
:host {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
height: 100%;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.label-studio-wrapper {
|
|
20
|
+
flex: 1;
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.error {
|
|
27
|
+
padding: 20px;
|
|
28
|
+
color: var(--md-sys-color-error);
|
|
29
|
+
text-align: center;
|
|
30
|
+
}
|
|
31
|
+
`
|
|
32
|
+
|
|
33
|
+
@property({ type: String }) projectId: string = ''
|
|
34
|
+
|
|
35
|
+
async pageUpdated(changes, lifecycle, changedBefore) {
|
|
36
|
+
if (this.active && lifecycle.resourceId) {
|
|
37
|
+
this.projectId = lifecycle.resourceId
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
render() {
|
|
42
|
+
const projectId = this.projectId
|
|
43
|
+
|
|
44
|
+
if (!projectId) {
|
|
45
|
+
return html`<div class="error">Error: No project ID provided</div>`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const path = `/projects/${projectId}`
|
|
49
|
+
|
|
50
|
+
return html` <label-studio-wrapper .path=${path}></label-studio-wrapper> `
|
|
51
|
+
}
|
|
52
|
+
}
|