hazo_auth 3.0.4 → 4.0.0
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 +146 -0
- package/SETUP_CHECKLIST.md +369 -0
- 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 +2 -2
- package/dist/components/layouts/rbac_test/index.d.ts +15 -0
- package/dist/components/layouts/rbac_test/index.d.ts.map +1 -0
- package/dist/components/layouts/rbac_test/index.js +378 -0
- package/dist/components/layouts/shared/components/password_field.js +1 -1
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +2 -2
- package/dist/components/layouts/shared/components/two_column_auth_layout.js +1 -1
- package/dist/components/layouts/user_management/components/roles_matrix.d.ts +2 -3
- package/dist/components/layouts/user_management/components/roles_matrix.d.ts.map +1 -1
- package/dist/components/layouts/user_management/components/roles_matrix.js +133 -8
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts +12 -0
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts.map +1 -0
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.js +291 -0
- package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts +13 -0
- package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts.map +1 -0
- package/dist/components/layouts/user_management/components/scope_labels_tab.js +158 -0
- package/dist/components/layouts/user_management/components/user_scopes_tab.d.ts +11 -0
- package/dist/components/layouts/user_management/components/user_scopes_tab.d.ts.map +1 -0
- package/dist/components/layouts/user_management/components/user_scopes_tab.js +267 -0
- package/dist/components/layouts/user_management/index.d.ts +9 -2
- package/dist/components/layouts/user_management/index.d.ts.map +1 -1
- package/dist/components/layouts/user_management/index.js +22 -6
- package/dist/components/ui/select.d.ts +14 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/select.js +59 -0
- package/dist/components/ui/tree-view.d.ts +108 -0
- package/dist/components/ui/tree-view.d.ts.map +1 -0
- package/dist/components/ui/tree-view.js +194 -0
- package/dist/lib/auth/auth_types.d.ts +45 -0
- package/dist/lib/auth/auth_types.d.ts.map +1 -1
- package/dist/lib/auth/auth_types.js +13 -0
- package/dist/lib/auth/hazo_get_auth.server.d.ts +4 -2
- package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_auth.server.js +107 -3
- package/dist/lib/auth/scope_cache.d.ts +92 -0
- package/dist/lib/auth/scope_cache.d.ts.map +1 -0
- package/dist/lib/auth/scope_cache.js +171 -0
- package/dist/lib/scope_hierarchy_config.server.d.ts +39 -0
- package/dist/lib/scope_hierarchy_config.server.d.ts.map +1 -0
- package/dist/lib/scope_hierarchy_config.server.js +96 -0
- package/dist/lib/services/email_service.d.ts.map +1 -1
- package/dist/lib/services/email_service.js +7 -2
- package/dist/lib/services/profile_picture_service.d.ts +1 -7
- package/dist/lib/services/profile_picture_service.d.ts.map +1 -1
- package/dist/lib/services/profile_picture_service.js +77 -32
- package/dist/lib/services/registration_service.js +1 -1
- package/dist/lib/services/scope_labels_service.d.ts +48 -0
- package/dist/lib/services/scope_labels_service.d.ts.map +1 -0
- package/dist/lib/services/scope_labels_service.js +277 -0
- package/dist/lib/services/scope_service.d.ts +114 -0
- package/dist/lib/services/scope_service.d.ts.map +1 -0
- package/dist/lib/services/scope_service.js +582 -0
- package/dist/lib/services/user_scope_service.d.ts +74 -0
- package/dist/lib/services/user_scope_service.d.ts.map +1 -0
- package/dist/lib/services/user_scope_service.js +415 -0
- package/hazo_auth_config.example.ini +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// file_description: Scope Labels tab component for configuring friendly names for scope levels
|
|
2
|
+
// section: client_directive
|
|
3
|
+
"use client";
|
|
4
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
5
|
+
// section: imports
|
|
6
|
+
import { useState, useEffect, useCallback } from "react";
|
|
7
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../../../ui/table";
|
|
8
|
+
import { Button } from "../../../ui/button";
|
|
9
|
+
import { Input } from "../../../ui/input";
|
|
10
|
+
import { Label } from "../../../ui/label";
|
|
11
|
+
import { Loader2, Save } from "lucide-react";
|
|
12
|
+
import { toast } from "sonner";
|
|
13
|
+
import { useHazoAuthConfig } from "../../../../contexts/hazo_auth_provider";
|
|
14
|
+
const SCOPE_LEVELS = [
|
|
15
|
+
"hazo_scopes_l1",
|
|
16
|
+
"hazo_scopes_l2",
|
|
17
|
+
"hazo_scopes_l3",
|
|
18
|
+
"hazo_scopes_l4",
|
|
19
|
+
"hazo_scopes_l5",
|
|
20
|
+
"hazo_scopes_l6",
|
|
21
|
+
"hazo_scopes_l7",
|
|
22
|
+
];
|
|
23
|
+
// section: component
|
|
24
|
+
/**
|
|
25
|
+
* Scope Labels tab component for configuring friendly names for scope levels
|
|
26
|
+
* Shows all 7 scope levels with their current labels from database
|
|
27
|
+
* Empty inputs for levels without labels - no placeholders
|
|
28
|
+
* @param props - Component props
|
|
29
|
+
* @returns Scope Labels tab component
|
|
30
|
+
*/
|
|
31
|
+
export function ScopeLabelsTab({ className, defaultOrg = "" }) {
|
|
32
|
+
const { apiBasePath } = useHazoAuthConfig();
|
|
33
|
+
// State - simple record of scope_type to label string (empty string if not set)
|
|
34
|
+
const [labels, setLabels] = useState(() => {
|
|
35
|
+
const initial = {};
|
|
36
|
+
for (const level of SCOPE_LEVELS) {
|
|
37
|
+
initial[level] = "";
|
|
38
|
+
}
|
|
39
|
+
return initial;
|
|
40
|
+
});
|
|
41
|
+
const [originalLabels, setOriginalLabels] = useState(null);
|
|
42
|
+
const [loading, setLoading] = useState(true);
|
|
43
|
+
const [saving, setSaving] = useState(false);
|
|
44
|
+
const [org, setOrg] = useState(defaultOrg);
|
|
45
|
+
// Load labels from database (only real DB records, not synthetic defaults)
|
|
46
|
+
const loadLabels = useCallback(async () => {
|
|
47
|
+
if (!org.trim()) {
|
|
48
|
+
// Reset to empty if no org
|
|
49
|
+
const empty = {};
|
|
50
|
+
for (const level of SCOPE_LEVELS) {
|
|
51
|
+
empty[level] = "";
|
|
52
|
+
}
|
|
53
|
+
setLabels(empty);
|
|
54
|
+
setOriginalLabels(empty);
|
|
55
|
+
setLoading(false);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
setLoading(true);
|
|
59
|
+
try {
|
|
60
|
+
// Fetch WITHOUT defaults - only get actual DB records
|
|
61
|
+
const params = new URLSearchParams({ org: org.trim(), include_defaults: "false" });
|
|
62
|
+
const response = await fetch(`${apiBasePath}/scope_management/labels?${params}`);
|
|
63
|
+
const data = await response.json();
|
|
64
|
+
if (data.success) {
|
|
65
|
+
// Start with empty labels
|
|
66
|
+
const newLabels = {};
|
|
67
|
+
for (const level of SCOPE_LEVELS) {
|
|
68
|
+
newLabels[level] = "";
|
|
69
|
+
}
|
|
70
|
+
// Fill in labels from database
|
|
71
|
+
const dbLabels = data.labels || [];
|
|
72
|
+
for (const dbLabel of dbLabels) {
|
|
73
|
+
if (dbLabel.scope_type && dbLabel.label) {
|
|
74
|
+
newLabels[dbLabel.scope_type] = dbLabel.label;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
setLabels(newLabels);
|
|
78
|
+
setOriginalLabels(Object.assign({}, newLabels));
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
toast.error(data.error || "Failed to load labels");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (_a) {
|
|
85
|
+
toast.error("Failed to load labels");
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
setLoading(false);
|
|
89
|
+
}
|
|
90
|
+
}, [apiBasePath, org]);
|
|
91
|
+
// Load labels when org changes
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
void loadLabels();
|
|
94
|
+
}, [loadLabels]);
|
|
95
|
+
// Handle label change
|
|
96
|
+
const handleLabelChange = (level, value) => {
|
|
97
|
+
setLabels((prev) => (Object.assign(Object.assign({}, prev), { [level]: value })));
|
|
98
|
+
};
|
|
99
|
+
// Check if there are unsaved changes
|
|
100
|
+
const hasChanges = () => {
|
|
101
|
+
if (!originalLabels)
|
|
102
|
+
return false;
|
|
103
|
+
for (const level of SCOPE_LEVELS) {
|
|
104
|
+
if (labels[level] !== originalLabels[level]) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
};
|
|
110
|
+
// Save all labels - send all non-empty labels to be upserted
|
|
111
|
+
const handleSave = async () => {
|
|
112
|
+
if (!org.trim()) {
|
|
113
|
+
toast.error("Organization is required");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Collect all non-empty labels
|
|
117
|
+
const labelsToSave = [];
|
|
118
|
+
for (const level of SCOPE_LEVELS) {
|
|
119
|
+
const label = labels[level].trim();
|
|
120
|
+
if (label) {
|
|
121
|
+
labelsToSave.push({
|
|
122
|
+
scope_type: level,
|
|
123
|
+
label: label,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
setSaving(true);
|
|
128
|
+
try {
|
|
129
|
+
const response = await fetch(`${apiBasePath}/scope_management/labels`, {
|
|
130
|
+
method: "PUT",
|
|
131
|
+
headers: { "Content-Type": "application/json" },
|
|
132
|
+
body: JSON.stringify({
|
|
133
|
+
org: org.trim(),
|
|
134
|
+
labels: labelsToSave,
|
|
135
|
+
}),
|
|
136
|
+
});
|
|
137
|
+
const data = await response.json();
|
|
138
|
+
if (data.success) {
|
|
139
|
+
toast.success("Labels saved successfully");
|
|
140
|
+
// Reload to get fresh state
|
|
141
|
+
await loadLabels();
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
toast.error(data.error || "Failed to save labels");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch (_a) {
|
|
148
|
+
toast.error("Failed to save labels");
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
setSaving(false);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
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: _jsxs("div", { className: "cls_scope_labels_org_input flex items-center gap-2", children: [_jsx(Label, { htmlFor: "labels_org", className: "text-sm font-medium", children: "Organization:" }), _jsx(Input, { id: "labels_org", value: org, onChange: (e) => setOrg(e.target.value), placeholder: "Enter organization name", className: "w-[200px]" })] }) }), _jsx("div", { className: "cls_scope_labels_header_right", children: _jsx(Button, { onClick: handleSave, disabled: saving || !hasChanges() || !org.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 ? (_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" }) })) : (_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
|
+
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: !org.trim() }) })] }, level));
|
|
157
|
+
}) })] }) })), !org.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 name to customize scope labels." }))] }));
|
|
158
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type UserScopesTabProps = {
|
|
2
|
+
className?: string;
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* User Scopes tab component for assigning scopes to users
|
|
6
|
+
* Two-panel layout: Users list | Scope assignments
|
|
7
|
+
* @param props - Component props
|
|
8
|
+
* @returns User Scopes tab component
|
|
9
|
+
*/
|
|
10
|
+
export declare function UserScopesTab({ className }: UserScopesTabProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=user_scopes_tab.d.ts.map
|
|
@@ -0,0 +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;AA6EF;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,EAAE,kBAAkB,2CA8f9D"}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// file_description: User Scopes tab component for assigning scopes to users in HRBAC
|
|
2
|
+
// section: client_directive
|
|
3
|
+
"use client";
|
|
4
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
5
|
+
// section: imports
|
|
6
|
+
import { useState, useEffect, useCallback, useMemo } from "react";
|
|
7
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../../../ui/table";
|
|
8
|
+
import { Button } from "../../../ui/button";
|
|
9
|
+
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../../../ui/dialog";
|
|
10
|
+
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "../../../ui/alert-dialog";
|
|
11
|
+
import { Input } from "../../../ui/input";
|
|
12
|
+
import { Label } from "../../../ui/label";
|
|
13
|
+
import { Avatar, AvatarFallback, AvatarImage } from "../../../ui/avatar";
|
|
14
|
+
import { TreeView } from "../../../ui/tree-view";
|
|
15
|
+
import { Loader2, Plus, Trash2, Search, CircleCheck, CircleX, ChevronRight, Building2, FolderTree, } from "lucide-react";
|
|
16
|
+
import { toast } from "sonner";
|
|
17
|
+
import { useHazoAuthConfig } from "../../../../contexts/hazo_auth_provider";
|
|
18
|
+
const SCOPE_LEVEL_LABELS = {
|
|
19
|
+
hazo_scopes_l1: "Level 1",
|
|
20
|
+
hazo_scopes_l2: "Level 2",
|
|
21
|
+
hazo_scopes_l3: "Level 3",
|
|
22
|
+
hazo_scopes_l4: "Level 4",
|
|
23
|
+
hazo_scopes_l5: "Level 5",
|
|
24
|
+
hazo_scopes_l6: "Level 6",
|
|
25
|
+
hazo_scopes_l7: "Level 7",
|
|
26
|
+
};
|
|
27
|
+
// Convert ScopeTreeNode to TreeDataItem format for selection
|
|
28
|
+
function convertToTreeData(nodes) {
|
|
29
|
+
return nodes.map((node) => {
|
|
30
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
31
|
+
const item = {
|
|
32
|
+
id: node.id,
|
|
33
|
+
name: `${node.name} (${node.seq})`,
|
|
34
|
+
icon: Building2,
|
|
35
|
+
scopeData: node,
|
|
36
|
+
};
|
|
37
|
+
if (hasChildren) {
|
|
38
|
+
item.children = convertToTreeData(node.children);
|
|
39
|
+
}
|
|
40
|
+
return item;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// section: component
|
|
44
|
+
/**
|
|
45
|
+
* User Scopes tab component for assigning scopes to users
|
|
46
|
+
* Two-panel layout: Users list | Scope assignments
|
|
47
|
+
* @param props - Component props
|
|
48
|
+
* @returns User Scopes tab component
|
|
49
|
+
*/
|
|
50
|
+
export function UserScopesTab({ className }) {
|
|
51
|
+
const { apiBasePath } = useHazoAuthConfig();
|
|
52
|
+
// Users state
|
|
53
|
+
const [users, setUsers] = useState([]);
|
|
54
|
+
const [usersLoading, setUsersLoading] = useState(true);
|
|
55
|
+
const [userSearch, setUserSearch] = useState("");
|
|
56
|
+
const [selectedUser, setSelectedUser] = useState(null);
|
|
57
|
+
// User scopes state
|
|
58
|
+
const [userScopes, setUserScopes] = useState([]);
|
|
59
|
+
const [scopesLoading, setScopesLoading] = useState(false);
|
|
60
|
+
const [inheritedLevels, setInheritedLevels] = useState([]);
|
|
61
|
+
// Add scope dialog state
|
|
62
|
+
const [addDialogOpen, setAddDialogOpen] = useState(false);
|
|
63
|
+
const [scopeTree, setScopeTree] = useState([]);
|
|
64
|
+
const [treeLoading, setTreeLoading] = useState(false);
|
|
65
|
+
const [selectedTreeItem, setSelectedTreeItem] = useState();
|
|
66
|
+
const [actionLoading, setActionLoading] = useState(false);
|
|
67
|
+
// Delete scope dialog state
|
|
68
|
+
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
69
|
+
const [scopeToDelete, setScopeToDelete] = useState(null);
|
|
70
|
+
// Load users
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
const loadUsers = async () => {
|
|
73
|
+
setUsersLoading(true);
|
|
74
|
+
try {
|
|
75
|
+
const response = await fetch(`${apiBasePath}/user_management/users`);
|
|
76
|
+
const data = await response.json();
|
|
77
|
+
if (data.success) {
|
|
78
|
+
setUsers(data.users || []);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
toast.error(data.error || "Failed to load users");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
toast.error("Failed to load users");
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
setUsersLoading(false);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
void loadUsers();
|
|
92
|
+
}, [apiBasePath]);
|
|
93
|
+
// Load user scopes when user selected
|
|
94
|
+
const loadUserScopes = useCallback(async () => {
|
|
95
|
+
if (!selectedUser) {
|
|
96
|
+
setUserScopes([]);
|
|
97
|
+
setInheritedLevels([]);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
setScopesLoading(true);
|
|
101
|
+
try {
|
|
102
|
+
const params = new URLSearchParams({
|
|
103
|
+
user_id: selectedUser.id,
|
|
104
|
+
include_effective: "true",
|
|
105
|
+
});
|
|
106
|
+
const response = await fetch(`${apiBasePath}/user_management/users/scopes?${params}`);
|
|
107
|
+
const data = await response.json();
|
|
108
|
+
if (data.success) {
|
|
109
|
+
setUserScopes(data.direct_scopes || []);
|
|
110
|
+
setInheritedLevels(data.inherited_scope_types || []);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
toast.error(data.error || "Failed to load user scopes");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
toast.error("Failed to load user scopes");
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
setScopesLoading(false);
|
|
121
|
+
}
|
|
122
|
+
}, [apiBasePath, selectedUser]);
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
void loadUserScopes();
|
|
125
|
+
}, [loadUserScopes]);
|
|
126
|
+
// Load scope tree for add dialog (all scopes across all orgs)
|
|
127
|
+
const loadScopeTree = useCallback(async () => {
|
|
128
|
+
setTreeLoading(true);
|
|
129
|
+
try {
|
|
130
|
+
const params = new URLSearchParams({ action: "tree_all" });
|
|
131
|
+
const response = await fetch(`${apiBasePath}/scope_management/scopes?${params}`);
|
|
132
|
+
const data = await response.json();
|
|
133
|
+
if (data.success) {
|
|
134
|
+
setScopeTree(data.trees || []);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
setScopeTree([]);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
setScopeTree([]);
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
setTreeLoading(false);
|
|
145
|
+
}
|
|
146
|
+
}, [apiBasePath]);
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
if (addDialogOpen) {
|
|
149
|
+
void loadScopeTree();
|
|
150
|
+
}
|
|
151
|
+
}, [addDialogOpen, loadScopeTree]);
|
|
152
|
+
// Filter users by search
|
|
153
|
+
const filteredUsers = users.filter((user) => {
|
|
154
|
+
var _a;
|
|
155
|
+
const search = userSearch.toLowerCase();
|
|
156
|
+
return ((((_a = user.name) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes(search)) || false) ||
|
|
157
|
+
user.email_address.toLowerCase().includes(search));
|
|
158
|
+
});
|
|
159
|
+
// Get user initials
|
|
160
|
+
const getUserInitials = (user) => {
|
|
161
|
+
var _a, _b;
|
|
162
|
+
if (user.name) {
|
|
163
|
+
const parts = user.name.trim().split(" ");
|
|
164
|
+
if (parts.length >= 2) {
|
|
165
|
+
return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();
|
|
166
|
+
}
|
|
167
|
+
return ((_a = user.name[0]) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || "";
|
|
168
|
+
}
|
|
169
|
+
return ((_b = user.email_address[0]) === null || _b === void 0 ? void 0 : _b.toUpperCase()) || "?";
|
|
170
|
+
};
|
|
171
|
+
// Convert tree to TreeDataItem format
|
|
172
|
+
const treeData = useMemo(() => {
|
|
173
|
+
return convertToTreeData(scopeTree);
|
|
174
|
+
}, [scopeTree]);
|
|
175
|
+
// Handle tree item selection
|
|
176
|
+
const handleTreeSelectChange = (item) => {
|
|
177
|
+
setSelectedTreeItem(item);
|
|
178
|
+
};
|
|
179
|
+
// Handle add scope
|
|
180
|
+
const handleAddScope = async () => {
|
|
181
|
+
if (!selectedUser || !(selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.scopeData)) {
|
|
182
|
+
toast.error("Please select a scope from the tree");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const scope = selectedTreeItem.scopeData;
|
|
186
|
+
setActionLoading(true);
|
|
187
|
+
try {
|
|
188
|
+
const response = await fetch(`${apiBasePath}/user_management/users/scopes`, {
|
|
189
|
+
method: "POST",
|
|
190
|
+
headers: { "Content-Type": "application/json" },
|
|
191
|
+
body: JSON.stringify({
|
|
192
|
+
user_id: selectedUser.id,
|
|
193
|
+
scope_type: scope.level,
|
|
194
|
+
scope_id: scope.id,
|
|
195
|
+
scope_seq: scope.seq,
|
|
196
|
+
}),
|
|
197
|
+
});
|
|
198
|
+
const data = await response.json();
|
|
199
|
+
if (data.success) {
|
|
200
|
+
toast.success("Scope assigned successfully");
|
|
201
|
+
setAddDialogOpen(false);
|
|
202
|
+
setSelectedTreeItem(undefined);
|
|
203
|
+
await loadUserScopes();
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
toast.error(data.error || "Failed to assign scope");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
toast.error("Failed to assign scope");
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
setActionLoading(false);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
// Handle remove scope
|
|
217
|
+
const handleRemoveScope = async () => {
|
|
218
|
+
if (!selectedUser || !scopeToDelete)
|
|
219
|
+
return;
|
|
220
|
+
setActionLoading(true);
|
|
221
|
+
try {
|
|
222
|
+
const params = new URLSearchParams({
|
|
223
|
+
user_id: selectedUser.id,
|
|
224
|
+
scope_type: scopeToDelete.scope_type,
|
|
225
|
+
scope_id: scopeToDelete.scope_id,
|
|
226
|
+
});
|
|
227
|
+
const response = await fetch(`${apiBasePath}/user_management/users/scopes?${params}`, {
|
|
228
|
+
method: "DELETE",
|
|
229
|
+
});
|
|
230
|
+
const data = await response.json();
|
|
231
|
+
if (data.success) {
|
|
232
|
+
toast.success("Scope removed successfully");
|
|
233
|
+
setDeleteDialogOpen(false);
|
|
234
|
+
setScopeToDelete(null);
|
|
235
|
+
await loadUserScopes();
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
toast.error(data.error || "Failed to remove scope");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
toast.error("Failed to remove scope");
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
setActionLoading(false);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
// Get level label
|
|
249
|
+
const getLevelLabel = (level) => {
|
|
250
|
+
return SCOPE_LEVEL_LABELS[level] || level;
|
|
251
|
+
};
|
|
252
|
+
return (_jsxs("div", { className: `cls_user_scopes_tab flex flex-col lg:flex-row gap-4 w-full min-h-[500px] ${className || ""}`, children: [_jsxs("div", { className: "cls_user_scopes_users_panel w-full lg:w-1/3 flex flex-col border rounded-lg", children: [_jsxs("div", { className: "cls_user_scopes_users_header p-4 border-b bg-muted/30", children: [_jsx("h3", { className: "font-semibold mb-2", children: "Select User" }), _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" }), _jsx(Input, { value: userSearch, onChange: (e) => setUserSearch(e.target.value), placeholder: "Search users...", className: "pl-8" })] })] }), _jsx("div", { className: "cls_user_scopes_users_list flex-1 overflow-auto", children: usersLoading ? (_jsx("div", { className: "flex items-center justify-center p-8", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : filteredUsers.length === 0 ? (_jsx("div", { className: "text-center text-muted-foreground p-8", children: "No users found." })) : (_jsx("div", { className: "divide-y", children: filteredUsers.map((user) => (_jsxs("div", { className: `cls_user_scopes_user_item flex items-center gap-3 p-3 cursor-pointer hover:bg-muted/50 transition-colors ${(selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.id) === user.id ? "bg-muted" : ""}`, onClick: () => setSelectedUser(user), children: [_jsxs(Avatar, { className: "h-8 w-8", children: [_jsx(AvatarImage, { src: user.profile_picture_url || undefined }), _jsx(AvatarFallback, { className: "bg-slate-200 text-slate-600 text-xs", children: getUserInitials(user) })] }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "text-sm font-medium truncate", children: user.name || user.email_address }), user.name && (_jsx("p", { className: "text-xs text-muted-foreground truncate", children: user.email_address }))] }), (selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.id) === user.id && (_jsx(ChevronRight, { className: "h-4 w-4 text-muted-foreground" }))] }, user.id))) })) })] }), _jsxs("div", { className: "cls_user_scopes_assignments_panel w-full lg:w-2/3 flex flex-col border rounded-lg", children: [_jsxs("div", { className: "cls_user_scopes_assignments_header p-4 border-b bg-muted/30 flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h3", { className: "font-semibold", children: selectedUser
|
|
253
|
+
? `Scopes for ${selectedUser.name || selectedUser.email_address}`
|
|
254
|
+
: "Select a user to view scopes" }), selectedUser && inheritedLevels.length > 0 && (_jsxs("p", { className: "text-xs text-muted-foreground mt-1", children: ["Inherits access to: ", inheritedLevels.map(getLevelLabel).join(", ")] }))] }), selectedUser && (_jsxs(Button, { onClick: () => {
|
|
255
|
+
setSelectedTreeItem(undefined);
|
|
256
|
+
setAddDialogOpen(true);
|
|
257
|
+
}, variant: "default", size: "sm", children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Add Scope"] }))] }), _jsx("div", { className: "cls_user_scopes_assignments_content flex-1 overflow-auto", children: !selectedUser ? (_jsx("div", { className: "flex items-center justify-center h-full text-muted-foreground", children: "Select a user from the left panel to manage their scope assignments." })) : scopesLoading ? (_jsx("div", { className: "flex items-center justify-center h-full", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : userScopes.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-4 text-muted-foreground", children: [_jsx("p", { children: "No scopes assigned to this user." }), _jsxs(Button, { onClick: () => {
|
|
258
|
+
setSelectedTreeItem(undefined);
|
|
259
|
+
setAddDialogOpen(true);
|
|
260
|
+
}, variant: "outline", size: "sm", children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Assign First Scope"] })] })) : (_jsxs(Table, { className: "w-full", children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Level" }), _jsx(TableHead, { children: "Scope Seq" }), _jsx(TableHead, { children: "Scope ID" }), _jsx(TableHead, { children: "Assigned" }), _jsx(TableHead, { className: "text-right w-[80px]", children: "Actions" })] }) }), _jsx(TableBody, { children: userScopes.map((scope) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: getLevelLabel(scope.scope_type) }), _jsx(TableCell, { className: "font-mono text-sm", children: scope.scope_seq }), _jsxs(TableCell, { className: "font-mono text-xs text-muted-foreground", children: [scope.scope_id.substring(0, 8), "..."] }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: new Date(scope.created_at).toLocaleDateString() }), _jsx(TableCell, { className: "text-right", children: _jsx(Button, { onClick: () => {
|
|
261
|
+
setScopeToDelete(scope);
|
|
262
|
+
setDeleteDialogOpen(true);
|
|
263
|
+
}, variant: "outline", size: "sm", className: "text-destructive", children: _jsx(Trash2, { className: "h-4 w-4" }) }) })] }, `${scope.scope_type}-${scope.scope_id}`))) })] })) })] }), _jsx(Dialog, { open: addDialogOpen, onOpenChange: setAddDialogOpen, children: _jsxs(DialogContent, { className: "cls_user_scopes_add_dialog sm:max-w-[500px]", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Add Scope Assignment" }), _jsxs(DialogDescription, { children: ["Select a scope from the tree to assign to", " ", (selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.name) || (selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.email_address), "."] })] }), _jsxs("div", { className: "flex flex-col gap-4 py-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 the Scope Hierarchy tab first." })] })) : (_jsx("div", { className: "border rounded-lg max-h-[300px] 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, " (", selectedTreeItem.scopeData.org, ")"] })] }))] }), _jsxs(DialogFooter, { children: [_jsx(Button, { onClick: handleAddScope, disabled: actionLoading || !(selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.scopeData), variant: "default", children: actionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Assigning..."] })) : (_jsxs(_Fragment, { children: [_jsx(CircleCheck, { className: "h-4 w-4 mr-2" }), "Assign Scope"] })) }), _jsxs(Button, { onClick: () => setAddDialogOpen(false), 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: "Remove Scope Assignment" }), _jsxs(AlertDialogDescription, { children: ["Are you sure you want to remove the scope \"", scopeToDelete === null || scopeToDelete === void 0 ? void 0 : scopeToDelete.scope_seq, "\" from", " ", (selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.name) || (selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.email_address), "? This will also revoke access to any inherited scopes."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogAction, { onClick: handleRemoveScope, disabled: actionLoading, children: actionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Removing..."] })) : ("Remove") }), _jsx(AlertDialogCancel, { onClick: () => {
|
|
264
|
+
setDeleteDialogOpen(false);
|
|
265
|
+
setScopeToDelete(null);
|
|
266
|
+
}, children: "Cancel" })] })] }) })] }));
|
|
267
|
+
}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
export type UserManagementLayoutProps = {
|
|
2
2
|
className?: string;
|
|
3
|
+
/** Whether HRBAC is enabled (passed from server) */
|
|
4
|
+
hrbacEnabled?: boolean;
|
|
5
|
+
/** Default organization for HRBAC scopes */
|
|
6
|
+
defaultOrg?: string;
|
|
3
7
|
};
|
|
4
8
|
/**
|
|
5
|
-
* User Management layout component with
|
|
9
|
+
* User Management layout component with tabs for managing users, roles, permissions, and HRBAC scopes
|
|
6
10
|
* Tab 1: Manage Users - data table with user details and actions
|
|
7
11
|
* Tab 2: Roles - roles-permissions matrix
|
|
8
12
|
* Tab 3: Permissions - manage permissions from DB and config
|
|
13
|
+
* Tab 4: Scope Labels - customize scope level labels (if HRBAC enabled)
|
|
14
|
+
* Tab 5: Scope Hierarchy - manage HRBAC scopes (if HRBAC enabled)
|
|
15
|
+
* Tab 6: User Scopes - assign scopes to users (if HRBAC enabled)
|
|
9
16
|
* @param props - Component props
|
|
10
17
|
* @returns User Management layout component
|
|
11
18
|
*/
|
|
12
|
-
export declare function UserManagementLayout({ className }: UserManagementLayoutProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export declare function UserManagementLayout({ className, hrbacEnabled, defaultOrg }: UserManagementLayoutProps): import("react/jsx-runtime").JSX.Element;
|
|
13
20
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/user_management/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/user_management/index.tsx"],"names":[],"mappings":"AAgDA,MAAM,MAAM,yBAAyB,GAAG;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAsBF;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,YAAoB,EAAE,UAAe,EAAE,EAAE,yBAAyB,2CAivCnH"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// file_description: User Management layout component with
|
|
1
|
+
// file_description: User Management layout component with tabs for managing users, roles, permissions, and HRBAC scopes
|
|
2
2
|
// section: client_directive
|
|
3
3
|
"use client";
|
|
4
4
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
@@ -14,20 +14,26 @@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, D
|
|
|
14
14
|
import { Input } from "../../ui/input";
|
|
15
15
|
import { Label } from "../../ui/label";
|
|
16
16
|
import { RolesMatrix } from "./components/roles_matrix";
|
|
17
|
+
import { ScopeHierarchyTab } from "./components/scope_hierarchy_tab";
|
|
18
|
+
import { ScopeLabelsTab } from "./components/scope_labels_tab";
|
|
19
|
+
import { UserScopesTab } from "./components/user_scopes_tab";
|
|
17
20
|
import { UserX, KeyRound, Edit, Trash2, Loader2, CircleCheck, CircleX, Plus, UserPlus } from "lucide-react";
|
|
18
21
|
import { toast } from "sonner";
|
|
19
22
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../../ui/tooltip";
|
|
20
23
|
import { useHazoAuthConfig } from "../../../contexts/hazo_auth_provider";
|
|
21
24
|
// section: component
|
|
22
25
|
/**
|
|
23
|
-
* User Management layout component with
|
|
26
|
+
* User Management layout component with tabs for managing users, roles, permissions, and HRBAC scopes
|
|
24
27
|
* Tab 1: Manage Users - data table with user details and actions
|
|
25
28
|
* Tab 2: Roles - roles-permissions matrix
|
|
26
29
|
* Tab 3: Permissions - manage permissions from DB and config
|
|
30
|
+
* Tab 4: Scope Labels - customize scope level labels (if HRBAC enabled)
|
|
31
|
+
* Tab 5: Scope Hierarchy - manage HRBAC scopes (if HRBAC enabled)
|
|
32
|
+
* Tab 6: User Scopes - assign scopes to users (if HRBAC enabled)
|
|
27
33
|
* @param props - Component props
|
|
28
34
|
* @returns User Management layout component
|
|
29
35
|
*/
|
|
30
|
-
export function UserManagementLayout({ className }) {
|
|
36
|
+
export function UserManagementLayout({ className, hrbacEnabled = false, defaultOrg = "" }) {
|
|
31
37
|
const { apiBasePath } = useHazoAuthConfig();
|
|
32
38
|
// Permission checks
|
|
33
39
|
const authResult = use_hazo_auth();
|
|
@@ -37,11 +43,18 @@ export function UserManagementLayout({ className }) {
|
|
|
37
43
|
authResult.permissions.includes("admin_role_management");
|
|
38
44
|
const hasPermissionManagementPermission = authResult.authenticated &&
|
|
39
45
|
authResult.permissions.includes("admin_permission_management");
|
|
46
|
+
const hasScopeHierarchyPermission = authResult.authenticated &&
|
|
47
|
+
authResult.permissions.includes("admin_scope_hierarchy_management");
|
|
48
|
+
const hasUserScopeAssignmentPermission = authResult.authenticated &&
|
|
49
|
+
authResult.permissions.includes("admin_user_scope_assignment");
|
|
40
50
|
// Determine which tabs to show
|
|
41
51
|
const showUsersTab = hasUserManagementPermission;
|
|
42
52
|
const showRolesTab = hasRoleManagementPermission;
|
|
43
53
|
const showPermissionsTab = hasPermissionManagementPermission;
|
|
44
|
-
const
|
|
54
|
+
const showScopeHierarchyTab = hrbacEnabled && hasScopeHierarchyPermission;
|
|
55
|
+
const showScopeLabelsTab = hrbacEnabled && hasScopeHierarchyPermission;
|
|
56
|
+
const showUserScopesTab = hrbacEnabled && hasUserScopeAssignmentPermission;
|
|
57
|
+
const hasAnyPermission = showUsersTab || showRolesTab || showPermissionsTab || showScopeHierarchyTab || showScopeLabelsTab || showUserScopesTab;
|
|
45
58
|
// Tab 1: Users state
|
|
46
59
|
const [users, setUsers] = useState([]);
|
|
47
60
|
const [usersLoading, setUsersLoading] = useState(true);
|
|
@@ -420,7 +433,10 @@ export function UserManagementLayout({ className }) {
|
|
|
420
433
|
};
|
|
421
434
|
return (_jsxs("div", { className: `cls_user_management_layout flex flex-col gap-4 w-full ${className || ""}`, children: [authResult.loading ? (_jsx("div", { className: "cls_user_management_permissions_loading flex items-center justify-center p-8", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : !hasAnyPermission ? (_jsxs("div", { className: "cls_user_management_no_permissions flex flex-col items-center justify-center p-8 gap-4", children: [_jsx("p", { className: "text-lg font-semibold text-slate-700", children: "Access Denied" }), _jsx("p", { className: "text-sm text-muted-foreground text-center", children: "You don't have permission to access User Management. Please contact your administrator." })] })) : (_jsxs(Tabs, { defaultValue: showUsersTab ? "users" :
|
|
422
435
|
showRolesTab ? "roles" :
|
|
423
|
-
showPermissionsTab ? "permissions" :
|
|
436
|
+
showPermissionsTab ? "permissions" :
|
|
437
|
+
showScopeLabelsTab ? "scope_labels" :
|
|
438
|
+
showScopeHierarchyTab ? "scope_hierarchy" :
|
|
439
|
+
showUserScopesTab ? "user_scopes" : "users", className: "cls_user_management_tabs w-full", children: [_jsxs(TabsList, { className: "cls_user_management_tabs_list flex w-full flex-wrap", children: [showUsersTab && (_jsx(TabsTrigger, { value: "users", className: "cls_user_management_tabs_trigger flex-1", children: "Manage Users" })), showRolesTab && (_jsx(TabsTrigger, { value: "roles", className: "cls_user_management_tabs_trigger flex-1", children: "Roles" })), showPermissionsTab && (_jsx(TabsTrigger, { value: "permissions", className: "cls_user_management_tabs_trigger flex-1", children: "Permissions" })), showScopeLabelsTab && (_jsx(TabsTrigger, { value: "scope_labels", className: "cls_user_management_tabs_trigger flex-1", children: "Scope Labels" })), showScopeHierarchyTab && (_jsx(TabsTrigger, { value: "scope_hierarchy", className: "cls_user_management_tabs_trigger flex-1", children: "Scope Hierarchy" })), showUserScopesTab && (_jsx(TabsTrigger, { value: "user_scopes", className: "cls_user_management_tabs_trigger flex-1", children: "User Scopes" }))] }), showUsersTab && (_jsx(TabsContent, { value: "users", className: "cls_user_management_tab_users w-full", children: usersLoading ? (_jsx("div", { className: "cls_user_management_users_loading flex items-center justify-center p-8", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : (_jsx("div", { className: "cls_user_management_users_table_container border rounded-lg overflow-auto w-full", children: _jsxs(Table, { className: "cls_user_management_users_table w-full", children: [_jsx(TableHeader, { className: "cls_user_management_users_table_header", children: _jsxs(TableRow, { className: "cls_user_management_users_table_header_row", children: [_jsx(TableHead, { className: "cls_user_management_users_table_header_profile_pic w-16", children: "Photo" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_id", children: "ID" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_name", children: "Name" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_email", children: "Email" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_email_verified", children: "Email Verified" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_is_active", children: "Active" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_last_logon", children: "Last Logon" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_created_at", children: "Created At" }), _jsx(TableHead, { className: "cls_user_management_users_table_header_actions text-right", children: "Actions" })] }) }), _jsx(TableBody, { className: "cls_user_management_users_table_body", children: users.length === 0 ? (_jsx(TableRow, { className: "cls_user_management_users_table_row_empty", children: _jsx(TableCell, { colSpan: 9, className: "text-center text-muted-foreground py-8", children: "No users found." }) })) : (users.map((user) => (_jsxs(TableRow, { className: "cls_user_management_users_table_row cursor-pointer hover:bg-muted/50", onClick: () => {
|
|
424
440
|
setSelectedUser(user);
|
|
425
441
|
setUserDetailDialogOpen(true);
|
|
426
442
|
}, children: [_jsx(TableCell, { className: "cls_user_management_users_table_cell_profile_pic", children: _jsxs(Avatar, { className: "cls_user_management_users_table_avatar h-8 w-8", children: [_jsx(AvatarImage, { src: user.profile_picture_url || undefined, alt: user.name ? `Profile picture of ${user.name}` : "Profile picture", className: "cls_user_management_users_table_avatar_image" }), _jsx(AvatarFallback, { className: "cls_user_management_users_table_avatar_fallback bg-slate-200 text-slate-600 text-xs", children: getUserInitials(user) })] }) }), _jsxs(TableCell, { className: "cls_user_management_users_table_cell_id font-mono text-xs", children: [user.id.substring(0, 8), "..."] }), _jsx(TableCell, { className: "cls_user_management_users_table_cell_name", children: user.name || "-" }), _jsx(TableCell, { className: "cls_user_management_users_table_cell_email", children: user.email_address }), _jsx(TableCell, { className: "cls_user_management_users_table_cell_email_verified", children: user.email_verified ? (_jsx("span", { className: "text-green-600", children: "Yes" })) : (_jsx("span", { className: "text-red-600", children: "No" })) }), _jsx(TableCell, { className: "cls_user_management_users_table_cell_is_active", children: user.is_active ? (_jsx("span", { className: "text-green-600", children: "Active" })) : (_jsx("span", { className: "text-red-600", children: "Inactive" })) }), _jsx(TableCell, { className: "cls_user_management_users_table_cell_last_logon", children: user.last_logon
|
|
@@ -445,7 +461,7 @@ export function UserManagementLayout({ className }) {
|
|
|
445
461
|
setEditingPermission(permission);
|
|
446
462
|
setEditDescription(permission.description);
|
|
447
463
|
setEditPermissionDialogOpen(true);
|
|
448
|
-
}, variant: "outline", size: "sm", className: "cls_user_management_permissions_table_action_edit", children: [_jsx(Edit, { className: "h-4 w-4 mr-1" }), "Edit"] }), _jsxs(Button, { onClick: () => handleDeletePermission(permission), disabled: permissionsActionLoading, variant: "outline", size: "sm", className: "cls_user_management_permissions_table_action_delete text-destructive", children: [_jsx(Trash2, { className: "h-4 w-4 mr-1" }), "Delete"] })] })) }) })] }, `${permission.source}-${permission.id}-${permission.permission_name}`)))) })] }) }))] }) }))] })), _jsx(AlertDialog, { open: deactivateDialogOpen, onOpenChange: setDeactivateDialogOpen, children: _jsxs(AlertDialogContent, { className: "cls_user_management_deactivate_dialog", children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Deactivate User" }), _jsxs(AlertDialogDescription, { children: ["Are you sure you want to deactivate ", (selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.name) || (selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.email_address), "? They will not be able to log in until reactivated."] })] }), _jsxs(AlertDialogFooter, { className: "cls_user_management_deactivate_dialog_footer", children: [_jsx(AlertDialogAction, { onClick: handleDeactivateUser, disabled: usersActionLoading, className: "cls_user_management_deactivate_dialog_confirm", children: usersActionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Deactivating..."] })) : ("Deactivate") }), _jsx(AlertDialogCancel, { onClick: () => {
|
|
464
|
+
}, variant: "outline", size: "sm", className: "cls_user_management_permissions_table_action_edit", children: [_jsx(Edit, { className: "h-4 w-4 mr-1" }), "Edit"] }), _jsxs(Button, { onClick: () => handleDeletePermission(permission), disabled: permissionsActionLoading, variant: "outline", size: "sm", className: "cls_user_management_permissions_table_action_delete text-destructive", children: [_jsx(Trash2, { className: "h-4 w-4 mr-1" }), "Delete"] })] })) }) })] }, `${permission.source}-${permission.id}-${permission.permission_name}`)))) })] }) }))] }) })), showScopeLabelsTab && (_jsx(TabsContent, { value: "scope_labels", className: "cls_user_management_tab_scope_labels w-full", children: _jsx(ScopeLabelsTab, { defaultOrg: defaultOrg }) })), showScopeHierarchyTab && (_jsx(TabsContent, { value: "scope_hierarchy", className: "cls_user_management_tab_scope_hierarchy w-full", children: _jsx(ScopeHierarchyTab, { defaultOrg: defaultOrg }) })), showUserScopesTab && (_jsx(TabsContent, { value: "user_scopes", className: "cls_user_management_tab_user_scopes w-full", children: _jsx(UserScopesTab, {}) }))] })), _jsx(AlertDialog, { open: deactivateDialogOpen, onOpenChange: setDeactivateDialogOpen, children: _jsxs(AlertDialogContent, { className: "cls_user_management_deactivate_dialog", children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Deactivate User" }), _jsxs(AlertDialogDescription, { children: ["Are you sure you want to deactivate ", (selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.name) || (selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.email_address), "? They will not be able to log in until reactivated."] })] }), _jsxs(AlertDialogFooter, { className: "cls_user_management_deactivate_dialog_footer", children: [_jsx(AlertDialogAction, { onClick: handleDeactivateUser, disabled: usersActionLoading, className: "cls_user_management_deactivate_dialog_confirm", children: usersActionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Deactivating..."] })) : ("Deactivate") }), _jsx(AlertDialogCancel, { onClick: () => {
|
|
449
465
|
setDeactivateDialogOpen(false);
|
|
450
466
|
setSelectedUser(null);
|
|
451
467
|
}, className: "cls_user_management_deactivate_dialog_cancel", children: "Cancel" })] })] }) }), _jsx(AlertDialog, { open: resetPasswordDialogOpen, onOpenChange: setResetPasswordDialogOpen, children: _jsxs(AlertDialogContent, { className: "cls_user_management_reset_password_dialog", children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Reset Password" }), _jsxs(AlertDialogDescription, { children: ["Send a password reset email to ", selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.email_address, "? They will receive a link to reset their password."] })] }), _jsxs(AlertDialogFooter, { className: "cls_user_management_reset_password_dialog_footer", children: [_jsx(AlertDialogAction, { onClick: handleResetPassword, disabled: usersActionLoading, className: "cls_user_management_reset_password_dialog_confirm", children: usersActionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Sending..."] })) : ("Send Reset Email") }), _jsx(AlertDialogCancel, { onClick: () => {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
3
|
+
declare const Select: React.FC<SelectPrimitive.SelectProps>;
|
|
4
|
+
declare const SelectGroup: React.ForwardRefExoticComponent<SelectPrimitive.SelectGroupProps & React.RefAttributes<HTMLDivElement>>;
|
|
5
|
+
declare const SelectValue: React.ForwardRefExoticComponent<SelectPrimitive.SelectValueProps & React.RefAttributes<HTMLSpanElement>>;
|
|
6
|
+
declare const SelectTrigger: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
|
|
7
|
+
declare const SelectScrollUpButton: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollUpButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
8
|
+
declare const SelectScrollDownButton: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectScrollDownButtonProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
9
|
+
declare const SelectContent: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
10
|
+
declare const SelectLabel: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectLabelProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
11
|
+
declare const SelectItem: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
12
|
+
declare const SelectSeparator: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectSeparatorProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
13
|
+
export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton, };
|
|
14
|
+
//# sourceMappingURL=select.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../../src/components/ui/select.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAA;AAKzD,QAAA,MAAM,MAAM,uCAAuB,CAAA;AAEnC,QAAA,MAAM,WAAW,yGAAwB,CAAA;AAEzC,QAAA,MAAM,WAAW,0GAAwB,CAAA;AAEzC,QAAA,MAAM,aAAa,oKAiBjB,CAAA;AAGF,QAAA,MAAM,oBAAoB,qKAcxB,CAAA;AAGF,QAAA,MAAM,sBAAsB,uKAc1B,CAAA;AAIF,QAAA,MAAM,aAAa,8JA6BjB,CAAA;AAGF,QAAA,MAAM,WAAW,4JASf,CAAA;AAGF,QAAA,MAAM,UAAU,2JAoBd,CAAA;AAGF,QAAA,MAAM,eAAe,gKASnB,CAAA;AAGF,OAAO,EACL,MAAM,EACN,WAAW,EACX,WAAW,EACX,aAAa,EACb,aAAa,EACb,WAAW,EACX,UAAU,EACV,eAAe,EACf,oBAAoB,EACpB,sBAAsB,GACvB,CAAA"}
|