@flusys/ng-iam 4.1.1 → 5.0.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 +26 -382
- package/fesm2022/{flusys-ng-iam-action-form-page.component-DJbMu2aV.mjs → flusys-ng-iam-action-form-page.component-D_vwcCjG.mjs} +14 -26
- package/fesm2022/flusys-ng-iam-action-form-page.component-D_vwcCjG.mjs.map +1 -0
- package/fesm2022/{flusys-ng-iam-action-list-page.component-DYnYC7Nn.mjs → flusys-ng-iam-action-list-page.component-DRK79zUR.mjs} +4 -4
- package/fesm2022/{flusys-ng-iam-action-list-page.component-DYnYC7Nn.mjs.map → flusys-ng-iam-action-list-page.component-DRK79zUR.mjs.map} +1 -1
- package/fesm2022/{flusys-ng-iam-flusys-ng-iam-CnVOy_4s.mjs → flusys-ng-iam-flusys-ng-iam-C6I4k78L.mjs} +1140 -1340
- package/fesm2022/flusys-ng-iam-flusys-ng-iam-C6I4k78L.mjs.map +1 -0
- package/fesm2022/{flusys-ng-iam-permission-page.component-_cLVilgz.mjs → flusys-ng-iam-permission-page.component-CZebeUhC.mjs} +2 -2
- package/fesm2022/{flusys-ng-iam-permission-page.component-_cLVilgz.mjs.map → flusys-ng-iam-permission-page.component-CZebeUhC.mjs.map} +1 -1
- package/fesm2022/{flusys-ng-iam-role-form-page.component-BuZmko2b.mjs → flusys-ng-iam-role-form-page.component-49dKMKOj.mjs} +11 -20
- package/fesm2022/flusys-ng-iam-role-form-page.component-49dKMKOj.mjs.map +1 -0
- package/fesm2022/{flusys-ng-iam-role-list-page.component-C7GVYYIn.mjs → flusys-ng-iam-role-list-page.component-CT7CvvHj.mjs} +114 -39
- package/fesm2022/flusys-ng-iam-role-list-page.component-CT7CvvHj.mjs.map +1 -0
- package/fesm2022/flusys-ng-iam.mjs +1 -1
- package/package.json +4 -4
- package/types/flusys-ng-iam.d.ts +44 -250
- package/fesm2022/flusys-ng-iam-action-form-page.component-DJbMu2aV.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-flusys-ng-iam-CnVOy_4s.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-role-form-page.component-BuZmko2b.mjs.map +0 -1
- package/fesm2022/flusys-ng-iam-role-list-page.component-C7GVYYIn.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HttpClient } from '@angular/common/http';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { inject, Injectable, signal, input, output, effect, Component,
|
|
3
|
+
import { inject, Injectable, signal, input, output, effect, Component, computed, DestroyRef } from '@angular/core';
|
|
4
4
|
import { ApiResourceService, PermissionValidatorService, AngularModule, PrimeModule, TranslatePipe, ROLE_ACTION_PERMISSIONS, HasPermissionDirective, COMPANY_ACTION_PERMISSIONS, COMPANY_API_PROVIDER, USER_ROLE_PERMISSIONS, USER_PERMISSION_PROVIDER, UserSelectComponent, USER_ACTION_PERMISSIONS, PROFILE_PERMISSION_PROVIDER, permissionGuard, ACTION_PERMISSIONS, ROLE_PERMISSIONS, anyPermissionGuard, resolveTranslationModule, SHARED_MESSAGES } from '@flusys/ng-shared';
|
|
5
5
|
import { APP_CONFIG, getServiceUrl, TRANSLATE_ADAPTER, BaseApiService, isCompanyFeatureEnabled } from '@flusys/ng-core';
|
|
6
6
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
|
@@ -33,270 +33,153 @@ var ActionType;
|
|
|
33
33
|
const MAX_DROPDOWN_ITEMS = 100;
|
|
34
34
|
|
|
35
35
|
const IAM_MESSAGES = {
|
|
36
|
-
//
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
'iam.user.add': 'Add User',
|
|
49
|
-
'iam.user.new': 'New User',
|
|
50
|
-
'iam.user.create': 'Create User',
|
|
51
|
-
'iam.user.edit': 'Edit User',
|
|
52
|
-
'iam.user.name': 'Name',
|
|
53
|
-
'iam.user.email': 'Email',
|
|
54
|
-
'iam.user.phone': 'Phone',
|
|
55
|
-
'iam.user.password': 'Password',
|
|
56
|
-
'iam.user.password.optional': 'Password (leave blank to keep current)',
|
|
57
|
-
'iam.user.password.placeholder': 'Enter password',
|
|
58
|
-
'iam.user.name.placeholder': 'Enter full name',
|
|
59
|
-
'iam.user.email.placeholder': 'Enter email address',
|
|
60
|
-
'iam.user.phone.placeholder': 'Enter phone number',
|
|
61
|
-
'iam.user.email.verified': 'Email Verified',
|
|
62
|
-
'iam.user.no.users': 'No users found',
|
|
63
|
-
'iam.user.details': 'User Details',
|
|
64
|
-
'iam.user.auto.assign.to': 'Auto-assign to',
|
|
65
|
-
'iam.user.assign.role': 'Assign Role',
|
|
66
|
-
'iam.user.remove.role': 'Remove Role',
|
|
67
|
-
'iam.user.activate': 'Activate User',
|
|
68
|
-
'iam.user.deactivate': 'Deactivate User',
|
|
69
|
-
'iam.user.reset.password': 'Reset Password',
|
|
70
|
-
'iam.user.assign.branch': 'Assign Branch',
|
|
71
|
-
'iam.user.delete.title': 'Delete User',
|
|
72
|
-
'iam.user.delete.success': 'User deleted successfully',
|
|
73
|
-
// Roles
|
|
74
|
-
'iam.role.title': 'Roles',
|
|
75
|
-
'iam.role.new': 'New Role',
|
|
76
|
-
'iam.role.create': 'Create Role',
|
|
77
|
-
'iam.role.edit': 'Edit Role',
|
|
78
|
-
'iam.role.name': 'Role Name',
|
|
79
|
-
'iam.role.name.placeholder': 'Enter role name',
|
|
80
|
-
'iam.role.description': 'Role Description',
|
|
81
|
-
'iam.role.description.placeholder': 'Enter role description',
|
|
82
|
-
'iam.role.permissions': 'Permissions',
|
|
83
|
-
'iam.role.assigned.users': 'Assigned Users',
|
|
84
|
-
'iam.role.no.roles': 'No roles found',
|
|
85
|
-
'iam.role.no.roles.assigned': 'No roles assigned',
|
|
86
|
-
'iam.role.delete.title': 'Delete Role',
|
|
87
|
-
'iam.role.delete.success': 'Role deleted successfully',
|
|
88
|
-
'iam.role.create.success': 'Role created successfully',
|
|
89
|
-
'iam.role.update.success': 'Role updated successfully',
|
|
36
|
+
// Action CRUD (API keys)
|
|
37
|
+
'action.create.success': 'Action created successfully',
|
|
38
|
+
'action.create.many.success': '{{count}} actions created successfully',
|
|
39
|
+
'action.get.success': 'Action retrieved successfully',
|
|
40
|
+
'action.get.by.ids.success': 'Actions retrieved successfully by Ids',
|
|
41
|
+
'action.get.by.filter.success': 'Actions retrieved successfully by filter',
|
|
42
|
+
'action.get.all.success': 'Actions retrieved successfully',
|
|
43
|
+
'action.update.success': 'Action updated successfully',
|
|
44
|
+
'action.update.many.success': '{{count}} actions updated successfully',
|
|
45
|
+
'action.delete.success': 'Action deleted successfully',
|
|
46
|
+
'action.restore.success': 'Action restored successfully',
|
|
47
|
+
// Role CRUD (API keys)
|
|
90
48
|
'role.create.success': 'Role created successfully',
|
|
91
|
-
'role.
|
|
92
|
-
'role.delete.success': 'Role deleted successfully',
|
|
49
|
+
'role.create.many.success': '{{count}} roles created successfully',
|
|
93
50
|
'role.get.success': 'Role retrieved successfully',
|
|
51
|
+
'role.get.by.ids.success': 'Roles retrieved successfully by Ids',
|
|
52
|
+
'role.get.by.filter.success': 'Roles retrieved successfully by filter',
|
|
94
53
|
'role.get.all.success': 'Roles retrieved successfully',
|
|
95
|
-
|
|
54
|
+
'role.update.success': 'Role updated successfully',
|
|
55
|
+
'role.update.many.success': '{{count}} roles updated successfully',
|
|
56
|
+
'role.delete.success': 'Role deleted successfully',
|
|
57
|
+
'role.restore.success': 'Role restored successfully',
|
|
58
|
+
// PERMISSION_OPERATION_MESSAGES (API keys)
|
|
59
|
+
'permission.process.success': 'Successfully processed {{total}} items: {{added}} added, {{removed}} removed',
|
|
60
|
+
'permission.user.required': 'User is required for {{method}}',
|
|
61
|
+
'permission.already.exists': 'Permission already exists',
|
|
62
|
+
// ROLE_PERMISSION_MESSAGES (API keys)
|
|
63
|
+
'role.permission.actions.success': 'Role actions retrieved successfully',
|
|
64
|
+
'role.permission.user.roles.success': 'User roles retrieved successfully',
|
|
65
|
+
// USER_ACTION_PERMISSION_MESSAGES (API keys)
|
|
66
|
+
'user.action.permission.get.success': 'User action permissions retrieved successfully',
|
|
67
|
+
// COMPANY_ACTION_PERMISSION_MESSAGES (API keys)
|
|
68
|
+
'company.action.permission.get.success': 'Company action permissions retrieved successfully',
|
|
69
|
+
// MY_PERMISSION_MESSAGES (API keys)
|
|
70
|
+
'my.permission.get.success': 'Permissions loaded successfully',
|
|
71
|
+
// IAM_MODE_MESSAGES (API keys)
|
|
72
|
+
'iam.direct.mode.unavailable': 'Direct permission assignment not available in RBAC-only mode',
|
|
73
|
+
'iam.rbac.mode.unavailable': 'Role-based permission assignment not available in direct-only mode',
|
|
74
|
+
'iam.role.assignment.unavailable': 'Role assignment not available in direct-only mode',
|
|
75
|
+
// pages/iam-container/iam-container.component.ts
|
|
76
|
+
'iam.title': 'Identity & Access Management',
|
|
77
|
+
'iam.subtitle': 'Manage roles, permissions, and access control',
|
|
96
78
|
'iam.action.title': 'Actions',
|
|
79
|
+
'iam.role.title': 'Roles',
|
|
80
|
+
'iam.permission.title': 'Permissions',
|
|
81
|
+
// pages/action/action-list-page.component.ts
|
|
97
82
|
'iam.action.new': 'New Action',
|
|
98
|
-
'iam.action.create': 'Create Action',
|
|
99
|
-
'iam.action.edit': 'Edit Action',
|
|
100
83
|
'iam.action.name': 'Action Name',
|
|
101
|
-
'iam.action.name.placeholder': 'Enter action name',
|
|
102
84
|
'iam.action.code': 'Action Code',
|
|
103
|
-
'iam.action.code.placeholder': 'Enter action code',
|
|
104
85
|
'iam.action.type': 'Type',
|
|
105
86
|
'iam.action.no.actions': 'No actions found',
|
|
106
|
-
'iam.action.no.actions.assigned': 'No actions assigned',
|
|
107
|
-
'iam.action.direct.actions': 'Direct Actions',
|
|
108
87
|
'iam.action.type.backend': 'Backend',
|
|
109
88
|
'iam.action.type.frontend': 'Frontend',
|
|
110
89
|
'iam.action.type.both': 'Both',
|
|
111
|
-
'iam.action.type.backend.label': 'Backend Only',
|
|
112
|
-
'iam.action.type.frontend.label': 'Frontend Only',
|
|
113
|
-
'iam.action.type.both.label': 'Backend & Frontend',
|
|
114
90
|
'iam.action.delete.title': 'Delete Action',
|
|
115
|
-
|
|
116
|
-
'iam.action.
|
|
117
|
-
'iam.action.
|
|
91
|
+
// pages/action/action-form-page.component.ts
|
|
92
|
+
'iam.action.edit': 'Edit Action',
|
|
93
|
+
'iam.action.name.placeholder': 'Enter action name',
|
|
94
|
+
'iam.action.code.placeholder': 'Enter action code',
|
|
118
95
|
'iam.action.parent': 'Parent Action',
|
|
119
96
|
'iam.action.select.parent': 'Select Parent Action',
|
|
120
|
-
|
|
121
|
-
'iam.
|
|
122
|
-
'iam.
|
|
123
|
-
|
|
124
|
-
'iam.
|
|
125
|
-
'iam.
|
|
126
|
-
'iam.
|
|
127
|
-
'iam.
|
|
128
|
-
'iam.
|
|
97
|
+
'iam.action.type.backend.label': 'Backend Only',
|
|
98
|
+
'iam.action.type.frontend.label': 'Frontend Only',
|
|
99
|
+
'iam.action.type.both.label': 'Backend & Frontend',
|
|
100
|
+
// pages/role/role-list-page.component.ts
|
|
101
|
+
'iam.company.title': 'Companies',
|
|
102
|
+
'iam.role.new': 'New Role',
|
|
103
|
+
'iam.role.name': 'Role Name',
|
|
104
|
+
'iam.role.no.roles': 'No roles found',
|
|
105
|
+
'iam.role.delete.title': 'Delete Role',
|
|
106
|
+
// pages/role/role-form-page.component.ts
|
|
107
|
+
'iam.role.edit': 'Edit Role',
|
|
108
|
+
'iam.role.name.placeholder': 'Enter role name',
|
|
109
|
+
// pages/permission/permission-page.component.ts
|
|
129
110
|
'iam.permission.role.actions': 'Role Actions',
|
|
130
111
|
'iam.permission.user.roles': 'User Roles',
|
|
131
112
|
'iam.permission.user.actions': 'User Actions',
|
|
132
113
|
'iam.permission.company.actions': 'Company Actions',
|
|
133
|
-
|
|
134
|
-
'iam.
|
|
135
|
-
'iam.
|
|
136
|
-
'iam.
|
|
137
|
-
'iam.
|
|
114
|
+
// components/logic-builder.component.ts
|
|
115
|
+
'iam.logic.title': 'Permission Logic',
|
|
116
|
+
'iam.logic.add.logic': 'Add Logic',
|
|
117
|
+
'iam.logic.clear.logic': 'Clear Logic',
|
|
118
|
+
'iam.logic.description': 'Define permission requirements using AND/OR logic with actions',
|
|
119
|
+
'iam.logic.click.to.toggle': 'Click to toggle',
|
|
120
|
+
'iam.logic.conditions': '{{count}} conditions',
|
|
121
|
+
'iam.logic.select.action': 'Select Action...',
|
|
122
|
+
'iam.logic.actions.available': '{{count}} available',
|
|
123
|
+
'iam.logic.add.condition': 'Add Condition:',
|
|
124
|
+
'iam.logic.group': 'Group',
|
|
125
|
+
'iam.logic.action': 'Action',
|
|
126
|
+
// components/role-action-selector.component.ts
|
|
138
127
|
'iam.permission.select.role': 'Select Role',
|
|
139
128
|
'iam.permission.select.role.placeholder': 'Search and select a role',
|
|
129
|
+
'iam.permission.action.permissions': 'Action Permissions',
|
|
130
|
+
'iam.permission.actions.available': '{{count}} actions available',
|
|
131
|
+
'iam.validation.warning.title': 'Validation Warning',
|
|
132
|
+
'iam.validation.unmet.prerequisites.plural': '{{count}} selected actions have unmet prerequisites. Fix before saving or use auto-fix on save.',
|
|
133
|
+
'iam.validation.unmet.prerequisites.singular': '{{count}} selected action has unmet prerequisites. Fix before saving or use auto-fix on save.',
|
|
134
|
+
'iam.permission.requirements': 'Requirements',
|
|
135
|
+
'iam.validation.unmet.prerequisites.tooltip': 'This action has unmet prerequisites and will fail validation on save',
|
|
136
|
+
'iam.permission.has.prerequisites': 'Has prerequisites',
|
|
137
|
+
'iam.permission.no.actions.available': 'No actions available',
|
|
138
|
+
'iam.permission.no.actions.for.role': 'No actions available for this role',
|
|
139
|
+
'iam.tooltip.remove.action': 'Click to remove',
|
|
140
|
+
'iam.tooltip.add.action': 'Click to add (auto-selects required)',
|
|
141
|
+
'iam.tooltip.assigned.to.role': 'Assigned to role',
|
|
142
|
+
'iam.tooltip.click.to.remove.role': 'Click to remove',
|
|
143
|
+
'iam.tooltip.click.to.assign.role': 'Click to assign to role',
|
|
144
|
+
// components/user-action-selector.component.ts
|
|
140
145
|
'iam.permission.select.user': 'Select User',
|
|
141
146
|
'iam.permission.select.user.placeholder': 'Search and select a user',
|
|
142
147
|
'iam.permission.select.branch': 'Select Branch',
|
|
143
148
|
'iam.permission.select.branch.placeholder': 'Search and select a branch',
|
|
144
|
-
'iam.
|
|
145
|
-
'iam.
|
|
149
|
+
'iam.branch.permitted.count.plural': '{{count}} permitted branches in current company',
|
|
150
|
+
'iam.branch.permitted.count': '{{count}} permitted branch in current company',
|
|
146
151
|
'iam.permission.direct.action.permissions': 'Direct Action Permissions',
|
|
147
|
-
'iam.permission.role.assignments': 'Role Assignments',
|
|
148
|
-
'iam.permission.actions.available': '{{count}} actions available',
|
|
149
|
-
'iam.permission.roles.available': '{{count}} roles available',
|
|
150
|
-
'iam.permission.no.actions.available': 'No actions available',
|
|
151
|
-
'iam.permission.no.roles.available': 'No roles available',
|
|
152
|
-
'iam.permission.no.actions.for.company': 'No actions available for this company',
|
|
153
|
-
'iam.permission.no.actions.for.role': 'No actions available for this role',
|
|
154
152
|
'iam.permission.no.actions.for.user': 'No actions available for this user',
|
|
155
|
-
'iam.
|
|
153
|
+
'iam.tooltip.assigned.to.user': 'Assigned to user',
|
|
154
|
+
'iam.tooltip.click.to.remove.user': 'Click to remove direct permission',
|
|
155
|
+
'iam.tooltip.click.to.assign.user': 'Click to assign direct permission',
|
|
156
156
|
'iam.permission.company.required': 'Please select a company first',
|
|
157
|
-
|
|
158
|
-
'iam.permission.
|
|
159
|
-
'iam.permission.company.
|
|
160
|
-
'iam.permission.
|
|
161
|
-
'iam.permission.
|
|
162
|
-
'iam.
|
|
157
|
+
// components/company-action-selector.component.ts
|
|
158
|
+
'iam.permission.select.company': 'Select Company',
|
|
159
|
+
'iam.permission.select.company.placeholder': 'Search and select a company',
|
|
160
|
+
'iam.permission.action.whitelist': 'Action Whitelist',
|
|
161
|
+
'iam.permission.no.actions.for.company': 'No actions available for this company',
|
|
162
|
+
'iam.tooltip.selected': 'Selected',
|
|
163
|
+
'iam.tooltip.click.to.remove': 'Click to remove from company whitelist',
|
|
164
|
+
'iam.tooltip.click.to.add': 'Click to add to company whitelist',
|
|
163
165
|
'iam.permission.requires': 'requires',
|
|
164
166
|
'iam.permission.prerequisite.validation.failed': 'Prerequisite Validation Failed',
|
|
165
167
|
'iam.permission.prerequisite.error.message': 'The following actions have unmet prerequisites:',
|
|
166
|
-
'iam.permission.auto.select.prompt': 'Would you like to auto-select the required actions?',
|
|
167
168
|
'iam.permission.auto.select.required': 'Auto-select Required',
|
|
168
169
|
'iam.permission.actions.selected': 'Actions Selected',
|
|
169
170
|
'iam.permission.auto.selected.prerequisites': 'Required prerequisites have been auto-selected. Click Save to apply changes.',
|
|
170
171
|
'iam.permission.changes.reverted': 'Changes Reverted',
|
|
171
172
|
'iam.permission.selection.reverted': 'Selection has been reverted to the initial state.',
|
|
172
|
-
|
|
173
|
-
'permission.
|
|
174
|
-
'permission.
|
|
175
|
-
'
|
|
176
|
-
'permission.
|
|
177
|
-
'permission.
|
|
178
|
-
'permission.user.required': 'User is required for {{method}}',
|
|
179
|
-
'permission.already.exists': 'Permission already exists',
|
|
180
|
-
// Access control
|
|
181
|
-
'iam.access.denied': 'Access Denied',
|
|
182
|
-
'iam.no.permission': 'You do not have permission to perform this action.',
|
|
183
|
-
'iam.contact.admin': 'Please contact your administrator for access.',
|
|
184
|
-
'iam.insufficient.permissions': 'Insufficient permissions',
|
|
185
|
-
// Company
|
|
186
|
-
'iam.company.title': 'Companies',
|
|
187
|
-
'iam.company.add': 'Add Company',
|
|
188
|
-
'iam.company.new': 'New Company',
|
|
189
|
-
'iam.company.create': 'Create Company',
|
|
190
|
-
'iam.company.edit': 'Edit Company',
|
|
191
|
-
'iam.company.select': 'Select Company',
|
|
192
|
-
'iam.company.name': 'Company Name',
|
|
193
|
-
'iam.company.name.placeholder': 'Enter company name',
|
|
194
|
-
'iam.company.slug': 'Company Slug',
|
|
195
|
-
'iam.company.slug.placeholder': 'Enter company slug',
|
|
196
|
-
'iam.company.code': 'Company Code',
|
|
197
|
-
'iam.company.code.placeholder': 'Enter company code',
|
|
198
|
-
'iam.company.details': 'Company Details',
|
|
199
|
-
'iam.company.address': 'Address',
|
|
200
|
-
'iam.company.address.placeholder': 'Enter company address',
|
|
201
|
-
'iam.company.phone': 'Phone',
|
|
202
|
-
'iam.company.phone.placeholder': 'Enter phone number',
|
|
203
|
-
'iam.company.email': 'Email',
|
|
204
|
-
'iam.company.email.placeholder': 'Enter email address',
|
|
205
|
-
'iam.company.website': 'Website',
|
|
206
|
-
'iam.company.website.placeholder': 'Enter website URL',
|
|
207
|
-
'iam.company.no.companies': 'No companies found',
|
|
208
|
-
'iam.company.delete.title': 'Delete Company',
|
|
209
|
-
'iam.company.delete.success': 'Company deleted successfully',
|
|
210
|
-
'iam.company.slug.required': 'Company slug is required',
|
|
211
|
-
// Branch
|
|
212
|
-
'iam.branch.title': 'Branches',
|
|
213
|
-
'iam.branch.add': 'Add Branch',
|
|
214
|
-
'iam.branch.new': 'New Branch',
|
|
215
|
-
'iam.branch.create': 'Create Branch',
|
|
216
|
-
'iam.branch.edit': 'Edit Branch',
|
|
217
|
-
'iam.branch.name': 'Branch Name',
|
|
218
|
-
'iam.branch.name.placeholder': 'Enter branch name',
|
|
219
|
-
'iam.branch.slug': 'Branch Slug',
|
|
220
|
-
'iam.branch.slug.placeholder': 'Enter branch slug',
|
|
221
|
-
'iam.branch.code': 'Branch Code',
|
|
222
|
-
'iam.branch.code.placeholder': 'Enter branch code',
|
|
223
|
-
'iam.branch.company': 'Company',
|
|
224
|
-
'iam.branch.no.branches': 'No branches found',
|
|
225
|
-
'iam.branch.select.company.first': 'Please select a company first',
|
|
226
|
-
'iam.branch.view.branches': 'View Branches',
|
|
227
|
-
'iam.branch.delete.title': 'Delete Branch',
|
|
228
|
-
'iam.branch.delete.success': 'Branch deleted successfully',
|
|
229
|
-
'iam.branch.slug.required': 'Branch slug is required',
|
|
230
|
-
// Action CRUD messages (API keys)
|
|
231
|
-
'action.create.success': 'Action created successfully',
|
|
232
|
-
'action.create.many.success': '{{count}} actions created successfully',
|
|
233
|
-
'action.get.success': 'Action retrieved successfully',
|
|
234
|
-
'action.get.all.success': 'Actions retrieved successfully',
|
|
235
|
-
'action.update.success': 'Action updated successfully',
|
|
236
|
-
'action.update.many.success': '{{count}} actions updated successfully',
|
|
237
|
-
'action.delete.success': 'Action deleted successfully',
|
|
238
|
-
'action.restore.success': 'Action restored successfully',
|
|
239
|
-
'action.not.found': 'Action not found',
|
|
240
|
-
// Role CRUD messages (API keys)
|
|
241
|
-
'role.create.many.success': '{{count}} roles created successfully',
|
|
242
|
-
'role.update.many.success': '{{count}} roles updated successfully',
|
|
243
|
-
'role.restore.success': 'Role restored successfully',
|
|
244
|
-
'role.not.found': 'Role not found',
|
|
245
|
-
// Role permission messages (API keys)
|
|
246
|
-
'role.permission.get.success': 'Role permissions retrieved successfully',
|
|
247
|
-
'role.permission.assign.success': 'Role permissions assigned successfully',
|
|
248
|
-
'role.permission.actions.success': 'Role actions retrieved successfully',
|
|
249
|
-
'role.permission.users.success': 'Role users retrieved successfully',
|
|
250
|
-
'role.permission.user.roles.success': 'User roles retrieved successfully',
|
|
251
|
-
// User action permission messages (API keys)
|
|
252
|
-
'user.action.permission.get.success': 'User action permissions retrieved successfully',
|
|
253
|
-
'user.action.permission.assign.success': 'User action permissions assigned successfully',
|
|
254
|
-
'user.action.permission.revoke.success': 'User action permissions revoked successfully',
|
|
255
|
-
// Company action permission messages (API keys)
|
|
256
|
-
'company.action.permission.get.success': 'Company action permissions retrieved successfully',
|
|
257
|
-
'company.action.permission.assign.success': 'Company action permissions assigned successfully',
|
|
258
|
-
'company.action.permission.revoke.success': 'Company action permissions revoked successfully',
|
|
259
|
-
// My permission messages (API keys)
|
|
260
|
-
'my.permission.get.success': 'Permissions loaded successfully',
|
|
261
|
-
// IAM mode messages (API keys)
|
|
262
|
-
'iam.direct.mode.unavailable': 'Direct permission assignment not available in RBAC-only mode',
|
|
263
|
-
'iam.rbac.mode.unavailable': 'Role-based permission assignment not available in direct-only mode',
|
|
264
|
-
'iam.role.assignment.unavailable': 'Role assignment not available in direct-only mode',
|
|
265
|
-
// Validation warnings
|
|
266
|
-
'iam.validation.warning.title': 'Validation Warning',
|
|
267
|
-
'iam.validation.unmet.prerequisites.singular': '{{count}} selected action has unmet prerequisites. Fix before saving or use auto-fix on save.',
|
|
268
|
-
'iam.validation.unmet.prerequisites.plural': '{{count}} selected actions have unmet prerequisites. Fix before saving or use auto-fix on save.',
|
|
269
|
-
'iam.validation.unmet.prerequisites.tooltip': 'This action has unmet prerequisites and will fail validation on save',
|
|
270
|
-
// Branch selector
|
|
271
|
-
'iam.branch.permitted.count': '{{count}} permitted branch in current company',
|
|
272
|
-
'iam.branch.permitted.count.plural': '{{count}} permitted branches in current company',
|
|
273
|
-
// Tooltips
|
|
274
|
-
'iam.tooltip.remove.action': 'Click to remove',
|
|
275
|
-
'iam.tooltip.add.action': 'Click to add (auto-selects required)',
|
|
276
|
-
'iam.tooltip.selected': 'Selected',
|
|
277
|
-
'iam.tooltip.click.to.remove': 'Click to remove from company whitelist',
|
|
278
|
-
'iam.tooltip.click.to.add': 'Click to add to company whitelist',
|
|
279
|
-
'iam.tooltip.assigned.to.role': 'Assigned to role',
|
|
280
|
-
'iam.tooltip.click.to.remove.role': 'Click to remove',
|
|
281
|
-
'iam.tooltip.click.to.assign.role': 'Click to assign to role',
|
|
282
|
-
'iam.tooltip.assigned.to.user': 'Assigned to user',
|
|
283
|
-
'iam.tooltip.click.to.remove.user': 'Click to remove direct permission',
|
|
284
|
-
'iam.tooltip.click.to.assign.user': 'Click to assign direct permission',
|
|
173
|
+
// components/user-role-selector.component.ts
|
|
174
|
+
'iam.permission.role.assignments': 'Role Assignments',
|
|
175
|
+
'iam.permission.roles.available': '{{count}} roles available',
|
|
176
|
+
'iam.pagination.roles.template': 'Showing {first} to {last} of {totalRecords} roles',
|
|
177
|
+
'iam.permission.no.roles.available': 'No roles available',
|
|
178
|
+
'iam.permission.no.roles.for.user': 'No roles available for this user',
|
|
285
179
|
'iam.tooltip.click.to.remove.role.from.user': 'Click to remove role',
|
|
286
180
|
'iam.tooltip.click.to.assign.role.to.user': 'Click to assign role to user',
|
|
287
|
-
//
|
|
288
|
-
'iam.logic.
|
|
289
|
-
'iam.logic.add.logic': 'Add Logic',
|
|
290
|
-
'iam.logic.clear.logic': 'Clear Logic',
|
|
291
|
-
'iam.logic.description': 'Define permission requirements using AND/OR logic with actions',
|
|
292
|
-
'iam.logic.conditions': '{{count}} conditions',
|
|
293
|
-
'iam.logic.select.action': 'Select Action...',
|
|
294
|
-
'iam.logic.actions.available': '{{count}} available',
|
|
295
|
-
'iam.logic.remove': 'Remove',
|
|
296
|
-
'iam.logic.add.condition': 'Add Condition:',
|
|
297
|
-
'iam.logic.group': 'Group',
|
|
298
|
-
'iam.logic.action': 'Action',
|
|
299
|
-
// Permission logic dialogs
|
|
181
|
+
// services/action-permission-logic.service.ts
|
|
182
|
+
'iam.logic.unknown.action': 'Unknown Action',
|
|
300
183
|
'iam.logic.validation.failed': 'Validation Failed',
|
|
301
184
|
'iam.logic.validation.failed.message': 'The following actions have unmet prerequisites:',
|
|
302
185
|
'iam.logic.validation.failed.prompt': 'Would you like to:',
|
|
@@ -305,47 +188,39 @@ const IAM_MESSAGES = {
|
|
|
305
188
|
'iam.logic.removed.actions.detail': 'Removed {{count}} action(s) with unmet prerequisites. You can now save.',
|
|
306
189
|
'iam.logic.save.cancelled': 'Save Cancelled',
|
|
307
190
|
'iam.logic.fix.prerequisites.manually': 'Please fix the prerequisites manually before saving',
|
|
308
|
-
'iam.logic.
|
|
309
|
-
'iam.logic.
|
|
310
|
-
'iam.logic.
|
|
311
|
-
'iam.logic.
|
|
312
|
-
'iam.logic.would.you.continue': 'Would you like to continue?',
|
|
191
|
+
'iam.logic.prerequisites.satisfied': '[OK] Prerequisites Satisfied',
|
|
192
|
+
'iam.logic.all.required.selected': 'All required actions are already selected.\nYou can safely add this action.',
|
|
193
|
+
'iam.logic.prerequisites.required': 'Prerequisites Required ({{count}} missing)',
|
|
194
|
+
'iam.logic.click.to.auto.select': 'Click to auto-select required actions',
|
|
313
195
|
'iam.logic.select.action.label': 'Select Action',
|
|
314
196
|
'iam.logic.auto.select.actions': 'Auto-select Actions',
|
|
315
|
-
'iam.logic.actions.selected.summary': 'Actions Selected',
|
|
316
197
|
'iam.logic.action.selected.detail': 'Action selected successfully (prerequisites already satisfied)',
|
|
317
198
|
'iam.logic.auto.selected.detail': 'Automatically selected {{count}} action(s) including prerequisites',
|
|
199
|
+
'iam.logic.minimum.required': ' (minimum required)',
|
|
200
|
+
'iam.logic.auto.select.prompt': 'Auto-select will choose {{count}} action(s){{suffix}}.',
|
|
201
|
+
'iam.logic.missing.prerequisites': 'Missing Prerequisites',
|
|
202
|
+
'iam.logic.requires.conditions': 'requires the following conditions to be satisfied:',
|
|
203
|
+
'iam.logic.would.you.continue': 'Would you like to continue?',
|
|
204
|
+
'iam.logic.actions.selected.summary': 'Actions Selected',
|
|
318
205
|
'iam.logic.selection.cancelled': 'Selection Cancelled',
|
|
319
206
|
'iam.logic.action.not.added': 'Action not added to whitelist',
|
|
320
|
-
'iam.logic.dependency.warning': 'Dependency Warning',
|
|
321
207
|
'iam.logic.required.by.actions': 'is required by the following action(s):',
|
|
322
208
|
'iam.logic.alternatives.available': 'Alternative options available:',
|
|
323
209
|
'iam.logic.switch.to.alternatives': 'Would you like to automatically switch to alternatives?',
|
|
324
210
|
'iam.logic.remove.dependents': 'Removing this will also remove the dependent action(s).',
|
|
211
|
+
'iam.logic.dependency.warning': 'Dependency Warning',
|
|
325
212
|
'iam.logic.use.alternatives': 'Use Alternatives',
|
|
326
213
|
'iam.logic.remove.all': 'Remove All',
|
|
327
214
|
'iam.logic.alternatives.applied': 'Alternatives Applied',
|
|
328
|
-
'iam.logic.switched.to.alternatives': '
|
|
215
|
+
'iam.logic.switched.to.alternatives': 'Automatically switched to alternative action(s)',
|
|
329
216
|
'iam.logic.actions.removed': 'Actions Removed',
|
|
330
217
|
'iam.logic.removed.with.dependents': 'Removed {{name}} and {{count}} dependent action(s)',
|
|
331
218
|
'iam.logic.cancelled': 'Cancelled',
|
|
332
219
|
'iam.logic.no.changes.made': 'No changes made',
|
|
333
|
-
'iam.logic.prerequisites.satisfied': '[OK] Prerequisites Satisfied',
|
|
334
|
-
'iam.logic.all.required.selected': 'All required actions are already selected.\nYou can safely add this action.',
|
|
335
|
-
'iam.logic.prerequisites.required': 'Prerequisites Required ({{count}} missing)',
|
|
336
|
-
'iam.logic.click.to.auto.select': 'Click to auto-select required actions',
|
|
337
220
|
'iam.logic.satisfied': '(satisfied)',
|
|
338
221
|
'iam.logic.missing': '(missing)',
|
|
339
|
-
'iam.logic.unknown.action': 'Unknown Action',
|
|
340
222
|
'iam.logic.invalid.node': 'Invalid logic node',
|
|
341
223
|
'iam.logic.invalid': '(invalid)',
|
|
342
|
-
'iam.logic.click.to.toggle': 'Click to toggle',
|
|
343
|
-
// Pagination templates
|
|
344
|
-
'iam.pagination.roles.template': 'Showing {first} to {last} of {totalRecords} roles',
|
|
345
|
-
'iam.pagination.actions.template': 'Showing {first} to {last} of {totalRecords} actions',
|
|
346
|
-
'iam.pagination.users.template': 'Showing {first} to {last} of {totalRecords} users',
|
|
347
|
-
'iam.pagination.companies.template': 'Showing {first} to {last} of {totalRecords} companies',
|
|
348
|
-
'iam.pagination.branches.template': 'Showing {first} to {last} of {totalRecords} branches',
|
|
349
224
|
};
|
|
350
225
|
|
|
351
226
|
class RoleApiService extends ApiResourceService {
|
|
@@ -1121,13 +996,16 @@ class LogicBuilderComponent {
|
|
|
1121
996
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: LogicBuilderComponent, isStandalone: true, selector: "lib-logic-builder", inputs: { logic: { classPropertyName: "logic", publicName: "logic", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { logicChange: "logicChange" }, ngImport: i0, template: `
|
|
1122
997
|
<div class="logic-builder">
|
|
1123
998
|
<div class="flex justify-between items-center mb-3">
|
|
1124
|
-
<h4 class="text-sm font-semibold m-0">
|
|
999
|
+
<h4 class="text-sm font-semibold m-0">
|
|
1000
|
+
{{ 'iam.logic.title' | translate }}
|
|
1001
|
+
</h4>
|
|
1125
1002
|
@if (!builderLogic()) {
|
|
1126
1003
|
<p-button
|
|
1127
1004
|
[label]="'iam.logic.add.logic' | translate"
|
|
1128
1005
|
icon="pi pi-plus"
|
|
1129
1006
|
size="small"
|
|
1130
|
-
(onClick)="initializeLogic()"
|
|
1007
|
+
(onClick)="initializeLogic()"
|
|
1008
|
+
/>
|
|
1131
1009
|
} @else {
|
|
1132
1010
|
<p-button
|
|
1133
1011
|
[label]="'iam.logic.clear.logic' | translate"
|
|
@@ -1135,7 +1013,8 @@ class LogicBuilderComponent {
|
|
|
1135
1013
|
size="small"
|
|
1136
1014
|
severity="danger"
|
|
1137
1015
|
[outlined]="true"
|
|
1138
|
-
(onClick)="clearLogic()"
|
|
1016
|
+
(onClick)="clearLogic()"
|
|
1017
|
+
/>
|
|
1139
1018
|
}
|
|
1140
1019
|
</div>
|
|
1141
1020
|
|
|
@@ -1147,7 +1026,12 @@ class LogicBuilderComponent {
|
|
|
1147
1026
|
|
|
1148
1027
|
<!-- Root Node -->
|
|
1149
1028
|
<div class="text-sm bg-surface-0 rounded border border-surface">
|
|
1150
|
-
<ng-container
|
|
1029
|
+
<ng-container
|
|
1030
|
+
*ngTemplateOutlet="
|
|
1031
|
+
nodeTemplate;
|
|
1032
|
+
context: { $implicit: builderLogic()!, depth: 0 }
|
|
1033
|
+
"
|
|
1034
|
+
></ng-container>
|
|
1151
1035
|
</div>
|
|
1152
1036
|
</div>
|
|
1153
1037
|
}
|
|
@@ -1155,9 +1039,14 @@ class LogicBuilderComponent {
|
|
|
1155
1039
|
|
|
1156
1040
|
<!-- Recursive Node Template -->
|
|
1157
1041
|
<ng-template #nodeTemplate let-node let-depth="depth">
|
|
1158
|
-
<div
|
|
1042
|
+
<div
|
|
1043
|
+
class="p-3 border-b border-surface"
|
|
1044
|
+
[ngClass]="depth % 2 === 0 ? 'bg-surface-0' : 'bg-surface-50'"
|
|
1045
|
+
>
|
|
1159
1046
|
<div class="flex items-center gap-3 mb-2">
|
|
1160
|
-
<span class="node-type" [ngClass]="node.type">{{
|
|
1047
|
+
<span class="node-type" [ngClass]="node.type">{{
|
|
1048
|
+
node.type.toUpperCase()
|
|
1049
|
+
}}</span>
|
|
1161
1050
|
|
|
1162
1051
|
<div class="flex-1 flex items-center gap-2">
|
|
1163
1052
|
@if (node.type === 'group') {
|
|
@@ -1166,10 +1055,16 @@ class LogicBuilderComponent {
|
|
|
1166
1055
|
class="operator-badge"
|
|
1167
1056
|
[ngClass]="node.operator === 'AND' ? 'and' : 'or'"
|
|
1168
1057
|
(click)="toggleOperator(node.id)"
|
|
1169
|
-
[title]="'iam.logic.click.to.toggle' | translate"
|
|
1058
|
+
[title]="'iam.logic.click.to.toggle' | translate"
|
|
1059
|
+
>
|
|
1170
1060
|
{{ node.operator }}
|
|
1171
1061
|
</span>
|
|
1172
|
-
<span class="text-muted-color text-xs"
|
|
1062
|
+
<span class="text-muted-color text-xs"
|
|
1063
|
+
>({{
|
|
1064
|
+
'iam.logic.conditions'
|
|
1065
|
+
| translate: { count: node.children?.length || 0 }
|
|
1066
|
+
}})</span
|
|
1067
|
+
>
|
|
1173
1068
|
}
|
|
1174
1069
|
|
|
1175
1070
|
@if (node.type === 'action') {
|
|
@@ -1177,8 +1072,14 @@ class LogicBuilderComponent {
|
|
|
1177
1072
|
<select
|
|
1178
1073
|
class="action-select flex-1 p-2 border border-surface rounded text-sm"
|
|
1179
1074
|
[ngModel]="node.actionId"
|
|
1180
|
-
(ngModelChange)="updateActionId(node.id, $event)"
|
|
1181
|
-
|
|
1075
|
+
(ngModelChange)="updateActionId(node.id, $event)"
|
|
1076
|
+
>
|
|
1077
|
+
<option [value]="null">
|
|
1078
|
+
{{ 'iam.logic.select.action' | translate }} ({{
|
|
1079
|
+
'iam.logic.actions.available'
|
|
1080
|
+
| translate: { count: actions().length }
|
|
1081
|
+
}})
|
|
1082
|
+
</option>
|
|
1182
1083
|
@for (action of actions(); track action.id) {
|
|
1183
1084
|
<option [ngValue]="action.id">{{ action.name }}</option>
|
|
1184
1085
|
}
|
|
@@ -1193,37 +1094,51 @@ class LogicBuilderComponent {
|
|
|
1193
1094
|
severity="danger"
|
|
1194
1095
|
size="small"
|
|
1195
1096
|
(onClick)="removeNode(node.id)"
|
|
1196
|
-
[pTooltip]="'
|
|
1097
|
+
[pTooltip]="'shared.remove' | translate"
|
|
1098
|
+
/>
|
|
1197
1099
|
</div>
|
|
1198
1100
|
</div>
|
|
1199
1101
|
|
|
1200
1102
|
<!-- Children for group nodes -->
|
|
1201
|
-
@if (
|
|
1103
|
+
@if (
|
|
1104
|
+
node.type === 'group' && node.children && node.children.length > 0
|
|
1105
|
+
) {
|
|
1202
1106
|
<div class="ml-5 mt-2 pl-3 border-l-2 border-surface">
|
|
1203
1107
|
@for (child of node.children; track child.id) {
|
|
1204
|
-
<ng-container
|
|
1108
|
+
<ng-container
|
|
1109
|
+
*ngTemplateOutlet="
|
|
1110
|
+
nodeTemplate;
|
|
1111
|
+
context: { $implicit: child, depth: depth + 1 }
|
|
1112
|
+
"
|
|
1113
|
+
></ng-container>
|
|
1205
1114
|
}
|
|
1206
1115
|
</div>
|
|
1207
1116
|
}
|
|
1208
1117
|
|
|
1209
1118
|
<!-- Add child buttons for group nodes -->
|
|
1210
1119
|
@if (node.type === 'group') {
|
|
1211
|
-
<div
|
|
1212
|
-
|
|
1120
|
+
<div
|
|
1121
|
+
class="mt-3 p-3 bg-surface-50 rounded border border-dashed border-surface"
|
|
1122
|
+
>
|
|
1123
|
+
<div class="text-xs font-semibold text-muted-color mb-2">
|
|
1124
|
+
{{ 'iam.logic.add.condition' | translate }}
|
|
1125
|
+
</div>
|
|
1213
1126
|
<div class="flex gap-2 flex-wrap">
|
|
1214
1127
|
<p-button
|
|
1215
1128
|
[label]="'iam.logic.group' | translate"
|
|
1216
1129
|
icon="pi pi-sitemap"
|
|
1217
1130
|
size="small"
|
|
1218
1131
|
[outlined]="true"
|
|
1219
|
-
(onClick)="addChildNode(node.id, 'group')"
|
|
1132
|
+
(onClick)="addChildNode(node.id, 'group')"
|
|
1133
|
+
/>
|
|
1220
1134
|
<p-button
|
|
1221
1135
|
[label]="'iam.logic.action' | translate"
|
|
1222
1136
|
icon="pi pi-bolt"
|
|
1223
1137
|
size="small"
|
|
1224
1138
|
severity="success"
|
|
1225
1139
|
[outlined]="true"
|
|
1226
|
-
(onClick)="addChildNode(node.id, 'action')"
|
|
1140
|
+
(onClick)="addChildNode(node.id, 'action')"
|
|
1141
|
+
/>
|
|
1227
1142
|
</div>
|
|
1228
1143
|
</div>
|
|
1229
1144
|
}
|
|
@@ -1236,13 +1151,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1236
1151
|
args: [{ selector: 'lib-logic-builder', imports: [AngularModule, PrimeModule, TranslatePipe], template: `
|
|
1237
1152
|
<div class="logic-builder">
|
|
1238
1153
|
<div class="flex justify-between items-center mb-3">
|
|
1239
|
-
<h4 class="text-sm font-semibold m-0">
|
|
1154
|
+
<h4 class="text-sm font-semibold m-0">
|
|
1155
|
+
{{ 'iam.logic.title' | translate }}
|
|
1156
|
+
</h4>
|
|
1240
1157
|
@if (!builderLogic()) {
|
|
1241
1158
|
<p-button
|
|
1242
1159
|
[label]="'iam.logic.add.logic' | translate"
|
|
1243
1160
|
icon="pi pi-plus"
|
|
1244
1161
|
size="small"
|
|
1245
|
-
(onClick)="initializeLogic()"
|
|
1162
|
+
(onClick)="initializeLogic()"
|
|
1163
|
+
/>
|
|
1246
1164
|
} @else {
|
|
1247
1165
|
<p-button
|
|
1248
1166
|
[label]="'iam.logic.clear.logic' | translate"
|
|
@@ -1250,7 +1168,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1250
1168
|
size="small"
|
|
1251
1169
|
severity="danger"
|
|
1252
1170
|
[outlined]="true"
|
|
1253
|
-
(onClick)="clearLogic()"
|
|
1171
|
+
(onClick)="clearLogic()"
|
|
1172
|
+
/>
|
|
1254
1173
|
}
|
|
1255
1174
|
</div>
|
|
1256
1175
|
|
|
@@ -1262,7 +1181,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1262
1181
|
|
|
1263
1182
|
<!-- Root Node -->
|
|
1264
1183
|
<div class="text-sm bg-surface-0 rounded border border-surface">
|
|
1265
|
-
<ng-container
|
|
1184
|
+
<ng-container
|
|
1185
|
+
*ngTemplateOutlet="
|
|
1186
|
+
nodeTemplate;
|
|
1187
|
+
context: { $implicit: builderLogic()!, depth: 0 }
|
|
1188
|
+
"
|
|
1189
|
+
></ng-container>
|
|
1266
1190
|
</div>
|
|
1267
1191
|
</div>
|
|
1268
1192
|
}
|
|
@@ -1270,9 +1194,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1270
1194
|
|
|
1271
1195
|
<!-- Recursive Node Template -->
|
|
1272
1196
|
<ng-template #nodeTemplate let-node let-depth="depth">
|
|
1273
|
-
<div
|
|
1197
|
+
<div
|
|
1198
|
+
class="p-3 border-b border-surface"
|
|
1199
|
+
[ngClass]="depth % 2 === 0 ? 'bg-surface-0' : 'bg-surface-50'"
|
|
1200
|
+
>
|
|
1274
1201
|
<div class="flex items-center gap-3 mb-2">
|
|
1275
|
-
<span class="node-type" [ngClass]="node.type">{{
|
|
1202
|
+
<span class="node-type" [ngClass]="node.type">{{
|
|
1203
|
+
node.type.toUpperCase()
|
|
1204
|
+
}}</span>
|
|
1276
1205
|
|
|
1277
1206
|
<div class="flex-1 flex items-center gap-2">
|
|
1278
1207
|
@if (node.type === 'group') {
|
|
@@ -1281,10 +1210,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1281
1210
|
class="operator-badge"
|
|
1282
1211
|
[ngClass]="node.operator === 'AND' ? 'and' : 'or'"
|
|
1283
1212
|
(click)="toggleOperator(node.id)"
|
|
1284
|
-
[title]="'iam.logic.click.to.toggle' | translate"
|
|
1213
|
+
[title]="'iam.logic.click.to.toggle' | translate"
|
|
1214
|
+
>
|
|
1285
1215
|
{{ node.operator }}
|
|
1286
1216
|
</span>
|
|
1287
|
-
<span class="text-muted-color text-xs"
|
|
1217
|
+
<span class="text-muted-color text-xs"
|
|
1218
|
+
>({{
|
|
1219
|
+
'iam.logic.conditions'
|
|
1220
|
+
| translate: { count: node.children?.length || 0 }
|
|
1221
|
+
}})</span
|
|
1222
|
+
>
|
|
1288
1223
|
}
|
|
1289
1224
|
|
|
1290
1225
|
@if (node.type === 'action') {
|
|
@@ -1292,8 +1227,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1292
1227
|
<select
|
|
1293
1228
|
class="action-select flex-1 p-2 border border-surface rounded text-sm"
|
|
1294
1229
|
[ngModel]="node.actionId"
|
|
1295
|
-
(ngModelChange)="updateActionId(node.id, $event)"
|
|
1296
|
-
|
|
1230
|
+
(ngModelChange)="updateActionId(node.id, $event)"
|
|
1231
|
+
>
|
|
1232
|
+
<option [value]="null">
|
|
1233
|
+
{{ 'iam.logic.select.action' | translate }} ({{
|
|
1234
|
+
'iam.logic.actions.available'
|
|
1235
|
+
| translate: { count: actions().length }
|
|
1236
|
+
}})
|
|
1237
|
+
</option>
|
|
1297
1238
|
@for (action of actions(); track action.id) {
|
|
1298
1239
|
<option [ngValue]="action.id">{{ action.name }}</option>
|
|
1299
1240
|
}
|
|
@@ -1308,37 +1249,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1308
1249
|
severity="danger"
|
|
1309
1250
|
size="small"
|
|
1310
1251
|
(onClick)="removeNode(node.id)"
|
|
1311
|
-
[pTooltip]="'
|
|
1252
|
+
[pTooltip]="'shared.remove' | translate"
|
|
1253
|
+
/>
|
|
1312
1254
|
</div>
|
|
1313
1255
|
</div>
|
|
1314
1256
|
|
|
1315
1257
|
<!-- Children for group nodes -->
|
|
1316
|
-
@if (
|
|
1258
|
+
@if (
|
|
1259
|
+
node.type === 'group' && node.children && node.children.length > 0
|
|
1260
|
+
) {
|
|
1317
1261
|
<div class="ml-5 mt-2 pl-3 border-l-2 border-surface">
|
|
1318
1262
|
@for (child of node.children; track child.id) {
|
|
1319
|
-
<ng-container
|
|
1263
|
+
<ng-container
|
|
1264
|
+
*ngTemplateOutlet="
|
|
1265
|
+
nodeTemplate;
|
|
1266
|
+
context: { $implicit: child, depth: depth + 1 }
|
|
1267
|
+
"
|
|
1268
|
+
></ng-container>
|
|
1320
1269
|
}
|
|
1321
1270
|
</div>
|
|
1322
1271
|
}
|
|
1323
1272
|
|
|
1324
1273
|
<!-- Add child buttons for group nodes -->
|
|
1325
1274
|
@if (node.type === 'group') {
|
|
1326
|
-
<div
|
|
1327
|
-
|
|
1275
|
+
<div
|
|
1276
|
+
class="mt-3 p-3 bg-surface-50 rounded border border-dashed border-surface"
|
|
1277
|
+
>
|
|
1278
|
+
<div class="text-xs font-semibold text-muted-color mb-2">
|
|
1279
|
+
{{ 'iam.logic.add.condition' | translate }}
|
|
1280
|
+
</div>
|
|
1328
1281
|
<div class="flex gap-2 flex-wrap">
|
|
1329
1282
|
<p-button
|
|
1330
1283
|
[label]="'iam.logic.group' | translate"
|
|
1331
1284
|
icon="pi pi-sitemap"
|
|
1332
1285
|
size="small"
|
|
1333
1286
|
[outlined]="true"
|
|
1334
|
-
(onClick)="addChildNode(node.id, 'group')"
|
|
1287
|
+
(onClick)="addChildNode(node.id, 'group')"
|
|
1288
|
+
/>
|
|
1335
1289
|
<p-button
|
|
1336
1290
|
[label]="'iam.logic.action' | translate"
|
|
1337
1291
|
icon="pi pi-bolt"
|
|
1338
1292
|
size="small"
|
|
1339
1293
|
severity="success"
|
|
1340
1294
|
[outlined]="true"
|
|
1341
|
-
(onClick)="addChildNode(node.id, 'action')"
|
|
1295
|
+
(onClick)="addChildNode(node.id, 'action')"
|
|
1296
|
+
/>
|
|
1342
1297
|
</div>
|
|
1343
1298
|
</div>
|
|
1344
1299
|
}
|
|
@@ -1406,92 +1361,35 @@ function convertActionToTreeNode(actions) {
|
|
|
1406
1361
|
return actions.map(convert);
|
|
1407
1362
|
}
|
|
1408
1363
|
|
|
1409
|
-
|
|
1410
|
-
* Role-Action Selector Component
|
|
1411
|
-
*
|
|
1412
|
-
* Manages role action permissions with hierarchical tree view and intelligent dependency management.
|
|
1413
|
-
*
|
|
1414
|
-
* **Architecture:**
|
|
1415
|
-
* - Presentation: TreeTable for hierarchical display
|
|
1416
|
-
* - Business Logic: Flat action list for selection/validation
|
|
1417
|
-
* - State: Signals with computed dependencies
|
|
1418
|
-
*
|
|
1419
|
-
* **Core Features:**
|
|
1420
|
-
* - Role dropdown with async loading
|
|
1421
|
-
* - Hierarchical TreeTable with expand/collapse
|
|
1422
|
-
* - Action permission assignment (add/remove)
|
|
1423
|
-
* - Real-time change tracking
|
|
1424
|
-
*
|
|
1425
|
-
* **Dependency Management:**
|
|
1426
|
-
* - Auto-selection with AND/OR logic
|
|
1427
|
-
* - Dependency detection on uncheck
|
|
1428
|
-
* - Cascade remove for AND dependencies
|
|
1429
|
-
* - Visual warnings for unmet prerequisites
|
|
1430
|
-
* - Pre-save validation with auto-fix
|
|
1431
|
-
*
|
|
1432
|
-
* **Performance:**
|
|
1433
|
-
* - Computed signals for reactive updates
|
|
1434
|
-
* - Memoized prerequisite validation
|
|
1435
|
-
* - AbortController for request cancellation
|
|
1436
|
-
*
|
|
1437
|
-
* @example
|
|
1438
|
-
* ```html
|
|
1439
|
-
* <flusys-role-action-selector />
|
|
1440
|
-
* ```
|
|
1441
|
-
*/
|
|
1442
|
-
class RoleActionSelectorComponent {
|
|
1443
|
-
// Permission constants for template
|
|
1444
|
-
ROLE_ACTION_PERMISSIONS = ROLE_ACTION_PERMISSIONS;
|
|
1445
|
-
// Dependencies
|
|
1446
|
-
destroyRef = inject(DestroyRef);
|
|
1447
|
-
roleApi = inject(RoleApiService);
|
|
1448
|
-
actionApi = inject(ActionApiService);
|
|
1449
|
-
permissionApi = inject(PermissionApiService);
|
|
1450
|
-
messageService = inject(MessageService);
|
|
1364
|
+
class BaseActionSelectorComponent {
|
|
1451
1365
|
permissionLogic = inject(ActionPermissionLogicService);
|
|
1452
1366
|
translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
|
|
1453
|
-
translate(key, vars) {
|
|
1454
|
-
return this.translateAdapter?.translate(key, vars) ?? key;
|
|
1455
|
-
}
|
|
1456
|
-
// State - Role Selection
|
|
1457
|
-
selectedRoleId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedRoleId" }] : []));
|
|
1458
|
-
roles = signal([], ...(ngDevMode ? [{ debugName: "roles" }] : []));
|
|
1459
|
-
// State - Loading/Saving
|
|
1460
1367
|
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
1461
1368
|
saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : []));
|
|
1462
|
-
// State - Actions (Business Logic)
|
|
1463
1369
|
_actions = signal([], ...(ngDevMode ? [{ debugName: "_actions" }] : []));
|
|
1464
1370
|
actions = this._actions.asReadonly();
|
|
1465
|
-
// State - Actions (Display)
|
|
1466
1371
|
_actionsTree = signal([], ...(ngDevMode ? [{ debugName: "_actionsTree" }] : []));
|
|
1467
1372
|
actionsTree = this._actionsTree.asReadonly();
|
|
1468
|
-
// State - Selection
|
|
1469
1373
|
_selectionMap = signal({}, ...(ngDevMode ? [{ debugName: "_selectionMap" }] : []));
|
|
1470
1374
|
selectionMap = this._selectionMap.asReadonly();
|
|
1471
1375
|
_initialSelection = signal({}, ...(ngDevMode ? [{ debugName: "_initialSelection" }] : []));
|
|
1472
1376
|
initialSelection = this._initialSelection.asReadonly();
|
|
1473
|
-
// Computed - Tree Nodes (for PrimeNG TreeTable)
|
|
1474
1377
|
treeNodes = computed(() => convertActionToTreeNode(this.actionsTree()), ...(ngDevMode ? [{ debugName: "treeNodes" }] : []));
|
|
1475
|
-
// Computed - Selection State
|
|
1476
1378
|
allSelected = computed(() => {
|
|
1477
1379
|
const selMap = this.selectionMap();
|
|
1478
1380
|
const total = this.actions().length;
|
|
1479
1381
|
if (total === 0)
|
|
1480
1382
|
return false;
|
|
1481
|
-
|
|
1482
|
-
return selected === total;
|
|
1383
|
+
return Object.values(selMap).filter((v) => v).length === total;
|
|
1483
1384
|
}, ...(ngDevMode ? [{ debugName: "allSelected" }] : []));
|
|
1484
|
-
// Computed - Change Detection
|
|
1485
1385
|
hasChanges = computed(() => {
|
|
1486
1386
|
const current = this.selectionMap();
|
|
1487
1387
|
const initial = this.initialSelection();
|
|
1488
1388
|
const currentKeys = Object.keys(current);
|
|
1489
|
-
|
|
1490
|
-
if (currentKeys.length !== initialKeys.length)
|
|
1389
|
+
if (currentKeys.length !== Object.keys(initial).length)
|
|
1491
1390
|
return true;
|
|
1492
1391
|
return currentKeys.some((key) => current[key] !== initial[key]);
|
|
1493
1392
|
}, ...(ngDevMode ? [{ debugName: "hasChanges" }] : []));
|
|
1494
|
-
// Computed - Validation
|
|
1495
1393
|
actionsWithUnmetPrerequisites = computed(() => {
|
|
1496
1394
|
const selMap = this.selectionMap();
|
|
1497
1395
|
const allActions = this.actions();
|
|
@@ -1504,41 +1402,105 @@ class RoleActionSelectorComponent {
|
|
|
1504
1402
|
return unmetSet;
|
|
1505
1403
|
}, ...(ngDevMode ? [{ debugName: "actionsWithUnmetPrerequisites" }] : []));
|
|
1506
1404
|
invalidActionsCount = computed(() => this.actionsWithUnmetPrerequisites().size, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
|
|
1507
|
-
// Computed - Pending Changes
|
|
1508
1405
|
pendingAdd = computed(() => {
|
|
1509
1406
|
const current = this.selectionMap();
|
|
1510
1407
|
const initial = this.initialSelection();
|
|
1511
|
-
|
|
1512
|
-
return allActions.filter((action) => current[action.id] && !initial[action.id]);
|
|
1408
|
+
return this.actions().filter((a) => current[a.id] && !initial[a.id]);
|
|
1513
1409
|
}, ...(ngDevMode ? [{ debugName: "pendingAdd" }] : []));
|
|
1514
1410
|
pendingRemove = computed(() => {
|
|
1515
1411
|
const current = this.selectionMap();
|
|
1516
1412
|
const initial = this.initialSelection();
|
|
1517
|
-
|
|
1518
|
-
return allActions.filter((action) => !current[action.id] && initial[action.id]);
|
|
1413
|
+
return this.actions().filter((a) => !current[a.id] && initial[a.id]);
|
|
1519
1414
|
}, ...(ngDevMode ? [{ debugName: "pendingRemove" }] : []));
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
return
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1415
|
+
canSave = computed(() => this.hasChanges() && !this.saving() && this.invalidActionsCount() === 0, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
|
|
1416
|
+
translate(key, vars) {
|
|
1417
|
+
return this.translateAdapter?.translate(key, vars) ?? key;
|
|
1418
|
+
}
|
|
1419
|
+
hasUnmetPrerequisites(action) {
|
|
1420
|
+
return action.id ? this.actionsWithUnmetPrerequisites().has(action.id) : false;
|
|
1421
|
+
}
|
|
1422
|
+
getTooltip(action) {
|
|
1423
|
+
const selMap = this.selectionMap();
|
|
1424
|
+
const isCurrentlySelected = selMap[action.id];
|
|
1425
|
+
if (action.permissionLogic) {
|
|
1426
|
+
const prerequisiteInfo = this.permissionLogic.getPrerequisiteTooltip(action, selMap, this.actions());
|
|
1427
|
+
if (prerequisiteInfo) {
|
|
1428
|
+
const actionHint = isCurrentlySelected
|
|
1429
|
+
? `\n\n---\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.remove.action')}`
|
|
1430
|
+
: `\n\n---\n[${this.translate('shared.add')}] ${this.translate('iam.tooltip.add.action')}`;
|
|
1431
|
+
return `${prerequisiteInfo}${actionHint}`;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
return this.getDefaultTooltip(isCurrentlySelected);
|
|
1435
|
+
}
|
|
1436
|
+
onActionToggle(action, newValue) {
|
|
1437
|
+
if (!newValue) {
|
|
1438
|
+
this.permissionLogic.handleUncheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
|
|
1439
|
+
}
|
|
1440
|
+
else {
|
|
1441
|
+
this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
toggleAll() {
|
|
1445
|
+
this.setAllSelection(!this.allSelected());
|
|
1446
|
+
}
|
|
1447
|
+
selectAll() {
|
|
1448
|
+
this.setAllSelection(true);
|
|
1449
|
+
}
|
|
1450
|
+
deselectAll() {
|
|
1451
|
+
this.setAllSelection(false);
|
|
1452
|
+
}
|
|
1453
|
+
setAllSelection(value) {
|
|
1454
|
+
this._selectionMap.set(this.buildSelectionMap(this.actions(), () => value));
|
|
1455
|
+
}
|
|
1456
|
+
buildSelectionMap(actions, predicate) {
|
|
1457
|
+
const selMap = {};
|
|
1458
|
+
for (const action of actions) {
|
|
1459
|
+
selMap[action.id] = predicate(action);
|
|
1460
|
+
}
|
|
1461
|
+
return selMap;
|
|
1462
|
+
}
|
|
1463
|
+
buildPayloadItems() {
|
|
1464
|
+
const items = [];
|
|
1465
|
+
for (const action of this.pendingAdd()) {
|
|
1466
|
+
items.push({ id: action.id, action: 'add' });
|
|
1467
|
+
}
|
|
1468
|
+
for (const action of this.pendingRemove()) {
|
|
1469
|
+
items.push({ id: action.id, action: 'remove' });
|
|
1470
|
+
}
|
|
1471
|
+
return items;
|
|
1472
|
+
}
|
|
1473
|
+
resetState() {
|
|
1474
|
+
this._actionsTree.set([]);
|
|
1475
|
+
this._actions.set([]);
|
|
1476
|
+
this._selectionMap.set({});
|
|
1477
|
+
this._initialSelection.set({});
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
class RoleActionSelectorComponent extends BaseActionSelectorComponent {
|
|
1482
|
+
ROLE_ACTION_PERMISSIONS = ROLE_ACTION_PERMISSIONS;
|
|
1483
|
+
destroyRef = inject(DestroyRef);
|
|
1484
|
+
roleApi = inject(RoleApiService);
|
|
1485
|
+
actionApi = inject(ActionApiService);
|
|
1486
|
+
permissionApi = inject(PermissionApiService);
|
|
1487
|
+
messageService = inject(MessageService);
|
|
1488
|
+
selectedRoleId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedRoleId" }] : []));
|
|
1489
|
+
roles = signal([], ...(ngDevMode ? [{ debugName: "roles" }] : []));
|
|
1490
|
+
loadDataAbortController = null;
|
|
1491
|
+
constructor() {
|
|
1492
|
+
super();
|
|
1493
|
+
this.destroyRef.onDestroy(() => {
|
|
1494
|
+
this.loadDataAbortController?.abort();
|
|
1495
|
+
});
|
|
1496
|
+
this.loadRoles();
|
|
1533
1497
|
effect(() => {
|
|
1534
1498
|
const roleId = this.selectedRoleId();
|
|
1535
|
-
// Abort previous request
|
|
1536
1499
|
this.loadDataAbortController?.abort();
|
|
1537
1500
|
if (roleId) {
|
|
1538
1501
|
this.loadDataAbortController = new AbortController();
|
|
1539
1502
|
this.onRoleChange(this.loadDataAbortController.signal).catch((err) => {
|
|
1540
1503
|
if (err.name !== 'AbortError') {
|
|
1541
|
-
// Error toast handled by global interceptor
|
|
1542
1504
|
this.loading.set(false);
|
|
1543
1505
|
}
|
|
1544
1506
|
});
|
|
@@ -1548,23 +1510,22 @@ class RoleActionSelectorComponent {
|
|
|
1548
1510
|
}
|
|
1549
1511
|
});
|
|
1550
1512
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1513
|
+
getDefaultTooltip(isSelected) {
|
|
1514
|
+
if (isSelected) {
|
|
1515
|
+
return `[${this.translate('shared.assigned')}] ${this.translate('iam.tooltip.assigned.to.role')}\n\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.click.to.remove.role')}`;
|
|
1516
|
+
}
|
|
1517
|
+
return `[${this.translate('shared.add')}] ${this.translate('iam.tooltip.click.to.assign.role')}`;
|
|
1518
|
+
}
|
|
1554
1519
|
async loadRoles() {
|
|
1555
1520
|
try {
|
|
1556
|
-
const response = await
|
|
1521
|
+
const response = await this.roleApi.getAll({
|
|
1557
1522
|
pagination: { currentPage: 0, pageSize: MAX_DROPDOWN_ITEMS },
|
|
1558
|
-
})
|
|
1523
|
+
});
|
|
1559
1524
|
this.roles.set(response?.data ?? []);
|
|
1560
1525
|
}
|
|
1561
1526
|
catch {
|
|
1562
|
-
// Error toast handled by global interceptor
|
|
1563
1527
|
}
|
|
1564
1528
|
}
|
|
1565
|
-
/**
|
|
1566
|
-
* Load actions and assignments for selected role
|
|
1567
|
-
*/
|
|
1568
1529
|
async onRoleChange(signal) {
|
|
1569
1530
|
const roleId = this.selectedRoleId();
|
|
1570
1531
|
if (!roleId)
|
|
@@ -1574,26 +1535,17 @@ class RoleActionSelectorComponent {
|
|
|
1574
1535
|
const actionsResponse = await firstValueFrom(this.actionApi.getActionsForPermission());
|
|
1575
1536
|
if (signal.aborted)
|
|
1576
1537
|
return;
|
|
1577
|
-
// Get flat list (filtered by company if enabled)
|
|
1578
1538
|
const flatActions = (actionsResponse?.data ?? []).filter((a) => !!a.id);
|
|
1579
|
-
// Build tree structure from flat list with parentId
|
|
1580
1539
|
const actionsTree = buildTreeFromFlat(flatActions);
|
|
1581
|
-
// Store both representations
|
|
1582
1540
|
this._actionsTree.set(actionsTree);
|
|
1583
1541
|
this._actions.set(flatActions);
|
|
1584
|
-
// Load existing assignments
|
|
1585
1542
|
const assignedResponse = await firstValueFrom(this.permissionApi.getRoleActions(roleId));
|
|
1586
1543
|
if (signal.aborted)
|
|
1587
1544
|
return;
|
|
1588
1545
|
const assignedIds = new Set((assignedResponse?.data || []).map((a) => a.actionId));
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
if (action.id) {
|
|
1593
|
-
selMap[action.id] = assignedIds.has(action.id);
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
this.applySelection(selMap);
|
|
1546
|
+
const selMap = this.buildSelectionMap(flatActions, (action) => assignedIds.has(action.id));
|
|
1547
|
+
this._selectionMap.set(selMap);
|
|
1548
|
+
this._initialSelection.set({ ...selMap });
|
|
1597
1549
|
}
|
|
1598
1550
|
finally {
|
|
1599
1551
|
if (!signal.aborted) {
|
|
@@ -1601,146 +1553,63 @@ class RoleActionSelectorComponent {
|
|
|
1601
1553
|
}
|
|
1602
1554
|
}
|
|
1603
1555
|
}
|
|
1604
|
-
/**
|
|
1605
|
-
* Get tooltip for action checkbox
|
|
1606
|
-
*/
|
|
1607
|
-
getTooltip(action) {
|
|
1608
|
-
const selMap = this.selectionMap();
|
|
1609
|
-
const isCurrentlySelected = selMap[action.id];
|
|
1610
|
-
if (action.permissionLogic) {
|
|
1611
|
-
const prerequisiteInfo = this.permissionLogic.getPrerequisiteTooltip(action, selMap, this.actions());
|
|
1612
|
-
if (prerequisiteInfo) {
|
|
1613
|
-
const actionHint = isCurrentlySelected
|
|
1614
|
-
? `\n\n---\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.remove.action')}`
|
|
1615
|
-
: `\n\n---\n[${this.translate('shared.add')}] ${this.translate('iam.tooltip.add.action')}`;
|
|
1616
|
-
return `${prerequisiteInfo}${actionHint}`;
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
if (isCurrentlySelected) {
|
|
1620
|
-
return `[${this.translate('shared.assigned')}] ${this.translate('iam.tooltip.assigned.to.role')}\n\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.click.to.remove.role')}`;
|
|
1621
|
-
}
|
|
1622
|
-
return `[${this.translate('shared.add')}] ${this.translate('iam.tooltip.click.to.assign.role')}`;
|
|
1623
|
-
}
|
|
1624
|
-
/**
|
|
1625
|
-
* Check if action has unmet prerequisites
|
|
1626
|
-
*/
|
|
1627
|
-
hasUnmetPrerequisites(action) {
|
|
1628
|
-
return action.id
|
|
1629
|
-
? this.actionsWithUnmetPrerequisites().has(action.id)
|
|
1630
|
-
: false;
|
|
1631
|
-
}
|
|
1632
|
-
/**
|
|
1633
|
-
* Handle action toggle with dependency management
|
|
1634
|
-
*/
|
|
1635
|
-
onActionToggle(action, newValue) {
|
|
1636
|
-
if (!newValue) {
|
|
1637
|
-
// Unchecking - validate dependencies
|
|
1638
|
-
this.permissionLogic.handleUncheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
|
|
1639
|
-
}
|
|
1640
|
-
else {
|
|
1641
|
-
// Checking - validate prerequisites
|
|
1642
|
-
this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
toggleAll() {
|
|
1646
|
-
this.setAllSelection(!this.allSelected());
|
|
1647
|
-
}
|
|
1648
|
-
selectAll() {
|
|
1649
|
-
this.setAllSelection(true);
|
|
1650
|
-
}
|
|
1651
|
-
deselectAll() {
|
|
1652
|
-
this.setAllSelection(false);
|
|
1653
|
-
}
|
|
1654
|
-
setAllSelection(value) {
|
|
1655
|
-
const selMap = {};
|
|
1656
|
-
for (const action of this.actions()) {
|
|
1657
|
-
selMap[action.id] = value;
|
|
1658
|
-
}
|
|
1659
|
-
this._selectionMap.set(selMap);
|
|
1660
|
-
}
|
|
1661
|
-
/**
|
|
1662
|
-
* Save changes to backend
|
|
1663
|
-
*/
|
|
1664
1556
|
async saveChanges() {
|
|
1665
1557
|
const roleId = this.selectedRoleId();
|
|
1666
1558
|
if (!roleId)
|
|
1667
1559
|
return;
|
|
1668
|
-
// Pre-save validation
|
|
1669
1560
|
const invalidActions = this.permissionLogic.getActionsWithUnmetPrerequisites(this.selectionMap(), this.actions());
|
|
1670
1561
|
if (invalidActions.length > 0) {
|
|
1671
1562
|
this.permissionLogic.showValidationErrorDialog(invalidActions, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
|
|
1672
1563
|
return;
|
|
1673
1564
|
}
|
|
1674
|
-
// Build payload
|
|
1675
1565
|
const items = this.buildPayloadItems();
|
|
1676
1566
|
if (items.length === 0)
|
|
1677
1567
|
return;
|
|
1678
1568
|
this.saving.set(true);
|
|
1679
1569
|
try {
|
|
1680
|
-
const response = await firstValueFrom(this.permissionApi.assignRoleActions({
|
|
1681
|
-
roleId,
|
|
1682
|
-
items,
|
|
1683
|
-
}));
|
|
1570
|
+
const response = await firstValueFrom(this.permissionApi.assignRoleActions({ roleId, items }));
|
|
1684
1571
|
this.messageService.add({
|
|
1685
1572
|
severity: 'success',
|
|
1686
1573
|
summary: this.translate('shared.success'),
|
|
1687
|
-
detail: response?.
|
|
1574
|
+
detail: response?.messageKey
|
|
1575
|
+
? this.translate(response.messageKey, response.messageVariables)
|
|
1576
|
+
: response?.message,
|
|
1688
1577
|
});
|
|
1689
|
-
// Update baseline
|
|
1690
1578
|
this._initialSelection.set({ ...this.selectionMap() });
|
|
1691
1579
|
}
|
|
1692
1580
|
catch {
|
|
1693
|
-
// Error toast handled by global interceptor
|
|
1694
1581
|
}
|
|
1695
1582
|
finally {
|
|
1696
1583
|
this.saving.set(false);
|
|
1697
1584
|
}
|
|
1698
1585
|
}
|
|
1699
|
-
applySelection(selMap) {
|
|
1700
|
-
this._selectionMap.set(selMap);
|
|
1701
|
-
this._initialSelection.set({ ...selMap });
|
|
1702
|
-
}
|
|
1703
|
-
buildPayloadItems() {
|
|
1704
|
-
const items = [];
|
|
1705
|
-
for (const action of this.pendingAdd()) {
|
|
1706
|
-
items.push({ id: action.id, action: 'add' });
|
|
1707
|
-
}
|
|
1708
|
-
for (const action of this.pendingRemove()) {
|
|
1709
|
-
items.push({ id: action.id, action: 'remove' });
|
|
1710
|
-
}
|
|
1711
|
-
return items;
|
|
1712
|
-
}
|
|
1713
|
-
resetState() {
|
|
1714
|
-
this._actionsTree.set([]);
|
|
1715
|
-
this._actions.set([]);
|
|
1716
|
-
this._selectionMap.set({});
|
|
1717
|
-
this._initialSelection.set({});
|
|
1718
|
-
}
|
|
1719
1586
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: RoleActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1720
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: RoleActionSelectorComponent, isStandalone: true, selector: "flusys-role-action-selector", ngImport: i0, template: `
|
|
1587
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: RoleActionSelectorComponent, isStandalone: true, selector: "flusys-role-action-selector", usesInheritance: true, ngImport: i0, template: `
|
|
1721
1588
|
<div class="role-action-selector">
|
|
1722
|
-
<!-- Role Selector -->
|
|
1723
1589
|
<div class="mb-4">
|
|
1724
1590
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
1725
1591
|
<div>
|
|
1726
|
-
<label class="block font-semibold mb-2">{{
|
|
1592
|
+
<label class="block font-semibold mb-2">{{
|
|
1593
|
+
'iam.permission.select.role' | translate
|
|
1594
|
+
}}</label>
|
|
1727
1595
|
<p-select
|
|
1728
1596
|
[ngModel]="selectedRoleId()"
|
|
1729
1597
|
(ngModelChange)="selectedRoleId.set($event)"
|
|
1730
1598
|
[options]="roles()"
|
|
1731
1599
|
optionLabel="name"
|
|
1732
1600
|
optionValue="id"
|
|
1733
|
-
[placeholder]="
|
|
1601
|
+
[placeholder]="
|
|
1602
|
+
'iam.permission.select.role.placeholder' | translate
|
|
1603
|
+
"
|
|
1734
1604
|
[showClear]="true"
|
|
1735
1605
|
[filter]="true"
|
|
1736
|
-
|
|
1606
|
+
class="w-full"
|
|
1737
1607
|
/>
|
|
1738
1608
|
</div>
|
|
1739
1609
|
</div>
|
|
1740
1610
|
</div>
|
|
1741
1611
|
|
|
1742
1612
|
@if (selectedRoleId()) {
|
|
1743
|
-
<!-- Loading State -->
|
|
1744
1613
|
@if (loading()) {
|
|
1745
1614
|
<div
|
|
1746
1615
|
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
@@ -1749,16 +1618,20 @@ class RoleActionSelectorComponent {
|
|
|
1749
1618
|
</div>
|
|
1750
1619
|
}
|
|
1751
1620
|
|
|
1752
|
-
<!-- Action List -->
|
|
1753
1621
|
@if (!loading() && actions().length > 0) {
|
|
1754
1622
|
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
1755
1623
|
<div
|
|
1756
1624
|
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
1757
1625
|
>
|
|
1758
1626
|
<div>
|
|
1759
|
-
<h5 class="m-0 mb-1">
|
|
1627
|
+
<h5 class="m-0 mb-1">
|
|
1628
|
+
{{ 'iam.permission.action.permissions' | translate }}
|
|
1629
|
+
</h5>
|
|
1760
1630
|
<p class="text-sm text-muted-color m-0">
|
|
1761
|
-
{{
|
|
1631
|
+
{{
|
|
1632
|
+
'iam.permission.actions.available'
|
|
1633
|
+
| translate: { count: actions().length }
|
|
1634
|
+
}}
|
|
1762
1635
|
</p>
|
|
1763
1636
|
</div>
|
|
1764
1637
|
<div class="flex flex-wrap gap-2">
|
|
@@ -1791,27 +1664,32 @@ class RoleActionSelectorComponent {
|
|
|
1791
1664
|
</div>
|
|
1792
1665
|
</div>
|
|
1793
1666
|
|
|
1794
|
-
<!-- Validation Warning -->
|
|
1795
1667
|
@if (invalidActionsCount() > 0) {
|
|
1796
1668
|
<div class="validation-warning rounded-border p-3 mb-4">
|
|
1797
1669
|
<div class="flex items-start gap-2">
|
|
1798
1670
|
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
1799
1671
|
<div class="flex-1">
|
|
1800
|
-
<span class="font-semibold"
|
|
1672
|
+
<span class="font-semibold"
|
|
1673
|
+
>{{ 'iam.validation.warning.title' | translate }}:</span
|
|
1674
|
+
>
|
|
1801
1675
|
<p class="text-sm mt-1 mb-0">
|
|
1802
|
-
{{
|
|
1676
|
+
{{
|
|
1677
|
+
(invalidActionsCount() > 1
|
|
1678
|
+
? 'iam.validation.unmet.prerequisites.plural'
|
|
1679
|
+
: 'iam.validation.unmet.prerequisites.singular'
|
|
1680
|
+
) | translate: { count: invalidActionsCount() }
|
|
1681
|
+
}}
|
|
1803
1682
|
</p>
|
|
1804
1683
|
</div>
|
|
1805
1684
|
</div>
|
|
1806
1685
|
</div>
|
|
1807
1686
|
}
|
|
1808
1687
|
|
|
1809
|
-
<!-- Action Tree Table -->
|
|
1810
1688
|
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
1811
1689
|
<p-treeTable
|
|
1812
1690
|
[value]="treeNodes()"
|
|
1813
1691
|
dataKey="id"
|
|
1814
|
-
|
|
1692
|
+
class="p-treetable-sm"
|
|
1815
1693
|
[tableStyle]="{ 'min-width': '50rem' }"
|
|
1816
1694
|
>
|
|
1817
1695
|
<ng-template #header>
|
|
@@ -1826,79 +1704,100 @@ class RoleActionSelectorComponent {
|
|
|
1826
1704
|
/>
|
|
1827
1705
|
</th>
|
|
1828
1706
|
<th>{{ 'shared.name' | translate }}</th>
|
|
1829
|
-
<th class="hidden md:table-cell">
|
|
1707
|
+
<th class="hidden md:table-cell">
|
|
1708
|
+
{{ 'shared.code' | translate }}
|
|
1709
|
+
</th>
|
|
1830
1710
|
<th>{{ 'iam.action.type' | translate }}</th>
|
|
1831
|
-
<th class="hidden lg:table-cell">
|
|
1711
|
+
<th class="hidden lg:table-cell">
|
|
1712
|
+
{{ 'iam.permission.requirements' | translate }}
|
|
1713
|
+
</th>
|
|
1714
|
+
</tr>
|
|
1715
|
+
</ng-template>
|
|
1716
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
1717
|
+
@let hasUnmet = hasUnmetPrerequisites(rowData);
|
|
1718
|
+
@let tooltip = getTooltip(rowData);
|
|
1719
|
+
<tr [class.highlight-warning]="hasUnmet">
|
|
1720
|
+
<td class="w-12">
|
|
1721
|
+
<p-checkbox
|
|
1722
|
+
[ngModel]="selectionMap()[rowData.id]"
|
|
1723
|
+
[binary]="true"
|
|
1724
|
+
(ngModelChange)="onActionToggle(rowData, $event)"
|
|
1725
|
+
[pTooltip]="tooltip"
|
|
1726
|
+
tooltipPosition="top"
|
|
1727
|
+
/>
|
|
1728
|
+
</td>
|
|
1729
|
+
<td>
|
|
1730
|
+
<p-treeTableToggler [rowNode]="rowNode" />
|
|
1731
|
+
<span class="inline-flex items-center gap-2">
|
|
1732
|
+
{{ rowData.name }}
|
|
1733
|
+
@if (hasUnmet) {
|
|
1734
|
+
<i
|
|
1735
|
+
class="pi pi-exclamation-triangle text-orange-500"
|
|
1736
|
+
[pTooltip]="
|
|
1737
|
+
'iam.validation.unmet.prerequisites.tooltip'
|
|
1738
|
+
| translate
|
|
1739
|
+
"
|
|
1740
|
+
tooltipPosition="top"
|
|
1741
|
+
></i>
|
|
1742
|
+
}
|
|
1743
|
+
</span>
|
|
1744
|
+
</td>
|
|
1745
|
+
<td class="hidden md:table-cell">
|
|
1746
|
+
{{ rowData.code || '-' }}
|
|
1747
|
+
</td>
|
|
1748
|
+
<td>
|
|
1749
|
+
<p-tag
|
|
1750
|
+
[value]="rowData.actionType"
|
|
1751
|
+
[severity]="
|
|
1752
|
+
rowData.actionType === 'backend' ? 'info' : 'success'
|
|
1753
|
+
"
|
|
1754
|
+
/>
|
|
1755
|
+
</td>
|
|
1756
|
+
<td class="hidden lg:table-cell">
|
|
1757
|
+
@if (rowData.permissionLogic) {
|
|
1758
|
+
<span class="text-sm text-muted-color">{{
|
|
1759
|
+
'iam.permission.has.prerequisites' | translate
|
|
1760
|
+
}}</span>
|
|
1761
|
+
} @else {
|
|
1762
|
+
<span class="text-muted-color">-</span>
|
|
1763
|
+
}
|
|
1764
|
+
</td>
|
|
1832
1765
|
</tr>
|
|
1833
1766
|
</ng-template>
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
tooltipPosition="top"
|
|
1843
|
-
/>
|
|
1844
|
-
</td>
|
|
1845
|
-
<td>
|
|
1846
|
-
<p-treeTableToggler [rowNode]="rowNode" />
|
|
1847
|
-
<span class="inline-flex items-center gap-2">
|
|
1848
|
-
{{ rowData.name }}
|
|
1849
|
-
@if (hasUnmetPrerequisites(rowData)) {
|
|
1850
|
-
<i
|
|
1851
|
-
class="pi pi-exclamation-triangle text-orange-500"
|
|
1852
|
-
[pTooltip]="'iam.validation.unmet.prerequisites.tooltip' | translate"
|
|
1853
|
-
tooltipPosition="top"
|
|
1854
|
-
></i>
|
|
1767
|
+
<ng-template #emptymessage>
|
|
1768
|
+
<tr>
|
|
1769
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
1770
|
+
@if (loading()) {
|
|
1771
|
+
<i class="pi pi-spin pi-spinner"></i>
|
|
1772
|
+
{{ 'shared.loading.actions' | translate }}
|
|
1773
|
+
} @else {
|
|
1774
|
+
{{ 'iam.permission.no.actions.available' | translate }}
|
|
1855
1775
|
}
|
|
1856
|
-
</
|
|
1857
|
-
</
|
|
1858
|
-
|
|
1859
|
-
<td>
|
|
1860
|
-
<p-tag
|
|
1861
|
-
[value]="rowData.actionType"
|
|
1862
|
-
[severity]="
|
|
1863
|
-
rowData.actionType === 'backend' ? 'info' : 'success'
|
|
1864
|
-
"
|
|
1865
|
-
/>
|
|
1866
|
-
</td>
|
|
1867
|
-
<td class="hidden lg:table-cell">
|
|
1868
|
-
@if (rowData.permissionLogic) {
|
|
1869
|
-
<span class="text-sm text-muted-color">{{ 'iam.permission.has.prerequisites' | translate }}</span>
|
|
1870
|
-
} @else {
|
|
1871
|
-
<span class="text-muted-color">-</span>
|
|
1872
|
-
}
|
|
1873
|
-
</td>
|
|
1874
|
-
</tr>
|
|
1875
|
-
</ng-template>
|
|
1876
|
-
<ng-template #emptymessage>
|
|
1877
|
-
<tr>
|
|
1878
|
-
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
1879
|
-
@if (loading()) {
|
|
1880
|
-
<i class="pi pi-spin pi-spinner"></i> {{ 'shared.loading.actions' | translate }}
|
|
1881
|
-
} @else { {{ 'iam.permission.no.actions.available' | translate }} }
|
|
1882
|
-
</td>
|
|
1883
|
-
</tr>
|
|
1884
|
-
</ng-template>
|
|
1776
|
+
</td>
|
|
1777
|
+
</tr>
|
|
1778
|
+
</ng-template>
|
|
1885
1779
|
</p-treeTable>
|
|
1886
1780
|
</div>
|
|
1887
1781
|
</div>
|
|
1888
1782
|
|
|
1889
|
-
<!-- Change Summary -->
|
|
1890
1783
|
@if (hasChanges()) {
|
|
1891
1784
|
<div class="border border-surface rounded-border p-3 mt-4">
|
|
1892
1785
|
<div class="flex items-center gap-2 mb-3">
|
|
1893
1786
|
<i class="pi pi-info-circle text-primary"></i>
|
|
1894
|
-
<span class="font-bold">{{
|
|
1787
|
+
<span class="font-bold">{{
|
|
1788
|
+
'shared.pending.changes' | translate
|
|
1789
|
+
}}</span>
|
|
1895
1790
|
</div>
|
|
1896
1791
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
1897
1792
|
@if (pendingAdd().length > 0) {
|
|
1898
1793
|
<div>
|
|
1899
1794
|
<div class="flex items-center gap-2 mb-2">
|
|
1900
1795
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
1901
|
-
<strong class="text-sm"
|
|
1796
|
+
<strong class="text-sm"
|
|
1797
|
+
>{{ 'shared.to.add' | translate }} ({{
|
|
1798
|
+
pendingAdd().length
|
|
1799
|
+
}})</strong
|
|
1800
|
+
>
|
|
1902
1801
|
</div>
|
|
1903
1802
|
<ul class="list-none p-0 m-0 pl-4">
|
|
1904
1803
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -1911,7 +1810,11 @@ class RoleActionSelectorComponent {
|
|
|
1911
1810
|
<div>
|
|
1912
1811
|
<div class="flex items-center gap-2 mb-2">
|
|
1913
1812
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
1914
|
-
<strong class="text-sm"
|
|
1813
|
+
<strong class="text-sm"
|
|
1814
|
+
>{{ 'shared.to.remove' | translate }} ({{
|
|
1815
|
+
pendingRemove().length
|
|
1816
|
+
}})</strong
|
|
1817
|
+
>
|
|
1915
1818
|
</div>
|
|
1916
1819
|
<ul class="list-none p-0 m-0 pl-4">
|
|
1917
1820
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -1927,7 +1830,9 @@ class RoleActionSelectorComponent {
|
|
|
1927
1830
|
|
|
1928
1831
|
@if (!loading() && actions().length === 0) {
|
|
1929
1832
|
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
1930
|
-
<i
|
|
1833
|
+
<i
|
|
1834
|
+
class="pi pi-info-circle text-muted-color mb-3 block text-5xl"
|
|
1835
|
+
></i>
|
|
1931
1836
|
<p class="text-muted-color m-0">
|
|
1932
1837
|
{{ 'iam.permission.no.actions.for.role' | translate }}
|
|
1933
1838
|
</p>
|
|
@@ -1935,34 +1840,36 @@ class RoleActionSelectorComponent {
|
|
|
1935
1840
|
}
|
|
1936
1841
|
}
|
|
1937
1842
|
</div>
|
|
1938
|
-
`, isInline: true, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"], dependencies: [{ kind: "ngmodule", type:
|
|
1843
|
+
`, isInline: true, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i4.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i5.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: i7.TreeTable, selector: "p-treeTable, p-treetable, p-tree-table", inputs: ["columns", "styleClass", "tableStyle", "tableStyleClass", "autoLayout", "lazy", "lazyLoadOnInit", "paginator", "rows", "first", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "customSort", "selectionMode", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "compareSelectionBy", "rowHover", "loading", "loadingIcon", "showLoader", "scrollable", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "frozenColumns", "resizableColumns", "columnResizeMode", "reorderableColumns", "contextMenu", "rowTrackBy", "filters", "globalFilterFields", "filterDelay", "filterMode", "filterLocale", "paginatorLocale", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "value", "virtualRowHeight", "selectionKeys", "showGridlines"], outputs: ["selectionChange", "contextMenuSelectionChange", "onFilter", "onNodeExpand", "onNodeCollapse", "onPage", "onSort", "onLazyLoad", "sortFunction", "onColResize", "onColReorder", "onNodeSelect", "onNodeUnselect", "onContextMenuSelect", "onHeaderCheckboxToggle", "onEditInit", "onEditComplete", "onEditCancel", "selectionKeysChange"] }, { kind: "component", type: i7.TreeTableToggler, selector: "p-treeTableToggler, p-treetabletoggler, p-treetable-toggler", inputs: ["rowNode"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1939
1844
|
}
|
|
1940
1845
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: RoleActionSelectorComponent, decorators: [{
|
|
1941
1846
|
type: Component,
|
|
1942
|
-
args: [{ selector: 'flusys-role-action-selector', imports: [
|
|
1847
|
+
args: [{ selector: 'flusys-role-action-selector', imports: [FormsModule, PrimeModule, HasPermissionDirective, TranslatePipe], template: `
|
|
1943
1848
|
<div class="role-action-selector">
|
|
1944
|
-
<!-- Role Selector -->
|
|
1945
1849
|
<div class="mb-4">
|
|
1946
1850
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
1947
1851
|
<div>
|
|
1948
|
-
<label class="block font-semibold mb-2">{{
|
|
1852
|
+
<label class="block font-semibold mb-2">{{
|
|
1853
|
+
'iam.permission.select.role' | translate
|
|
1854
|
+
}}</label>
|
|
1949
1855
|
<p-select
|
|
1950
1856
|
[ngModel]="selectedRoleId()"
|
|
1951
1857
|
(ngModelChange)="selectedRoleId.set($event)"
|
|
1952
1858
|
[options]="roles()"
|
|
1953
1859
|
optionLabel="name"
|
|
1954
1860
|
optionValue="id"
|
|
1955
|
-
[placeholder]="
|
|
1861
|
+
[placeholder]="
|
|
1862
|
+
'iam.permission.select.role.placeholder' | translate
|
|
1863
|
+
"
|
|
1956
1864
|
[showClear]="true"
|
|
1957
1865
|
[filter]="true"
|
|
1958
|
-
|
|
1866
|
+
class="w-full"
|
|
1959
1867
|
/>
|
|
1960
1868
|
</div>
|
|
1961
1869
|
</div>
|
|
1962
1870
|
</div>
|
|
1963
1871
|
|
|
1964
1872
|
@if (selectedRoleId()) {
|
|
1965
|
-
<!-- Loading State -->
|
|
1966
1873
|
@if (loading()) {
|
|
1967
1874
|
<div
|
|
1968
1875
|
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
@@ -1971,16 +1878,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
1971
1878
|
</div>
|
|
1972
1879
|
}
|
|
1973
1880
|
|
|
1974
|
-
<!-- Action List -->
|
|
1975
1881
|
@if (!loading() && actions().length > 0) {
|
|
1976
1882
|
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
1977
1883
|
<div
|
|
1978
1884
|
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
1979
1885
|
>
|
|
1980
1886
|
<div>
|
|
1981
|
-
<h5 class="m-0 mb-1">
|
|
1887
|
+
<h5 class="m-0 mb-1">
|
|
1888
|
+
{{ 'iam.permission.action.permissions' | translate }}
|
|
1889
|
+
</h5>
|
|
1982
1890
|
<p class="text-sm text-muted-color m-0">
|
|
1983
|
-
{{
|
|
1891
|
+
{{
|
|
1892
|
+
'iam.permission.actions.available'
|
|
1893
|
+
| translate: { count: actions().length }
|
|
1894
|
+
}}
|
|
1984
1895
|
</p>
|
|
1985
1896
|
</div>
|
|
1986
1897
|
<div class="flex flex-wrap gap-2">
|
|
@@ -2013,27 +1924,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2013
1924
|
</div>
|
|
2014
1925
|
</div>
|
|
2015
1926
|
|
|
2016
|
-
<!-- Validation Warning -->
|
|
2017
1927
|
@if (invalidActionsCount() > 0) {
|
|
2018
1928
|
<div class="validation-warning rounded-border p-3 mb-4">
|
|
2019
1929
|
<div class="flex items-start gap-2">
|
|
2020
1930
|
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
2021
1931
|
<div class="flex-1">
|
|
2022
|
-
<span class="font-semibold"
|
|
1932
|
+
<span class="font-semibold"
|
|
1933
|
+
>{{ 'iam.validation.warning.title' | translate }}:</span
|
|
1934
|
+
>
|
|
2023
1935
|
<p class="text-sm mt-1 mb-0">
|
|
2024
|
-
{{
|
|
1936
|
+
{{
|
|
1937
|
+
(invalidActionsCount() > 1
|
|
1938
|
+
? 'iam.validation.unmet.prerequisites.plural'
|
|
1939
|
+
: 'iam.validation.unmet.prerequisites.singular'
|
|
1940
|
+
) | translate: { count: invalidActionsCount() }
|
|
1941
|
+
}}
|
|
2025
1942
|
</p>
|
|
2026
1943
|
</div>
|
|
2027
1944
|
</div>
|
|
2028
1945
|
</div>
|
|
2029
1946
|
}
|
|
2030
1947
|
|
|
2031
|
-
<!-- Action Tree Table -->
|
|
2032
1948
|
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
2033
1949
|
<p-treeTable
|
|
2034
1950
|
[value]="treeNodes()"
|
|
2035
1951
|
dataKey="id"
|
|
2036
|
-
|
|
1952
|
+
class="p-treetable-sm"
|
|
2037
1953
|
[tableStyle]="{ 'min-width': '50rem' }"
|
|
2038
1954
|
>
|
|
2039
1955
|
<ng-template #header>
|
|
@@ -2048,79 +1964,100 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2048
1964
|
/>
|
|
2049
1965
|
</th>
|
|
2050
1966
|
<th>{{ 'shared.name' | translate }}</th>
|
|
2051
|
-
<th class="hidden md:table-cell">
|
|
1967
|
+
<th class="hidden md:table-cell">
|
|
1968
|
+
{{ 'shared.code' | translate }}
|
|
1969
|
+
</th>
|
|
2052
1970
|
<th>{{ 'iam.action.type' | translate }}</th>
|
|
2053
|
-
<th class="hidden lg:table-cell">
|
|
1971
|
+
<th class="hidden lg:table-cell">
|
|
1972
|
+
{{ 'iam.permission.requirements' | translate }}
|
|
1973
|
+
</th>
|
|
2054
1974
|
</tr>
|
|
2055
1975
|
</ng-template>
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
<
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
1976
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
1977
|
+
@let hasUnmet = hasUnmetPrerequisites(rowData);
|
|
1978
|
+
@let tooltip = getTooltip(rowData);
|
|
1979
|
+
<tr [class.highlight-warning]="hasUnmet">
|
|
1980
|
+
<td class="w-12">
|
|
1981
|
+
<p-checkbox
|
|
1982
|
+
[ngModel]="selectionMap()[rowData.id]"
|
|
1983
|
+
[binary]="true"
|
|
1984
|
+
(ngModelChange)="onActionToggle(rowData, $event)"
|
|
1985
|
+
[pTooltip]="tooltip"
|
|
1986
|
+
tooltipPosition="top"
|
|
1987
|
+
/>
|
|
1988
|
+
</td>
|
|
1989
|
+
<td>
|
|
1990
|
+
<p-treeTableToggler [rowNode]="rowNode" />
|
|
1991
|
+
<span class="inline-flex items-center gap-2">
|
|
1992
|
+
{{ rowData.name }}
|
|
1993
|
+
@if (hasUnmet) {
|
|
1994
|
+
<i
|
|
1995
|
+
class="pi pi-exclamation-triangle text-orange-500"
|
|
1996
|
+
[pTooltip]="
|
|
1997
|
+
'iam.validation.unmet.prerequisites.tooltip'
|
|
1998
|
+
| translate
|
|
1999
|
+
"
|
|
2000
|
+
tooltipPosition="top"
|
|
2001
|
+
></i>
|
|
2002
|
+
}
|
|
2003
|
+
</span>
|
|
2004
|
+
</td>
|
|
2005
|
+
<td class="hidden md:table-cell">
|
|
2006
|
+
{{ rowData.code || '-' }}
|
|
2007
|
+
</td>
|
|
2008
|
+
<td>
|
|
2009
|
+
<p-tag
|
|
2010
|
+
[value]="rowData.actionType"
|
|
2011
|
+
[severity]="
|
|
2012
|
+
rowData.actionType === 'backend' ? 'info' : 'success'
|
|
2013
|
+
"
|
|
2014
|
+
/>
|
|
2015
|
+
</td>
|
|
2016
|
+
<td class="hidden lg:table-cell">
|
|
2017
|
+
@if (rowData.permissionLogic) {
|
|
2018
|
+
<span class="text-sm text-muted-color">{{
|
|
2019
|
+
'iam.permission.has.prerequisites' | translate
|
|
2020
|
+
}}</span>
|
|
2021
|
+
} @else {
|
|
2022
|
+
<span class="text-muted-color">-</span>
|
|
2077
2023
|
}
|
|
2078
|
-
</
|
|
2079
|
-
</
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
<span class="text-muted-color">-</span>
|
|
2094
|
-
}
|
|
2095
|
-
</td>
|
|
2096
|
-
</tr>
|
|
2097
|
-
</ng-template>
|
|
2098
|
-
<ng-template #emptymessage>
|
|
2099
|
-
<tr>
|
|
2100
|
-
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
2101
|
-
@if (loading()) {
|
|
2102
|
-
<i class="pi pi-spin pi-spinner"></i> {{ 'shared.loading.actions' | translate }}
|
|
2103
|
-
} @else { {{ 'iam.permission.no.actions.available' | translate }} }
|
|
2104
|
-
</td>
|
|
2105
|
-
</tr>
|
|
2106
|
-
</ng-template>
|
|
2024
|
+
</td>
|
|
2025
|
+
</tr>
|
|
2026
|
+
</ng-template>
|
|
2027
|
+
<ng-template #emptymessage>
|
|
2028
|
+
<tr>
|
|
2029
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
2030
|
+
@if (loading()) {
|
|
2031
|
+
<i class="pi pi-spin pi-spinner"></i>
|
|
2032
|
+
{{ 'shared.loading.actions' | translate }}
|
|
2033
|
+
} @else {
|
|
2034
|
+
{{ 'iam.permission.no.actions.available' | translate }}
|
|
2035
|
+
}
|
|
2036
|
+
</td>
|
|
2037
|
+
</tr>
|
|
2038
|
+
</ng-template>
|
|
2107
2039
|
</p-treeTable>
|
|
2108
2040
|
</div>
|
|
2109
2041
|
</div>
|
|
2110
2042
|
|
|
2111
|
-
<!-- Change Summary -->
|
|
2112
2043
|
@if (hasChanges()) {
|
|
2113
2044
|
<div class="border border-surface rounded-border p-3 mt-4">
|
|
2114
2045
|
<div class="flex items-center gap-2 mb-3">
|
|
2115
2046
|
<i class="pi pi-info-circle text-primary"></i>
|
|
2116
|
-
<span class="font-bold">{{
|
|
2047
|
+
<span class="font-bold">{{
|
|
2048
|
+
'shared.pending.changes' | translate
|
|
2049
|
+
}}</span>
|
|
2117
2050
|
</div>
|
|
2118
2051
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
2119
2052
|
@if (pendingAdd().length > 0) {
|
|
2120
2053
|
<div>
|
|
2121
2054
|
<div class="flex items-center gap-2 mb-2">
|
|
2122
2055
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
2123
|
-
<strong class="text-sm"
|
|
2056
|
+
<strong class="text-sm"
|
|
2057
|
+
>{{ 'shared.to.add' | translate }} ({{
|
|
2058
|
+
pendingAdd().length
|
|
2059
|
+
}})</strong
|
|
2060
|
+
>
|
|
2124
2061
|
</div>
|
|
2125
2062
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2126
2063
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -2133,7 +2070,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2133
2070
|
<div>
|
|
2134
2071
|
<div class="flex items-center gap-2 mb-2">
|
|
2135
2072
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
2136
|
-
<strong class="text-sm"
|
|
2073
|
+
<strong class="text-sm"
|
|
2074
|
+
>{{ 'shared.to.remove' | translate }} ({{
|
|
2075
|
+
pendingRemove().length
|
|
2076
|
+
}})</strong
|
|
2077
|
+
>
|
|
2137
2078
|
</div>
|
|
2138
2079
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2139
2080
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -2149,7 +2090,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2149
2090
|
|
|
2150
2091
|
@if (!loading() && actions().length === 0) {
|
|
2151
2092
|
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
2152
|
-
<i
|
|
2093
|
+
<i
|
|
2094
|
+
class="pi pi-info-circle text-muted-color mb-3 block text-5xl"
|
|
2095
|
+
></i>
|
|
2153
2096
|
<p class="text-muted-color m-0">
|
|
2154
2097
|
{{ 'iam.permission.no.actions.for.role' | translate }}
|
|
2155
2098
|
</p>
|
|
@@ -2160,140 +2103,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2160
2103
|
`, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"] }]
|
|
2161
2104
|
}], ctorParameters: () => [] });
|
|
2162
2105
|
|
|
2163
|
-
|
|
2164
|
-
* Company-Action Selector Component
|
|
2165
|
-
*
|
|
2166
|
-
* Manages company action whitelisting with hierarchical tree view and intelligent dependency management.
|
|
2167
|
-
*
|
|
2168
|
-
* **Architecture:**
|
|
2169
|
-
* - Presentation: TreeTable for hierarchical display
|
|
2170
|
-
* - Business Logic: Flat action list for selection/validation
|
|
2171
|
-
* - State: Signals with computed dependencies
|
|
2172
|
-
*
|
|
2173
|
-
* **Core Features:**
|
|
2174
|
-
* - Company dropdown with async loading
|
|
2175
|
-
* - Hierarchical TreeTable with expand/collapse
|
|
2176
|
-
* - Action whitelist management (add/remove)
|
|
2177
|
-
* - Real-time change tracking
|
|
2178
|
-
*
|
|
2179
|
-
* **Dependency Management:**
|
|
2180
|
-
* - Auto-selection with AND/OR logic
|
|
2181
|
-
* - Dependency detection on uncheck
|
|
2182
|
-
* - Cascade remove for AND dependencies
|
|
2183
|
-
* - Visual warnings for unmet prerequisites
|
|
2184
|
-
* - Pre-save validation with auto-fix
|
|
2185
|
-
*
|
|
2186
|
-
* **Performance:**
|
|
2187
|
-
* - Computed signals for reactive updates
|
|
2188
|
-
* - Memoized prerequisite validation
|
|
2189
|
-
* - AbortController for request cancellation
|
|
2190
|
-
*
|
|
2191
|
-
* @example
|
|
2192
|
-
* ```html
|
|
2193
|
-
* <flusys-company-action-selector />
|
|
2194
|
-
* ```
|
|
2195
|
-
*/
|
|
2196
|
-
class CompanyActionSelectorComponent {
|
|
2197
|
-
// Permission constants for template
|
|
2106
|
+
class CompanyActionSelectorComponent extends BaseActionSelectorComponent {
|
|
2198
2107
|
COMPANY_ACTION_PERMISSIONS = COMPANY_ACTION_PERMISSIONS;
|
|
2199
|
-
// Dependencies
|
|
2200
2108
|
companyApiProvider = inject(COMPANY_API_PROVIDER);
|
|
2201
2109
|
actionApi = inject(ActionApiService);
|
|
2202
2110
|
permissionApi = inject(PermissionApiService);
|
|
2203
2111
|
messageService = inject(MessageService);
|
|
2204
2112
|
confirmationService = inject(ConfirmationService);
|
|
2205
|
-
permissionLogic = inject(ActionPermissionLogicService);
|
|
2206
2113
|
destroyRef = inject(DestroyRef);
|
|
2207
|
-
translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
|
|
2208
|
-
translate(key, vars) {
|
|
2209
|
-
return this.translateAdapter?.translate(key, vars) ?? key;
|
|
2210
|
-
}
|
|
2211
|
-
// State - Company Selection
|
|
2212
2114
|
selectedCompanyId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedCompanyId" }] : []));
|
|
2213
2115
|
companies = signal([], ...(ngDevMode ? [{ debugName: "companies" }] : []));
|
|
2214
|
-
// State - Loading/Saving
|
|
2215
|
-
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
2216
|
-
saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : []));
|
|
2217
|
-
// State - Actions (Business Logic)
|
|
2218
|
-
_actions = signal([], ...(ngDevMode ? [{ debugName: "_actions" }] : []));
|
|
2219
|
-
actions = this._actions.asReadonly();
|
|
2220
|
-
// State - Actions (Display)
|
|
2221
|
-
_actionsTree = signal([], ...(ngDevMode ? [{ debugName: "_actionsTree" }] : []));
|
|
2222
|
-
actionsTree = this._actionsTree.asReadonly();
|
|
2223
|
-
// State - Selection
|
|
2224
|
-
_selectionMap = signal({}, ...(ngDevMode ? [{ debugName: "_selectionMap" }] : []));
|
|
2225
|
-
selectionMap = this._selectionMap.asReadonly();
|
|
2226
|
-
_initialSelection = signal({}, ...(ngDevMode ? [{ debugName: "_initialSelection" }] : []));
|
|
2227
|
-
initialSelection = this._initialSelection.asReadonly();
|
|
2228
|
-
// Computed - Tree Nodes (for PrimeNG TreeTable)
|
|
2229
|
-
treeNodes = computed(() => convertActionToTreeNode(this.actionsTree()), ...(ngDevMode ? [{ debugName: "treeNodes" }] : []));
|
|
2230
|
-
// Computed - Selection State
|
|
2231
|
-
allSelected = computed(() => {
|
|
2232
|
-
const selMap = this.selectionMap();
|
|
2233
|
-
const total = this.actions().length;
|
|
2234
|
-
if (total === 0)
|
|
2235
|
-
return false;
|
|
2236
|
-
const selected = Object.values(selMap).filter((v) => v).length;
|
|
2237
|
-
return selected === total;
|
|
2238
|
-
}, ...(ngDevMode ? [{ debugName: "allSelected" }] : []));
|
|
2239
|
-
// Computed - Change Detection
|
|
2240
|
-
hasChanges = computed(() => {
|
|
2241
|
-
const current = this.selectionMap();
|
|
2242
|
-
const initial = this.initialSelection();
|
|
2243
|
-
const currentKeys = Object.keys(current);
|
|
2244
|
-
const initialKeys = Object.keys(initial);
|
|
2245
|
-
if (currentKeys.length !== initialKeys.length)
|
|
2246
|
-
return true;
|
|
2247
|
-
return currentKeys.some((key) => current[key] !== initial[key]);
|
|
2248
|
-
}, ...(ngDevMode ? [{ debugName: "hasChanges" }] : []));
|
|
2249
|
-
// Computed - Validation
|
|
2250
|
-
actionsWithUnmetPrerequisites = computed(() => {
|
|
2251
|
-
const selMap = this.selectionMap();
|
|
2252
|
-
const allActions = this.actions();
|
|
2253
|
-
const unmetSet = new Set();
|
|
2254
|
-
for (const action of allActions) {
|
|
2255
|
-
if (action.id && this.permissionLogic.hasUnmetPrerequisites(action, selMap, allActions)) {
|
|
2256
|
-
unmetSet.add(action.id);
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
|
-
return unmetSet;
|
|
2260
|
-
}, ...(ngDevMode ? [{ debugName: "actionsWithUnmetPrerequisites" }] : []));
|
|
2261
|
-
invalidActionsCount = computed(() => this.actionsWithUnmetPrerequisites().size, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
|
|
2262
|
-
// Computed - Pending Changes
|
|
2263
|
-
pendingAdd = computed(() => {
|
|
2264
|
-
const current = this.selectionMap();
|
|
2265
|
-
const initial = this.initialSelection();
|
|
2266
|
-
const allActions = this.actions();
|
|
2267
|
-
return allActions.filter((action) => current[action.id] && !initial[action.id]);
|
|
2268
|
-
}, ...(ngDevMode ? [{ debugName: "pendingAdd" }] : []));
|
|
2269
|
-
pendingRemove = computed(() => {
|
|
2270
|
-
const current = this.selectionMap();
|
|
2271
|
-
const initial = this.initialSelection();
|
|
2272
|
-
const allActions = this.actions();
|
|
2273
|
-
return allActions.filter((action) => !current[action.id] && initial[action.id]);
|
|
2274
|
-
}, ...(ngDevMode ? [{ debugName: "pendingRemove" }] : []));
|
|
2275
|
-
// Computed - Save Button State
|
|
2276
|
-
canSave = computed(() => {
|
|
2277
|
-
return this.hasChanges() && !this.saving() && this.invalidActionsCount() === 0;
|
|
2278
|
-
}, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
|
|
2279
|
-
// AbortController for data loading
|
|
2280
2116
|
loadDataAbortController = null;
|
|
2281
2117
|
constructor() {
|
|
2282
|
-
|
|
2118
|
+
super();
|
|
2283
2119
|
this.destroyRef.onDestroy(() => {
|
|
2284
2120
|
this.loadDataAbortController?.abort();
|
|
2285
2121
|
});
|
|
2286
2122
|
this.loadCompanies();
|
|
2287
|
-
// Effect: Load data when company selection changes
|
|
2288
2123
|
effect(() => {
|
|
2289
2124
|
const companyId = this.selectedCompanyId();
|
|
2290
|
-
// Abort previous request
|
|
2291
2125
|
this.loadDataAbortController?.abort();
|
|
2292
2126
|
if (companyId) {
|
|
2293
2127
|
this.loadDataAbortController = new AbortController();
|
|
2294
2128
|
this.loadData(this.loadDataAbortController.signal).catch((err) => {
|
|
2295
2129
|
if (err.name !== 'AbortError') {
|
|
2296
|
-
// Error toast handled by global interceptor
|
|
2297
2130
|
this.loading.set(false);
|
|
2298
2131
|
}
|
|
2299
2132
|
});
|
|
@@ -2303,9 +2136,12 @@ class CompanyActionSelectorComponent {
|
|
|
2303
2136
|
}
|
|
2304
2137
|
});
|
|
2305
2138
|
}
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2139
|
+
getDefaultTooltip(isSelected) {
|
|
2140
|
+
if (isSelected) {
|
|
2141
|
+
return `[${this.translate('iam.tooltip.selected')}] ${this.translate('iam.tooltip.selected')}\n\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.click.to.remove')}`;
|
|
2142
|
+
}
|
|
2143
|
+
return `[${this.translate('shared.add')}] ${this.translate('iam.tooltip.click.to.add')}`;
|
|
2144
|
+
}
|
|
2309
2145
|
async loadCompanies() {
|
|
2310
2146
|
try {
|
|
2311
2147
|
const response = await firstValueFrom(this.companyApiProvider.getCompanies({
|
|
@@ -2315,29 +2151,21 @@ class CompanyActionSelectorComponent {
|
|
|
2315
2151
|
this.companies.set(response?.data ?? []);
|
|
2316
2152
|
}
|
|
2317
2153
|
catch {
|
|
2318
|
-
// Error toast handled by global interceptor
|
|
2319
2154
|
}
|
|
2320
2155
|
}
|
|
2321
|
-
/**
|
|
2322
|
-
* Load actions and assignments for selected company
|
|
2323
|
-
*/
|
|
2324
2156
|
async loadData(signal) {
|
|
2325
2157
|
const companyId = this.selectedCompanyId();
|
|
2326
2158
|
if (!companyId)
|
|
2327
2159
|
return;
|
|
2328
2160
|
this.loading.set(true);
|
|
2329
2161
|
try {
|
|
2330
|
-
// Fetch hierarchical action tree
|
|
2331
2162
|
const actionsResponse = await firstValueFrom(this.actionApi.getTree(undefined, undefined, false));
|
|
2332
2163
|
if (signal.aborted)
|
|
2333
2164
|
return;
|
|
2334
2165
|
const actionsTree = actionsResponse?.data ?? [];
|
|
2335
|
-
// Flatten tree for business logic
|
|
2336
2166
|
const actionsList = flattenTree(actionsTree).filter((a) => !!a.id);
|
|
2337
|
-
// Store both representations
|
|
2338
2167
|
this._actionsTree.set(actionsTree);
|
|
2339
2168
|
this._actions.set(actionsList);
|
|
2340
|
-
// Load existing assignments
|
|
2341
2169
|
const assignedResponse = await firstValueFrom(this.permissionApi.getCompanyActions(companyId));
|
|
2342
2170
|
if (signal.aborted)
|
|
2343
2171
|
return;
|
|
@@ -2352,67 +2180,10 @@ class CompanyActionSelectorComponent {
|
|
|
2352
2180
|
}
|
|
2353
2181
|
}
|
|
2354
2182
|
}
|
|
2355
|
-
/**
|
|
2356
|
-
* Check if action has unmet prerequisites
|
|
2357
|
-
*/
|
|
2358
|
-
hasUnmetPrerequisites(action) {
|
|
2359
|
-
return action.id
|
|
2360
|
-
? this.actionsWithUnmetPrerequisites().has(action.id)
|
|
2361
|
-
: false;
|
|
2362
|
-
}
|
|
2363
|
-
/**
|
|
2364
|
-
* Get tooltip for action checkbox
|
|
2365
|
-
*/
|
|
2366
|
-
getTooltip(action) {
|
|
2367
|
-
const selMap = this.selectionMap();
|
|
2368
|
-
const isCurrentlySelected = selMap[action.id];
|
|
2369
|
-
if (action.permissionLogic) {
|
|
2370
|
-
const prerequisiteInfo = this.permissionLogic.getPrerequisiteTooltip(action, selMap, this.actions());
|
|
2371
|
-
if (prerequisiteInfo) {
|
|
2372
|
-
const actionHint = isCurrentlySelected
|
|
2373
|
-
? `\n\n---\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.remove.action')}`
|
|
2374
|
-
: `\n\n---\n[${this.translate('shared.add')}] ${this.translate('iam.tooltip.add.action')}`;
|
|
2375
|
-
return `${prerequisiteInfo}${actionHint}`;
|
|
2376
|
-
}
|
|
2377
|
-
}
|
|
2378
|
-
if (isCurrentlySelected) {
|
|
2379
|
-
return `[${this.translate('iam.tooltip.selected')}] ${this.translate('iam.tooltip.selected')}\n\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.click.to.remove')}`;
|
|
2380
|
-
}
|
|
2381
|
-
return `[${this.translate('shared.add')}] ${this.translate('iam.tooltip.click.to.add')}`;
|
|
2382
|
-
}
|
|
2383
|
-
/**
|
|
2384
|
-
* Handle action checkbox toggle
|
|
2385
|
-
*/
|
|
2386
|
-
onActionToggle(action, newValue) {
|
|
2387
|
-
if (!newValue) {
|
|
2388
|
-
// Unchecking - validate dependencies
|
|
2389
|
-
this.permissionLogic.handleUncheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
|
|
2390
|
-
}
|
|
2391
|
-
else {
|
|
2392
|
-
// Checking - validate prerequisites
|
|
2393
|
-
this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2396
|
-
toggleAll() {
|
|
2397
|
-
this.setAllSelection(!this.allSelected());
|
|
2398
|
-
}
|
|
2399
|
-
selectAll() {
|
|
2400
|
-
this.setAllSelection(true);
|
|
2401
|
-
}
|
|
2402
|
-
deselectAll() {
|
|
2403
|
-
this.setAllSelection(false);
|
|
2404
|
-
}
|
|
2405
|
-
setAllSelection(value) {
|
|
2406
|
-
this._selectionMap.set(this.buildSelectionMap(this.actions(), () => value));
|
|
2407
|
-
}
|
|
2408
|
-
/**
|
|
2409
|
-
* Save changes to backend
|
|
2410
|
-
*/
|
|
2411
2183
|
async saveChanges() {
|
|
2412
2184
|
const companyId = this.selectedCompanyId();
|
|
2413
2185
|
if (!companyId)
|
|
2414
2186
|
return;
|
|
2415
|
-
// Pre-save validation
|
|
2416
2187
|
const invalidActions = this.permissionLogic.getActionsWithUnmetPrerequisites(this.selectionMap(), this.actions());
|
|
2417
2188
|
if (invalidActions.length > 0) {
|
|
2418
2189
|
this.permissionLogic.showValidationErrorDialog(invalidActions, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
|
|
@@ -2423,11 +2194,7 @@ class CompanyActionSelectorComponent {
|
|
|
2423
2194
|
return;
|
|
2424
2195
|
this.saving.set(true);
|
|
2425
2196
|
try {
|
|
2426
|
-
const response = await firstValueFrom(this.permissionApi.assignCompanyActions({
|
|
2427
|
-
companyId,
|
|
2428
|
-
items,
|
|
2429
|
-
}));
|
|
2430
|
-
// Handle backend prerequisite errors
|
|
2197
|
+
const response = await firstValueFrom(this.permissionApi.assignCompanyActions({ companyId, items }));
|
|
2431
2198
|
if (response?.data?.prerequisiteErrors &&
|
|
2432
2199
|
response.data.prerequisiteErrors.length > 0) {
|
|
2433
2200
|
this.handleBackendPrerequisiteErrors(response.data.prerequisiteErrors);
|
|
@@ -2436,22 +2203,18 @@ class CompanyActionSelectorComponent {
|
|
|
2436
2203
|
this.messageService.add({
|
|
2437
2204
|
severity: 'success',
|
|
2438
2205
|
summary: this.translate('shared.success'),
|
|
2439
|
-
detail: response?.
|
|
2206
|
+
detail: response?.messageKey
|
|
2207
|
+
? this.translate(response.messageKey, response.messageVariables)
|
|
2208
|
+
: (response?.message ?? ''),
|
|
2440
2209
|
});
|
|
2441
|
-
// Update baseline
|
|
2442
2210
|
this._initialSelection.set({ ...this.selectionMap() });
|
|
2443
2211
|
}
|
|
2444
2212
|
catch {
|
|
2445
|
-
// Error toast handled by global interceptor
|
|
2446
2213
|
}
|
|
2447
2214
|
finally {
|
|
2448
2215
|
this.saving.set(false);
|
|
2449
2216
|
}
|
|
2450
2217
|
}
|
|
2451
|
-
/**
|
|
2452
|
-
* Handle backend prerequisite validation errors
|
|
2453
|
-
* Shows confirmation dialog with auto-fix option
|
|
2454
|
-
*/
|
|
2455
2218
|
handleBackendPrerequisiteErrors(prerequisiteErrors) {
|
|
2456
2219
|
const errorList = prerequisiteErrors
|
|
2457
2220
|
.map((error) => {
|
|
@@ -2470,7 +2233,6 @@ class CompanyActionSelectorComponent {
|
|
|
2470
2233
|
acceptIcon: 'pi pi-check',
|
|
2471
2234
|
rejectIcon: 'pi pi-times',
|
|
2472
2235
|
accept: () => {
|
|
2473
|
-
// Auto-select missing prerequisites
|
|
2474
2236
|
const selMap = { ...this.selectionMap() };
|
|
2475
2237
|
prerequisiteErrors.forEach((error) => {
|
|
2476
2238
|
error.requiredActions.forEach((a) => {
|
|
@@ -2485,7 +2247,6 @@ class CompanyActionSelectorComponent {
|
|
|
2485
2247
|
});
|
|
2486
2248
|
},
|
|
2487
2249
|
reject: () => {
|
|
2488
|
-
// Revert to initial state
|
|
2489
2250
|
this._selectionMap.set({ ...this.initialSelection() });
|
|
2490
2251
|
this.messageService.add({
|
|
2491
2252
|
severity: 'info',
|
|
@@ -2495,54 +2256,33 @@ class CompanyActionSelectorComponent {
|
|
|
2495
2256
|
},
|
|
2496
2257
|
});
|
|
2497
2258
|
}
|
|
2498
|
-
resetState() {
|
|
2499
|
-
this._actionsTree.set([]);
|
|
2500
|
-
this._actions.set([]);
|
|
2501
|
-
this._selectionMap.set({});
|
|
2502
|
-
this._initialSelection.set({});
|
|
2503
|
-
}
|
|
2504
|
-
buildSelectionMap(actions, predicate) {
|
|
2505
|
-
const selMap = {};
|
|
2506
|
-
for (const action of actions) {
|
|
2507
|
-
selMap[action.id] = predicate(action);
|
|
2508
|
-
}
|
|
2509
|
-
return selMap;
|
|
2510
|
-
}
|
|
2511
|
-
buildPayloadItems() {
|
|
2512
|
-
const items = [];
|
|
2513
|
-
for (const action of this.pendingAdd()) {
|
|
2514
|
-
items.push({ id: action.id, action: 'add' });
|
|
2515
|
-
}
|
|
2516
|
-
for (const action of this.pendingRemove()) {
|
|
2517
|
-
items.push({ id: action.id, action: 'remove' });
|
|
2518
|
-
}
|
|
2519
|
-
return items;
|
|
2520
|
-
}
|
|
2521
2259
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CompanyActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2522
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: CompanyActionSelectorComponent, isStandalone: true, selector: "flusys-company-action-selector", ngImport: i0, template: `
|
|
2260
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: CompanyActionSelectorComponent, isStandalone: true, selector: "flusys-company-action-selector", usesInheritance: true, ngImport: i0, template: `
|
|
2523
2261
|
<div class="company-action-selector">
|
|
2524
|
-
<!-- Company Selector -->
|
|
2525
2262
|
<div class="mb-4">
|
|
2526
2263
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
2527
2264
|
<div>
|
|
2528
|
-
<label class="block font-semibold mb-2">{{
|
|
2265
|
+
<label class="block font-semibold mb-2">{{
|
|
2266
|
+
'iam.permission.select.company' | translate
|
|
2267
|
+
}}</label>
|
|
2529
2268
|
<p-select
|
|
2530
2269
|
[ngModel]="selectedCompanyId()"
|
|
2531
2270
|
(ngModelChange)="selectedCompanyId.set($event)"
|
|
2532
2271
|
[options]="companies()"
|
|
2533
2272
|
optionLabel="name"
|
|
2534
2273
|
optionValue="id"
|
|
2535
|
-
[placeholder]="
|
|
2274
|
+
[placeholder]="
|
|
2275
|
+
'iam.permission.select.company.placeholder' | translate
|
|
2276
|
+
"
|
|
2536
2277
|
[showClear]="true"
|
|
2537
2278
|
[filter]="true"
|
|
2538
|
-
|
|
2279
|
+
class="w-full"
|
|
2539
2280
|
/>
|
|
2540
2281
|
</div>
|
|
2541
2282
|
</div>
|
|
2542
2283
|
</div>
|
|
2543
2284
|
|
|
2544
2285
|
@if (selectedCompanyId()) {
|
|
2545
|
-
<!-- Loading State -->
|
|
2546
2286
|
@if (loading()) {
|
|
2547
2287
|
<div
|
|
2548
2288
|
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
@@ -2551,16 +2291,20 @@ class CompanyActionSelectorComponent {
|
|
|
2551
2291
|
</div>
|
|
2552
2292
|
}
|
|
2553
2293
|
|
|
2554
|
-
<!-- Action List -->
|
|
2555
2294
|
@if (!loading() && actions().length > 0) {
|
|
2556
2295
|
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
2557
2296
|
<div
|
|
2558
2297
|
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
2559
2298
|
>
|
|
2560
2299
|
<div>
|
|
2561
|
-
<h5 class="m-0 mb-1">
|
|
2300
|
+
<h5 class="m-0 mb-1">
|
|
2301
|
+
{{ 'iam.permission.action.whitelist' | translate }}
|
|
2302
|
+
</h5>
|
|
2562
2303
|
<p class="text-sm text-muted-color m-0">
|
|
2563
|
-
{{
|
|
2304
|
+
{{
|
|
2305
|
+
'iam.permission.actions.available'
|
|
2306
|
+
| translate: { count: actions().length }
|
|
2307
|
+
}}
|
|
2564
2308
|
</p>
|
|
2565
2309
|
</div>
|
|
2566
2310
|
<div class="flex flex-wrap gap-2">
|
|
@@ -2593,27 +2337,32 @@ class CompanyActionSelectorComponent {
|
|
|
2593
2337
|
</div>
|
|
2594
2338
|
</div>
|
|
2595
2339
|
|
|
2596
|
-
<!-- Validation Warning -->
|
|
2597
2340
|
@if (invalidActionsCount() > 0) {
|
|
2598
2341
|
<div class="validation-warning rounded-border p-3 mb-4">
|
|
2599
2342
|
<div class="flex items-start gap-2">
|
|
2600
2343
|
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
2601
2344
|
<div class="flex-1">
|
|
2602
|
-
<span class="font-semibold"
|
|
2345
|
+
<span class="font-semibold"
|
|
2346
|
+
>{{ 'iam.validation.warning.title' | translate }}:</span
|
|
2347
|
+
>
|
|
2603
2348
|
<p class="text-sm mt-1 mb-0">
|
|
2604
|
-
{{
|
|
2349
|
+
{{
|
|
2350
|
+
(invalidActionsCount() > 1
|
|
2351
|
+
? 'iam.validation.unmet.prerequisites.plural'
|
|
2352
|
+
: 'iam.validation.unmet.prerequisites.singular'
|
|
2353
|
+
) | translate: { count: invalidActionsCount() }
|
|
2354
|
+
}}
|
|
2605
2355
|
</p>
|
|
2606
2356
|
</div>
|
|
2607
2357
|
</div>
|
|
2608
2358
|
</div>
|
|
2609
2359
|
}
|
|
2610
2360
|
|
|
2611
|
-
<!-- Action Tree Table -->
|
|
2612
2361
|
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
2613
2362
|
<p-treeTable
|
|
2614
2363
|
[value]="treeNodes()"
|
|
2615
2364
|
dataKey="id"
|
|
2616
|
-
|
|
2365
|
+
class="p-treetable-sm"
|
|
2617
2366
|
[tableStyle]="{ 'min-width': '50rem' }"
|
|
2618
2367
|
>
|
|
2619
2368
|
<ng-template #header>
|
|
@@ -2628,73 +2377,93 @@ class CompanyActionSelectorComponent {
|
|
|
2628
2377
|
/>
|
|
2629
2378
|
</th>
|
|
2630
2379
|
<th>{{ 'shared.name' | translate }}</th>
|
|
2631
|
-
<th class="hidden md:table-cell">
|
|
2380
|
+
<th class="hidden md:table-cell">
|
|
2381
|
+
{{ 'shared.code' | translate }}
|
|
2382
|
+
</th>
|
|
2632
2383
|
<th>{{ 'iam.action.type' | translate }}</th>
|
|
2633
|
-
<th class="hidden lg:table-cell">
|
|
2384
|
+
<th class="hidden lg:table-cell">
|
|
2385
|
+
{{ 'shared.description' | translate }}
|
|
2386
|
+
</th>
|
|
2634
2387
|
</tr>
|
|
2635
2388
|
</ng-template>
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
<
|
|
2639
|
-
<
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
<
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2389
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
2390
|
+
@let hasUnmet = hasUnmetPrerequisites(rowData);
|
|
2391
|
+
<tr [class.highlight-warning]="hasUnmet">
|
|
2392
|
+
<td class="w-12">
|
|
2393
|
+
<p-checkbox
|
|
2394
|
+
[ngModel]="selectionMap()[rowData.id]"
|
|
2395
|
+
[binary]="true"
|
|
2396
|
+
(ngModelChange)="onActionToggle(rowData, $event)"
|
|
2397
|
+
[pTooltip]="getTooltip(rowData)"
|
|
2398
|
+
tooltipPosition="top"
|
|
2399
|
+
/>
|
|
2400
|
+
</td>
|
|
2401
|
+
<td>
|
|
2402
|
+
<p-treeTableToggler [rowNode]="rowNode" />
|
|
2403
|
+
<span class="inline-flex items-center gap-2">
|
|
2404
|
+
{{ rowData.name }}
|
|
2405
|
+
@if (hasUnmet) {
|
|
2406
|
+
<i
|
|
2407
|
+
class="pi pi-exclamation-triangle text-orange-500"
|
|
2408
|
+
[pTooltip]="
|
|
2409
|
+
'iam.validation.unmet.prerequisites.tooltip'
|
|
2410
|
+
| translate
|
|
2411
|
+
"
|
|
2412
|
+
tooltipPosition="top"
|
|
2413
|
+
></i>
|
|
2414
|
+
}
|
|
2415
|
+
</span>
|
|
2416
|
+
</td>
|
|
2417
|
+
<td class="hidden md:table-cell">
|
|
2418
|
+
{{ rowData.code || '-' }}
|
|
2419
|
+
</td>
|
|
2420
|
+
<td>
|
|
2421
|
+
<p-tag
|
|
2422
|
+
[value]="rowData.actionType"
|
|
2423
|
+
[severity]="
|
|
2424
|
+
rowData.actionType === 'backend' ? 'info' : 'success'
|
|
2425
|
+
"
|
|
2426
|
+
/>
|
|
2427
|
+
</td>
|
|
2428
|
+
<td class="hidden lg:table-cell">
|
|
2429
|
+
{{ rowData.description || '-' }}
|
|
2430
|
+
</td>
|
|
2431
|
+
</tr>
|
|
2432
|
+
</ng-template>
|
|
2433
|
+
<ng-template #emptymessage>
|
|
2434
|
+
<tr>
|
|
2435
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
2436
|
+
@if (loading()) {
|
|
2437
|
+
<i class="pi pi-spin pi-spinner"></i>
|
|
2438
|
+
{{ 'shared.loading.actions' | translate }}
|
|
2439
|
+
} @else {
|
|
2440
|
+
{{ 'iam.permission.no.actions.available' | translate }}
|
|
2657
2441
|
}
|
|
2658
|
-
</
|
|
2659
|
-
</
|
|
2660
|
-
|
|
2661
|
-
<td>
|
|
2662
|
-
<p-tag
|
|
2663
|
-
[value]="rowData.actionType"
|
|
2664
|
-
[severity]="
|
|
2665
|
-
rowData.actionType === 'backend' ? 'info' : 'success'
|
|
2666
|
-
"
|
|
2667
|
-
/>
|
|
2668
|
-
</td>
|
|
2669
|
-
<td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
|
|
2670
|
-
</tr>
|
|
2671
|
-
</ng-template>
|
|
2672
|
-
<ng-template #emptymessage>
|
|
2673
|
-
<tr>
|
|
2674
|
-
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
2675
|
-
@if (loading()) {
|
|
2676
|
-
<i class="pi pi-spin pi-spinner"></i> {{ 'shared.loading.actions' | translate }}
|
|
2677
|
-
} @else { {{ 'iam.permission.no.actions.available' | translate }} }
|
|
2678
|
-
</td>
|
|
2679
|
-
</tr>
|
|
2680
|
-
</ng-template>
|
|
2442
|
+
</td>
|
|
2443
|
+
</tr>
|
|
2444
|
+
</ng-template>
|
|
2681
2445
|
</p-treeTable>
|
|
2682
2446
|
</div>
|
|
2683
2447
|
</div>
|
|
2684
2448
|
|
|
2685
|
-
<!-- Change Summary -->
|
|
2686
2449
|
@if (hasChanges()) {
|
|
2687
2450
|
<div class="border border-surface rounded-border p-3 mt-4">
|
|
2688
2451
|
<div class="flex items-center gap-2 mb-3">
|
|
2689
2452
|
<i class="pi pi-info-circle text-primary"></i>
|
|
2690
|
-
<span class="font-bold">{{
|
|
2453
|
+
<span class="font-bold">{{
|
|
2454
|
+
'shared.pending.changes' | translate
|
|
2455
|
+
}}</span>
|
|
2691
2456
|
</div>
|
|
2692
2457
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
2693
2458
|
@if (pendingAdd().length > 0) {
|
|
2694
2459
|
<div>
|
|
2695
2460
|
<div class="flex items-center gap-2 mb-2">
|
|
2696
2461
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
2697
|
-
<strong class="text-sm"
|
|
2462
|
+
<strong class="text-sm"
|
|
2463
|
+
>{{ 'shared.to.whitelist' | translate }} ({{
|
|
2464
|
+
pendingAdd().length
|
|
2465
|
+
}})</strong
|
|
2466
|
+
>
|
|
2698
2467
|
</div>
|
|
2699
2468
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2700
2469
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -2707,7 +2476,11 @@ class CompanyActionSelectorComponent {
|
|
|
2707
2476
|
<div>
|
|
2708
2477
|
<div class="flex items-center gap-2 mb-2">
|
|
2709
2478
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
2710
|
-
<strong class="text-sm"
|
|
2479
|
+
<strong class="text-sm"
|
|
2480
|
+
>{{ 'shared.to.remove' | translate }} ({{
|
|
2481
|
+
pendingRemove().length
|
|
2482
|
+
}})</strong
|
|
2483
|
+
>
|
|
2711
2484
|
</div>
|
|
2712
2485
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2713
2486
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -2723,7 +2496,9 @@ class CompanyActionSelectorComponent {
|
|
|
2723
2496
|
|
|
2724
2497
|
@if (!loading() && actions().length === 0) {
|
|
2725
2498
|
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
2726
|
-
<i
|
|
2499
|
+
<i
|
|
2500
|
+
class="pi pi-info-circle text-muted-color mb-3 block text-5xl"
|
|
2501
|
+
></i>
|
|
2727
2502
|
<p class="text-muted-color m-0">
|
|
2728
2503
|
{{ 'iam.permission.no.actions.for.company' | translate }}
|
|
2729
2504
|
</p>
|
|
@@ -2731,34 +2506,41 @@ class CompanyActionSelectorComponent {
|
|
|
2731
2506
|
}
|
|
2732
2507
|
}
|
|
2733
2508
|
</div>
|
|
2734
|
-
`, isInline: true, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"], dependencies: [{ kind: "ngmodule", type:
|
|
2509
|
+
`, isInline: true, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i4.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i5.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: i7.TreeTable, selector: "p-treeTable, p-treetable, p-tree-table", inputs: ["columns", "styleClass", "tableStyle", "tableStyleClass", "autoLayout", "lazy", "lazyLoadOnInit", "paginator", "rows", "first", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "customSort", "selectionMode", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "compareSelectionBy", "rowHover", "loading", "loadingIcon", "showLoader", "scrollable", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "frozenColumns", "resizableColumns", "columnResizeMode", "reorderableColumns", "contextMenu", "rowTrackBy", "filters", "globalFilterFields", "filterDelay", "filterMode", "filterLocale", "paginatorLocale", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "value", "virtualRowHeight", "selectionKeys", "showGridlines"], outputs: ["selectionChange", "contextMenuSelectionChange", "onFilter", "onNodeExpand", "onNodeCollapse", "onPage", "onSort", "onLazyLoad", "sortFunction", "onColResize", "onColReorder", "onNodeSelect", "onNodeUnselect", "onContextMenuSelect", "onHeaderCheckboxToggle", "onEditInit", "onEditComplete", "onEditCancel", "selectionKeysChange"] }, { kind: "component", type: i7.TreeTableToggler, selector: "p-treeTableToggler, p-treetabletoggler, p-treetable-toggler", inputs: ["rowNode"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
2735
2510
|
}
|
|
2736
2511
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: CompanyActionSelectorComponent, decorators: [{
|
|
2737
2512
|
type: Component,
|
|
2738
|
-
args: [{ selector: 'flusys-company-action-selector', imports: [
|
|
2513
|
+
args: [{ selector: 'flusys-company-action-selector', imports: [
|
|
2514
|
+
FormsModule,
|
|
2515
|
+
PrimeModule,
|
|
2516
|
+
HasPermissionDirective,
|
|
2517
|
+
TranslatePipe,
|
|
2518
|
+
], template: `
|
|
2739
2519
|
<div class="company-action-selector">
|
|
2740
|
-
<!-- Company Selector -->
|
|
2741
2520
|
<div class="mb-4">
|
|
2742
2521
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
2743
2522
|
<div>
|
|
2744
|
-
<label class="block font-semibold mb-2">{{
|
|
2523
|
+
<label class="block font-semibold mb-2">{{
|
|
2524
|
+
'iam.permission.select.company' | translate
|
|
2525
|
+
}}</label>
|
|
2745
2526
|
<p-select
|
|
2746
2527
|
[ngModel]="selectedCompanyId()"
|
|
2747
2528
|
(ngModelChange)="selectedCompanyId.set($event)"
|
|
2748
2529
|
[options]="companies()"
|
|
2749
2530
|
optionLabel="name"
|
|
2750
2531
|
optionValue="id"
|
|
2751
|
-
[placeholder]="
|
|
2532
|
+
[placeholder]="
|
|
2533
|
+
'iam.permission.select.company.placeholder' | translate
|
|
2534
|
+
"
|
|
2752
2535
|
[showClear]="true"
|
|
2753
2536
|
[filter]="true"
|
|
2754
|
-
|
|
2537
|
+
class="w-full"
|
|
2755
2538
|
/>
|
|
2756
2539
|
</div>
|
|
2757
2540
|
</div>
|
|
2758
2541
|
</div>
|
|
2759
2542
|
|
|
2760
2543
|
@if (selectedCompanyId()) {
|
|
2761
|
-
<!-- Loading State -->
|
|
2762
2544
|
@if (loading()) {
|
|
2763
2545
|
<div
|
|
2764
2546
|
class="surface-card p-5 rounded-border shadow-sm flex justify-center"
|
|
@@ -2767,16 +2549,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2767
2549
|
</div>
|
|
2768
2550
|
}
|
|
2769
2551
|
|
|
2770
|
-
<!-- Action List -->
|
|
2771
2552
|
@if (!loading() && actions().length > 0) {
|
|
2772
2553
|
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
2773
2554
|
<div
|
|
2774
2555
|
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
2775
2556
|
>
|
|
2776
2557
|
<div>
|
|
2777
|
-
<h5 class="m-0 mb-1">
|
|
2558
|
+
<h5 class="m-0 mb-1">
|
|
2559
|
+
{{ 'iam.permission.action.whitelist' | translate }}
|
|
2560
|
+
</h5>
|
|
2778
2561
|
<p class="text-sm text-muted-color m-0">
|
|
2779
|
-
{{
|
|
2562
|
+
{{
|
|
2563
|
+
'iam.permission.actions.available'
|
|
2564
|
+
| translate: { count: actions().length }
|
|
2565
|
+
}}
|
|
2780
2566
|
</p>
|
|
2781
2567
|
</div>
|
|
2782
2568
|
<div class="flex flex-wrap gap-2">
|
|
@@ -2809,27 +2595,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2809
2595
|
</div>
|
|
2810
2596
|
</div>
|
|
2811
2597
|
|
|
2812
|
-
<!-- Validation Warning -->
|
|
2813
2598
|
@if (invalidActionsCount() > 0) {
|
|
2814
2599
|
<div class="validation-warning rounded-border p-3 mb-4">
|
|
2815
2600
|
<div class="flex items-start gap-2">
|
|
2816
2601
|
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
2817
2602
|
<div class="flex-1">
|
|
2818
|
-
<span class="font-semibold"
|
|
2603
|
+
<span class="font-semibold"
|
|
2604
|
+
>{{ 'iam.validation.warning.title' | translate }}:</span
|
|
2605
|
+
>
|
|
2819
2606
|
<p class="text-sm mt-1 mb-0">
|
|
2820
|
-
{{
|
|
2607
|
+
{{
|
|
2608
|
+
(invalidActionsCount() > 1
|
|
2609
|
+
? 'iam.validation.unmet.prerequisites.plural'
|
|
2610
|
+
: 'iam.validation.unmet.prerequisites.singular'
|
|
2611
|
+
) | translate: { count: invalidActionsCount() }
|
|
2612
|
+
}}
|
|
2821
2613
|
</p>
|
|
2822
2614
|
</div>
|
|
2823
2615
|
</div>
|
|
2824
2616
|
</div>
|
|
2825
2617
|
}
|
|
2826
2618
|
|
|
2827
|
-
<!-- Action Tree Table -->
|
|
2828
2619
|
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
2829
2620
|
<p-treeTable
|
|
2830
2621
|
[value]="treeNodes()"
|
|
2831
2622
|
dataKey="id"
|
|
2832
|
-
|
|
2623
|
+
class="p-treetable-sm"
|
|
2833
2624
|
[tableStyle]="{ 'min-width': '50rem' }"
|
|
2834
2625
|
>
|
|
2835
2626
|
<ng-template #header>
|
|
@@ -2844,73 +2635,93 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2844
2635
|
/>
|
|
2845
2636
|
</th>
|
|
2846
2637
|
<th>{{ 'shared.name' | translate }}</th>
|
|
2847
|
-
<th class="hidden md:table-cell">
|
|
2638
|
+
<th class="hidden md:table-cell">
|
|
2639
|
+
{{ 'shared.code' | translate }}
|
|
2640
|
+
</th>
|
|
2848
2641
|
<th>{{ 'iam.action.type' | translate }}</th>
|
|
2849
|
-
<th class="hidden lg:table-cell">
|
|
2642
|
+
<th class="hidden lg:table-cell">
|
|
2643
|
+
{{ 'shared.description' | translate }}
|
|
2644
|
+
</th>
|
|
2645
|
+
</tr>
|
|
2646
|
+
</ng-template>
|
|
2647
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
2648
|
+
@let hasUnmet = hasUnmetPrerequisites(rowData);
|
|
2649
|
+
<tr [class.highlight-warning]="hasUnmet">
|
|
2650
|
+
<td class="w-12">
|
|
2651
|
+
<p-checkbox
|
|
2652
|
+
[ngModel]="selectionMap()[rowData.id]"
|
|
2653
|
+
[binary]="true"
|
|
2654
|
+
(ngModelChange)="onActionToggle(rowData, $event)"
|
|
2655
|
+
[pTooltip]="getTooltip(rowData)"
|
|
2656
|
+
tooltipPosition="top"
|
|
2657
|
+
/>
|
|
2658
|
+
</td>
|
|
2659
|
+
<td>
|
|
2660
|
+
<p-treeTableToggler [rowNode]="rowNode" />
|
|
2661
|
+
<span class="inline-flex items-center gap-2">
|
|
2662
|
+
{{ rowData.name }}
|
|
2663
|
+
@if (hasUnmet) {
|
|
2664
|
+
<i
|
|
2665
|
+
class="pi pi-exclamation-triangle text-orange-500"
|
|
2666
|
+
[pTooltip]="
|
|
2667
|
+
'iam.validation.unmet.prerequisites.tooltip'
|
|
2668
|
+
| translate
|
|
2669
|
+
"
|
|
2670
|
+
tooltipPosition="top"
|
|
2671
|
+
></i>
|
|
2672
|
+
}
|
|
2673
|
+
</span>
|
|
2674
|
+
</td>
|
|
2675
|
+
<td class="hidden md:table-cell">
|
|
2676
|
+
{{ rowData.code || '-' }}
|
|
2677
|
+
</td>
|
|
2678
|
+
<td>
|
|
2679
|
+
<p-tag
|
|
2680
|
+
[value]="rowData.actionType"
|
|
2681
|
+
[severity]="
|
|
2682
|
+
rowData.actionType === 'backend' ? 'info' : 'success'
|
|
2683
|
+
"
|
|
2684
|
+
/>
|
|
2685
|
+
</td>
|
|
2686
|
+
<td class="hidden lg:table-cell">
|
|
2687
|
+
{{ rowData.description || '-' }}
|
|
2688
|
+
</td>
|
|
2850
2689
|
</tr>
|
|
2851
2690
|
</ng-template>
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
tooltipPosition="top"
|
|
2861
|
-
/>
|
|
2862
|
-
</td>
|
|
2863
|
-
<td>
|
|
2864
|
-
<p-treeTableToggler [rowNode]="rowNode" />
|
|
2865
|
-
<span class="inline-flex items-center gap-2">
|
|
2866
|
-
{{ rowData.name }}
|
|
2867
|
-
@if (hasUnmetPrerequisites(rowData)) {
|
|
2868
|
-
<i
|
|
2869
|
-
class="pi pi-exclamation-triangle text-orange-500"
|
|
2870
|
-
[pTooltip]="'iam.validation.unmet.prerequisites.tooltip' | translate"
|
|
2871
|
-
tooltipPosition="top"
|
|
2872
|
-
></i>
|
|
2691
|
+
<ng-template #emptymessage>
|
|
2692
|
+
<tr>
|
|
2693
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
2694
|
+
@if (loading()) {
|
|
2695
|
+
<i class="pi pi-spin pi-spinner"></i>
|
|
2696
|
+
{{ 'shared.loading.actions' | translate }}
|
|
2697
|
+
} @else {
|
|
2698
|
+
{{ 'iam.permission.no.actions.available' | translate }}
|
|
2873
2699
|
}
|
|
2874
|
-
</
|
|
2875
|
-
</
|
|
2876
|
-
|
|
2877
|
-
<td>
|
|
2878
|
-
<p-tag
|
|
2879
|
-
[value]="rowData.actionType"
|
|
2880
|
-
[severity]="
|
|
2881
|
-
rowData.actionType === 'backend' ? 'info' : 'success'
|
|
2882
|
-
"
|
|
2883
|
-
/>
|
|
2884
|
-
</td>
|
|
2885
|
-
<td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
|
|
2886
|
-
</tr>
|
|
2887
|
-
</ng-template>
|
|
2888
|
-
<ng-template #emptymessage>
|
|
2889
|
-
<tr>
|
|
2890
|
-
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
2891
|
-
@if (loading()) {
|
|
2892
|
-
<i class="pi pi-spin pi-spinner"></i> {{ 'shared.loading.actions' | translate }}
|
|
2893
|
-
} @else { {{ 'iam.permission.no.actions.available' | translate }} }
|
|
2894
|
-
</td>
|
|
2895
|
-
</tr>
|
|
2896
|
-
</ng-template>
|
|
2700
|
+
</td>
|
|
2701
|
+
</tr>
|
|
2702
|
+
</ng-template>
|
|
2897
2703
|
</p-treeTable>
|
|
2898
2704
|
</div>
|
|
2899
2705
|
</div>
|
|
2900
2706
|
|
|
2901
|
-
<!-- Change Summary -->
|
|
2902
2707
|
@if (hasChanges()) {
|
|
2903
2708
|
<div class="border border-surface rounded-border p-3 mt-4">
|
|
2904
2709
|
<div class="flex items-center gap-2 mb-3">
|
|
2905
2710
|
<i class="pi pi-info-circle text-primary"></i>
|
|
2906
|
-
<span class="font-bold">{{
|
|
2711
|
+
<span class="font-bold">{{
|
|
2712
|
+
'shared.pending.changes' | translate
|
|
2713
|
+
}}</span>
|
|
2907
2714
|
</div>
|
|
2908
2715
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
2909
2716
|
@if (pendingAdd().length > 0) {
|
|
2910
2717
|
<div>
|
|
2911
2718
|
<div class="flex items-center gap-2 mb-2">
|
|
2912
2719
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
2913
|
-
<strong class="text-sm"
|
|
2720
|
+
<strong class="text-sm"
|
|
2721
|
+
>{{ 'shared.to.whitelist' | translate }} ({{
|
|
2722
|
+
pendingAdd().length
|
|
2723
|
+
}})</strong
|
|
2724
|
+
>
|
|
2914
2725
|
</div>
|
|
2915
2726
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2916
2727
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -2923,7 +2734,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2923
2734
|
<div>
|
|
2924
2735
|
<div class="flex items-center gap-2 mb-2">
|
|
2925
2736
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
2926
|
-
<strong class="text-sm"
|
|
2737
|
+
<strong class="text-sm"
|
|
2738
|
+
>{{ 'shared.to.remove' | translate }} ({{
|
|
2739
|
+
pendingRemove().length
|
|
2740
|
+
}})</strong
|
|
2741
|
+
>
|
|
2927
2742
|
</div>
|
|
2928
2743
|
<ul class="list-none p-0 m-0 pl-4">
|
|
2929
2744
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -2939,7 +2754,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
2939
2754
|
|
|
2940
2755
|
@if (!loading() && actions().length === 0) {
|
|
2941
2756
|
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
2942
|
-
<i
|
|
2757
|
+
<i
|
|
2758
|
+
class="pi pi-info-circle text-muted-color mb-3 block text-5xl"
|
|
2759
|
+
></i>
|
|
2943
2760
|
<p class="text-muted-color m-0">
|
|
2944
2761
|
{{ 'iam.permission.no.actions.for.company' | translate }}
|
|
2945
2762
|
</p>
|
|
@@ -2989,7 +2806,9 @@ class UserRoleSelectorComponent {
|
|
|
2989
2806
|
roleApi = inject(RoleApiService);
|
|
2990
2807
|
permissionApi = inject(PermissionApiService);
|
|
2991
2808
|
messageService = inject(MessageService);
|
|
2992
|
-
translateAdapter = inject(TRANSLATE_ADAPTER, {
|
|
2809
|
+
translateAdapter = inject(TRANSLATE_ADAPTER, {
|
|
2810
|
+
optional: true,
|
|
2811
|
+
});
|
|
2993
2812
|
translate(key, vars) {
|
|
2994
2813
|
return this.translateAdapter?.translate(key, vars) ?? key;
|
|
2995
2814
|
}
|
|
@@ -3112,9 +2931,9 @@ class UserRoleSelectorComponent {
|
|
|
3112
2931
|
this.loading.set(true);
|
|
3113
2932
|
try {
|
|
3114
2933
|
// Load all roles
|
|
3115
|
-
const rolesResponse = await
|
|
2934
|
+
const rolesResponse = await this.roleApi.getAll({
|
|
3116
2935
|
pagination: { currentPage: 0, pageSize: MAX_DROPDOWN_ITEMS },
|
|
3117
|
-
})
|
|
2936
|
+
});
|
|
3118
2937
|
const rolesList = rolesResponse?.data ?? [];
|
|
3119
2938
|
this._roles.set(rolesList);
|
|
3120
2939
|
// Load existing user-role assignments
|
|
@@ -3228,7 +3047,9 @@ class UserRoleSelectorComponent {
|
|
|
3228
3047
|
this.messageService.add({
|
|
3229
3048
|
severity: 'success',
|
|
3230
3049
|
summary: this.translate('shared.success'),
|
|
3231
|
-
detail: response?.
|
|
3050
|
+
detail: response?.messageKey
|
|
3051
|
+
? this.translate(response.messageKey, response.messageVariables)
|
|
3052
|
+
: response?.message,
|
|
3232
3053
|
});
|
|
3233
3054
|
// Update baseline
|
|
3234
3055
|
this._initialSelection.set({ ...this.selectionMap() });
|
|
@@ -3263,7 +3084,9 @@ class UserRoleSelectorComponent {
|
|
|
3263
3084
|
[value]="selectedUserId()"
|
|
3264
3085
|
(valueChange)="selectedUserId.set($event)"
|
|
3265
3086
|
[isEditMode]="true"
|
|
3266
|
-
[placeHolder]="
|
|
3087
|
+
[placeHolder]="
|
|
3088
|
+
'iam.permission.select.user.placeholder' | translate
|
|
3089
|
+
"
|
|
3267
3090
|
/>
|
|
3268
3091
|
</div>
|
|
3269
3092
|
|
|
@@ -3279,7 +3102,9 @@ class UserRoleSelectorComponent {
|
|
|
3279
3102
|
[options]="filteredBranches()"
|
|
3280
3103
|
optionLabel="name"
|
|
3281
3104
|
optionValue="id"
|
|
3282
|
-
[placeholder]="
|
|
3105
|
+
[placeholder]="
|
|
3106
|
+
'iam.permission.select.branch.placeholder' | translate
|
|
3107
|
+
"
|
|
3283
3108
|
[showClear]="true"
|
|
3284
3109
|
[filter]="true"
|
|
3285
3110
|
styleClass="w-full"
|
|
@@ -3300,7 +3125,12 @@ class UserRoleSelectorComponent {
|
|
|
3300
3125
|
</ng-template>
|
|
3301
3126
|
</p-select>
|
|
3302
3127
|
<small class="text-muted-color block mt-1">
|
|
3303
|
-
{{
|
|
3128
|
+
{{
|
|
3129
|
+
(filteredBranches().length !== 1
|
|
3130
|
+
? 'iam.branch.permitted.count.plural'
|
|
3131
|
+
: 'iam.branch.permitted.count'
|
|
3132
|
+
) | translate: { count: filteredBranches().length }
|
|
3133
|
+
}}
|
|
3304
3134
|
</small>
|
|
3305
3135
|
</div>
|
|
3306
3136
|
}
|
|
@@ -3324,9 +3154,14 @@ class UserRoleSelectorComponent {
|
|
|
3324
3154
|
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
3325
3155
|
>
|
|
3326
3156
|
<div>
|
|
3327
|
-
<h5 class="m-0 mb-1">
|
|
3157
|
+
<h5 class="m-0 mb-1">
|
|
3158
|
+
{{ 'iam.permission.role.assignments' | translate }}
|
|
3159
|
+
</h5>
|
|
3328
3160
|
<p class="text-sm text-muted-color m-0">
|
|
3329
|
-
{{
|
|
3161
|
+
{{
|
|
3162
|
+
'iam.permission.roles.available'
|
|
3163
|
+
| translate: { count: roles().length }
|
|
3164
|
+
}}
|
|
3330
3165
|
</p>
|
|
3331
3166
|
</div>
|
|
3332
3167
|
<div class="flex flex-wrap gap-2">
|
|
@@ -3368,7 +3203,9 @@ class UserRoleSelectorComponent {
|
|
|
3368
3203
|
[rowsPerPageOptions]="[10, 20, 50]"
|
|
3369
3204
|
[globalFilterFields]="['name', 'code', 'description']"
|
|
3370
3205
|
[showCurrentPageReport]="true"
|
|
3371
|
-
[currentPageReportTemplate]="
|
|
3206
|
+
[currentPageReportTemplate]="
|
|
3207
|
+
'iam.pagination.roles.template' | translate
|
|
3208
|
+
"
|
|
3372
3209
|
styleClass="p-datatable-sm"
|
|
3373
3210
|
[tableStyle]="{ 'min-width': '35rem' }"
|
|
3374
3211
|
>
|
|
@@ -3384,8 +3221,12 @@ class UserRoleSelectorComponent {
|
|
|
3384
3221
|
/>
|
|
3385
3222
|
</th>
|
|
3386
3223
|
<th>{{ 'shared.name' | translate }}</th>
|
|
3387
|
-
<th class="hidden sm:table-cell">
|
|
3388
|
-
|
|
3224
|
+
<th class="hidden sm:table-cell">
|
|
3225
|
+
{{ 'shared.code' | translate }}
|
|
3226
|
+
</th>
|
|
3227
|
+
<th class="hidden md:table-cell">
|
|
3228
|
+
{{ 'shared.description' | translate }}
|
|
3229
|
+
</th>
|
|
3389
3230
|
</tr>
|
|
3390
3231
|
</ng-template>
|
|
3391
3232
|
<ng-template #body let-role>
|
|
@@ -3401,14 +3242,17 @@ class UserRoleSelectorComponent {
|
|
|
3401
3242
|
</td>
|
|
3402
3243
|
<td>{{ role.name }}</td>
|
|
3403
3244
|
<td class="hidden sm:table-cell">{{ role.code || '-' }}</td>
|
|
3404
|
-
<td class="hidden md:table-cell">
|
|
3245
|
+
<td class="hidden md:table-cell">
|
|
3246
|
+
{{ role.description || '-' }}
|
|
3247
|
+
</td>
|
|
3405
3248
|
</tr>
|
|
3406
3249
|
</ng-template>
|
|
3407
3250
|
<ng-template #emptymessage>
|
|
3408
3251
|
<tr>
|
|
3409
3252
|
<td colspan="4" class="text-center p-4 text-muted-color">
|
|
3410
3253
|
@if (loading()) {
|
|
3411
|
-
<i class="pi pi-spin pi-spinner"></i>
|
|
3254
|
+
<i class="pi pi-spin pi-spinner"></i>
|
|
3255
|
+
{{ 'shared.loading.roles' | translate }}
|
|
3412
3256
|
} @else {
|
|
3413
3257
|
{{ 'iam.permission.no.roles.available' | translate }}
|
|
3414
3258
|
}
|
|
@@ -3424,14 +3268,20 @@ class UserRoleSelectorComponent {
|
|
|
3424
3268
|
<div class="border border-surface rounded-border p-3 mt-4">
|
|
3425
3269
|
<div class="flex items-center gap-2 mb-3">
|
|
3426
3270
|
<i class="pi pi-info-circle text-primary"></i>
|
|
3427
|
-
<span class="font-bold">{{
|
|
3271
|
+
<span class="font-bold">{{
|
|
3272
|
+
'shared.pending.changes' | translate
|
|
3273
|
+
}}</span>
|
|
3428
3274
|
</div>
|
|
3429
3275
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
3430
3276
|
@if (pendingAdd().length > 0) {
|
|
3431
3277
|
<div>
|
|
3432
3278
|
<div class="flex items-center gap-2 mb-2">
|
|
3433
3279
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
3434
|
-
<strong class="text-sm"
|
|
3280
|
+
<strong class="text-sm"
|
|
3281
|
+
>{{ 'shared.to.assign' | translate }} ({{
|
|
3282
|
+
pendingAdd().length
|
|
3283
|
+
}})</strong
|
|
3284
|
+
>
|
|
3435
3285
|
</div>
|
|
3436
3286
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3437
3287
|
@for (role of pendingAdd(); track role.id) {
|
|
@@ -3444,7 +3294,11 @@ class UserRoleSelectorComponent {
|
|
|
3444
3294
|
<div>
|
|
3445
3295
|
<div class="flex items-center gap-2 mb-2">
|
|
3446
3296
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
3447
|
-
<strong class="text-sm"
|
|
3297
|
+
<strong class="text-sm"
|
|
3298
|
+
>{{ 'shared.to.remove' | translate }} ({{
|
|
3299
|
+
pendingRemove().length
|
|
3300
|
+
}})</strong
|
|
3301
|
+
>
|
|
3448
3302
|
</div>
|
|
3449
3303
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3450
3304
|
@for (role of pendingRemove(); track role.id) {
|
|
@@ -3460,7 +3314,9 @@ class UserRoleSelectorComponent {
|
|
|
3460
3314
|
|
|
3461
3315
|
@if (!loading() && roles().length === 0) {
|
|
3462
3316
|
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
3463
|
-
<i
|
|
3317
|
+
<i
|
|
3318
|
+
class="pi pi-info-circle text-muted-color mb-3 block text-5xl"
|
|
3319
|
+
></i>
|
|
3464
3320
|
<p class="text-muted-color m-0">
|
|
3465
3321
|
{{ 'iam.permission.no.roles.for.user' | translate }}
|
|
3466
3322
|
</p>
|
|
@@ -3472,7 +3328,14 @@ class UserRoleSelectorComponent {
|
|
|
3472
3328
|
}
|
|
3473
3329
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: UserRoleSelectorComponent, decorators: [{
|
|
3474
3330
|
type: Component,
|
|
3475
|
-
args: [{ selector: 'flusys-user-role-selector', imports: [
|
|
3331
|
+
args: [{ selector: 'flusys-user-role-selector', imports: [
|
|
3332
|
+
CommonModule,
|
|
3333
|
+
FormsModule,
|
|
3334
|
+
PrimeModule,
|
|
3335
|
+
HasPermissionDirective,
|
|
3336
|
+
UserSelectComponent,
|
|
3337
|
+
TranslatePipe,
|
|
3338
|
+
], template: `
|
|
3476
3339
|
<div class="user-role-selector">
|
|
3477
3340
|
<!-- User and Branch Selectors -->
|
|
3478
3341
|
<div class="surface-card p-4 rounded-border mb-4 shadow-sm">
|
|
@@ -3486,7 +3349,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
3486
3349
|
[value]="selectedUserId()"
|
|
3487
3350
|
(valueChange)="selectedUserId.set($event)"
|
|
3488
3351
|
[isEditMode]="true"
|
|
3489
|
-
[placeHolder]="
|
|
3352
|
+
[placeHolder]="
|
|
3353
|
+
'iam.permission.select.user.placeholder' | translate
|
|
3354
|
+
"
|
|
3490
3355
|
/>
|
|
3491
3356
|
</div>
|
|
3492
3357
|
|
|
@@ -3502,7 +3367,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
3502
3367
|
[options]="filteredBranches()"
|
|
3503
3368
|
optionLabel="name"
|
|
3504
3369
|
optionValue="id"
|
|
3505
|
-
[placeholder]="
|
|
3370
|
+
[placeholder]="
|
|
3371
|
+
'iam.permission.select.branch.placeholder' | translate
|
|
3372
|
+
"
|
|
3506
3373
|
[showClear]="true"
|
|
3507
3374
|
[filter]="true"
|
|
3508
3375
|
styleClass="w-full"
|
|
@@ -3523,7 +3390,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
3523
3390
|
</ng-template>
|
|
3524
3391
|
</p-select>
|
|
3525
3392
|
<small class="text-muted-color block mt-1">
|
|
3526
|
-
{{
|
|
3393
|
+
{{
|
|
3394
|
+
(filteredBranches().length !== 1
|
|
3395
|
+
? 'iam.branch.permitted.count.plural'
|
|
3396
|
+
: 'iam.branch.permitted.count'
|
|
3397
|
+
) | translate: { count: filteredBranches().length }
|
|
3398
|
+
}}
|
|
3527
3399
|
</small>
|
|
3528
3400
|
</div>
|
|
3529
3401
|
}
|
|
@@ -3547,9 +3419,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
3547
3419
|
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
3548
3420
|
>
|
|
3549
3421
|
<div>
|
|
3550
|
-
<h5 class="m-0 mb-1">
|
|
3422
|
+
<h5 class="m-0 mb-1">
|
|
3423
|
+
{{ 'iam.permission.role.assignments' | translate }}
|
|
3424
|
+
</h5>
|
|
3551
3425
|
<p class="text-sm text-muted-color m-0">
|
|
3552
|
-
{{
|
|
3426
|
+
{{
|
|
3427
|
+
'iam.permission.roles.available'
|
|
3428
|
+
| translate: { count: roles().length }
|
|
3429
|
+
}}
|
|
3553
3430
|
</p>
|
|
3554
3431
|
</div>
|
|
3555
3432
|
<div class="flex flex-wrap gap-2">
|
|
@@ -3591,7 +3468,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
3591
3468
|
[rowsPerPageOptions]="[10, 20, 50]"
|
|
3592
3469
|
[globalFilterFields]="['name', 'code', 'description']"
|
|
3593
3470
|
[showCurrentPageReport]="true"
|
|
3594
|
-
[currentPageReportTemplate]="
|
|
3471
|
+
[currentPageReportTemplate]="
|
|
3472
|
+
'iam.pagination.roles.template' | translate
|
|
3473
|
+
"
|
|
3595
3474
|
styleClass="p-datatable-sm"
|
|
3596
3475
|
[tableStyle]="{ 'min-width': '35rem' }"
|
|
3597
3476
|
>
|
|
@@ -3607,8 +3486,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
3607
3486
|
/>
|
|
3608
3487
|
</th>
|
|
3609
3488
|
<th>{{ 'shared.name' | translate }}</th>
|
|
3610
|
-
<th class="hidden sm:table-cell">
|
|
3611
|
-
|
|
3489
|
+
<th class="hidden sm:table-cell">
|
|
3490
|
+
{{ 'shared.code' | translate }}
|
|
3491
|
+
</th>
|
|
3492
|
+
<th class="hidden md:table-cell">
|
|
3493
|
+
{{ 'shared.description' | translate }}
|
|
3494
|
+
</th>
|
|
3612
3495
|
</tr>
|
|
3613
3496
|
</ng-template>
|
|
3614
3497
|
<ng-template #body let-role>
|
|
@@ -3624,14 +3507,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
3624
3507
|
</td>
|
|
3625
3508
|
<td>{{ role.name }}</td>
|
|
3626
3509
|
<td class="hidden sm:table-cell">{{ role.code || '-' }}</td>
|
|
3627
|
-
<td class="hidden md:table-cell">
|
|
3510
|
+
<td class="hidden md:table-cell">
|
|
3511
|
+
{{ role.description || '-' }}
|
|
3512
|
+
</td>
|
|
3628
3513
|
</tr>
|
|
3629
3514
|
</ng-template>
|
|
3630
3515
|
<ng-template #emptymessage>
|
|
3631
3516
|
<tr>
|
|
3632
3517
|
<td colspan="4" class="text-center p-4 text-muted-color">
|
|
3633
3518
|
@if (loading()) {
|
|
3634
|
-
<i class="pi pi-spin pi-spinner"></i>
|
|
3519
|
+
<i class="pi pi-spin pi-spinner"></i>
|
|
3520
|
+
{{ 'shared.loading.roles' | translate }}
|
|
3635
3521
|
} @else {
|
|
3636
3522
|
{{ 'iam.permission.no.roles.available' | translate }}
|
|
3637
3523
|
}
|
|
@@ -3647,14 +3533,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
3647
3533
|
<div class="border border-surface rounded-border p-3 mt-4">
|
|
3648
3534
|
<div class="flex items-center gap-2 mb-3">
|
|
3649
3535
|
<i class="pi pi-info-circle text-primary"></i>
|
|
3650
|
-
<span class="font-bold">{{
|
|
3536
|
+
<span class="font-bold">{{
|
|
3537
|
+
'shared.pending.changes' | translate
|
|
3538
|
+
}}</span>
|
|
3651
3539
|
</div>
|
|
3652
3540
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
3653
3541
|
@if (pendingAdd().length > 0) {
|
|
3654
3542
|
<div>
|
|
3655
3543
|
<div class="flex items-center gap-2 mb-2">
|
|
3656
3544
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
3657
|
-
<strong class="text-sm"
|
|
3545
|
+
<strong class="text-sm"
|
|
3546
|
+
>{{ 'shared.to.assign' | translate }} ({{
|
|
3547
|
+
pendingAdd().length
|
|
3548
|
+
}})</strong
|
|
3549
|
+
>
|
|
3658
3550
|
</div>
|
|
3659
3551
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3660
3552
|
@for (role of pendingAdd(); track role.id) {
|
|
@@ -3667,7 +3559,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
3667
3559
|
<div>
|
|
3668
3560
|
<div class="flex items-center gap-2 mb-2">
|
|
3669
3561
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
3670
|
-
<strong class="text-sm"
|
|
3562
|
+
<strong class="text-sm"
|
|
3563
|
+
>{{ 'shared.to.remove' | translate }} ({{
|
|
3564
|
+
pendingRemove().length
|
|
3565
|
+
}})</strong
|
|
3566
|
+
>
|
|
3671
3567
|
</div>
|
|
3672
3568
|
<ul class="list-none p-0 m-0 pl-4">
|
|
3673
3569
|
@for (role of pendingRemove(); track role.id) {
|
|
@@ -3683,7 +3579,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
3683
3579
|
|
|
3684
3580
|
@if (!loading() && roles().length === 0) {
|
|
3685
3581
|
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
3686
|
-
<i
|
|
3582
|
+
<i
|
|
3583
|
+
class="pi pi-info-circle text-muted-color mb-3 block text-5xl"
|
|
3584
|
+
></i>
|
|
3687
3585
|
<p class="text-muted-color m-0">
|
|
3688
3586
|
{{ 'iam.permission.no.roles.for.user' | translate }}
|
|
3689
3587
|
</p>
|
|
@@ -3694,72 +3592,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
3694
3592
|
`, styles: [":host{display:block}\n"] }]
|
|
3695
3593
|
}], ctorParameters: () => [] });
|
|
3696
3594
|
|
|
3697
|
-
|
|
3698
|
-
* User-Action Selector Component
|
|
3699
|
-
*
|
|
3700
|
-
* Manages direct user action permissions with hierarchical tree view.
|
|
3701
|
-
*
|
|
3702
|
-
* **Architecture:**
|
|
3703
|
-
* - Presentation: TreeTable for hierarchical display
|
|
3704
|
-
* - Business Logic: Flat action list for selection/validation
|
|
3705
|
-
* - State: Signals with computed dependencies
|
|
3706
|
-
*
|
|
3707
|
-
* **Core Features:**
|
|
3708
|
-
* - User dropdown with async loading
|
|
3709
|
-
* - Branch selector (if company feature enabled)
|
|
3710
|
-
* - Hierarchical TreeTable with expand/collapse
|
|
3711
|
-
* - Direct user-action permission assignment (bypassing roles)
|
|
3712
|
-
* - Real-time change tracking
|
|
3713
|
-
*
|
|
3714
|
-
* **Granularity:**
|
|
3715
|
-
* - Company-wide: branchId = undefined
|
|
3716
|
-
* - Branch-specific: branchId = selected value
|
|
3717
|
-
*
|
|
3718
|
-
* **Performance:**
|
|
3719
|
-
* - Computed signals for reactive updates
|
|
3720
|
-
* - Branch filtering on company context change
|
|
3721
|
-
*
|
|
3722
|
-
* @example
|
|
3723
|
-
* ```html
|
|
3724
|
-
* <flusys-user-action-selector />
|
|
3725
|
-
* ```
|
|
3726
|
-
*/
|
|
3727
|
-
class UserActionSelectorComponent {
|
|
3728
|
-
// Permission constants for template
|
|
3595
|
+
class UserActionSelectorComponent extends BaseActionSelectorComponent {
|
|
3729
3596
|
USER_ACTION_PERMISSIONS = USER_ACTION_PERMISSIONS;
|
|
3730
|
-
// Dependencies
|
|
3731
3597
|
appConfig = inject(APP_CONFIG);
|
|
3732
3598
|
companyContext = inject(LAYOUT_AUTH_STATE);
|
|
3733
3599
|
userPermissionProvider = inject(USER_PERMISSION_PROVIDER);
|
|
3734
3600
|
actionApi = inject(ActionApiService);
|
|
3735
3601
|
permissionApi = inject(PermissionApiService);
|
|
3736
|
-
permissionLogic = inject(ActionPermissionLogicService);
|
|
3737
3602
|
messageService = inject(MessageService);
|
|
3738
|
-
translateAdapter = inject(TRANSLATE_ADAPTER, { optional: true });
|
|
3739
|
-
translate(key, vars) {
|
|
3740
|
-
return this.translateAdapter?.translate(key, vars) ?? key;
|
|
3741
|
-
}
|
|
3742
|
-
// State - User/Branch Selection
|
|
3743
3603
|
selectedUserId = signal(null, ...(ngDevMode ? [{ debugName: "selectedUserId" }] : []));
|
|
3744
3604
|
selectedBranchId = signal(undefined, ...(ngDevMode ? [{ debugName: "selectedBranchId" }] : []));
|
|
3745
3605
|
branches = signal([], ...(ngDevMode ? [{ debugName: "branches" }] : []));
|
|
3746
|
-
// State - Loading/Saving
|
|
3747
|
-
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
|
|
3748
|
-
saving = signal(false, ...(ngDevMode ? [{ debugName: "saving" }] : []));
|
|
3749
|
-
// State - Actions (Business Logic)
|
|
3750
|
-
_actions = signal([], ...(ngDevMode ? [{ debugName: "_actions" }] : []));
|
|
3751
|
-
actions = this._actions.asReadonly();
|
|
3752
|
-
// State - Actions (Display)
|
|
3753
|
-
_actionsTree = signal([], ...(ngDevMode ? [{ debugName: "_actionsTree" }] : []));
|
|
3754
|
-
actionsTree = this._actionsTree.asReadonly();
|
|
3755
|
-
// State - Selection
|
|
3756
|
-
_selectionMap = signal({}, ...(ngDevMode ? [{ debugName: "_selectionMap" }] : []));
|
|
3757
|
-
selectionMap = this._selectionMap.asReadonly();
|
|
3758
|
-
_initialSelection = signal({}, ...(ngDevMode ? [{ debugName: "_initialSelection" }] : []));
|
|
3759
|
-
initialSelection = this._initialSelection.asReadonly();
|
|
3760
|
-
// Computed - Company Feature Flag (cached)
|
|
3761
3606
|
isCompanyFeatureActive = computed(() => isCompanyFeatureEnabled(this.appConfig) === true, ...(ngDevMode ? [{ debugName: "isCompanyFeatureActive" }] : []));
|
|
3762
|
-
// Computed - UI Flags
|
|
3763
3607
|
showBranchSelector = this.isCompanyFeatureActive;
|
|
3764
3608
|
filteredBranches = computed(() => {
|
|
3765
3609
|
const currentCompanyId = this.companyContext.currentCompanyInfo()?.id || undefined;
|
|
@@ -3767,64 +3611,11 @@ class UserActionSelectorComponent {
|
|
|
3767
3611
|
return this.branches();
|
|
3768
3612
|
return this.branches().filter((b) => b.companyId === currentCompanyId);
|
|
3769
3613
|
}, ...(ngDevMode ? [{ debugName: "filteredBranches" }] : []));
|
|
3770
|
-
// Computed - Tree Nodes (for PrimeNG TreeTable)
|
|
3771
|
-
treeNodes = computed(() => convertActionToTreeNode(this.actionsTree()), ...(ngDevMode ? [{ debugName: "treeNodes" }] : []));
|
|
3772
|
-
// Computed - Selection State
|
|
3773
|
-
allSelected = computed(() => {
|
|
3774
|
-
const selMap = this.selectionMap();
|
|
3775
|
-
const total = this.actions().length;
|
|
3776
|
-
if (total === 0)
|
|
3777
|
-
return false;
|
|
3778
|
-
const selected = Object.values(selMap).filter((v) => v).length;
|
|
3779
|
-
return selected === total;
|
|
3780
|
-
}, ...(ngDevMode ? [{ debugName: "allSelected" }] : []));
|
|
3781
|
-
// Computed - Change Detection
|
|
3782
|
-
hasChanges = computed(() => {
|
|
3783
|
-
const current = this.selectionMap();
|
|
3784
|
-
const initial = this.initialSelection();
|
|
3785
|
-
const currentKeys = Object.keys(current);
|
|
3786
|
-
const initialKeys = Object.keys(initial);
|
|
3787
|
-
if (currentKeys.length !== initialKeys.length)
|
|
3788
|
-
return true;
|
|
3789
|
-
return currentKeys.some((key) => current[key] !== initial[key]);
|
|
3790
|
-
}, ...(ngDevMode ? [{ debugName: "hasChanges" }] : []));
|
|
3791
|
-
// Computed - Validation
|
|
3792
|
-
actionsWithUnmetPrerequisites = computed(() => {
|
|
3793
|
-
const selMap = this.selectionMap();
|
|
3794
|
-
const allActions = this.actions();
|
|
3795
|
-
const unmetSet = new Set();
|
|
3796
|
-
allActions.forEach((action) => {
|
|
3797
|
-
if (action.id &&
|
|
3798
|
-
this.permissionLogic.hasUnmetPrerequisites(action, selMap, allActions)) {
|
|
3799
|
-
unmetSet.add(action.id);
|
|
3800
|
-
}
|
|
3801
|
-
});
|
|
3802
|
-
return unmetSet;
|
|
3803
|
-
}, ...(ngDevMode ? [{ debugName: "actionsWithUnmetPrerequisites" }] : []));
|
|
3804
|
-
invalidActionsCount = computed(() => {
|
|
3805
|
-
return this.actionsWithUnmetPrerequisites().size;
|
|
3806
|
-
}, ...(ngDevMode ? [{ debugName: "invalidActionsCount" }] : []));
|
|
3807
|
-
// Computed - Pending Changes
|
|
3808
|
-
pendingAdd = computed(() => {
|
|
3809
|
-
const current = this.selectionMap();
|
|
3810
|
-
const initial = this.initialSelection();
|
|
3811
|
-
const allActions = this.actions();
|
|
3812
|
-
return allActions.filter((action) => current[action.id] && !initial[action.id]);
|
|
3813
|
-
}, ...(ngDevMode ? [{ debugName: "pendingAdd" }] : []));
|
|
3814
|
-
pendingRemove = computed(() => {
|
|
3815
|
-
const current = this.selectionMap();
|
|
3816
|
-
const initial = this.initialSelection();
|
|
3817
|
-
const allActions = this.actions();
|
|
3818
|
-
return allActions.filter((action) => !current[action.id] && initial[action.id]);
|
|
3819
|
-
}, ...(ngDevMode ? [{ debugName: "pendingRemove" }] : []));
|
|
3820
|
-
// Computed - Save Button State
|
|
3821
|
-
canSave = computed(() => {
|
|
3822
|
-
return (this.hasChanges() && !this.saving() && this.invalidActionsCount() === 0);
|
|
3823
|
-
}, ...(ngDevMode ? [{ debugName: "canSave" }] : []));
|
|
3824
3614
|
constructor() {
|
|
3825
|
-
|
|
3615
|
+
super();
|
|
3826
3616
|
effect(() => {
|
|
3827
3617
|
const userId = this.selectedUserId();
|
|
3618
|
+
this.selectedBranchId.set(undefined);
|
|
3828
3619
|
if (userId) {
|
|
3829
3620
|
if (this.isCompanyFeatureActive()) {
|
|
3830
3621
|
this.loadUserBranches(userId);
|
|
@@ -3833,22 +3624,23 @@ class UserActionSelectorComponent {
|
|
|
3833
3624
|
}
|
|
3834
3625
|
else {
|
|
3835
3626
|
this.branches.set([]);
|
|
3836
|
-
this.selectedBranchId.set(undefined);
|
|
3837
3627
|
this.resetState();
|
|
3838
3628
|
}
|
|
3839
3629
|
});
|
|
3840
|
-
// Effect: Reload data when branch changes
|
|
3841
3630
|
effect(() => {
|
|
3842
|
-
const userId = this.selectedUserId();
|
|
3843
3631
|
const branchId = this.selectedBranchId();
|
|
3632
|
+
const userId = this.selectedUserId();
|
|
3844
3633
|
if (this.isCompanyFeatureActive() && userId && branchId !== undefined) {
|
|
3845
3634
|
this.loadData();
|
|
3846
3635
|
}
|
|
3847
3636
|
});
|
|
3848
3637
|
}
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3638
|
+
getDefaultTooltip(isSelected) {
|
|
3639
|
+
if (isSelected) {
|
|
3640
|
+
return `[${this.translate('shared.assigned')}] ${this.translate('iam.tooltip.assigned.to.user')}\n\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.click.to.remove.user')}`;
|
|
3641
|
+
}
|
|
3642
|
+
return `[${this.translate('shared.add')}] ${this.translate('iam.tooltip.click.to.assign.user')}`;
|
|
3643
|
+
}
|
|
3852
3644
|
async loadUserBranches(userId) {
|
|
3853
3645
|
const companyId = this.companyContext.currentCompanyInfo()?.id;
|
|
3854
3646
|
if (!companyId) {
|
|
@@ -3865,12 +3657,8 @@ class UserActionSelectorComponent {
|
|
|
3865
3657
|
this.branches.set(userBranches);
|
|
3866
3658
|
}
|
|
3867
3659
|
catch {
|
|
3868
|
-
// Error toast handled by global interceptor
|
|
3869
3660
|
}
|
|
3870
3661
|
}
|
|
3871
|
-
/**
|
|
3872
|
-
* Load actions and assignments for selected user
|
|
3873
|
-
*/
|
|
3874
3662
|
async loadData() {
|
|
3875
3663
|
const userId = this.selectedUserId();
|
|
3876
3664
|
if (!userId)
|
|
@@ -3891,65 +3679,11 @@ class UserActionSelectorComponent {
|
|
|
3891
3679
|
this._initialSelection.set({ ...selMap });
|
|
3892
3680
|
}
|
|
3893
3681
|
catch {
|
|
3894
|
-
// Error toast handled by global interceptor
|
|
3895
3682
|
}
|
|
3896
3683
|
finally {
|
|
3897
3684
|
this.loading.set(false);
|
|
3898
3685
|
}
|
|
3899
3686
|
}
|
|
3900
|
-
/**
|
|
3901
|
-
* Get tooltip for action checkbox
|
|
3902
|
-
*/
|
|
3903
|
-
getTooltip(action) {
|
|
3904
|
-
const selMap = this.selectionMap();
|
|
3905
|
-
const isCurrentlySelected = selMap[action.id];
|
|
3906
|
-
if (action.permissionLogic) {
|
|
3907
|
-
const prerequisiteInfo = this.permissionLogic.getPrerequisiteTooltip(action, selMap, this.actions());
|
|
3908
|
-
if (prerequisiteInfo) {
|
|
3909
|
-
const actionHint = isCurrentlySelected
|
|
3910
|
-
? `\n\n---\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.remove.action')}`
|
|
3911
|
-
: `\n\n---\n[${this.translate('shared.add')}] ${this.translate('iam.tooltip.add.action')}`;
|
|
3912
|
-
return `${prerequisiteInfo}${actionHint}`;
|
|
3913
|
-
}
|
|
3914
|
-
}
|
|
3915
|
-
if (isCurrentlySelected) {
|
|
3916
|
-
return `[${this.translate('shared.assigned')}] ${this.translate('iam.tooltip.assigned.to.user')}\n\n[${this.translate('shared.remove')}] ${this.translate('iam.tooltip.click.to.remove.user')}`;
|
|
3917
|
-
}
|
|
3918
|
-
return `[${this.translate('shared.add')}] ${this.translate('iam.tooltip.click.to.assign.user')}`;
|
|
3919
|
-
}
|
|
3920
|
-
/**
|
|
3921
|
-
* Check if action has unmet prerequisites
|
|
3922
|
-
*/
|
|
3923
|
-
hasUnmetPrerequisites(action) {
|
|
3924
|
-
return action.id
|
|
3925
|
-
? this.actionsWithUnmetPrerequisites().has(action.id)
|
|
3926
|
-
: false;
|
|
3927
|
-
}
|
|
3928
|
-
/**
|
|
3929
|
-
* Handle action toggle with dependency management
|
|
3930
|
-
*/
|
|
3931
|
-
onActionToggle(action, newValue) {
|
|
3932
|
-
if (!newValue) {
|
|
3933
|
-
// Unchecking - validate dependencies
|
|
3934
|
-
this.permissionLogic.handleUncheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
|
|
3935
|
-
}
|
|
3936
|
-
else {
|
|
3937
|
-
// Checking - validate prerequisites
|
|
3938
|
-
this.permissionLogic.handleCheck(action, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap), (previousState) => this._selectionMap.set(previousState));
|
|
3939
|
-
}
|
|
3940
|
-
}
|
|
3941
|
-
toggleAll() {
|
|
3942
|
-
this.setAllActions(!this.allSelected());
|
|
3943
|
-
}
|
|
3944
|
-
selectAll() {
|
|
3945
|
-
this.setAllActions(true);
|
|
3946
|
-
}
|
|
3947
|
-
deselectAll() {
|
|
3948
|
-
this.setAllActions(false);
|
|
3949
|
-
}
|
|
3950
|
-
setAllActions(value) {
|
|
3951
|
-
this._selectionMap.set(this.buildSelectionMap(this.actions(), () => value));
|
|
3952
|
-
}
|
|
3953
3687
|
async saveChanges() {
|
|
3954
3688
|
const userId = this.selectedUserId();
|
|
3955
3689
|
if (!userId)
|
|
@@ -3969,7 +3703,7 @@ class UserActionSelectorComponent {
|
|
|
3969
3703
|
this.permissionLogic.showValidationErrorDialog(invalidActions, this.selectionMap(), this.actions(), (newMap) => this._selectionMap.set(newMap));
|
|
3970
3704
|
return;
|
|
3971
3705
|
}
|
|
3972
|
-
const items = this.
|
|
3706
|
+
const items = this.buildPayloadItems();
|
|
3973
3707
|
if (items.length === 0)
|
|
3974
3708
|
return;
|
|
3975
3709
|
this.saving.set(true);
|
|
@@ -3985,44 +3719,21 @@ class UserActionSelectorComponent {
|
|
|
3985
3719
|
this.messageService.add({
|
|
3986
3720
|
severity: 'success',
|
|
3987
3721
|
summary: this.translate('shared.success'),
|
|
3988
|
-
detail: response?.
|
|
3722
|
+
detail: response?.messageKey
|
|
3723
|
+
? this.translate(response.messageKey, response.messageVariables)
|
|
3724
|
+
: response?.message,
|
|
3989
3725
|
});
|
|
3990
3726
|
this._initialSelection.set({ ...this.selectionMap() });
|
|
3991
3727
|
}
|
|
3992
3728
|
catch {
|
|
3993
|
-
// Error toast handled by global interceptor
|
|
3994
3729
|
}
|
|
3995
3730
|
finally {
|
|
3996
3731
|
this.saving.set(false);
|
|
3997
3732
|
}
|
|
3998
3733
|
}
|
|
3999
|
-
resetState() {
|
|
4000
|
-
this._actionsTree.set([]);
|
|
4001
|
-
this._actions.set([]);
|
|
4002
|
-
this._selectionMap.set({});
|
|
4003
|
-
this._initialSelection.set({});
|
|
4004
|
-
}
|
|
4005
|
-
buildSelectionMap(actions, predicate) {
|
|
4006
|
-
const selMap = {};
|
|
4007
|
-
actions.forEach((action) => {
|
|
4008
|
-
selMap[action.id] = predicate(action);
|
|
4009
|
-
});
|
|
4010
|
-
return selMap;
|
|
4011
|
-
}
|
|
4012
|
-
buildPermissionItems() {
|
|
4013
|
-
const items = [];
|
|
4014
|
-
this.pendingAdd().forEach((action) => {
|
|
4015
|
-
items.push({ id: action.id, action: 'add' });
|
|
4016
|
-
});
|
|
4017
|
-
this.pendingRemove().forEach((action) => {
|
|
4018
|
-
items.push({ id: action.id, action: 'remove' });
|
|
4019
|
-
});
|
|
4020
|
-
return items;
|
|
4021
|
-
}
|
|
4022
3734
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: UserActionSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4023
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: UserActionSelectorComponent, isStandalone: true, selector: "flusys-user-action-selector", ngImport: i0, template: `
|
|
3735
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: UserActionSelectorComponent, isStandalone: true, selector: "flusys-user-action-selector", usesInheritance: true, ngImport: i0, template: `
|
|
4024
3736
|
<div class="user-action-selector">
|
|
4025
|
-
<!-- User and Branch Selectors -->
|
|
4026
3737
|
<div class="surface-card p-4 rounded-border mb-4 shadow-sm">
|
|
4027
3738
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
|
4028
3739
|
<div>
|
|
@@ -4034,7 +3745,9 @@ class UserActionSelectorComponent {
|
|
|
4034
3745
|
[value]="selectedUserId()"
|
|
4035
3746
|
(valueChange)="selectedUserId.set($event)"
|
|
4036
3747
|
[isEditMode]="true"
|
|
4037
|
-
[placeHolder]="
|
|
3748
|
+
[placeHolder]="
|
|
3749
|
+
'iam.permission.select.user.placeholder' | translate
|
|
3750
|
+
"
|
|
4038
3751
|
/>
|
|
4039
3752
|
</div>
|
|
4040
3753
|
|
|
@@ -4050,10 +3763,12 @@ class UserActionSelectorComponent {
|
|
|
4050
3763
|
[options]="filteredBranches()"
|
|
4051
3764
|
optionLabel="name"
|
|
4052
3765
|
optionValue="id"
|
|
4053
|
-
[placeholder]="
|
|
3766
|
+
[placeholder]="
|
|
3767
|
+
'iam.permission.select.branch.placeholder' | translate
|
|
3768
|
+
"
|
|
4054
3769
|
[showClear]="true"
|
|
4055
3770
|
[filter]="true"
|
|
4056
|
-
|
|
3771
|
+
class="w-full"
|
|
4057
3772
|
>
|
|
4058
3773
|
<ng-template #selectedItem let-branch>
|
|
4059
3774
|
@if (branch) {
|
|
@@ -4071,7 +3786,12 @@ class UserActionSelectorComponent {
|
|
|
4071
3786
|
</ng-template>
|
|
4072
3787
|
</p-select>
|
|
4073
3788
|
<small class="text-muted-color block mt-1">
|
|
4074
|
-
{{
|
|
3789
|
+
{{
|
|
3790
|
+
(filteredBranches().length !== 1
|
|
3791
|
+
? 'iam.branch.permitted.count.plural'
|
|
3792
|
+
: 'iam.branch.permitted.count'
|
|
3793
|
+
) | translate: { count: filteredBranches().length }
|
|
3794
|
+
}}
|
|
4075
3795
|
</small>
|
|
4076
3796
|
</div>
|
|
4077
3797
|
}
|
|
@@ -4079,23 +3799,26 @@ class UserActionSelectorComponent {
|
|
|
4079
3799
|
</div>
|
|
4080
3800
|
|
|
4081
3801
|
@if (selectedUserId()) {
|
|
4082
|
-
<!-- Loading State -->
|
|
4083
3802
|
@if (loading()) {
|
|
4084
3803
|
<div class="flex justify-center p-5">
|
|
4085
3804
|
<i class="pi pi-spin pi-spinner text-4xl"></i>
|
|
4086
3805
|
</div>
|
|
4087
3806
|
}
|
|
4088
3807
|
|
|
4089
|
-
<!-- Action List -->
|
|
4090
3808
|
@if (!loading() && actions().length > 0) {
|
|
4091
3809
|
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
4092
3810
|
<div
|
|
4093
3811
|
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
4094
3812
|
>
|
|
4095
3813
|
<div>
|
|
4096
|
-
<h5 class="m-0 mb-1">
|
|
3814
|
+
<h5 class="m-0 mb-1">
|
|
3815
|
+
{{ 'iam.permission.direct.action.permissions' | translate }}
|
|
3816
|
+
</h5>
|
|
4097
3817
|
<p class="text-sm text-muted-color m-0">
|
|
4098
|
-
{{
|
|
3818
|
+
{{
|
|
3819
|
+
'iam.permission.actions.available'
|
|
3820
|
+
| translate: { count: actions().length }
|
|
3821
|
+
}}
|
|
4099
3822
|
</p>
|
|
4100
3823
|
</div>
|
|
4101
3824
|
<div class="flex flex-wrap gap-2">
|
|
@@ -4128,27 +3851,32 @@ class UserActionSelectorComponent {
|
|
|
4128
3851
|
</div>
|
|
4129
3852
|
</div>
|
|
4130
3853
|
|
|
4131
|
-
<!-- Validation Warning -->
|
|
4132
3854
|
@if (invalidActionsCount() > 0) {
|
|
4133
3855
|
<div class="validation-warning rounded-border p-3 mb-4">
|
|
4134
3856
|
<div class="flex items-start gap-2">
|
|
4135
3857
|
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
4136
3858
|
<div class="flex-1">
|
|
4137
|
-
<span class="font-semibold"
|
|
3859
|
+
<span class="font-semibold"
|
|
3860
|
+
>{{ 'iam.validation.warning.title' | translate }}:</span
|
|
3861
|
+
>
|
|
4138
3862
|
<p class="text-sm mt-1 mb-0">
|
|
4139
|
-
{{
|
|
3863
|
+
{{
|
|
3864
|
+
(invalidActionsCount() > 1
|
|
3865
|
+
? 'iam.validation.unmet.prerequisites.plural'
|
|
3866
|
+
: 'iam.validation.unmet.prerequisites.singular'
|
|
3867
|
+
) | translate: { count: invalidActionsCount() }
|
|
3868
|
+
}}
|
|
4140
3869
|
</p>
|
|
4141
3870
|
</div>
|
|
4142
3871
|
</div>
|
|
4143
3872
|
</div>
|
|
4144
3873
|
}
|
|
4145
3874
|
|
|
4146
|
-
<!-- Action Tree Table -->
|
|
4147
3875
|
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
4148
3876
|
<p-treeTable
|
|
4149
3877
|
[value]="treeNodes()"
|
|
4150
3878
|
dataKey="id"
|
|
4151
|
-
|
|
3879
|
+
class="p-treetable-sm"
|
|
4152
3880
|
[tableStyle]="{ 'min-width': '50rem' }"
|
|
4153
3881
|
>
|
|
4154
3882
|
<ng-template #header>
|
|
@@ -4163,75 +3891,94 @@ class UserActionSelectorComponent {
|
|
|
4163
3891
|
/>
|
|
4164
3892
|
</th>
|
|
4165
3893
|
<th>{{ 'shared.name' | translate }}</th>
|
|
4166
|
-
<th class="hidden md:table-cell">
|
|
3894
|
+
<th class="hidden md:table-cell">
|
|
3895
|
+
{{ 'shared.code' | translate }}
|
|
3896
|
+
</th>
|
|
4167
3897
|
<th>{{ 'iam.action.type' | translate }}</th>
|
|
4168
|
-
<th class="hidden lg:table-cell">
|
|
3898
|
+
<th class="hidden lg:table-cell">
|
|
3899
|
+
{{ 'shared.description' | translate }}
|
|
3900
|
+
</th>
|
|
3901
|
+
</tr>
|
|
3902
|
+
</ng-template>
|
|
3903
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
3904
|
+
@let hasUnmet = hasUnmetPrerequisites(rowData);
|
|
3905
|
+
@let tooltip = getTooltip(rowData);
|
|
3906
|
+
<tr [class.highlight-warning]="hasUnmet">
|
|
3907
|
+
<td class="w-12">
|
|
3908
|
+
<p-checkbox
|
|
3909
|
+
[ngModel]="selectionMap()[rowData.id]"
|
|
3910
|
+
[binary]="true"
|
|
3911
|
+
(ngModelChange)="onActionToggle(rowData, $event)"
|
|
3912
|
+
[pTooltip]="tooltip"
|
|
3913
|
+
tooltipPosition="top"
|
|
3914
|
+
/>
|
|
3915
|
+
</td>
|
|
3916
|
+
<td>
|
|
3917
|
+
<p-treeTableToggler [rowNode]="rowNode" />
|
|
3918
|
+
<span class="inline-flex items-center gap-2">
|
|
3919
|
+
{{ rowData.name }}
|
|
3920
|
+
@if (hasUnmet) {
|
|
3921
|
+
<i
|
|
3922
|
+
class="pi pi-exclamation-triangle text-orange-500"
|
|
3923
|
+
[pTooltip]="
|
|
3924
|
+
'iam.validation.unmet.prerequisites.tooltip'
|
|
3925
|
+
| translate
|
|
3926
|
+
"
|
|
3927
|
+
tooltipPosition="top"
|
|
3928
|
+
></i>
|
|
3929
|
+
}
|
|
3930
|
+
</span>
|
|
3931
|
+
</td>
|
|
3932
|
+
<td class="hidden md:table-cell">
|
|
3933
|
+
{{ rowData.code || '-' }}
|
|
3934
|
+
</td>
|
|
3935
|
+
<td>
|
|
3936
|
+
<p-tag
|
|
3937
|
+
[value]="rowData.actionType"
|
|
3938
|
+
[severity]="
|
|
3939
|
+
rowData.actionType === 'backend' ? 'info' : 'success'
|
|
3940
|
+
"
|
|
3941
|
+
/>
|
|
3942
|
+
</td>
|
|
3943
|
+
<td class="hidden lg:table-cell">
|
|
3944
|
+
{{ rowData.description || '-' }}
|
|
3945
|
+
</td>
|
|
4169
3946
|
</tr>
|
|
4170
3947
|
</ng-template>
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
tooltipPosition="top"
|
|
4180
|
-
/>
|
|
4181
|
-
</td>
|
|
4182
|
-
<td>
|
|
4183
|
-
<p-treeTableToggler [rowNode]="rowNode" />
|
|
4184
|
-
<span class="inline-flex items-center gap-2">
|
|
4185
|
-
{{ rowData.name }}
|
|
4186
|
-
@if (hasUnmetPrerequisites(rowData)) {
|
|
4187
|
-
<i
|
|
4188
|
-
class="pi pi-exclamation-triangle text-orange-500"
|
|
4189
|
-
[pTooltip]="'iam.validation.unmet.prerequisites.tooltip' | translate"
|
|
4190
|
-
tooltipPosition="top"
|
|
4191
|
-
></i>
|
|
3948
|
+
<ng-template #emptymessage>
|
|
3949
|
+
<tr>
|
|
3950
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
3951
|
+
@if (loading()) {
|
|
3952
|
+
<i class="pi pi-spin pi-spinner"></i>
|
|
3953
|
+
{{ 'shared.loading.actions' | translate }}
|
|
3954
|
+
} @else {
|
|
3955
|
+
{{ 'iam.permission.no.actions.available' | translate }}
|
|
4192
3956
|
}
|
|
4193
|
-
</
|
|
4194
|
-
</
|
|
4195
|
-
|
|
4196
|
-
<td>
|
|
4197
|
-
<p-tag
|
|
4198
|
-
[value]="rowData.actionType"
|
|
4199
|
-
[severity]="
|
|
4200
|
-
rowData.actionType === 'backend' ? 'info' : 'success'
|
|
4201
|
-
"
|
|
4202
|
-
/>
|
|
4203
|
-
</td>
|
|
4204
|
-
<td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
|
|
4205
|
-
</tr>
|
|
4206
|
-
</ng-template>
|
|
4207
|
-
<ng-template #emptymessage>
|
|
4208
|
-
<tr>
|
|
4209
|
-
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
4210
|
-
@if (loading()) {
|
|
4211
|
-
<i class="pi pi-spin pi-spinner"></i> {{ 'shared.loading.actions' | translate }}
|
|
4212
|
-
} @else {
|
|
4213
|
-
{{ 'iam.permission.no.actions.available' | translate }}
|
|
4214
|
-
}
|
|
4215
|
-
</td>
|
|
4216
|
-
</tr>
|
|
4217
|
-
</ng-template>
|
|
3957
|
+
</td>
|
|
3958
|
+
</tr>
|
|
3959
|
+
</ng-template>
|
|
4218
3960
|
</p-treeTable>
|
|
4219
3961
|
</div>
|
|
4220
3962
|
</div>
|
|
4221
3963
|
|
|
4222
|
-
<!-- Change Summary -->
|
|
4223
3964
|
@if (hasChanges()) {
|
|
4224
3965
|
<div class="border border-surface rounded-border p-3 mt-4">
|
|
4225
3966
|
<div class="flex items-center gap-2 mb-3">
|
|
4226
3967
|
<i class="pi pi-info-circle text-primary"></i>
|
|
4227
|
-
<span class="font-bold">{{
|
|
3968
|
+
<span class="font-bold">{{
|
|
3969
|
+
'shared.pending.changes' | translate
|
|
3970
|
+
}}</span>
|
|
4228
3971
|
</div>
|
|
4229
3972
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
4230
3973
|
@if (pendingAdd().length > 0) {
|
|
4231
3974
|
<div>
|
|
4232
3975
|
<div class="flex items-center gap-2 mb-2">
|
|
4233
3976
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
4234
|
-
<strong class="text-sm"
|
|
3977
|
+
<strong class="text-sm"
|
|
3978
|
+
>{{ 'shared.to.assign' | translate }} ({{
|
|
3979
|
+
pendingAdd().length
|
|
3980
|
+
}})</strong
|
|
3981
|
+
>
|
|
4235
3982
|
</div>
|
|
4236
3983
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4237
3984
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -4244,7 +3991,11 @@ class UserActionSelectorComponent {
|
|
|
4244
3991
|
<div>
|
|
4245
3992
|
<div class="flex items-center gap-2 mb-2">
|
|
4246
3993
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
4247
|
-
<strong class="text-sm"
|
|
3994
|
+
<strong class="text-sm"
|
|
3995
|
+
>{{ 'shared.to.remove' | translate }} ({{
|
|
3996
|
+
pendingRemove().length
|
|
3997
|
+
}})</strong
|
|
3998
|
+
>
|
|
4248
3999
|
</div>
|
|
4249
4000
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4250
4001
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -4260,7 +4011,9 @@ class UserActionSelectorComponent {
|
|
|
4260
4011
|
|
|
4261
4012
|
@if (!loading() && actions().length === 0) {
|
|
4262
4013
|
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
4263
|
-
<i
|
|
4014
|
+
<i
|
|
4015
|
+
class="pi pi-info-circle text-muted-color mb-3 block text-5xl"
|
|
4016
|
+
></i>
|
|
4264
4017
|
<p class="text-muted-color m-0">
|
|
4265
4018
|
{{ 'iam.permission.no.actions.for.user' | translate }}
|
|
4266
4019
|
</p>
|
|
@@ -4268,13 +4021,18 @@ class UserActionSelectorComponent {
|
|
|
4268
4021
|
}
|
|
4269
4022
|
}
|
|
4270
4023
|
</div>
|
|
4271
|
-
`, isInline: true, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"], dependencies: [{ kind: "ngmodule", type:
|
|
4024
|
+
`, isInline: true, styles: [":host{display:block}.validation-warning{background-color:var(--p-yellow-50, #fefce8);border-left:4px solid var(--p-yellow-500, #eab308);color:var(--p-yellow-700, #a16207)}:host-context(.p-dark) .validation-warning{background-color:#eab3081a;color:var(--p-yellow-400, #facc15)}:host ::ng-deep tr.highlight-warning{background-color:var(--p-red-50, rgba(239, 68, 68, .1))!important}:host-context(.p-dark) ::ng-deep tr.highlight-warning{background-color:#ef444426!important}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i4.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i5.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: i7.TreeTable, selector: "p-treeTable, p-treetable, p-tree-table", inputs: ["columns", "styleClass", "tableStyle", "tableStyleClass", "autoLayout", "lazy", "lazyLoadOnInit", "paginator", "rows", "first", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "customSort", "selectionMode", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "compareSelectionBy", "rowHover", "loading", "loadingIcon", "showLoader", "scrollable", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "frozenColumns", "resizableColumns", "columnResizeMode", "reorderableColumns", "contextMenu", "rowTrackBy", "filters", "globalFilterFields", "filterDelay", "filterMode", "filterLocale", "paginatorLocale", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "value", "virtualRowHeight", "selectionKeys", "showGridlines"], outputs: ["selectionChange", "contextMenuSelectionChange", "onFilter", "onNodeExpand", "onNodeCollapse", "onPage", "onSort", "onLazyLoad", "sortFunction", "onColResize", "onColReorder", "onNodeSelect", "onNodeUnselect", "onContextMenuSelect", "onHeaderCheckboxToggle", "onEditInit", "onEditComplete", "onEditCancel", "selectionKeysChange"] }, { kind: "component", type: i7.TreeTableToggler, selector: "p-treeTableToggler, p-treetabletoggler, p-treetable-toggler", inputs: ["rowNode"] }, { kind: "directive", type: HasPermissionDirective, selector: "[hasPermission]", inputs: ["hasPermission"] }, { kind: "component", type: UserSelectComponent, selector: "lib-user-select", inputs: ["showClear", "value"], outputs: ["valueChange", "userSelected"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
4272
4025
|
}
|
|
4273
4026
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: UserActionSelectorComponent, decorators: [{
|
|
4274
4027
|
type: Component,
|
|
4275
|
-
args: [{ selector: 'flusys-user-action-selector', imports: [
|
|
4028
|
+
args: [{ selector: 'flusys-user-action-selector', imports: [
|
|
4029
|
+
FormsModule,
|
|
4030
|
+
PrimeModule,
|
|
4031
|
+
HasPermissionDirective,
|
|
4032
|
+
UserSelectComponent,
|
|
4033
|
+
TranslatePipe,
|
|
4034
|
+
], template: `
|
|
4276
4035
|
<div class="user-action-selector">
|
|
4277
|
-
<!-- User and Branch Selectors -->
|
|
4278
4036
|
<div class="surface-card p-4 rounded-border mb-4 shadow-sm">
|
|
4279
4037
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
|
4280
4038
|
<div>
|
|
@@ -4286,7 +4044,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
4286
4044
|
[value]="selectedUserId()"
|
|
4287
4045
|
(valueChange)="selectedUserId.set($event)"
|
|
4288
4046
|
[isEditMode]="true"
|
|
4289
|
-
[placeHolder]="
|
|
4047
|
+
[placeHolder]="
|
|
4048
|
+
'iam.permission.select.user.placeholder' | translate
|
|
4049
|
+
"
|
|
4290
4050
|
/>
|
|
4291
4051
|
</div>
|
|
4292
4052
|
|
|
@@ -4302,10 +4062,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
4302
4062
|
[options]="filteredBranches()"
|
|
4303
4063
|
optionLabel="name"
|
|
4304
4064
|
optionValue="id"
|
|
4305
|
-
[placeholder]="
|
|
4065
|
+
[placeholder]="
|
|
4066
|
+
'iam.permission.select.branch.placeholder' | translate
|
|
4067
|
+
"
|
|
4306
4068
|
[showClear]="true"
|
|
4307
4069
|
[filter]="true"
|
|
4308
|
-
|
|
4070
|
+
class="w-full"
|
|
4309
4071
|
>
|
|
4310
4072
|
<ng-template #selectedItem let-branch>
|
|
4311
4073
|
@if (branch) {
|
|
@@ -4323,7 +4085,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
4323
4085
|
</ng-template>
|
|
4324
4086
|
</p-select>
|
|
4325
4087
|
<small class="text-muted-color block mt-1">
|
|
4326
|
-
{{
|
|
4088
|
+
{{
|
|
4089
|
+
(filteredBranches().length !== 1
|
|
4090
|
+
? 'iam.branch.permitted.count.plural'
|
|
4091
|
+
: 'iam.branch.permitted.count'
|
|
4092
|
+
) | translate: { count: filteredBranches().length }
|
|
4093
|
+
}}
|
|
4327
4094
|
</small>
|
|
4328
4095
|
</div>
|
|
4329
4096
|
}
|
|
@@ -4331,23 +4098,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
4331
4098
|
</div>
|
|
4332
4099
|
|
|
4333
4100
|
@if (selectedUserId()) {
|
|
4334
|
-
<!-- Loading State -->
|
|
4335
4101
|
@if (loading()) {
|
|
4336
4102
|
<div class="flex justify-center p-5">
|
|
4337
4103
|
<i class="pi pi-spin pi-spinner text-4xl"></i>
|
|
4338
4104
|
</div>
|
|
4339
4105
|
}
|
|
4340
4106
|
|
|
4341
|
-
<!-- Action List -->
|
|
4342
4107
|
@if (!loading() && actions().length > 0) {
|
|
4343
4108
|
<div class="surface-card p-4 rounded-border shadow-sm">
|
|
4344
4109
|
<div
|
|
4345
4110
|
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-3 mb-4"
|
|
4346
4111
|
>
|
|
4347
4112
|
<div>
|
|
4348
|
-
<h5 class="m-0 mb-1">
|
|
4113
|
+
<h5 class="m-0 mb-1">
|
|
4114
|
+
{{ 'iam.permission.direct.action.permissions' | translate }}
|
|
4115
|
+
</h5>
|
|
4349
4116
|
<p class="text-sm text-muted-color m-0">
|
|
4350
|
-
{{
|
|
4117
|
+
{{
|
|
4118
|
+
'iam.permission.actions.available'
|
|
4119
|
+
| translate: { count: actions().length }
|
|
4120
|
+
}}
|
|
4351
4121
|
</p>
|
|
4352
4122
|
</div>
|
|
4353
4123
|
<div class="flex flex-wrap gap-2">
|
|
@@ -4380,27 +4150,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
4380
4150
|
</div>
|
|
4381
4151
|
</div>
|
|
4382
4152
|
|
|
4383
|
-
<!-- Validation Warning -->
|
|
4384
4153
|
@if (invalidActionsCount() > 0) {
|
|
4385
4154
|
<div class="validation-warning rounded-border p-3 mb-4">
|
|
4386
4155
|
<div class="flex items-start gap-2">
|
|
4387
4156
|
<i class="pi pi-exclamation-triangle text-xl"></i>
|
|
4388
4157
|
<div class="flex-1">
|
|
4389
|
-
<span class="font-semibold"
|
|
4158
|
+
<span class="font-semibold"
|
|
4159
|
+
>{{ 'iam.validation.warning.title' | translate }}:</span
|
|
4160
|
+
>
|
|
4390
4161
|
<p class="text-sm mt-1 mb-0">
|
|
4391
|
-
{{
|
|
4162
|
+
{{
|
|
4163
|
+
(invalidActionsCount() > 1
|
|
4164
|
+
? 'iam.validation.unmet.prerequisites.plural'
|
|
4165
|
+
: 'iam.validation.unmet.prerequisites.singular'
|
|
4166
|
+
) | translate: { count: invalidActionsCount() }
|
|
4167
|
+
}}
|
|
4392
4168
|
</p>
|
|
4393
4169
|
</div>
|
|
4394
4170
|
</div>
|
|
4395
4171
|
</div>
|
|
4396
4172
|
}
|
|
4397
4173
|
|
|
4398
|
-
<!-- Action Tree Table -->
|
|
4399
4174
|
<div class="overflow-x-auto -mx-4 sm:mx-0">
|
|
4400
4175
|
<p-treeTable
|
|
4401
4176
|
[value]="treeNodes()"
|
|
4402
4177
|
dataKey="id"
|
|
4403
|
-
|
|
4178
|
+
class="p-treetable-sm"
|
|
4404
4179
|
[tableStyle]="{ 'min-width': '50rem' }"
|
|
4405
4180
|
>
|
|
4406
4181
|
<ng-template #header>
|
|
@@ -4415,75 +4190,94 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
4415
4190
|
/>
|
|
4416
4191
|
</th>
|
|
4417
4192
|
<th>{{ 'shared.name' | translate }}</th>
|
|
4418
|
-
<th class="hidden md:table-cell">
|
|
4193
|
+
<th class="hidden md:table-cell">
|
|
4194
|
+
{{ 'shared.code' | translate }}
|
|
4195
|
+
</th>
|
|
4419
4196
|
<th>{{ 'iam.action.type' | translate }}</th>
|
|
4420
|
-
<th class="hidden lg:table-cell">
|
|
4197
|
+
<th class="hidden lg:table-cell">
|
|
4198
|
+
{{ 'shared.description' | translate }}
|
|
4199
|
+
</th>
|
|
4200
|
+
</tr>
|
|
4201
|
+
</ng-template>
|
|
4202
|
+
<ng-template #body let-rowNode let-rowData="rowData">
|
|
4203
|
+
@let hasUnmet = hasUnmetPrerequisites(rowData);
|
|
4204
|
+
@let tooltip = getTooltip(rowData);
|
|
4205
|
+
<tr [class.highlight-warning]="hasUnmet">
|
|
4206
|
+
<td class="w-12">
|
|
4207
|
+
<p-checkbox
|
|
4208
|
+
[ngModel]="selectionMap()[rowData.id]"
|
|
4209
|
+
[binary]="true"
|
|
4210
|
+
(ngModelChange)="onActionToggle(rowData, $event)"
|
|
4211
|
+
[pTooltip]="tooltip"
|
|
4212
|
+
tooltipPosition="top"
|
|
4213
|
+
/>
|
|
4214
|
+
</td>
|
|
4215
|
+
<td>
|
|
4216
|
+
<p-treeTableToggler [rowNode]="rowNode" />
|
|
4217
|
+
<span class="inline-flex items-center gap-2">
|
|
4218
|
+
{{ rowData.name }}
|
|
4219
|
+
@if (hasUnmet) {
|
|
4220
|
+
<i
|
|
4221
|
+
class="pi pi-exclamation-triangle text-orange-500"
|
|
4222
|
+
[pTooltip]="
|
|
4223
|
+
'iam.validation.unmet.prerequisites.tooltip'
|
|
4224
|
+
| translate
|
|
4225
|
+
"
|
|
4226
|
+
tooltipPosition="top"
|
|
4227
|
+
></i>
|
|
4228
|
+
}
|
|
4229
|
+
</span>
|
|
4230
|
+
</td>
|
|
4231
|
+
<td class="hidden md:table-cell">
|
|
4232
|
+
{{ rowData.code || '-' }}
|
|
4233
|
+
</td>
|
|
4234
|
+
<td>
|
|
4235
|
+
<p-tag
|
|
4236
|
+
[value]="rowData.actionType"
|
|
4237
|
+
[severity]="
|
|
4238
|
+
rowData.actionType === 'backend' ? 'info' : 'success'
|
|
4239
|
+
"
|
|
4240
|
+
/>
|
|
4241
|
+
</td>
|
|
4242
|
+
<td class="hidden lg:table-cell">
|
|
4243
|
+
{{ rowData.description || '-' }}
|
|
4244
|
+
</td>
|
|
4421
4245
|
</tr>
|
|
4422
4246
|
</ng-template>
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
tooltipPosition="top"
|
|
4432
|
-
/>
|
|
4433
|
-
</td>
|
|
4434
|
-
<td>
|
|
4435
|
-
<p-treeTableToggler [rowNode]="rowNode" />
|
|
4436
|
-
<span class="inline-flex items-center gap-2">
|
|
4437
|
-
{{ rowData.name }}
|
|
4438
|
-
@if (hasUnmetPrerequisites(rowData)) {
|
|
4439
|
-
<i
|
|
4440
|
-
class="pi pi-exclamation-triangle text-orange-500"
|
|
4441
|
-
[pTooltip]="'iam.validation.unmet.prerequisites.tooltip' | translate"
|
|
4442
|
-
tooltipPosition="top"
|
|
4443
|
-
></i>
|
|
4247
|
+
<ng-template #emptymessage>
|
|
4248
|
+
<tr>
|
|
4249
|
+
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
4250
|
+
@if (loading()) {
|
|
4251
|
+
<i class="pi pi-spin pi-spinner"></i>
|
|
4252
|
+
{{ 'shared.loading.actions' | translate }}
|
|
4253
|
+
} @else {
|
|
4254
|
+
{{ 'iam.permission.no.actions.available' | translate }}
|
|
4444
4255
|
}
|
|
4445
|
-
</
|
|
4446
|
-
</
|
|
4447
|
-
|
|
4448
|
-
<td>
|
|
4449
|
-
<p-tag
|
|
4450
|
-
[value]="rowData.actionType"
|
|
4451
|
-
[severity]="
|
|
4452
|
-
rowData.actionType === 'backend' ? 'info' : 'success'
|
|
4453
|
-
"
|
|
4454
|
-
/>
|
|
4455
|
-
</td>
|
|
4456
|
-
<td class="hidden lg:table-cell">{{ rowData.description || '-' }}</td>
|
|
4457
|
-
</tr>
|
|
4458
|
-
</ng-template>
|
|
4459
|
-
<ng-template #emptymessage>
|
|
4460
|
-
<tr>
|
|
4461
|
-
<td colspan="5" class="text-center p-4 text-muted-color">
|
|
4462
|
-
@if (loading()) {
|
|
4463
|
-
<i class="pi pi-spin pi-spinner"></i> {{ 'shared.loading.actions' | translate }}
|
|
4464
|
-
} @else {
|
|
4465
|
-
{{ 'iam.permission.no.actions.available' | translate }}
|
|
4466
|
-
}
|
|
4467
|
-
</td>
|
|
4468
|
-
</tr>
|
|
4469
|
-
</ng-template>
|
|
4256
|
+
</td>
|
|
4257
|
+
</tr>
|
|
4258
|
+
</ng-template>
|
|
4470
4259
|
</p-treeTable>
|
|
4471
4260
|
</div>
|
|
4472
4261
|
</div>
|
|
4473
4262
|
|
|
4474
|
-
<!-- Change Summary -->
|
|
4475
4263
|
@if (hasChanges()) {
|
|
4476
4264
|
<div class="border border-surface rounded-border p-3 mt-4">
|
|
4477
4265
|
<div class="flex items-center gap-2 mb-3">
|
|
4478
4266
|
<i class="pi pi-info-circle text-primary"></i>
|
|
4479
|
-
<span class="font-bold">{{
|
|
4267
|
+
<span class="font-bold">{{
|
|
4268
|
+
'shared.pending.changes' | translate
|
|
4269
|
+
}}</span>
|
|
4480
4270
|
</div>
|
|
4481
4271
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
4482
4272
|
@if (pendingAdd().length > 0) {
|
|
4483
4273
|
<div>
|
|
4484
4274
|
<div class="flex items-center gap-2 mb-2">
|
|
4485
4275
|
<i class="pi pi-plus-circle text-green-500"></i>
|
|
4486
|
-
<strong class="text-sm"
|
|
4276
|
+
<strong class="text-sm"
|
|
4277
|
+
>{{ 'shared.to.assign' | translate }} ({{
|
|
4278
|
+
pendingAdd().length
|
|
4279
|
+
}})</strong
|
|
4280
|
+
>
|
|
4487
4281
|
</div>
|
|
4488
4282
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4489
4283
|
@for (action of pendingAdd(); track action.id) {
|
|
@@ -4496,7 +4290,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
4496
4290
|
<div>
|
|
4497
4291
|
<div class="flex items-center gap-2 mb-2">
|
|
4498
4292
|
<i class="pi pi-minus-circle text-red-500"></i>
|
|
4499
|
-
<strong class="text-sm"
|
|
4293
|
+
<strong class="text-sm"
|
|
4294
|
+
>{{ 'shared.to.remove' | translate }} ({{
|
|
4295
|
+
pendingRemove().length
|
|
4296
|
+
}})</strong
|
|
4297
|
+
>
|
|
4500
4298
|
</div>
|
|
4501
4299
|
<ul class="list-none p-0 m-0 pl-4">
|
|
4502
4300
|
@for (action of pendingRemove(); track action.id) {
|
|
@@ -4512,7 +4310,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
|
|
|
4512
4310
|
|
|
4513
4311
|
@if (!loading() && actions().length === 0) {
|
|
4514
4312
|
<div class="surface-card p-5 rounded-border shadow-sm text-center">
|
|
4515
|
-
<i
|
|
4313
|
+
<i
|
|
4314
|
+
class="pi pi-info-circle text-muted-color mb-3 block text-5xl"
|
|
4315
|
+
></i>
|
|
4516
4316
|
<p class="text-muted-color m-0">
|
|
4517
4317
|
{{ 'iam.permission.no.actions.for.user' | translate }}
|
|
4518
4318
|
</p>
|
|
@@ -4605,15 +4405,15 @@ const IAM_ROUTES = [
|
|
|
4605
4405
|
children: [
|
|
4606
4406
|
{
|
|
4607
4407
|
path: '',
|
|
4608
|
-
loadComponent: () => import('./flusys-ng-iam-action-list-page.component-
|
|
4408
|
+
loadComponent: () => import('./flusys-ng-iam-action-list-page.component-DRK79zUR.mjs').then((m) => m.ActionListPageComponent),
|
|
4609
4409
|
},
|
|
4610
4410
|
{
|
|
4611
4411
|
path: 'new',
|
|
4612
|
-
loadComponent: () => import('./flusys-ng-iam-action-form-page.component-
|
|
4412
|
+
loadComponent: () => import('./flusys-ng-iam-action-form-page.component-D_vwcCjG.mjs').then((m) => m.ActionFormPageComponent),
|
|
4613
4413
|
},
|
|
4614
4414
|
{
|
|
4615
4415
|
path: ':id',
|
|
4616
|
-
loadComponent: () => import('./flusys-ng-iam-action-form-page.component-
|
|
4416
|
+
loadComponent: () => import('./flusys-ng-iam-action-form-page.component-D_vwcCjG.mjs').then((m) => m.ActionFormPageComponent),
|
|
4617
4417
|
},
|
|
4618
4418
|
],
|
|
4619
4419
|
},
|
|
@@ -4624,15 +4424,15 @@ const IAM_ROUTES = [
|
|
|
4624
4424
|
children: [
|
|
4625
4425
|
{
|
|
4626
4426
|
path: '',
|
|
4627
|
-
loadComponent: () => import('./flusys-ng-iam-role-list-page.component-
|
|
4427
|
+
loadComponent: () => import('./flusys-ng-iam-role-list-page.component-CT7CvvHj.mjs').then((m) => m.RoleListPageComponent),
|
|
4628
4428
|
},
|
|
4629
4429
|
{
|
|
4630
4430
|
path: 'new',
|
|
4631
|
-
loadComponent: () => import('./flusys-ng-iam-role-form-page.component-
|
|
4431
|
+
loadComponent: () => import('./flusys-ng-iam-role-form-page.component-49dKMKOj.mjs').then((m) => m.RoleFormPageComponent),
|
|
4632
4432
|
},
|
|
4633
4433
|
{
|
|
4634
4434
|
path: ':id',
|
|
4635
|
-
loadComponent: () => import('./flusys-ng-iam-role-form-page.component-
|
|
4435
|
+
loadComponent: () => import('./flusys-ng-iam-role-form-page.component-49dKMKOj.mjs').then((m) => m.RoleFormPageComponent),
|
|
4636
4436
|
},
|
|
4637
4437
|
],
|
|
4638
4438
|
},
|
|
@@ -4647,7 +4447,7 @@ const IAM_ROUTES = [
|
|
|
4647
4447
|
COMPANY_ACTION_PERMISSIONS.READ,
|
|
4648
4448
|
]),
|
|
4649
4449
|
],
|
|
4650
|
-
loadComponent: () => import('./flusys-ng-iam-permission-page.component-
|
|
4450
|
+
loadComponent: () => import('./flusys-ng-iam-permission-page.component-CZebeUhC.mjs').then((m) => m.PermissionPageComponent),
|
|
4651
4451
|
},
|
|
4652
4452
|
// Default redirect to actions
|
|
4653
4453
|
{
|
|
@@ -4666,4 +4466,4 @@ const IAM_ROUTES = [
|
|
|
4666
4466
|
*/
|
|
4667
4467
|
|
|
4668
4468
|
export { ActionApiService as A, CompanyActionSelectorComponent as C, IAM_MESSAGES as I, LogicBuilderComponent as L, MAX_DROPDOWN_ITEMS as M, PermissionApiService as P, RoleApiService as R, UserRoleSelectorComponent as U, ActionType as a, RoleActionSelectorComponent as b, convertActionToTreeNode as c, UserActionSelectorComponent as d, ActionPermissionLogicService as e, IAM_ROUTES as f, MyPermissionsApiService as g, PermissionStateService as h, ProfilePermissionProviderAdapter as i, provideIamProviders as p };
|
|
4669
|
-
//# sourceMappingURL=flusys-ng-iam-flusys-ng-iam-
|
|
4469
|
+
//# sourceMappingURL=flusys-ng-iam-flusys-ng-iam-C6I4k78L.mjs.map
|