hazo_auth 4.4.1 → 4.5.1
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/README.md +207 -5
- package/SETUP_CHECKLIST.md +1 -1
- package/cli-src/lib/auth/auth_types.ts +22 -0
- package/cli-src/lib/auth/hazo_get_auth.server.ts +25 -1
- package/cli-src/lib/auth/session_token_validator.edge.ts +1 -0
- package/cli-src/lib/config/default_config.ts +36 -0
- package/cli-src/lib/navbar_config.server.ts +129 -0
- package/cli-src/lib/scope_hierarchy_config.server.ts +3 -14
- package/cli-src/lib/services/registration_service.ts +12 -0
- package/cli-src/lib/services/scope_labels_service.ts +21 -21
- package/cli-src/lib/services/scope_service.ts +15 -11
- package/cli-src/lib/services/session_token_service.ts +1 -0
- package/cli-src/lib/ui_shell_config.server.ts +15 -0
- package/cli-src/lib/user_types_config.server.ts +178 -0
- package/dist/app/api/hazo_auth/me/route.d.ts.map +1 -1
- package/dist/app/api/hazo_auth/me/route.js +17 -0
- package/dist/app/api/hazo_auth/org_management/orgs/route.d.ts +26 -0
- package/dist/app/api/hazo_auth/org_management/orgs/route.d.ts.map +1 -0
- package/dist/app/api/hazo_auth/org_management/orgs/route.js +315 -0
- package/dist/app/api/hazo_auth/user_management/users/route.d.ts +11 -1
- package/dist/app/api/hazo_auth/user_management/users/route.d.ts.map +1 -1
- package/dist/app/api/hazo_auth/user_management/users/route.js +121 -16
- package/dist/components/layouts/my_settings/components/profile_picture_library_tab.d.ts.map +1 -1
- package/dist/components/layouts/my_settings/components/profile_picture_library_tab.js +8 -14
- package/dist/components/layouts/rbac_test/index.d.ts +1 -3
- package/dist/components/layouts/rbac_test/index.d.ts.map +1 -1
- package/dist/components/layouts/rbac_test/index.js +2 -2
- package/dist/components/layouts/shared/components/auth_navbar.d.ts +26 -0
- package/dist/components/layouts/shared/components/auth_navbar.d.ts.map +1 -0
- package/dist/components/layouts/shared/components/auth_navbar.js +14 -0
- package/dist/components/layouts/shared/components/auth_page_shell.d.ts +3 -1
- package/dist/components/layouts/shared/components/auth_page_shell.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/auth_page_shell.js +17 -2
- package/dist/components/layouts/shared/components/standalone_layout_wrapper.d.ts +6 -1
- package/dist/components/layouts/shared/components/standalone_layout_wrapper.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/standalone_layout_wrapper.js +7 -2
- package/dist/components/layouts/shared/index.d.ts +2 -0
- package/dist/components/layouts/shared/index.d.ts.map +1 -1
- package/dist/components/layouts/shared/index.js +1 -0
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts +3 -2
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts.map +1 -1
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.js +45 -18
- package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts +3 -2
- package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts.map +1 -1
- package/dist/components/layouts/user_management/components/scope_labels_tab.js +48 -20
- package/dist/components/layouts/user_management/components/user_scopes_tab.d.ts.map +1 -1
- package/dist/components/layouts/user_management/components/user_scopes_tab.js +1 -1
- package/dist/components/layouts/user_management/index.d.ts +11 -3
- package/dist/components/layouts/user_management/index.d.ts.map +1 -1
- package/dist/components/layouts/user_management/index.js +52 -5
- package/dist/components/ui/user-type-badge.d.ts +23 -0
- package/dist/components/ui/user-type-badge.d.ts.map +1 -0
- package/dist/components/ui/user-type-badge.js +42 -0
- package/dist/lib/auth/auth_types.d.ts +17 -0
- package/dist/lib/auth/auth_types.d.ts.map +1 -1
- package/dist/lib/auth/auth_types.js +11 -0
- package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_auth.server.js +21 -1
- package/dist/lib/config/default_config.d.ts +60 -0
- package/dist/lib/config/default_config.d.ts.map +1 -1
- package/dist/lib/config/default_config.js +34 -0
- package/dist/lib/navbar_config.server.d.ts +36 -0
- package/dist/lib/navbar_config.server.d.ts.map +1 -0
- package/dist/lib/navbar_config.server.js +45 -0
- package/dist/lib/scope_hierarchy_config.server.d.ts +3 -7
- package/dist/lib/scope_hierarchy_config.server.d.ts.map +1 -1
- package/dist/lib/scope_hierarchy_config.server.js +1 -10
- package/dist/lib/services/registration_service.d.ts.map +1 -1
- package/dist/lib/services/registration_service.js +8 -0
- package/dist/lib/services/scope_labels_service.d.ts +7 -7
- package/dist/lib/services/scope_labels_service.d.ts.map +1 -1
- package/dist/lib/services/scope_labels_service.js +20 -20
- package/dist/lib/services/scope_service.d.ts +8 -5
- package/dist/lib/services/scope_service.d.ts.map +1 -1
- package/dist/lib/services/scope_service.js +9 -8
- package/dist/lib/ui_shell_config.server.d.ts +5 -0
- package/dist/lib/ui_shell_config.server.d.ts.map +1 -1
- package/dist/lib/ui_shell_config.server.js +5 -0
- package/dist/lib/user_types_config.server.d.ts +56 -0
- package/dist/lib/user_types_config.server.d.ts.map +1 -0
- package/dist/lib/user_types_config.server.js +100 -0
- package/dist/server/routes/index.d.ts +1 -0
- package/dist/server/routes/index.d.ts.map +1 -1
- package/dist/server/routes/index.js +2 -0
- package/dist/server/routes/org_management_orgs.d.ts +2 -0
- package/dist/server/routes/org_management_orgs.d.ts.map +1 -0
- package/dist/server/routes/org_management_orgs.js +2 -0
- package/hazo_auth_config.example.ini +9 -0
- package/package.json +1 -1
- package/cli-src/server/logging/logger_service.ts +0 -56
- /package/public/profile_pictures/library/Cars/{050 - citroe/314/210n_c3.jpeg" → 050 - citro/303/253n_c3.jpeg"} +0 -0
- /package/public/profile_pictures/library/Cars/{064 - lamborghini_huraca/314/201n.jpeg" → 064 - lamborghini_hurac/303/241n.jpeg"} +0 -0
- /package/public/profile_pictures/library/Cars/{099 - citroe/314/210n_2cv_(classic).jpeg" → 099 - citro/303/253n_2cv_(classic).jpeg"} +0 -0
- /package/public/profile_pictures/library/Cars/{131 - lamborghini_huraca/314/201n_sto.jpeg" → 131 - lamborghini_hurac/303/241n_sto.jpeg"} +0 -0
|
@@ -60,7 +60,7 @@ function getUserInitials(user) {
|
|
|
60
60
|
* @param props - Component props
|
|
61
61
|
* @returns RBAC test layout component
|
|
62
62
|
*/
|
|
63
|
-
export function RbacTestLayout({ className, hrbacEnabled = false,
|
|
63
|
+
export function RbacTestLayout({ className, hrbacEnabled = false, }) {
|
|
64
64
|
var _a;
|
|
65
65
|
const { apiBasePath } = useHazoAuthConfig();
|
|
66
66
|
const authResult = use_hazo_auth();
|
|
@@ -361,7 +361,7 @@ export function RbacTestLayout({ className, hrbacEnabled = false, defaultOrg = "
|
|
|
361
361
|
}
|
|
362
362
|
return (_jsxs("div", { className: `cls_rbac_test_layout flex flex-col gap-6 p-4 w-full max-w-5xl mx-auto ${className || ""}`, children: [_jsxs("div", { className: "cls_rbac_test_header", children: [_jsx("h1", { className: "text-2xl font-bold", children: "RBAC & HRBAC Test" }), _jsx("p", { className: "text-muted-foreground", children: "Test Role-Based Access Control (RBAC) and Hierarchical RBAC (HRBAC) for any user." })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs(CardTitle, { className: "text-lg flex items-center gap-2", children: [_jsx(User, { className: "h-5 w-5" }), "Select User to Test"] }), _jsx(CardDescription, { children: "Choose a user to test their permissions and scope access" })] }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "user_select", children: "User" }), usersLoading ? (_jsxs("div", { className: "flex items-center gap-2 p-2", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), _jsx("span", { className: "text-sm text-muted-foreground", children: "Loading users..." })] })) : (_jsxs(Select, { value: selectedUserId, onValueChange: setSelectedUserId, children: [_jsx(SelectTrigger, { id: "user_select", className: "w-full", children: _jsx(SelectValue, { placeholder: "Select a user" }) }), _jsx(SelectContent, { children: users.map((user) => (_jsx(SelectItem, { value: user.id, children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Avatar, { className: "h-6 w-6", children: [_jsx(AvatarImage, { src: user.profile_picture_url || undefined }), _jsx(AvatarFallback, { className: "bg-slate-200 text-slate-600 text-xs", children: getUserInitials(user) })] }), _jsx("span", { children: user.name || user.email_address }), user.name && (_jsxs("span", { className: "text-muted-foreground text-xs", children: ["(", user.email_address, ")"] }))] }) }, user.id))) })] }))] }), selectedUser && (_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 p-4 border rounded-lg bg-muted/30", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs(Avatar, { className: "h-12 w-12", children: [_jsx(AvatarImage, { src: selectedUser.profile_picture_url || undefined }), _jsx(AvatarFallback, { className: "bg-slate-200 text-slate-600", children: getUserInitials(selectedUser) })] }), _jsxs("div", { children: [_jsx("p", { className: "font-medium", children: selectedUser.name || selectedUser.email_address }), selectedUser.name && (_jsx("p", { className: "text-sm text-muted-foreground", children: selectedUser.email_address })), _jsxs("p", { className: "text-xs text-muted-foreground font-mono", children: [selectedUser.id.substring(0, 8), "..."] })] })] }), userDataLoading ? (_jsx("div", { className: "flex items-center justify-center", children: _jsx(Loader2, { className: "h-5 w-5 animate-spin text-slate-400" }) })) : (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Permissions" }), _jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: userPermissions.length > 0 ? (userPermissions.map((p) => (_jsx("span", { className: "px-2 py-0.5 bg-blue-100 text-blue-700 rounded text-xs", children: p }, p)))) : (_jsx("span", { className: "text-muted-foreground text-sm", children: "None" })) })] }), hrbacEnabled && (_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Assigned Scopes" }), _jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: userScopes.length > 0 ? (userScopes.map((s) => (_jsx("span", { className: "px-2 py-0.5 bg-green-100 text-green-700 rounded text-xs", title: `${SCOPE_LEVEL_LABELS[s.scope_type]}: ${s.scope_seq}`, children: s.scope_seq }, `${s.scope_type}-${s.scope_id}`)))) : (_jsx("span", { className: "text-muted-foreground text-sm", children: "None" })) })] }))] }))] }))] })] }), _jsxs(Tabs, { defaultValue: "rbac", className: "w-full", children: [_jsxs(TabsList, { className: "grid w-full grid-cols-2", children: [_jsxs(TabsTrigger, { value: "rbac", className: "flex items-center gap-2", children: [_jsx(Shield, { className: "h-4 w-4" }), "RBAC Test"] }), _jsxs(TabsTrigger, { value: "hrbac", className: "flex items-center gap-2", disabled: !hrbacEnabled, children: [_jsx(FolderTree, { className: "h-4 w-4" }), "HRBAC Test ", !hrbacEnabled && "(Disabled)"] })] }), _jsxs(TabsContent, { value: "rbac", className: "flex flex-col gap-4", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-lg", children: "Permission Test" }), _jsx(CardDescription, { children: "Select permissions to test if the selected user has them" })] }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [permissionsLoading ? (_jsx("div", { className: "flex items-center justify-center p-4", children: _jsx(Loader2, { className: "h-5 w-5 animate-spin text-slate-400" }) })) : (_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 max-h-[300px] overflow-auto p-2 border rounded", children: availablePermissions.map((perm) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: `rbac_${perm.permission_name}`, checked: selectedPermissions.includes(perm.permission_name), onCheckedChange: (checked) => handlePermissionToggle(perm.permission_name, checked) }), _jsxs("label", { htmlFor: `rbac_${perm.permission_name}`, className: "text-sm cursor-pointer flex-1", children: [perm.permission_name, userPermissions.includes(perm.permission_name) && (_jsx(CheckCircle, { className: "inline h-3 w-3 text-green-500 ml-1" }))] })] }, perm.permission_name))) })), _jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsxs("span", { children: ["Selected: ", selectedPermissions.length, " permission(s)"] }), selectedPermissions.length > 0 && (_jsxs("span", { className: "text-xs", children: ["(", selectedPermissions.join(", "), ")"] }))] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { onClick: handleRunRbacTest, disabled: rbacTesting || !selectedUserId, children: rbacTesting ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Testing..."] })) : (_jsxs(_Fragment, { children: [_jsx(Play, { className: "h-4 w-4 mr-2" }), "Test Permissions"] })) }), _jsx(Button, { onClick: handleClearRbac, variant: "outline", children: "Clear" })] })] })] }), rbacResult && (_jsxs(Card, { className: rbacResult.permission_ok
|
|
363
363
|
? "border-green-200 bg-green-50/50"
|
|
364
|
-
: "border-red-200 bg-red-50/50", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-lg flex items-center gap-2", children: rbacResult.permission_ok ? (_jsxs(_Fragment, { children: [_jsx(CheckCircle, { className: "h-5 w-5 text-green-500" }), _jsx("span", { className: "text-green-700", children: "Permission Check Passed" })] })) : (_jsxs(_Fragment, { children: [_jsx(XCircle, { className: "h-5 w-5 text-red-500" }), _jsx("span", { className: "text-red-700", children: "Permission Check Failed" })] })) }) }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [rbacResult.error && (_jsx("div", { className: "bg-red-100 border border-red-200 rounded p-3", children: _jsx("p", { className: "text-red-700 text-sm", children: rbacResult.error }) })), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Authenticated" }), _jsx("p", { className: `text-sm font-medium ${rbacResult.authenticated ? "text-green-600" : "text-red-600"}`, children: rbacResult.authenticated ? "Yes" : "No" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Permission OK" }), _jsx("p", { className: `text-sm font-medium ${rbacResult.permission_ok ? "text-green-600" : "text-red-600"}`, children: rbacResult.permission_ok ? "Yes" : "No" })] })] }), rbacResult.missing_permissions && rbacResult.missing_permissions.length > 0 && (_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Missing Permissions" }), _jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: rbacResult.missing_permissions.map((p) => (_jsx("span", { className: "px-2 py-0.5 bg-red-100 text-red-700 rounded text-xs", children: p }, p))) })] }))] })] }))] }), _jsx(TabsContent, { value: "hrbac", className: "flex flex-col gap-4", children: !hrbacEnabled ? (_jsx(Card, { children: _jsxs(CardContent, { className: "flex flex-col items-center justify-center p-8 gap-4", children: [_jsx(AlertCircle, { className: "h-12 w-12 text-amber-500" }), _jsx("h2", { className: "text-lg font-semibold", children: "HRBAC Not Enabled" }), _jsxs("p", { className: "text-muted-foreground text-center max-w-md", children: ["Enable HRBAC by setting", " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded", children: "enable_hrbac = true" }), " in the", " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded", children: "[hazo_auth__scope_hierarchy]" }), " ", "section."] })] }) })) : (_jsxs(_Fragment, { children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs(CardTitle, { className: "text-lg flex items-center justify-between", children: [_jsx("span", { children: "Scope Access Test" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void loadScopeTree(), disabled: treeLoading, children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${treeLoading ? "animate-spin" : ""}` }), "Refresh"] })] }), _jsx(CardDescription, { children: "Select a scope from the tree and test if the selected user has access" })] }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Select Scope" }), treeLoading ? (_jsx("div", { className: "flex items-center justify-center p-8 border rounded-lg", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : scopeTree.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center justify-center p-6 border rounded-lg border-dashed", children: [_jsx(FolderTree, { className: "h-8 w-8 text-muted-foreground mb-2" }), _jsx("p", { className: "text-sm text-muted-foreground text-center", children: "No scopes available. Create scopes in User Management first." })] })) : (_jsx("div", { className: "border rounded-lg max-h-[250px] overflow-auto", children: _jsx(TreeView, { data: treeData, expandAll: true, defaultNodeIcon: Building2, defaultLeafIcon: Building2, onSelectChange: handleTreeSelectChange, initialSelectedItemId: selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.id, className: "w-full" }) }))] }), (selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.scopeData) && (_jsxs("div", { className: "p-3 border rounded-lg bg-muted/50", children: [_jsxs("p", { className: "text-sm", children: [_jsx("span", { className: "font-medium", children: "Selected:" }), " ", selectedTreeItem.scopeData.name] }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [SCOPE_LEVEL_LABELS[selectedTreeItem.scopeData.level], " -", " ", selectedTreeItem.scopeData.seq
|
|
364
|
+
: "border-red-200 bg-red-50/50", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-lg flex items-center gap-2", children: rbacResult.permission_ok ? (_jsxs(_Fragment, { children: [_jsx(CheckCircle, { className: "h-5 w-5 text-green-500" }), _jsx("span", { className: "text-green-700", children: "Permission Check Passed" })] })) : (_jsxs(_Fragment, { children: [_jsx(XCircle, { className: "h-5 w-5 text-red-500" }), _jsx("span", { className: "text-red-700", children: "Permission Check Failed" })] })) }) }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [rbacResult.error && (_jsx("div", { className: "bg-red-100 border border-red-200 rounded p-3", children: _jsx("p", { className: "text-red-700 text-sm", children: rbacResult.error }) })), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Authenticated" }), _jsx("p", { className: `text-sm font-medium ${rbacResult.authenticated ? "text-green-600" : "text-red-600"}`, children: rbacResult.authenticated ? "Yes" : "No" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Permission OK" }), _jsx("p", { className: `text-sm font-medium ${rbacResult.permission_ok ? "text-green-600" : "text-red-600"}`, children: rbacResult.permission_ok ? "Yes" : "No" })] })] }), rbacResult.missing_permissions && rbacResult.missing_permissions.length > 0 && (_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Missing Permissions" }), _jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: rbacResult.missing_permissions.map((p) => (_jsx("span", { className: "px-2 py-0.5 bg-red-100 text-red-700 rounded text-xs", children: p }, p))) })] }))] })] }))] }), _jsx(TabsContent, { value: "hrbac", className: "flex flex-col gap-4", children: !hrbacEnabled ? (_jsx(Card, { children: _jsxs(CardContent, { className: "flex flex-col items-center justify-center p-8 gap-4", children: [_jsx(AlertCircle, { className: "h-12 w-12 text-amber-500" }), _jsx("h2", { className: "text-lg font-semibold", children: "HRBAC Not Enabled" }), _jsxs("p", { className: "text-muted-foreground text-center max-w-md", children: ["Enable HRBAC by setting", " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded", children: "enable_hrbac = true" }), " in the", " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded", children: "[hazo_auth__scope_hierarchy]" }), " ", "section."] })] }) })) : (_jsxs(_Fragment, { children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs(CardTitle, { className: "text-lg flex items-center justify-between", children: [_jsx("span", { children: "Scope Access Test" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void loadScopeTree(), disabled: treeLoading, children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${treeLoading ? "animate-spin" : ""}` }), "Refresh"] })] }), _jsx(CardDescription, { children: "Select a scope from the tree and test if the selected user has access" })] }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Select Scope" }), treeLoading ? (_jsx("div", { className: "flex items-center justify-center p-8 border rounded-lg", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : scopeTree.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center justify-center p-6 border rounded-lg border-dashed", children: [_jsx(FolderTree, { className: "h-8 w-8 text-muted-foreground mb-2" }), _jsx("p", { className: "text-sm text-muted-foreground text-center", children: "No scopes available. Create scopes in User Management first." })] })) : (_jsx("div", { className: "border rounded-lg max-h-[250px] overflow-auto", children: _jsx(TreeView, { data: treeData, expandAll: true, defaultNodeIcon: Building2, defaultLeafIcon: Building2, onSelectChange: handleTreeSelectChange, initialSelectedItemId: selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.id, className: "w-full" }) }))] }), (selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.scopeData) && (_jsxs("div", { className: "p-3 border rounded-lg bg-muted/50", children: [_jsxs("p", { className: "text-sm", children: [_jsx("span", { className: "font-medium", children: "Selected:" }), " ", selectedTreeItem.scopeData.name] }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [SCOPE_LEVEL_LABELS[selectedTreeItem.scopeData.level], " -", " ", selectedTreeItem.scopeData.seq] })] })), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Additional Permissions (Optional)" }), permissionsLoading ? (_jsx("div", { className: "flex items-center justify-center p-4", children: _jsx(Loader2, { className: "h-5 w-5 animate-spin text-slate-400" }) })) : (_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 max-h-[150px] overflow-auto p-2 border rounded", children: availablePermissions.map((perm) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: `hrbac_${perm.permission_name}`, checked: hrbacPermissions.includes(perm.permission_name), onCheckedChange: (checked) => handleHrbacPermissionToggle(perm.permission_name, checked) }), _jsx("label", { htmlFor: `hrbac_${perm.permission_name}`, className: "text-sm cursor-pointer flex-1", children: perm.permission_name })] }, perm.permission_name))) }))] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { onClick: handleRunHrbacTest, disabled: hrbacTesting || !selectedUserId || !(selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.scopeData), children: hrbacTesting ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Testing..."] })) : (_jsxs(_Fragment, { children: [_jsx(Play, { className: "h-4 w-4 mr-2" }), "Test Scope Access"] })) }), _jsx(Button, { onClick: handleClearHrbac, variant: "outline", children: "Clear" })] })] })] }), hrbacResult && (_jsxs(Card, { className: hrbacResult.scope_ok
|
|
365
365
|
? "border-green-200 bg-green-50/50"
|
|
366
366
|
: "border-red-200 bg-red-50/50", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-lg flex items-center gap-2", children: hrbacResult.scope_ok ? (_jsxs(_Fragment, { children: [_jsx(CheckCircle, { className: "h-5 w-5 text-green-500" }), _jsx("span", { className: "text-green-700", children: "Scope Access Granted" })] })) : (_jsxs(_Fragment, { children: [_jsx(XCircle, { className: "h-5 w-5 text-red-500" }), _jsx("span", { className: "text-red-700", children: "Scope Access Denied" })] })) }) }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [hrbacResult.error && (_jsx("div", { className: "bg-red-100 border border-red-200 rounded p-3", children: _jsx("p", { className: "text-red-700 text-sm", children: hrbacResult.error }) })), _jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Authenticated" }), _jsx("p", { className: `text-sm font-medium ${hrbacResult.authenticated ? "text-green-600" : "text-red-600"}`, children: hrbacResult.authenticated ? "Yes" : "No" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Permission OK" }), _jsx("p", { className: `text-sm font-medium ${hrbacResult.permission_ok ? "text-green-600" : "text-red-600"}`, children: hrbacResult.permission_ok ? "Yes" : "No" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Scope OK" }), _jsx("p", { className: `text-sm font-medium ${hrbacResult.scope_ok === undefined
|
|
367
367
|
? "text-muted-foreground"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type AuthNavbarProps = {
|
|
2
|
+
/** Logo image path */
|
|
3
|
+
logo_path?: string;
|
|
4
|
+
/** Logo width in pixels */
|
|
5
|
+
logo_width?: number;
|
|
6
|
+
/** Logo height in pixels */
|
|
7
|
+
logo_height?: number;
|
|
8
|
+
/** Company/application name displayed next to logo */
|
|
9
|
+
company_name?: string;
|
|
10
|
+
/** Home link path */
|
|
11
|
+
home_path?: string;
|
|
12
|
+
/** Home link label */
|
|
13
|
+
home_label?: string;
|
|
14
|
+
/** Show home link */
|
|
15
|
+
show_home_link?: boolean;
|
|
16
|
+
/** Navbar background color */
|
|
17
|
+
background_color?: string;
|
|
18
|
+
/** Navbar text color */
|
|
19
|
+
text_color?: string;
|
|
20
|
+
/** Navbar height in pixels */
|
|
21
|
+
height?: number;
|
|
22
|
+
/** Additional CSS class */
|
|
23
|
+
className?: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function AuthNavbar({ logo_path, logo_width, logo_height, company_name, home_path, home_label, show_home_link, background_color, text_color, height, className, }: AuthNavbarProps): import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
//# sourceMappingURL=auth_navbar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth_navbar.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/shared/components/auth_navbar.tsx"],"names":[],"mappings":"AAWA,MAAM,MAAM,eAAe,GAAG;IAC5B,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qBAAqB;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sBAAsB;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qBAAqB;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,8BAA8B;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wBAAwB;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAGF,wBAAgB,UAAU,CAAC,EACzB,SAAuB,EACvB,UAAe,EACf,WAAgB,EAChB,YAAiB,EACjB,SAAe,EACf,UAAmB,EACnB,cAAqB,EACrB,gBAAgB,EAChB,UAAU,EACV,MAAW,EACX,SAAS,GACV,EAAE,eAAe,2CAiDjB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// file_description: configurable navbar component for auth pages with logo, company name, and home link
|
|
2
|
+
// section: client_directive
|
|
3
|
+
"use client";
|
|
4
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
|
+
// section: imports
|
|
6
|
+
import Link from "next/link";
|
|
7
|
+
import Image from "next/image";
|
|
8
|
+
import { Home } from "lucide-react";
|
|
9
|
+
import { cn } from "../../../../lib/utils";
|
|
10
|
+
// section: component
|
|
11
|
+
export function AuthNavbar({ logo_path = "/logo.png", logo_width = 32, logo_height = 32, company_name = "", home_path = "/", home_label = "Home", show_home_link = true, background_color, text_color, height = 64, className, }) {
|
|
12
|
+
const navStyle = Object.assign(Object.assign({ height: `${height}px` }, (background_color && { backgroundColor: background_color })), (text_color && { color: text_color }));
|
|
13
|
+
return (_jsxs("nav", { className: cn("cls_auth_navbar flex w-full items-center justify-between border-b border-border/40 bg-background/95 px-4 backdrop-blur supports-[backdrop-filter]:bg-background/60", className), style: navStyle, "aria-label": "Authentication page navigation", children: [_jsx("div", { className: "cls_auth_navbar_brand flex items-center gap-3", children: _jsxs(Link, { href: home_path, className: "cls_auth_navbar_logo_link flex items-center gap-3", children: [_jsx(Image, { src: logo_path, alt: company_name ? `${company_name} logo` : "Logo", width: logo_width, height: logo_height, className: "cls_auth_navbar_logo object-contain" }), company_name && (_jsx("span", { className: "cls_auth_navbar_company_name text-lg font-semibold text-foreground", children: company_name }))] }) }), show_home_link && (_jsx("div", { className: "cls_auth_navbar_links flex items-center gap-4", children: _jsxs(Link, { href: home_path, className: "cls_auth_navbar_home_link flex items-center gap-2 text-sm font-medium text-muted-foreground transition-colors hover:text-foreground", "aria-label": `Navigate to ${home_label}`, children: [_jsx(Home, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: home_label })] }) }))] }));
|
|
14
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
type AuthPageShellProps = {
|
|
3
3
|
children: ReactNode;
|
|
4
|
+
/** Force disable navbar for this page (e.g., dev_lock) */
|
|
5
|
+
disableNavbar?: boolean;
|
|
4
6
|
};
|
|
5
|
-
export declare function AuthPageShell({ children }: AuthPageShellProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export declare function AuthPageShell({ children, disableNavbar }: AuthPageShellProps): import("react/jsx-runtime").JSX.Element;
|
|
6
8
|
export {};
|
|
7
9
|
//# sourceMappingURL=auth_page_shell.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth_page_shell.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/shared/components/auth_page_shell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMvC,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"auth_page_shell.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/shared/components/auth_page_shell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMvC,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,SAAS,CAAC;IACpB,0DAA0D;IAC1D,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAGF,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,aAAqB,EAAE,EAAE,kBAAkB,2CAsCpF"}
|
|
@@ -3,10 +3,25 @@ import { SidebarLayoutWrapper } from "./sidebar_layout_wrapper";
|
|
|
3
3
|
import { StandaloneLayoutWrapper } from "./standalone_layout_wrapper";
|
|
4
4
|
import { get_ui_shell_config } from "../../../../lib/ui_shell_config.server";
|
|
5
5
|
// section: component
|
|
6
|
-
export function AuthPageShell({ children }) {
|
|
6
|
+
export function AuthPageShell({ children, disableNavbar = false }) {
|
|
7
7
|
const uiShellConfig = get_ui_shell_config();
|
|
8
8
|
if (uiShellConfig.layout_mode === "standalone") {
|
|
9
|
-
|
|
9
|
+
// Build navbar props (or null if disabled)
|
|
10
|
+
const navbarProps = !disableNavbar && uiShellConfig.navbar.enable_navbar
|
|
11
|
+
? {
|
|
12
|
+
logo_path: uiShellConfig.navbar.logo_path,
|
|
13
|
+
logo_width: uiShellConfig.navbar.logo_width,
|
|
14
|
+
logo_height: uiShellConfig.navbar.logo_height,
|
|
15
|
+
company_name: uiShellConfig.navbar.company_name,
|
|
16
|
+
home_path: uiShellConfig.navbar.home_path,
|
|
17
|
+
home_label: uiShellConfig.navbar.home_label,
|
|
18
|
+
show_home_link: uiShellConfig.navbar.show_home_link,
|
|
19
|
+
background_color: uiShellConfig.navbar.background_color,
|
|
20
|
+
text_color: uiShellConfig.navbar.text_color,
|
|
21
|
+
height: uiShellConfig.navbar.height,
|
|
22
|
+
}
|
|
23
|
+
: null;
|
|
24
|
+
return (_jsx(StandaloneLayoutWrapper, { heading: uiShellConfig.standalone_heading, description: uiShellConfig.standalone_description, wrapperClassName: uiShellConfig.standalone_wrapper_class, contentClassName: uiShellConfig.standalone_content_class, showHeading: uiShellConfig.standalone_show_heading, showDescription: uiShellConfig.standalone_show_description, navbar: navbarProps, verticalCenter: uiShellConfig.vertical_center, children: children }));
|
|
10
25
|
}
|
|
11
26
|
return _jsx(SidebarLayoutWrapper, { children: children });
|
|
12
27
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AuthNavbarProps } from "./auth_navbar";
|
|
1
2
|
export type StandaloneLayoutWrapperProps = {
|
|
2
3
|
children: React.ReactNode;
|
|
3
4
|
heading?: string;
|
|
@@ -6,6 +7,10 @@ export type StandaloneLayoutWrapperProps = {
|
|
|
6
7
|
contentClassName?: string;
|
|
7
8
|
showHeading?: boolean;
|
|
8
9
|
showDescription?: boolean;
|
|
10
|
+
/** Navbar configuration (pass null to disable navbar) */
|
|
11
|
+
navbar?: AuthNavbarProps | null;
|
|
12
|
+
/** Enable vertical centering of content (default: true) */
|
|
13
|
+
verticalCenter?: boolean;
|
|
9
14
|
};
|
|
10
|
-
export declare function StandaloneLayoutWrapper({ children, heading, description, wrapperClassName, contentClassName, showHeading, showDescription, }: StandaloneLayoutWrapperProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export declare function StandaloneLayoutWrapper({ children, heading, description, wrapperClassName, contentClassName, showHeading, showDescription, navbar, verticalCenter, }: StandaloneLayoutWrapperProps): import("react/jsx-runtime").JSX.Element;
|
|
11
16
|
//# sourceMappingURL=standalone_layout_wrapper.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standalone_layout_wrapper.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/shared/components/standalone_layout_wrapper.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"standalone_layout_wrapper.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/shared/components/standalone_layout_wrapper.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAGjE,MAAM,MAAM,4BAA4B,GAAG;IACzC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,yDAAyD;IACzD,MAAM,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAChC,2DAA2D;IAC3D,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAGF,wBAAgB,uBAAuB,CAAC,EACtC,QAAQ,EACR,OAAqB,EACrB,WAA8E,EAC9E,gBAAgB,EAChB,gBAAgB,EAChB,WAAkB,EAClB,eAAsB,EACtB,MAAM,EACN,cAAqB,GACtB,EAAE,4BAA4B,2CAmD9B"}
|
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
5
|
// section: imports
|
|
6
6
|
import { cn } from "../../../../lib/utils";
|
|
7
|
+
import { AuthNavbar } from "./auth_navbar";
|
|
7
8
|
// section: component
|
|
8
|
-
export function StandaloneLayoutWrapper({ children, heading = "hazo auth", description = "Drop-in authentication flows that inherit your existing theme.", wrapperClassName, contentClassName, showHeading = true, showDescription = true, }) {
|
|
9
|
-
|
|
9
|
+
export function StandaloneLayoutWrapper({ children, heading = "hazo auth", description = "Drop-in authentication flows that inherit your existing theme.", wrapperClassName, contentClassName, showHeading = true, showDescription = true, navbar, verticalCenter = true, }) {
|
|
10
|
+
var _a;
|
|
11
|
+
const hasNavbar = navbar !== null && navbar !== undefined;
|
|
12
|
+
// Calculate navbar height for vertical centering offset
|
|
13
|
+
const navbarHeight = hasNavbar ? ((_a = navbar === null || navbar === void 0 ? void 0 : navbar.height) !== null && _a !== void 0 ? _a : 64) : 0;
|
|
14
|
+
return (_jsxs("div", { className: cn("cls_standalone_layout_wrapper flex min-h-screen w-full flex-col bg-background", wrapperClassName), children: [hasNavbar && _jsx(AuthNavbar, Object.assign({}, navbar)), _jsx("div", { className: cn("cls_standalone_layout_content_area flex-1", verticalCenter && "flex items-center justify-center"), style: verticalCenter ? { minHeight: `calc(100vh - ${navbarHeight}px)` } : undefined, children: _jsxs("div", { className: cn("cls_standalone_layout_content mx-auto flex w-full max-w-5xl flex-col gap-8 p-6", contentClassName), children: [(showHeading || showDescription) && (_jsxs("div", { className: "cls_standalone_layout_header text-center", children: [showHeading && (_jsx("h1", { className: "cls_standalone_layout_title text-2xl font-semibold tracking-tight text-foreground", children: heading })), showDescription && (_jsx("p", { className: "cls_standalone_layout_description mt-2 text-sm text-muted-foreground", children: description }))] })), _jsx("div", { className: "cls_standalone_layout_body", children: children })] }) })] }));
|
|
10
15
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { AlreadyLoggedInGuard } from "./components/already_logged_in_guard";
|
|
2
|
+
export { AuthNavbar } from "./components/auth_navbar";
|
|
3
|
+
export type { AuthNavbarProps } from "./components/auth_navbar";
|
|
2
4
|
export { FieldErrorMessage } from "./components/field_error_message";
|
|
3
5
|
export { FormActionButtons } from "./components/form_action_buttons";
|
|
4
6
|
export { FormFieldWrapper } from "./components/form_field_wrapper";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/shared/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/shared/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAE5D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,YAAY,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAE7F,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,YAAY,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,YAAY,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAGpE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AACjF,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAGnF,cAAc,+BAA+B,CAAC;AAG9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAGlE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,cAAc,oBAAoB,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// file_description: barrel export for shared layout components, hooks, and utilities
|
|
2
2
|
// section: component_exports
|
|
3
3
|
export { AlreadyLoggedInGuard } from "./components/already_logged_in_guard";
|
|
4
|
+
export { AuthNavbar } from "./components/auth_navbar";
|
|
4
5
|
// AuthPageShell - NOT exported (test workspace component only)
|
|
5
6
|
export { FieldErrorMessage } from "./components/field_error_message";
|
|
6
7
|
export { FormActionButtons } from "./components/form_action_buttons";
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
export type ScopeHierarchyTabProps = {
|
|
2
2
|
className?: string;
|
|
3
|
-
defaultOrg?: string;
|
|
4
3
|
};
|
|
5
4
|
/**
|
|
6
5
|
* Scope Hierarchy tab component for managing HRBAC scopes
|
|
7
6
|
* Displays scopes in a tree view for intuitive hierarchy configuration
|
|
7
|
+
* Non-global admins see only their org's scopes (auto-filtered by API)
|
|
8
|
+
* Global admins can view/manage any org's scopes by providing org_id
|
|
8
9
|
* @param props - Component props
|
|
9
10
|
* @returns Scope Hierarchy tab component
|
|
10
11
|
*/
|
|
11
|
-
export declare function ScopeHierarchyTab({ className,
|
|
12
|
+
export declare function ScopeHierarchyTab({ className, }: ScopeHierarchyTabProps): import("react/jsx-runtime").JSX.Element;
|
|
12
13
|
//# sourceMappingURL=scope_hierarchy_tab.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope_hierarchy_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/user_management/components/scope_hierarchy_tab.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scope_hierarchy_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/user_management/components/scope_hierarchy_tab.tsx"],"names":[],"mappings":"AA4CA,MAAM,MAAM,sBAAsB,GAAG;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AA+HF;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,GACV,EAAE,sBAAsB,2CA2gBxB"}
|
|
@@ -13,6 +13,7 @@ import { Label } from "../../../ui/label";
|
|
|
13
13
|
import { Loader2, Plus, Edit, Trash2, CircleCheck, CircleX, Building2, FolderTree, RefreshCw, } from "lucide-react";
|
|
14
14
|
import { toast } from "sonner";
|
|
15
15
|
import { useHazoAuthConfig } from "../../../../contexts/hazo_auth_provider";
|
|
16
|
+
import { use_hazo_auth } from "../../shared/hooks/use_hazo_auth";
|
|
16
17
|
const SCOPE_LEVEL_LABELS = {
|
|
17
18
|
hazo_scopes_l1: "Level 1",
|
|
18
19
|
hazo_scopes_l2: "Level 2",
|
|
@@ -64,16 +65,26 @@ function convertToTreeData(nodes, onEdit, onDelete, onAddChild) {
|
|
|
64
65
|
/**
|
|
65
66
|
* Scope Hierarchy tab component for managing HRBAC scopes
|
|
66
67
|
* Displays scopes in a tree view for intuitive hierarchy configuration
|
|
68
|
+
* Non-global admins see only their org's scopes (auto-filtered by API)
|
|
69
|
+
* Global admins can view/manage any org's scopes by providing org_id
|
|
67
70
|
* @param props - Component props
|
|
68
71
|
* @returns Scope Hierarchy tab component
|
|
69
72
|
*/
|
|
70
|
-
export function ScopeHierarchyTab({ className,
|
|
73
|
+
export function ScopeHierarchyTab({ className, }) {
|
|
74
|
+
var _a;
|
|
71
75
|
const { apiBasePath } = useHazoAuthConfig();
|
|
76
|
+
const authResult = use_hazo_auth();
|
|
77
|
+
// Determine if user is global admin (can manage any org)
|
|
78
|
+
const isGlobalAdmin = authResult.authenticated && authResult.permissions.includes("hazo_org_global_admin");
|
|
79
|
+
// Get user's org_id (for non-global admins, this is the only org they can manage)
|
|
80
|
+
const userOrgId = ((_a = authResult.user) === null || _a === void 0 ? void 0 : _a.org_id) || "";
|
|
72
81
|
// State
|
|
73
82
|
const [tree, setTree] = useState([]);
|
|
74
83
|
const [loading, setLoading] = useState(true);
|
|
75
84
|
const [actionLoading, setActionLoading] = useState(false);
|
|
76
|
-
|
|
85
|
+
// For non-global admins, org_id is fixed to their own org
|
|
86
|
+
// For global admins, they can enter any org_id to view that org's scopes
|
|
87
|
+
const [orgId, setOrgId] = useState("");
|
|
77
88
|
const [selectedItem, setSelectedItem] = useState();
|
|
78
89
|
// Dialog state
|
|
79
90
|
const [addDialogOpen, setAddDialogOpen] = useState(false);
|
|
@@ -83,18 +94,29 @@ export function ScopeHierarchyTab({ className, defaultOrg = "", }) {
|
|
|
83
94
|
const [addParentScope, setAddParentScope] = useState(null);
|
|
84
95
|
// Form state
|
|
85
96
|
const [newName, setNewName] = useState("");
|
|
86
|
-
const [newOrg, setNewOrg] = useState(defaultOrg);
|
|
87
97
|
const [editName, setEditName] = useState("");
|
|
98
|
+
// Initialize orgId from user's org when auth loads
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (!authResult.loading && userOrgId && !orgId) {
|
|
101
|
+
setOrgId(userOrgId);
|
|
102
|
+
}
|
|
103
|
+
}, [authResult.loading, userOrgId, orgId]);
|
|
88
104
|
// Load tree data
|
|
89
105
|
const loadTree = useCallback(async () => {
|
|
90
|
-
|
|
106
|
+
// For non-global admins, API will auto-filter to their org
|
|
107
|
+
// For global admins, org_id is required to specify which org to view
|
|
108
|
+
if (isGlobalAdmin && !orgId) {
|
|
91
109
|
setTree([]);
|
|
92
110
|
setLoading(false);
|
|
93
111
|
return;
|
|
94
112
|
}
|
|
95
113
|
setLoading(true);
|
|
96
114
|
try {
|
|
97
|
-
const params = new URLSearchParams({ action: "tree"
|
|
115
|
+
const params = new URLSearchParams({ action: "tree" });
|
|
116
|
+
// Only pass org_id for global admins (non-global admins are auto-filtered by API)
|
|
117
|
+
if (isGlobalAdmin && orgId) {
|
|
118
|
+
params.set("org_id", orgId);
|
|
119
|
+
}
|
|
98
120
|
const response = await fetch(`${apiBasePath}/scope_management/scopes?${params}`);
|
|
99
121
|
const data = await response.json();
|
|
100
122
|
if (data.success) {
|
|
@@ -112,22 +134,22 @@ export function ScopeHierarchyTab({ className, defaultOrg = "", }) {
|
|
|
112
134
|
finally {
|
|
113
135
|
setLoading(false);
|
|
114
136
|
}
|
|
115
|
-
}, [apiBasePath,
|
|
116
|
-
// Load data when
|
|
137
|
+
}, [apiBasePath, orgId, isGlobalAdmin]);
|
|
138
|
+
// Load data when orgId changes or auth finishes loading
|
|
117
139
|
useEffect(() => {
|
|
118
|
-
|
|
119
|
-
|
|
140
|
+
if (!authResult.loading) {
|
|
141
|
+
void loadTree();
|
|
142
|
+
}
|
|
143
|
+
}, [loadTree, authResult.loading]);
|
|
120
144
|
// Handle add scope (root level)
|
|
121
145
|
const handleAddRootScope = () => {
|
|
122
146
|
setAddParentScope(null);
|
|
123
|
-
setNewOrg(org || defaultOrg);
|
|
124
147
|
setNewName("");
|
|
125
148
|
setAddDialogOpen(true);
|
|
126
149
|
};
|
|
127
150
|
// Handle add child scope
|
|
128
151
|
const handleAddChildScope = (parent) => {
|
|
129
152
|
setAddParentScope(parent);
|
|
130
|
-
setNewOrg(parent.org);
|
|
131
153
|
setNewName("");
|
|
132
154
|
setAddDialogOpen(true);
|
|
133
155
|
};
|
|
@@ -148,10 +170,6 @@ export function ScopeHierarchyTab({ className, defaultOrg = "", }) {
|
|
|
148
170
|
toast.error("Name is required");
|
|
149
171
|
return;
|
|
150
172
|
}
|
|
151
|
-
if (!newOrg.trim()) {
|
|
152
|
-
toast.error("Organization is required");
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
173
|
setActionLoading(true);
|
|
156
174
|
try {
|
|
157
175
|
const level = addParentScope
|
|
@@ -161,13 +179,22 @@ export function ScopeHierarchyTab({ className, defaultOrg = "", }) {
|
|
|
161
179
|
toast.error("Cannot add children to Level 7 scopes");
|
|
162
180
|
return;
|
|
163
181
|
}
|
|
182
|
+
// Build request body - API will use user's org_id for non-global admins
|
|
183
|
+
// or the provided org_id for global admins
|
|
164
184
|
const body = {
|
|
165
185
|
level,
|
|
166
|
-
org: newOrg.trim(),
|
|
167
186
|
name: newName.trim(),
|
|
168
187
|
};
|
|
188
|
+
// For global admins, pass the selected org_id
|
|
189
|
+
if (isGlobalAdmin && orgId) {
|
|
190
|
+
body.org_id = orgId;
|
|
191
|
+
body.root_org_id = orgId; // Default root_org_id to same as org_id
|
|
192
|
+
}
|
|
169
193
|
if (addParentScope) {
|
|
170
194
|
body.parent_scope_id = addParentScope.id;
|
|
195
|
+
// Inherit org_id from parent scope
|
|
196
|
+
body.org_id = addParentScope.org_id;
|
|
197
|
+
body.root_org_id = addParentScope.root_org_id;
|
|
171
198
|
}
|
|
172
199
|
const response = await fetch(`${apiBasePath}/scope_management/scopes`, {
|
|
173
200
|
method: "POST",
|
|
@@ -278,10 +305,10 @@ export function ScopeHierarchyTab({ className, defaultOrg = "", }) {
|
|
|
278
305
|
const childLevel = getChildLevel(addParentScope.level);
|
|
279
306
|
return childLevel ? SCOPE_LEVEL_LABELS[childLevel] : "Unknown";
|
|
280
307
|
};
|
|
281
|
-
return (_jsxs("div", { className: `cls_scope_hierarchy_tab flex flex-col gap-4 w-full ${className || ""}`, children: [_jsxs("div", { className: "cls_scope_hierarchy_header flex items-center justify-between gap-4 flex-wrap", children: [_jsxs("div", { className: "cls_scope_hierarchy_header_left flex items-center gap-4", children: [_jsxs("div", { className: "cls_scope_hierarchy_org_filter flex items-center gap-2", children: [_jsx(Label, { htmlFor: "
|
|
308
|
+
return (_jsxs("div", { className: `cls_scope_hierarchy_tab flex flex-col gap-4 w-full ${className || ""}`, children: [_jsxs("div", { className: "cls_scope_hierarchy_header flex items-center justify-between gap-4 flex-wrap", children: [_jsxs("div", { className: "cls_scope_hierarchy_header_left flex items-center gap-4", children: [isGlobalAdmin && (_jsxs("div", { className: "cls_scope_hierarchy_org_filter flex items-center gap-2", children: [_jsx(Label, { htmlFor: "scope_org_id", className: "text-sm font-medium", children: "Organization ID:" }), _jsx(Input, { id: "scope_org_id", value: orgId, onChange: (e) => setOrgId(e.target.value), placeholder: "Enter org UUID", className: "w-[280px] font-mono text-sm" })] })), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void loadTree(), disabled: loading || (isGlobalAdmin && !orgId), children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${loading ? "animate-spin" : ""}` }), "Refresh"] })] }), _jsx("div", { className: "cls_scope_hierarchy_header_right", children: _jsxs(Button, { onClick: handleAddRootScope, variant: "default", size: "sm", disabled: isGlobalAdmin && !orgId, children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Add Root Scope"] }) })] }), loading || authResult.loading ? (_jsx("div", { className: "cls_scope_hierarchy_loading flex items-center justify-center p-8", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : isGlobalAdmin && !orgId ? (_jsxs("div", { className: "cls_scope_hierarchy_empty flex flex-col items-center justify-center p-8 border rounded-lg border-dashed", children: [_jsx(Building2, { className: "h-12 w-12 text-muted-foreground mb-4" }), _jsx("p", { className: "text-muted-foreground text-center", children: "Enter an organization ID to view scope hierarchy" })] })) : tree.length === 0 ? (_jsxs("div", { className: "cls_scope_hierarchy_empty flex flex-col items-center justify-center p-8 border rounded-lg border-dashed", children: [_jsx(FolderTree, { className: "h-12 w-12 text-muted-foreground mb-4" }), _jsx("p", { className: "text-muted-foreground text-center mb-4", children: "No scopes found for this organization" }), _jsxs(Button, { onClick: handleAddRootScope, variant: "outline", size: "sm", children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Create First Scope"] })] })) : (_jsx("div", { className: "cls_scope_hierarchy_tree_container border rounded-lg overflow-auto w-full min-h-[300px]", children: _jsx(TreeView, { data: treeData, expandAll: true, defaultNodeIcon: Building2, defaultLeafIcon: Building2, onSelectChange: handleSelectChange, className: "w-full" }) })), (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.scopeData) && (_jsxs("div", { className: "cls_scope_hierarchy_selected_info p-4 border rounded-lg bg-muted/50", children: [_jsx("h4", { className: "font-medium mb-2", children: "Selected Scope" }), _jsxs("div", { className: "grid grid-cols-2 gap-2 text-sm", children: [_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Name:" }), " ", selectedItem.scopeData.name] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Seq:" }), " ", selectedItem.scopeData.seq] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Level:" }), " ", SCOPE_LEVEL_LABELS[selectedItem.scopeData.level]] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Org ID:" }), " ", _jsx("span", { className: "font-mono text-xs", children: selectedItem.scopeData.org_id })] })] })] })), _jsx(Dialog, { open: addDialogOpen, onOpenChange: setAddDialogOpen, children: _jsxs(DialogContent, { className: "cls_scope_hierarchy_add_dialog", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: addParentScope
|
|
282
309
|
? `Add Child Scope to "${addParentScope.name}"`
|
|
283
310
|
: "Add Root Scope" }), _jsxs(DialogDescription, { children: ["Create a new scope at ", getAddDialogLevelLabel(), ".", addParentScope &&
|
|
284
|
-
` This will be a child of "${addParentScope.name}".`] })] }),
|
|
311
|
+
` This will be a child of "${addParentScope.name}".`] })] }), _jsx("div", { className: "flex flex-col gap-4 py-4", children: _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "new_scope_name", children: "Name *" }), _jsx(Input, { id: "new_scope_name", value: newName, onChange: (e) => setNewName(e.target.value), placeholder: "Enter scope name" })] }) }), _jsxs(DialogFooter, { children: [_jsx(Button, { onClick: handleCreateScope, disabled: actionLoading, variant: "default", children: actionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Creating..."] })) : (_jsxs(_Fragment, { children: [_jsx(CircleCheck, { className: "h-4 w-4 mr-2" }), "Create"] })) }), _jsxs(Button, { onClick: () => setAddDialogOpen(false), variant: "outline", children: [_jsx(CircleX, { className: "h-4 w-4 mr-2" }), "Cancel"] })] })] }) }), _jsx(Dialog, { open: editDialogOpen, onOpenChange: setEditDialogOpen, children: _jsxs(DialogContent, { className: "cls_scope_hierarchy_edit_dialog", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Edit Scope" }), _jsxs(DialogDescription, { children: ["Update scope: ", selectedScope === null || selectedScope === void 0 ? void 0 : selectedScope.name, " (", selectedScope === null || selectedScope === void 0 ? void 0 : selectedScope.seq, ")"] })] }), _jsx("div", { className: "flex flex-col gap-4 py-4", children: _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "edit_scope_name", children: "Name *" }), _jsx(Input, { id: "edit_scope_name", value: editName, onChange: (e) => setEditName(e.target.value), placeholder: "Enter scope name" })] }) }), _jsxs(DialogFooter, { children: [_jsx(Button, { onClick: handleUpdateScope, disabled: actionLoading, variant: "default", children: actionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Saving..."] })) : (_jsxs(_Fragment, { children: [_jsx(CircleCheck, { className: "h-4 w-4 mr-2" }), "Save"] })) }), _jsxs(Button, { onClick: () => {
|
|
285
312
|
setEditDialogOpen(false);
|
|
286
313
|
setSelectedScope(null);
|
|
287
314
|
}, variant: "outline", children: [_jsx(CircleX, { className: "h-4 w-4 mr-2" }), "Cancel"] })] })] }) }), _jsx(AlertDialog, { open: deleteDialogOpen, onOpenChange: setDeleteDialogOpen, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Delete Scope" }), _jsxs(AlertDialogDescription, { children: ["Are you sure you want to delete \"", selectedScope === null || selectedScope === void 0 ? void 0 : selectedScope.name, "\" (", selectedScope === null || selectedScope === void 0 ? void 0 : selectedScope.seq, ")? This action cannot be undone and will also delete all child scopes."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogAction, { onClick: handleDeleteScope, disabled: actionLoading, children: actionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Deleting..."] })) : ("Delete") }), _jsx(AlertDialogCancel, { onClick: () => {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
export type ScopeLabelsTabProps = {
|
|
2
2
|
className?: string;
|
|
3
|
-
defaultOrg?: string;
|
|
4
3
|
};
|
|
5
4
|
/**
|
|
6
5
|
* Scope Labels tab component for configuring friendly names for scope levels
|
|
7
6
|
* Shows all 7 scope levels with their current labels from database
|
|
8
7
|
* Empty inputs for levels without labels - no placeholders
|
|
8
|
+
* Non-global admins see only their org's labels (auto-filtered by API)
|
|
9
|
+
* Global admins can view/manage any org's labels by providing org_id
|
|
9
10
|
* @param props - Component props
|
|
10
11
|
* @returns Scope Labels tab component
|
|
11
12
|
*/
|
|
12
|
-
export declare function ScopeLabelsTab({ className
|
|
13
|
+
export declare function ScopeLabelsTab({ className }: ScopeLabelsTabProps): import("react/jsx-runtime").JSX.Element;
|
|
13
14
|
//# sourceMappingURL=scope_labels_tab.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope_labels_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/user_management/components/scope_labels_tab.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scope_labels_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/user_management/components/scope_labels_tab.tsx"],"names":[],"mappings":"AAuBA,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAeF;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,EAAE,SAAS,EAAE,EAAE,mBAAmB,2CAwPhE"}
|
|
@@ -11,6 +11,7 @@ import { Label } from "../../../ui/label";
|
|
|
11
11
|
import { Loader2, Save } from "lucide-react";
|
|
12
12
|
import { toast } from "sonner";
|
|
13
13
|
import { useHazoAuthConfig } from "../../../../contexts/hazo_auth_provider";
|
|
14
|
+
import { use_hazo_auth } from "../../shared/hooks/use_hazo_auth";
|
|
14
15
|
const SCOPE_LEVELS = [
|
|
15
16
|
"hazo_scopes_l1",
|
|
16
17
|
"hazo_scopes_l2",
|
|
@@ -25,11 +26,19 @@ const SCOPE_LEVELS = [
|
|
|
25
26
|
* Scope Labels tab component for configuring friendly names for scope levels
|
|
26
27
|
* Shows all 7 scope levels with their current labels from database
|
|
27
28
|
* Empty inputs for levels without labels - no placeholders
|
|
29
|
+
* Non-global admins see only their org's labels (auto-filtered by API)
|
|
30
|
+
* Global admins can view/manage any org's labels by providing org_id
|
|
28
31
|
* @param props - Component props
|
|
29
32
|
* @returns Scope Labels tab component
|
|
30
33
|
*/
|
|
31
|
-
export function ScopeLabelsTab({ className
|
|
34
|
+
export function ScopeLabelsTab({ className }) {
|
|
35
|
+
var _a;
|
|
32
36
|
const { apiBasePath } = useHazoAuthConfig();
|
|
37
|
+
const authResult = use_hazo_auth();
|
|
38
|
+
// Determine if user is global admin (can manage any org)
|
|
39
|
+
const isGlobalAdmin = authResult.authenticated && authResult.permissions.includes("hazo_org_global_admin");
|
|
40
|
+
// Get user's org_id (for non-global admins, this is the only org they can manage)
|
|
41
|
+
const userOrgId = ((_a = authResult.user) === null || _a === void 0 ? void 0 : _a.org_id) || "";
|
|
33
42
|
// State - simple record of scope_type to label string (empty string if not set)
|
|
34
43
|
const [labels, setLabels] = useState(() => {
|
|
35
44
|
const initial = {};
|
|
@@ -41,11 +50,21 @@ export function ScopeLabelsTab({ className, defaultOrg = "" }) {
|
|
|
41
50
|
const [originalLabels, setOriginalLabels] = useState(null);
|
|
42
51
|
const [loading, setLoading] = useState(true);
|
|
43
52
|
const [saving, setSaving] = useState(false);
|
|
44
|
-
|
|
53
|
+
// For non-global admins, org_id is fixed to their own org
|
|
54
|
+
// For global admins, they can enter any org_id to view that org's labels
|
|
55
|
+
const [orgId, setOrgId] = useState("");
|
|
56
|
+
// Initialize orgId from user's org when auth loads
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!authResult.loading && userOrgId && !orgId) {
|
|
59
|
+
setOrgId(userOrgId);
|
|
60
|
+
}
|
|
61
|
+
}, [authResult.loading, userOrgId, orgId]);
|
|
45
62
|
// Load labels from database (only real DB records, not synthetic defaults)
|
|
46
63
|
const loadLabels = useCallback(async () => {
|
|
47
|
-
|
|
48
|
-
|
|
64
|
+
// For non-global admins, API will auto-filter to their org
|
|
65
|
+
// For global admins, org_id is required to specify which org to view
|
|
66
|
+
if (isGlobalAdmin && !orgId.trim()) {
|
|
67
|
+
// Reset to empty if global admin hasn't specified org
|
|
49
68
|
const empty = {};
|
|
50
69
|
for (const level of SCOPE_LEVELS) {
|
|
51
70
|
empty[level] = "";
|
|
@@ -58,7 +77,11 @@ export function ScopeLabelsTab({ className, defaultOrg = "" }) {
|
|
|
58
77
|
setLoading(true);
|
|
59
78
|
try {
|
|
60
79
|
// Fetch WITHOUT defaults - only get actual DB records
|
|
61
|
-
const params = new URLSearchParams({
|
|
80
|
+
const params = new URLSearchParams({ include_defaults: "false" });
|
|
81
|
+
// Only pass org_id for global admins (non-global admins are auto-filtered by API)
|
|
82
|
+
if (isGlobalAdmin && orgId.trim()) {
|
|
83
|
+
params.set("org_id", orgId.trim());
|
|
84
|
+
}
|
|
62
85
|
const response = await fetch(`${apiBasePath}/scope_management/labels?${params}`);
|
|
63
86
|
const data = await response.json();
|
|
64
87
|
if (data.success) {
|
|
@@ -87,11 +110,13 @@ export function ScopeLabelsTab({ className, defaultOrg = "" }) {
|
|
|
87
110
|
finally {
|
|
88
111
|
setLoading(false);
|
|
89
112
|
}
|
|
90
|
-
}, [apiBasePath,
|
|
91
|
-
// Load labels when
|
|
113
|
+
}, [apiBasePath, orgId, isGlobalAdmin]);
|
|
114
|
+
// Load labels when orgId changes or auth finishes loading
|
|
92
115
|
useEffect(() => {
|
|
93
|
-
|
|
94
|
-
|
|
116
|
+
if (!authResult.loading) {
|
|
117
|
+
void loadLabels();
|
|
118
|
+
}
|
|
119
|
+
}, [loadLabels, authResult.loading]);
|
|
95
120
|
// Handle label change
|
|
96
121
|
const handleLabelChange = (level, value) => {
|
|
97
122
|
setLabels((prev) => (Object.assign(Object.assign({}, prev), { [level]: value })));
|
|
@@ -109,10 +134,6 @@ export function ScopeLabelsTab({ className, defaultOrg = "" }) {
|
|
|
109
134
|
};
|
|
110
135
|
// Save all labels - send all non-empty labels to be upserted
|
|
111
136
|
const handleSave = async () => {
|
|
112
|
-
if (!org.trim()) {
|
|
113
|
-
toast.error("Organization is required");
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
137
|
// Collect all non-empty labels
|
|
117
138
|
const labelsToSave = [];
|
|
118
139
|
for (const level of SCOPE_LEVELS) {
|
|
@@ -126,13 +147,18 @@ export function ScopeLabelsTab({ className, defaultOrg = "" }) {
|
|
|
126
147
|
}
|
|
127
148
|
setSaving(true);
|
|
128
149
|
try {
|
|
150
|
+
// Build request body - API will use user's org_id for non-global admins
|
|
151
|
+
const body = {
|
|
152
|
+
labels: labelsToSave,
|
|
153
|
+
};
|
|
154
|
+
// For global admins, pass the selected org_id
|
|
155
|
+
if (isGlobalAdmin && orgId.trim()) {
|
|
156
|
+
body.org_id = orgId.trim();
|
|
157
|
+
}
|
|
129
158
|
const response = await fetch(`${apiBasePath}/scope_management/labels`, {
|
|
130
159
|
method: "PUT",
|
|
131
160
|
headers: { "Content-Type": "application/json" },
|
|
132
|
-
body: JSON.stringify(
|
|
133
|
-
org: org.trim(),
|
|
134
|
-
labels: labelsToSave,
|
|
135
|
-
}),
|
|
161
|
+
body: JSON.stringify(body),
|
|
136
162
|
});
|
|
137
163
|
const data = await response.json();
|
|
138
164
|
if (data.success) {
|
|
@@ -151,8 +177,10 @@ export function ScopeLabelsTab({ className, defaultOrg = "" }) {
|
|
|
151
177
|
setSaving(false);
|
|
152
178
|
}
|
|
153
179
|
};
|
|
154
|
-
|
|
180
|
+
// Check if inputs should be enabled
|
|
181
|
+
const inputsEnabled = !isGlobalAdmin || orgId.trim().length > 0;
|
|
182
|
+
return (_jsxs("div", { className: `cls_scope_labels_tab flex flex-col gap-4 w-full ${className || ""}`, children: [_jsxs("div", { className: "cls_scope_labels_header flex items-center justify-between gap-4 flex-wrap", children: [_jsx("div", { className: "cls_scope_labels_header_left flex items-center gap-4", children: isGlobalAdmin && (_jsxs("div", { className: "cls_scope_labels_org_input flex items-center gap-2", children: [_jsx(Label, { htmlFor: "labels_org_id", className: "text-sm font-medium", children: "Organization ID:" }), _jsx(Input, { id: "labels_org_id", value: orgId, onChange: (e) => setOrgId(e.target.value), placeholder: "Enter org UUID", className: "w-[280px] font-mono text-sm" })] })) }), _jsx("div", { className: "cls_scope_labels_header_right", children: _jsx(Button, { onClick: handleSave, disabled: saving || !hasChanges() || (isGlobalAdmin && !orgId.trim()), variant: "default", size: "sm", children: saving ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Saving..."] })) : (_jsxs(_Fragment, { children: [_jsx(Save, { className: "h-4 w-4 mr-2" }), "Save Changes"] })) }) })] }), loading || authResult.loading ? (_jsx("div", { className: "cls_scope_labels_loading flex items-center justify-center p-8", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : isGlobalAdmin && !orgId.trim() ? (_jsx("div", { className: "cls_scope_labels_info text-sm text-muted-foreground text-center p-4 bg-muted/50 rounded-lg", children: "Enter an organization ID to customize scope labels." })) : (_jsx("div", { className: "cls_scope_labels_table_container border rounded-lg overflow-auto w-full", children: _jsxs(Table, { className: "cls_scope_labels_table w-full", children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-[180px]", children: "Scope Level" }), _jsx(TableHead, { children: "Label" })] }) }), _jsx(TableBody, { children: SCOPE_LEVELS.map((level) => {
|
|
155
183
|
const label = labels[level];
|
|
156
|
-
return (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono text-sm", children: level }), _jsx(TableCell, { children: _jsx(Input, { value: label, onChange: (e) => handleLabelChange(level, e.target.value), className: "max-w-[400px]", disabled: !
|
|
157
|
-
}) })] }) }))
|
|
184
|
+
return (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono text-sm", children: level }), _jsx(TableCell, { children: _jsx(Input, { value: label, onChange: (e) => handleLabelChange(level, e.target.value), className: "max-w-[400px]", disabled: !inputsEnabled }) })] }, level));
|
|
185
|
+
}) })] }) }))] }));
|
|
158
186
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user_scopes_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/user_management/components/user_scopes_tab.tsx"],"names":[],"mappings":"AAoDA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;
|
|
1
|
+
{"version":3,"file":"user_scopes_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/user_management/components/user_scopes_tab.tsx"],"names":[],"mappings":"AAoDA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AA8EF;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,EAAE,kBAAkB,2CA8f9D"}
|