@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.
Files changed (152) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/EXTERNAL_DATA_SOURCING.md +484 -0
  3. package/IMPLEMENTATION_GUIDE.md +469 -0
  4. package/INTEGRATION.md +279 -0
  5. package/README.md +1014 -0
  6. package/SETUP_GUIDE.md +577 -0
  7. package/TEST_GUIDE.md +387 -0
  8. package/UI_CUSTOMIZATION.md +395 -0
  9. package/USER_SYNC_GUIDE.md +514 -0
  10. package/client/bootstrap.ts +1 -0
  11. package/client/index.ts +1 -0
  12. package/client/label-studio-label-page.ts +52 -0
  13. package/client/label-studio-project-create.ts +216 -0
  14. package/client/label-studio-project-list.ts +214 -0
  15. package/client/label-studio-wrapper.ts +294 -0
  16. package/client/route.ts +15 -0
  17. package/client/tsconfig.json +13 -0
  18. package/config/config.development.js +124 -0
  19. package/config/config.production.js +182 -0
  20. package/dist-client/bootstrap.d.ts +1 -0
  21. package/dist-client/bootstrap.js +2 -0
  22. package/dist-client/bootstrap.js.map +1 -0
  23. package/dist-client/index.d.ts +1 -0
  24. package/dist-client/index.js +2 -0
  25. package/dist-client/index.js.map +1 -0
  26. package/dist-client/label-studio-label-page.d.ts +8 -0
  27. package/dist-client/label-studio-label-page.js +54 -0
  28. package/dist-client/label-studio-label-page.js.map +1 -0
  29. package/dist-client/label-studio-project-create.d.ts +16 -0
  30. package/dist-client/label-studio-project-create.js +235 -0
  31. package/dist-client/label-studio-project-create.js.map +1 -0
  32. package/dist-client/label-studio-project-list.d.ts +16 -0
  33. package/dist-client/label-studio-project-list.js +222 -0
  34. package/dist-client/label-studio-project-list.js.map +1 -0
  35. package/dist-client/label-studio-wrapper.d.ts +57 -0
  36. package/dist-client/label-studio-wrapper.js +304 -0
  37. package/dist-client/label-studio-wrapper.js.map +1 -0
  38. package/dist-client/route.d.ts +1 -0
  39. package/dist-client/route.js +14 -0
  40. package/dist-client/route.js.map +1 -0
  41. package/dist-client/tsconfig.tsbuildinfo +1 -0
  42. package/dist-server/controller/label-studio-role-mapper.d.ts +35 -0
  43. package/dist-server/controller/label-studio-role-mapper.js +65 -0
  44. package/dist-server/controller/label-studio-role-mapper.js.map +1 -0
  45. package/dist-server/controller/user-provisioning-service.d.ts +66 -0
  46. package/dist-server/controller/user-provisioning-service.js +264 -0
  47. package/dist-server/controller/user-provisioning-service.js.map +1 -0
  48. package/dist-server/index.d.ts +7 -0
  49. package/dist-server/index.js +19 -0
  50. package/dist-server/index.js.map +1 -0
  51. package/dist-server/route/label-studio-sso.d.ts +2 -0
  52. package/dist-server/route/label-studio-sso.js +156 -0
  53. package/dist-server/route/label-studio-sso.js.map +1 -0
  54. package/dist-server/route/webhook.d.ts +65 -0
  55. package/dist-server/route/webhook.js +248 -0
  56. package/dist-server/route/webhook.js.map +1 -0
  57. package/dist-server/route.d.ts +1 -0
  58. package/dist-server/route.js +21 -0
  59. package/dist-server/route.js.map +1 -0
  60. package/dist-server/service/ai-prediction-service.d.ts +27 -0
  61. package/dist-server/service/ai-prediction-service.js +222 -0
  62. package/dist-server/service/ai-prediction-service.js.map +1 -0
  63. package/dist-server/service/dataset-labeling-integration.d.ts +44 -0
  64. package/dist-server/service/dataset-labeling-integration.js +512 -0
  65. package/dist-server/service/dataset-labeling-integration.js.map +1 -0
  66. package/dist-server/service/external-data-source-service.d.ts +78 -0
  67. package/dist-server/service/external-data-source-service.js +415 -0
  68. package/dist-server/service/external-data-source-service.js.map +1 -0
  69. package/dist-server/service/index.d.ts +12 -0
  70. package/dist-server/service/index.js +27 -0
  71. package/dist-server/service/index.js.map +1 -0
  72. package/dist-server/service/label-studio-sso-service.d.ts +38 -0
  73. package/dist-server/service/label-studio-sso-service.js +98 -0
  74. package/dist-server/service/label-studio-sso-service.js.map +1 -0
  75. package/dist-server/service/ml/ml-backend-service.d.ts +23 -0
  76. package/dist-server/service/ml/ml-backend-service.js +153 -0
  77. package/dist-server/service/ml/ml-backend-service.js.map +1 -0
  78. package/dist-server/service/prediction/prediction-management.d.ts +32 -0
  79. package/dist-server/service/prediction/prediction-management.js +299 -0
  80. package/dist-server/service/prediction/prediction-management.js.map +1 -0
  81. package/dist-server/service/project/project-management.d.ts +36 -0
  82. package/dist-server/service/project/project-management.js +309 -0
  83. package/dist-server/service/project/project-management.js.map +1 -0
  84. package/dist-server/service/task/task-management.d.ts +42 -0
  85. package/dist-server/service/task/task-management.js +372 -0
  86. package/dist-server/service/task/task-management.js.map +1 -0
  87. package/dist-server/service/user-provisioning/user-sync-mutation.d.ts +28 -0
  88. package/dist-server/service/user-provisioning/user-sync-mutation.js +111 -0
  89. package/dist-server/service/user-provisioning/user-sync-mutation.js.map +1 -0
  90. package/dist-server/service/webhook/webhook-management.d.ts +21 -0
  91. package/dist-server/service/webhook/webhook-management.js +134 -0
  92. package/dist-server/service/webhook/webhook-management.js.map +1 -0
  93. package/dist-server/tsconfig.tsbuildinfo +1 -0
  94. package/dist-server/types/dataset-labeling-types.d.ts +71 -0
  95. package/dist-server/types/dataset-labeling-types.js +259 -0
  96. package/dist-server/types/dataset-labeling-types.js.map +1 -0
  97. package/dist-server/types/label-studio-types.d.ts +128 -0
  98. package/dist-server/types/label-studio-types.js +494 -0
  99. package/dist-server/types/label-studio-types.js.map +1 -0
  100. package/dist-server/types/prediction-types.d.ts +39 -0
  101. package/dist-server/types/prediction-types.js +121 -0
  102. package/dist-server/types/prediction-types.js.map +1 -0
  103. package/dist-server/utils/annotation-exporter.d.ts +104 -0
  104. package/dist-server/utils/annotation-exporter.js +261 -0
  105. package/dist-server/utils/annotation-exporter.js.map +1 -0
  106. package/dist-server/utils/label-config-builder.d.ts +117 -0
  107. package/dist-server/utils/label-config-builder.js +286 -0
  108. package/dist-server/utils/label-config-builder.js.map +1 -0
  109. package/dist-server/utils/label-studio-api-client.d.ts +180 -0
  110. package/dist-server/utils/label-studio-api-client.js +401 -0
  111. package/dist-server/utils/label-studio-api-client.js.map +1 -0
  112. package/dist-server/utils/media-url-extractor.d.ts +45 -0
  113. package/dist-server/utils/media-url-extractor.js +152 -0
  114. package/dist-server/utils/media-url-extractor.js.map +1 -0
  115. package/dist-server/utils/task-transformer.d.ts +108 -0
  116. package/dist-server/utils/task-transformer.js +260 -0
  117. package/dist-server/utils/task-transformer.js.map +1 -0
  118. package/package.json +47 -0
  119. package/server/SERVER_STRUCTURE.md +351 -0
  120. package/server/controller/label-studio-role-mapper.ts +76 -0
  121. package/server/controller/user-provisioning-service.ts +340 -0
  122. package/server/index.ts +19 -0
  123. package/server/route/label-studio-sso.ts +194 -0
  124. package/server/route/webhook.ts +304 -0
  125. package/server/route.ts +35 -0
  126. package/server/service/ai-prediction-service.ts +239 -0
  127. package/server/service/dataset-labeling-integration.ts +590 -0
  128. package/server/service/external-data-source-service.ts +438 -0
  129. package/server/service/index.ts +24 -0
  130. package/server/service/label-studio-sso-service.ts +108 -0
  131. package/server/service/labeling-scenario-service.ts.deprecated +566 -0
  132. package/server/service/ml/ml-backend-service.ts +127 -0
  133. package/server/service/prediction/prediction-management.ts +281 -0
  134. package/server/service/project/project-management.ts +284 -0
  135. package/server/service/task/task-management.ts +363 -0
  136. package/server/service/user-provisioning/user-sync-mutation.ts +80 -0
  137. package/server/service/webhook/webhook-management.ts +109 -0
  138. package/server/tsconfig.json +11 -0
  139. package/server/types/dataset-labeling-types.ts +181 -0
  140. package/server/types/global.d.ts +23 -0
  141. package/server/types/label-studio-types.ts +346 -0
  142. package/server/types/prediction-types.ts +86 -0
  143. package/server/types/scenario-types.ts.deprecated +362 -0
  144. package/server/utils/annotation-exporter.ts +340 -0
  145. package/server/utils/label-config-builder.ts +340 -0
  146. package/server/utils/label-studio-api-client.ts +487 -0
  147. package/server/utils/media-url-extractor.ts +193 -0
  148. package/server/utils/task-transformer.ts +342 -0
  149. package/test-ai-prediction.js +268 -0
  150. package/test-dataset-integration.js +449 -0
  151. package/test-simple.js +89 -0
  152. 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() {}
@@ -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
+ }