diff --git a/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx b/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx index bf715d01..a64b0c38 100644 --- a/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx +++ b/adminfront/src/features/user-groups/routes/TenantUserGroupsTab.tsx @@ -127,8 +127,9 @@ const SidebarNode: React.FC<{ return (
-
{node.recursiveMemberCount} -
+ {isExpanded && hasChildren && (
@@ -233,7 +234,7 @@ const MemberTable: React.FC<{ {t("ui.admin.users.table.role", "역할")} - + @@ -307,22 +308,22 @@ function TenantUserGroupsTab() { return buildTenantFullTree(allItems, tenantId); }, [allTenantsData, tenantId]); - // Find selected node in the built tree - const findNode = (nodes: TenantNode[], id: string): TenantNode | null => { - if (!currentBase) return null; - if (currentBase.id === id) return currentBase; - - for (const node of nodes) { - if (node.id === id) return node; - if (node.children.length > 0) { - const found = findNode(node.children, id); - if (found) return found; - } - } - return null; - }; - const selectedNode = useMemo(() => { + // Find selected node in the built tree + const findNode = (nodes: TenantNode[], id: string): TenantNode | null => { + if (!currentBase) return null; + if (currentBase.id === id) return currentBase; + + for (const node of nodes) { + if (node.id === id) return node; + if (node.children.length > 0) { + const found = findNode(node.children, id); + if (found) return found; + } + } + return null; + }; + if (!currentBase) return null; return findNode(currentBase.children, selectedNodeId) || currentBase; }, [currentBase, selectedNodeId]); @@ -707,9 +708,10 @@ const UserAddDialog: React.FC<{ toast.success(t("msg.info.saved_success", "사용자가 배정되었습니다.")); onOpenChange(false); resetFields(); - } catch (err: any) { + } catch (err) { + const error = err as AxiosError<{ error?: string }>; toast.error( - err.response?.data?.error || + error.response?.data?.error || t("msg.admin.users.detail.update_error", "배정 실패"), ); } finally { diff --git a/docker/compose.ory.yaml b/docker/compose.ory.yaml index 5f18ca75..aae3dca6 100644 --- a/docker/compose.ory.yaml +++ b/docker/compose.ory.yaml @@ -88,7 +88,6 @@ services: - ory-net - hydranet - # [수정됨] Oathkeeper 서비스 추가 (Backend 연결 문제 해결) oathkeeper: image: oryd/oathkeeper:${OATHKEEPER_VERSION:-v0.40.6} container_name: oathkeeper @@ -104,18 +103,83 @@ services: - oathkeeper_logs:/var/log/oathkeeper networks: - ory-net - - baron_net # Backend가 통신하기 위해 필수 + - baron_net - public_net ports: - - "4455:4455" # Proxy - - "4456:4456" # API (Backend 헬스체크용) + - "4455:4455" + - "4456:4456" healthcheck: test: ["CMD", "wget", "-qO-", "http://127.0.0.1:4456/health/ready"] interval: 5s timeout: 5s retries: 5 + + ory_stack_check: + image: alpine:latest + container_name: ory_stack_check + command: > + /bin/sh -c " + apk add --no-cache curl; + echo 'Wait for services...'; + until curl -s http://kratos:4433/health/ready; do sleep 1; done; + until curl -s http://hydra:4444/health/ready; do sleep 1; done; + echo 'Ory Stack is fully operational!';" + depends_on: + - kratos + - hydra + networks: + - ory-net + + init-rp: + image: oryd/hydra:${HYDRA_VERSION:-v25.4.0} + container_name: init-rp + entrypoint: ["/bin/sh"] + command: + - -ec + - | + echo "Creating/Updating OAuth2 Clients..." + + hydra create oauth2-client \ + --endpoint http://hydra:4445 \ + --id adminfront \ + --name "AdminFront" \ + --grant-type authorization_code,refresh_token \ + --response-type code \ + --scope openid,offline_access,profile,email \ + --token-endpoint-auth-method none \ + --redirect-uri ${ADMINFRONT_CALLBACK_URLS:-http://localhost:5173/auth/callback,http://172.16.10.176:5173/auth/callback} + + hydra create oauth2-client \ + --endpoint http://hydra:4445 \ + --id devfront \ + --name "DevFront" \ + --grant-type authorization_code,refresh_token \ + --response-type code \ + --scope openid,offline_access,profile,email \ + --token-endpoint-auth-method none \ + --redirect-uri ${DEVFRONT_CALLBACK_URLS:-http://localhost:5174/auth/callback,http://172.16.10.176:5174/auth/callback} + + hydra create oauth2-client \ + --endpoint http://hydra:4445 \ + --id orgfront \ + --name "OrgFront" \ + --grant-type authorization_code,refresh_token \ + --response-type code \ + --scope openid,offline_access,profile,email \ + --token-endpoint-auth-method none \ + --redirect-uri ${ORGFRONT_CALLBACK_URLS:-http://localhost:5175/auth/callback,http://172.16.10.176:5175/auth/callback,https://baron-orgchart.hmac.kr/auth/callback} + + echo "All RP clients initialized successfully." + depends_on: + ory_stack_check: + condition: service_completed_successfully + networks: + - ory-net + - hydranet + volumes: ory_postgres_data: + oathkeeper_logs: networks: ory-net: @@ -130,7 +194,6 @@ networks: public_net: external: true name: public_net - # [수정됨] Baron Net 추가 정의 (Oathkeeper 연결용) baron_net: external: true name: baron_net