@acorex-platform/module-security-management-client 0.0.15 → 0.0.21
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/fesm2022/acorex-platform-module-security-management-client-acorex-platform-module-security-management-client-jSDkyMMZ.mjs +2088 -0
- package/fesm2022/acorex-platform-module-security-management-client-acorex-platform-module-security-management-client-jSDkyMMZ.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-security-management-client-contracts.mjs +158 -1677
- package/fesm2022/acorex-platform-module-security-management-client-contracts.mjs.map +1 -1
- package/fesm2022/acorex-platform-module-security-management-client-index-DOHWBEzM.mjs +462 -0
- package/fesm2022/acorex-platform-module-security-management-client-index-DOHWBEzM.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-security-management-client-send-password-reset-email.command-CoQgWdya.mjs +81 -0
- package/fesm2022/acorex-platform-module-security-management-client-send-password-reset-email.command-CoQgWdya.mjs.map +1 -0
- package/fesm2022/{acorex-platform-module-security-management-client-terminate-session.command-CKq-4Z4O.mjs → acorex-platform-module-security-management-client-terminate-session.command-dsU7scOK.mjs} +19 -12
- package/fesm2022/acorex-platform-module-security-management-client-terminate-session.command-dsU7scOK.mjs.map +1 -0
- package/fesm2022/acorex-platform-module-security-management-client.mjs +1 -1704
- package/fesm2022/acorex-platform-module-security-management-client.mjs.map +1 -1
- package/package.json +1 -1
- package/types/acorex-platform-module-security-management-client-contracts.d.ts +208 -33
- package/types/acorex-platform-module-security-management-client.d.ts +139 -35
- package/fesm2022/acorex-platform-module-security-management-client-terminate-session.command-CKq-4Z4O.mjs.map +0 -1
|
@@ -0,0 +1,2088 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Injectable, NgModule, inject, signal, input, computed, output, ChangeDetectionStrategy, ViewEncapsulation, Component, effect, untracked } from '@angular/core';
|
|
3
|
+
import { AXPSessionService, AXP_PERMISSION_DEFINITION_PROVIDER, AXPPermissionDefinitionService } from '@acorex-platform/framework-client/auth';
|
|
4
|
+
import { AXPSearchCommandProvider, createEntitySearchCommand, AXP_MENU_PROVIDER, AXP_SEARCH_PROVIDER, AXP_STATUS_PROVIDERS, AXPSettingsService, AXPCommonSettings } from '@acorex-platform/framework-client/common';
|
|
5
|
+
import { AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER, AXP_MODULE_MANIFEST_PROVIDER, AXP_FEATURE_DEFINITION_PROVIDER, AXP_CONTEXT_DEFINITION_PROVIDER, provideLazyProvider, setupPopupFooterKeyboardShortcuts, AXPEntityBroadcastService } from '@acorex-platform/framework-client/core';
|
|
6
|
+
import { AXP_PAGE_COMPONENT_PROVIDER, AXPStateMessageComponent } from '@acorex-platform/framework-client/layout/components';
|
|
7
|
+
import { provideCommandSetups, AXPCommandService } from '@acorex-platform/framework-client/runtime';
|
|
8
|
+
import { AXM_SECURITY_CONTEXT_DEFINITIONS } from '@acorex-platform/module-security-management-contracts';
|
|
9
|
+
import { RootConfig, AXPSecurityManagementFeatureKeys, AXM_USER_ACCESS_STATE_DEFINITION_KEY, AXMUserAccessState, AXM_USER_EFFECTIVE_STATE_DEFINITION_KEY, AXMUserEffectiveState, AXPSecurityManagementMenuKeys, AXPAdministrationPermissionKeys, ROLE_PERMISSIONS_PAGE_COMPONENT_KEY } from '@acorex-platform/module-security-management-client/contracts';
|
|
10
|
+
import { AXPWidgetsList, AXPStatusProvider, AXPSystemStatusType } from '@acorex-platform/framework-shared/core';
|
|
11
|
+
import { AXPWidgetCoreModule, AXP_WIDGET_DEFINITION_PROVIDER, AXPValueWidgetComponent, AXPColumnWidgetComponent } from '@acorex-platform/framework-client/layout/widget-core';
|
|
12
|
+
import { AXPWidgetGroupEnum } from '@acorex-platform/framework-shared/widget-core';
|
|
13
|
+
import { AXP_NAME_PROPERTY, AXP_DATA_PATH_PROPERTY, AXP_DISABLED_PROPERTY } from '@acorex-platform/framework-client/layout/widgets';
|
|
14
|
+
import { AXPCommonMenuKeys, AXPCommonPermissionGroupKeys } from '@acorex-platform/module-common-client/contracts';
|
|
15
|
+
import { AXPEntityService, AXPEntityDefinitionRegistryService, dispatchEntityModifiedSideEffects } from '@acorex-platform/framework-client/layout/entity';
|
|
16
|
+
import * as i1 from '@angular/common';
|
|
17
|
+
import { CommonModule } from '@angular/common';
|
|
18
|
+
import { AXBasePageComponent } from '@acorex/components/page';
|
|
19
|
+
import { AXFormModule } from '@acorex/components/form';
|
|
20
|
+
import * as i4 from '@acorex/components/button';
|
|
21
|
+
import { AXButtonModule } from '@acorex/components/button';
|
|
22
|
+
import { AXTextBoxModule } from '@acorex/components/text-box';
|
|
23
|
+
import * as i6 from '@acorex/components/check-box';
|
|
24
|
+
import { AXCheckBoxModule } from '@acorex/components/check-box';
|
|
25
|
+
import * as i5 from '@acorex/components/label';
|
|
26
|
+
import { AXLabelModule } from '@acorex/components/label';
|
|
27
|
+
import * as i8 from '@acorex/components/decorators';
|
|
28
|
+
import { AXDecoratorModule } from '@acorex/components/decorators';
|
|
29
|
+
import * as i12 from '@acorex/core/translation';
|
|
30
|
+
import { AXTranslationService, AXTranslationModule, resolveMultiLanguageString } from '@acorex/core/translation';
|
|
31
|
+
import * as i7 from '@acorex/components/tabs';
|
|
32
|
+
import { AXTabsModule } from '@acorex/components/tabs';
|
|
33
|
+
import * as i9 from '@acorex/components/badge';
|
|
34
|
+
import { AXBadgeModule } from '@acorex/components/badge';
|
|
35
|
+
import * as i2 from '@angular/forms';
|
|
36
|
+
import { FormsModule } from '@angular/forms';
|
|
37
|
+
import * as i3 from '@acorex/cdk/common';
|
|
38
|
+
import { AXCommonModule } from '@acorex/cdk/common';
|
|
39
|
+
import { AXTooltipModule } from '@acorex/components/tooltip';
|
|
40
|
+
import * as i10 from '@acorex/components/skeleton';
|
|
41
|
+
import { AXSkeletonModule } from '@acorex/components/skeleton';
|
|
42
|
+
import * as i11 from '@acorex/components/search-box';
|
|
43
|
+
import { AXSearchBoxModule } from '@acorex/components/search-box';
|
|
44
|
+
import { isEmpty, trim, isString, isArray, isEqual, castArray } from 'lodash-es';
|
|
45
|
+
import { AXPopupService } from '@acorex/components/popup';
|
|
46
|
+
import { AXToastService } from '@acorex/components/toast';
|
|
47
|
+
import { AXPPageLayoutBaseComponent, AXPPageLayoutBase } from '@acorex-platform/framework-client/layout/views';
|
|
48
|
+
import * as i1$1 from '@acorex/components/loading';
|
|
49
|
+
import { AXLoadingModule } from '@acorex/components/loading';
|
|
50
|
+
|
|
51
|
+
//#region ---- Device Sessions Service ----
|
|
52
|
+
/**
|
|
53
|
+
* Terminates device sessions for the current or specified session.
|
|
54
|
+
* Implementations are provided by connectivity layers (e.g. mock or API).
|
|
55
|
+
*/
|
|
56
|
+
class AXMDeviceSessionsService {
|
|
57
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMDeviceSessionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
58
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMDeviceSessionsService }); }
|
|
59
|
+
}
|
|
60
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMDeviceSessionsService, decorators: [{
|
|
61
|
+
type: Injectable
|
|
62
|
+
}] });
|
|
63
|
+
|
|
64
|
+
//#region ---- User context definitions ----
|
|
65
|
+
class AXMSecurityManagementContextDefinitionProvider {
|
|
66
|
+
async items() {
|
|
67
|
+
return AXM_SECURITY_CONTEXT_DEFINITIONS;
|
|
68
|
+
}
|
|
69
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMSecurityManagementContextDefinitionProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
70
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMSecurityManagementContextDefinitionProvider }); }
|
|
71
|
+
}
|
|
72
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMSecurityManagementContextDefinitionProvider, decorators: [{
|
|
73
|
+
type: Injectable
|
|
74
|
+
}] });
|
|
75
|
+
|
|
76
|
+
//#region ---- Imports ----
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region ---- Feature Definition Provider ----
|
|
79
|
+
/**
|
|
80
|
+
* Security Management Module Feature Definition Provider.
|
|
81
|
+
* Provides feature definitions for the Security Management module.
|
|
82
|
+
*/
|
|
83
|
+
class AXMSecurityManagementFeatureDefinitionProvider {
|
|
84
|
+
async provide(context) {
|
|
85
|
+
const i18n = RootConfig.config.i18n;
|
|
86
|
+
// Manage Roles feature
|
|
87
|
+
context.addFeature(AXPSecurityManagementFeatureKeys.ManageRoles, {
|
|
88
|
+
title: `@${i18n}:features.manage-roles.title`,
|
|
89
|
+
description: `@${i18n}:features.manage-roles.description`,
|
|
90
|
+
defaultValue: true,
|
|
91
|
+
type: 'boolean',
|
|
92
|
+
});
|
|
93
|
+
// Manage Users feature
|
|
94
|
+
context.addFeature(AXPSecurityManagementFeatureKeys.ManageUsers, {
|
|
95
|
+
title: `@${i18n}:features.manage-users.title`,
|
|
96
|
+
description: `@${i18n}:features.manage-users.description`,
|
|
97
|
+
defaultValue: true,
|
|
98
|
+
type: 'boolean',
|
|
99
|
+
});
|
|
100
|
+
// Max Total Users feature (widget configuration)
|
|
101
|
+
context.addFeature(AXPSecurityManagementFeatureKeys.MaxTotalUsers, {
|
|
102
|
+
title: `@${i18n}:features.max-total-users.title`,
|
|
103
|
+
description: `@${i18n}:features.max-total-users.description`,
|
|
104
|
+
defaultValue: 100,
|
|
105
|
+
type: 'number',
|
|
106
|
+
interface: {
|
|
107
|
+
type: AXPWidgetsList.Editors.NumberBox,
|
|
108
|
+
options: {
|
|
109
|
+
placeholder: `@${i18n}:features.max-total-users.title`,
|
|
110
|
+
min: 1,
|
|
111
|
+
max: 10000,
|
|
112
|
+
step: 1,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
// Manage Sessions feature
|
|
117
|
+
context.addFeature(AXPSecurityManagementFeatureKeys.ManageSessions, {
|
|
118
|
+
title: `@${i18n}:features.manage-sessions.title`,
|
|
119
|
+
description: `@${i18n}:features.manage-sessions.description`,
|
|
120
|
+
defaultValue: true,
|
|
121
|
+
type: 'boolean',
|
|
122
|
+
});
|
|
123
|
+
// Manage Policies feature
|
|
124
|
+
context.addFeature(AXPSecurityManagementFeatureKeys.ManagePolicies, {
|
|
125
|
+
title: `@${i18n}:features.manage-policies.title`,
|
|
126
|
+
description: `@${i18n}:features.manage-policies.description`,
|
|
127
|
+
defaultValue: true,
|
|
128
|
+
type: 'boolean',
|
|
129
|
+
});
|
|
130
|
+
// Identity Providers — platform-tenant management only (edition feature on Platform Console)
|
|
131
|
+
context.addFeature(AXPSecurityManagementFeatureKeys.ManageIdentityProviders, {
|
|
132
|
+
title: `@${i18n}:features.manage-identity-providers.title`,
|
|
133
|
+
description: `@${i18n}:features.manage-identity-providers.description`,
|
|
134
|
+
defaultValue: false,
|
|
135
|
+
type: 'boolean',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
|
|
141
|
+
const AXPPermissionsWidget = {
|
|
142
|
+
name: "permissions-editor",
|
|
143
|
+
title: "Permissions Editor",
|
|
144
|
+
description: 'Widget for managing permissions with full editor interface',
|
|
145
|
+
type: 'view',
|
|
146
|
+
categories: [],
|
|
147
|
+
groups: [AXPWidgetGroupEnum.EntityWidget],
|
|
148
|
+
icon: "fa-light fa-user-shield",
|
|
149
|
+
properties: [
|
|
150
|
+
AXP_NAME_PROPERTY,
|
|
151
|
+
AXP_DATA_PATH_PROPERTY,
|
|
152
|
+
AXP_DISABLED_PROPERTY,
|
|
153
|
+
],
|
|
154
|
+
components: {
|
|
155
|
+
view: {
|
|
156
|
+
component: () => Promise.resolve().then(function () { return permissionsWidgetView_component; }).then((c) => c.AXPPermissionsWidgetViewComponent),
|
|
157
|
+
},
|
|
158
|
+
edit: {
|
|
159
|
+
component: () => Promise.resolve().then(function () { return permissionsWidgetEdit_component; }).then((c) => c.AXPPermissionsWidgetEditComponent),
|
|
160
|
+
},
|
|
161
|
+
column: {
|
|
162
|
+
component: () => Promise.resolve().then(function () { return permissionsWidgetColumn_component; }).then((c) => c.AXPPermissionsWidgetColumnComponent),
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region ---- Permissions Widgets Provider ----
|
|
169
|
+
const WIDGETS = [AXPPermissionsWidget];
|
|
170
|
+
class AXPPermissionsWidgetsProvider {
|
|
171
|
+
getWidgets() {
|
|
172
|
+
return WIDGETS;
|
|
173
|
+
}
|
|
174
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPPermissionsWidgetsProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
175
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPPermissionsWidgetsProvider }); }
|
|
176
|
+
}
|
|
177
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPPermissionsWidgetsProvider, decorators: [{
|
|
178
|
+
type: Injectable
|
|
179
|
+
}] });
|
|
180
|
+
|
|
181
|
+
class AXMPermissionsModule {
|
|
182
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPermissionsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
183
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXMPermissionsModule, imports: [AXPWidgetCoreModule] }); }
|
|
184
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPermissionsModule, providers: [
|
|
185
|
+
{ provide: AXP_WIDGET_DEFINITION_PROVIDER, useClass: AXPPermissionsWidgetsProvider, multi: true },
|
|
186
|
+
provideCommandSetups([
|
|
187
|
+
{
|
|
188
|
+
key: 'assign-permissions',
|
|
189
|
+
command: () => Promise.resolve().then(function () { return assignPermissions_command; }).then((c) => c.AXMAssignPermissionsCommand),
|
|
190
|
+
},
|
|
191
|
+
]),
|
|
192
|
+
], imports: [AXPWidgetCoreModule] }); }
|
|
193
|
+
}
|
|
194
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPermissionsModule, decorators: [{
|
|
195
|
+
type: NgModule,
|
|
196
|
+
args: [{
|
|
197
|
+
imports: [
|
|
198
|
+
AXPWidgetCoreModule,
|
|
199
|
+
],
|
|
200
|
+
providers: [
|
|
201
|
+
{ provide: AXP_WIDGET_DEFINITION_PROVIDER, useClass: AXPPermissionsWidgetsProvider, multi: true },
|
|
202
|
+
provideCommandSetups([
|
|
203
|
+
{
|
|
204
|
+
key: 'assign-permissions',
|
|
205
|
+
command: () => Promise.resolve().then(function () { return assignPermissions_command; }).then((c) => c.AXMAssignPermissionsCommand),
|
|
206
|
+
},
|
|
207
|
+
]),
|
|
208
|
+
],
|
|
209
|
+
}]
|
|
210
|
+
}] });
|
|
211
|
+
|
|
212
|
+
class AXMSecurityManagementEvaluatorScopeProvider {
|
|
213
|
+
constructor() {
|
|
214
|
+
this.sessionService = inject(AXPSessionService);
|
|
215
|
+
}
|
|
216
|
+
async provide(context) {
|
|
217
|
+
context.addScope('session', {
|
|
218
|
+
currentUser: () => {
|
|
219
|
+
return this.sessionService.user;
|
|
220
|
+
},
|
|
221
|
+
user: () => {
|
|
222
|
+
return this.sessionService.user;
|
|
223
|
+
},
|
|
224
|
+
currentApplication: () => {
|
|
225
|
+
return this.sessionService.application;
|
|
226
|
+
},
|
|
227
|
+
currentTenant: () => {
|
|
228
|
+
return this.sessionService.tenant;
|
|
229
|
+
},
|
|
230
|
+
currentPermissions: () => {
|
|
231
|
+
return this.sessionService.permissions;
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
//#region ---- Imports ----
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region ---- User access state provider ----
|
|
240
|
+
/**
|
|
241
|
+
* Sign-in readiness from the authentication subsystem (distinct from account lifecycle / `accountState`).
|
|
242
|
+
* Transitions listed here are the only paths offered in the status UI and validated when executing a status change command.
|
|
243
|
+
*/
|
|
244
|
+
class AXMUserAccessStateProvider extends AXPStatusProvider {
|
|
245
|
+
constructor() {
|
|
246
|
+
super(...arguments);
|
|
247
|
+
this.key = AXM_USER_ACCESS_STATE_DEFINITION_KEY;
|
|
248
|
+
this.defaultStatus = AXMUserAccessState.Allowed;
|
|
249
|
+
this.statuses = [
|
|
250
|
+
{
|
|
251
|
+
name: AXMUserAccessState.Allowed,
|
|
252
|
+
title: '@security-management:users.entities.user.access.states.allowed',
|
|
253
|
+
description: '@security-management:users.entities.user.access.descriptions.allowed',
|
|
254
|
+
icon: 'fa-light fa-circle-check',
|
|
255
|
+
color: 'success',
|
|
256
|
+
order: 1,
|
|
257
|
+
isInitial: true,
|
|
258
|
+
isFinal: false,
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: AXMUserAccessState.Blocked,
|
|
262
|
+
title: '@security-management:users.entities.user.access.states.blocked',
|
|
263
|
+
description: '@security-management:users.entities.user.access.descriptions.blocked',
|
|
264
|
+
icon: 'fa-light fa-ban',
|
|
265
|
+
color: 'danger',
|
|
266
|
+
order: 2,
|
|
267
|
+
isInitial: false,
|
|
268
|
+
isFinal: false,
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
name: AXMUserAccessState.Expired,
|
|
272
|
+
title: '@security-management:users.entities.user.access.states.expired',
|
|
273
|
+
description: '@security-management:users.entities.user.access.descriptions.expired',
|
|
274
|
+
icon: 'fa-light fa-clock',
|
|
275
|
+
color: 'danger',
|
|
276
|
+
order: 3,
|
|
277
|
+
isInitial: false,
|
|
278
|
+
isFinal: true,
|
|
279
|
+
generateView: false,
|
|
280
|
+
},
|
|
281
|
+
];
|
|
282
|
+
this.transitions = [
|
|
283
|
+
{
|
|
284
|
+
from: AXMUserAccessState.Allowed,
|
|
285
|
+
to: AXMUserAccessState.Blocked,
|
|
286
|
+
title: '@security-management:users.entities.user.access.transitions.block-sign-in',
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
from: AXMUserAccessState.Allowed,
|
|
290
|
+
to: AXMUserAccessState.Expired,
|
|
291
|
+
title: '@security-management:users.entities.user.access.transitions.require-credential-renewal',
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
from: AXMUserAccessState.Blocked,
|
|
295
|
+
to: AXMUserAccessState.Allowed,
|
|
296
|
+
title: '@security-management:users.entities.user.access.transitions.unblock-sign-in',
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
from: AXMUserAccessState.Expired,
|
|
300
|
+
to: AXMUserAccessState.Allowed,
|
|
301
|
+
title: '@security-management:users.entities.user.access.transitions.restore-after-credential-renewal',
|
|
302
|
+
},
|
|
303
|
+
];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
//#endregion
|
|
307
|
+
|
|
308
|
+
//#region ---- Imports ----
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region ---- User effective state provider ----
|
|
311
|
+
/**
|
|
312
|
+
* Unified operational state for lists, filters, and page chrome (derived from lifecycle + sign-in gate).
|
|
313
|
+
*/
|
|
314
|
+
class AXMUserEffectiveStateProvider extends AXPStatusProvider {
|
|
315
|
+
constructor() {
|
|
316
|
+
super(...arguments);
|
|
317
|
+
this.key = AXM_USER_EFFECTIVE_STATE_DEFINITION_KEY;
|
|
318
|
+
this.defaultStatus = AXMUserEffectiveState.Active;
|
|
319
|
+
this.statuses = [
|
|
320
|
+
{
|
|
321
|
+
name: AXMUserEffectiveState.Pending,
|
|
322
|
+
title: '@security-management:users.entities.user.effective.states.pending',
|
|
323
|
+
description: '@security-management:users.entities.user.effective.descriptions.pending',
|
|
324
|
+
icon: 'fa-light fa-hourglass-half',
|
|
325
|
+
color: 'warning',
|
|
326
|
+
order: 1,
|
|
327
|
+
generateView: true,
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: AXMUserEffectiveState.Active,
|
|
331
|
+
title: '@security-management:users.entities.user.effective.states.active',
|
|
332
|
+
description: '@security-management:users.entities.user.effective.descriptions.active',
|
|
333
|
+
icon: 'fa-light fa-circle-check',
|
|
334
|
+
color: 'success',
|
|
335
|
+
order: 2,
|
|
336
|
+
generateView: true,
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
name: AXMUserEffectiveState.SignInBlocked,
|
|
340
|
+
title: '@security-management:users.entities.user.effective.states.sign-in-blocked',
|
|
341
|
+
description: '@security-management:users.entities.user.effective.descriptions.sign-in-blocked',
|
|
342
|
+
icon: 'fa-light fa-shield-halved',
|
|
343
|
+
color: 'danger',
|
|
344
|
+
order: 3,
|
|
345
|
+
generateView: true,
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
name: AXMUserEffectiveState.CredentialExpired,
|
|
349
|
+
title: '@security-management:users.entities.user.effective.states.credential-expired',
|
|
350
|
+
description: '@security-management:users.entities.user.effective.descriptions.credential-expired',
|
|
351
|
+
icon: 'fa-light fa-key',
|
|
352
|
+
color: 'danger',
|
|
353
|
+
order: 4,
|
|
354
|
+
generateView: true,
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
name: AXMUserEffectiveState.Suspended,
|
|
358
|
+
title: '@security-management:users.entities.user.effective.states.suspended',
|
|
359
|
+
description: '@security-management:users.entities.user.effective.descriptions.suspended',
|
|
360
|
+
icon: 'fa-light fa-pause-circle',
|
|
361
|
+
color: 'warning',
|
|
362
|
+
order: 5,
|
|
363
|
+
generateView: true,
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
name: AXMUserEffectiveState.Inactive,
|
|
367
|
+
title: '@security-management:users.entities.user.effective.states.inactive',
|
|
368
|
+
description: '@security-management:users.entities.user.effective.descriptions.inactive',
|
|
369
|
+
icon: 'fa-light fa-user-slash',
|
|
370
|
+
color: 'danger',
|
|
371
|
+
order: 6,
|
|
372
|
+
generateView: true,
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
name: AXMUserEffectiveState.Archived,
|
|
376
|
+
title: '@security-management:users.entities.user.effective.states.archived',
|
|
377
|
+
description: '@security-management:users.entities.user.effective.descriptions.archived',
|
|
378
|
+
icon: 'fa-light fa-box-archive',
|
|
379
|
+
color: 'danger',
|
|
380
|
+
order: 7,
|
|
381
|
+
generateView: true,
|
|
382
|
+
},
|
|
383
|
+
];
|
|
384
|
+
this.transitions = [];
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
//#endregion
|
|
388
|
+
|
|
389
|
+
//#region ---- Imports ----
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region ---- User account status provider ----
|
|
392
|
+
/**
|
|
393
|
+
* Account-level state for SecurityManagement.User (lifecycle / admin).
|
|
394
|
+
* Sign-in lockout is expressed as `accessState === blocked`, not as `accountState`.
|
|
395
|
+
*/
|
|
396
|
+
class AXMUserStatusProvider extends AXPStatusProvider {
|
|
397
|
+
constructor() {
|
|
398
|
+
super(...arguments);
|
|
399
|
+
this.key = 'security-management.account-state.user';
|
|
400
|
+
this.defaultStatus = AXPSystemStatusType.Active;
|
|
401
|
+
this.statuses = [
|
|
402
|
+
{
|
|
403
|
+
name: AXPSystemStatusType.Pending,
|
|
404
|
+
title: '@security-management:users.entities.user.states.pending',
|
|
405
|
+
description: '@security-management:users.entities.user.account.descriptions.pending',
|
|
406
|
+
icon: 'fa-light fa-envelope-open-text',
|
|
407
|
+
color: 'warning',
|
|
408
|
+
order: 1,
|
|
409
|
+
isInitial: true,
|
|
410
|
+
isFinal: false,
|
|
411
|
+
generateView: true,
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
name: AXPSystemStatusType.Active,
|
|
415
|
+
title: '@security-management:users.entities.user.states.active',
|
|
416
|
+
description: '@security-management:users.entities.user.account.descriptions.active',
|
|
417
|
+
icon: 'fa-light fa-user-check',
|
|
418
|
+
color: 'success',
|
|
419
|
+
order: 1,
|
|
420
|
+
isInitial: false,
|
|
421
|
+
isFinal: false,
|
|
422
|
+
generateView: true,
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
name: AXPSystemStatusType.Suspended,
|
|
426
|
+
title: '@security-management:users.entities.user.states.suspended',
|
|
427
|
+
description: '@security-management:users.entities.user.account.descriptions.suspended',
|
|
428
|
+
icon: 'fa-light fa-pause-circle',
|
|
429
|
+
color: 'warning',
|
|
430
|
+
order: 5,
|
|
431
|
+
isInitial: false,
|
|
432
|
+
isFinal: false,
|
|
433
|
+
generateView: true,
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
name: AXPSystemStatusType.Inactive,
|
|
437
|
+
title: '@security-management:users.entities.user.states.inactive',
|
|
438
|
+
description: '@security-management:users.entities.user.account.descriptions.inactive',
|
|
439
|
+
icon: 'fa-light fa-user-slash',
|
|
440
|
+
color: 'danger',
|
|
441
|
+
order: 2,
|
|
442
|
+
isInitial: false,
|
|
443
|
+
isFinal: false,
|
|
444
|
+
generateView: true,
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
name: AXPSystemStatusType.Archived,
|
|
448
|
+
title: '@security-management:users.entities.user.states.archived',
|
|
449
|
+
description: '@security-management:users.entities.user.account.descriptions.archived',
|
|
450
|
+
icon: 'fa-light fa-box-archive',
|
|
451
|
+
color: 'danger',
|
|
452
|
+
order: 5,
|
|
453
|
+
isInitial: false,
|
|
454
|
+
isFinal: true,
|
|
455
|
+
generateView: true,
|
|
456
|
+
},
|
|
457
|
+
];
|
|
458
|
+
this.transitions = [
|
|
459
|
+
{
|
|
460
|
+
from: AXPSystemStatusType.Pending,
|
|
461
|
+
to: AXPSystemStatusType.Active,
|
|
462
|
+
title: '@security-management:users.entities.user.transitions.activate',
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
from: AXPSystemStatusType.Pending,
|
|
466
|
+
to: AXPSystemStatusType.Inactive,
|
|
467
|
+
title: '@security-management:users.entities.user.transitions.cancel-invitation',
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
from: AXPSystemStatusType.Active,
|
|
471
|
+
to: AXPSystemStatusType.Suspended,
|
|
472
|
+
title: '@security-management:users.entities.user.transitions.suspend',
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
from: AXPSystemStatusType.Suspended,
|
|
476
|
+
to: AXPSystemStatusType.Active,
|
|
477
|
+
title: '@security-management:users.entities.user.transitions.restore',
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
from: AXPSystemStatusType.Active,
|
|
481
|
+
to: AXPSystemStatusType.Inactive,
|
|
482
|
+
title: '@security-management:users.entities.user.transitions.disable',
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
from: AXPSystemStatusType.Inactive,
|
|
486
|
+
to: AXPSystemStatusType.Active,
|
|
487
|
+
title: '@security-management:users.entities.user.transitions.enable',
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
from: AXPSystemStatusType.Suspended,
|
|
491
|
+
to: AXPSystemStatusType.Inactive,
|
|
492
|
+
title: '@security-management:users.entities.user.transitions.disable',
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
from: AXPSystemStatusType.Active,
|
|
496
|
+
to: AXPSystemStatusType.Archived,
|
|
497
|
+
title: '@security-management:users.entities.user.transitions.archive',
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
from: AXPSystemStatusType.Suspended,
|
|
501
|
+
to: AXPSystemStatusType.Archived,
|
|
502
|
+
title: '@security-management:users.entities.user.transitions.archive',
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
from: AXPSystemStatusType.Inactive,
|
|
506
|
+
to: AXPSystemStatusType.Archived,
|
|
507
|
+
title: '@security-management:users.entities.user.transitions.archive',
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
from: AXPSystemStatusType.Pending,
|
|
511
|
+
to: AXPSystemStatusType.Archived,
|
|
512
|
+
title: '@security-management:users.entities.user.transitions.archive',
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
from: AXPSystemStatusType.Archived,
|
|
516
|
+
to: AXPSystemStatusType.Active,
|
|
517
|
+
title: '@security-management:users.entities.user.transitions.restore-archived',
|
|
518
|
+
},
|
|
519
|
+
];
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
//#endregion
|
|
523
|
+
|
|
524
|
+
//#region ---- Imports ----
|
|
525
|
+
//#endregion
|
|
526
|
+
//#region ---- Manifest Definition ----
|
|
527
|
+
/**
|
|
528
|
+
* Security Management Module Manifest.
|
|
529
|
+
* Defines module metadata, features, and provider references.
|
|
530
|
+
*/
|
|
531
|
+
const SecurityManagementManifest = {
|
|
532
|
+
name: RootConfig.module.name,
|
|
533
|
+
version: '1.0.0',
|
|
534
|
+
title: RootConfig.module.title,
|
|
535
|
+
icon: RootConfig.module.icon,
|
|
536
|
+
i18n: RootConfig.config.i18n,
|
|
537
|
+
type: 'platform',
|
|
538
|
+
domain: 'platform',
|
|
539
|
+
required: true,
|
|
540
|
+
// Module dependencies (optional)
|
|
541
|
+
};
|
|
542
|
+
//#endregion
|
|
543
|
+
|
|
544
|
+
class AXMMenuProvider {
|
|
545
|
+
constructor() {
|
|
546
|
+
this.entityService = inject(AXPEntityService);
|
|
547
|
+
}
|
|
548
|
+
async provide(context) {
|
|
549
|
+
const moduleName = RootConfig.module.name;
|
|
550
|
+
// Check if Security submenu exists under Administration, create it if not
|
|
551
|
+
const securityMenu = context.find(AXPSecurityManagementMenuKeys.Root);
|
|
552
|
+
if (!securityMenu.exists) {
|
|
553
|
+
// Add Security submenu to Administration group
|
|
554
|
+
context.find(AXPCommonMenuKeys.Administration).addItems([
|
|
555
|
+
{
|
|
556
|
+
priority: 1,
|
|
557
|
+
text: '@security-management:module.menus.root.title',
|
|
558
|
+
icon: RootConfig.module.icon,
|
|
559
|
+
name: AXPSecurityManagementMenuKeys.Root,
|
|
560
|
+
type: 'menu',
|
|
561
|
+
children: [],
|
|
562
|
+
policy: {
|
|
563
|
+
features: [RootConfig.module.name],
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
]);
|
|
567
|
+
}
|
|
568
|
+
// Add menu items under Security submenu
|
|
569
|
+
context.find(AXPSecurityManagementMenuKeys.Root).addItems([
|
|
570
|
+
{
|
|
571
|
+
priority: 9003,
|
|
572
|
+
text: RootConfig.entities.users.titlePlural,
|
|
573
|
+
icon: RootConfig.entities.users.icon,
|
|
574
|
+
name: AXPSecurityManagementMenuKeys.Users,
|
|
575
|
+
path: this.entityService.createPath(moduleName, RootConfig.entities.users.name),
|
|
576
|
+
policy: {
|
|
577
|
+
features: [moduleName],
|
|
578
|
+
permissions: [AXPAdministrationPermissionKeys.MaintainUsersAndRoles],
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
priority: 9004,
|
|
583
|
+
text: RootConfig.entities.roles.titlePlural,
|
|
584
|
+
path: this.entityService.createPath(moduleName, RootConfig.entities.roles.name),
|
|
585
|
+
icon: RootConfig.entities.roles.icon,
|
|
586
|
+
name: AXPSecurityManagementMenuKeys.Roles,
|
|
587
|
+
policy: {
|
|
588
|
+
features: [moduleName],
|
|
589
|
+
permissions: [AXPAdministrationPermissionKeys.MaintainUsersAndRoles],
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
priority: 9005,
|
|
594
|
+
text: RootConfig.entities.deviceSessions.titlePlural,
|
|
595
|
+
path: this.entityService.createPath(moduleName, RootConfig.entities.deviceSessions.name),
|
|
596
|
+
icon: RootConfig.entities.deviceSessions.icon,
|
|
597
|
+
name: AXPSecurityManagementMenuKeys.DeviceSessions,
|
|
598
|
+
policy: {
|
|
599
|
+
features: [moduleName],
|
|
600
|
+
permissions: [AXPAdministrationPermissionKeys.ManageSessions],
|
|
601
|
+
},
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
priority: 9007,
|
|
605
|
+
text: RootConfig.entities.identityProvider.titlePlural,
|
|
606
|
+
path: this.entityService.createPath(moduleName, RootConfig.entities.identityProvider.name),
|
|
607
|
+
icon: RootConfig.entities.identityProvider.icon,
|
|
608
|
+
name: AXPSecurityManagementMenuKeys.IdentityProviders,
|
|
609
|
+
policy: {
|
|
610
|
+
// Platform Console edition only (not on Safetyminder / tenant apps)
|
|
611
|
+
features: [AXPSecurityManagementFeatureKeys.ManageIdentityProviders],
|
|
612
|
+
permissions: [AXPAdministrationPermissionKeys.MaintainPolicies],
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
// {
|
|
616
|
+
// priority: 9006,
|
|
617
|
+
// text: RootConfig.entities.policy.titlePlural,
|
|
618
|
+
// path: this.entityService.createPath(moduleName, RootConfig.entities.policy.name),
|
|
619
|
+
// icon: RootConfig.entities.policy.icon,
|
|
620
|
+
// name: AXPSecurityManagementMenuKeys.Policy,
|
|
621
|
+
// policy: {
|
|
622
|
+
// permissions: [AXPAdministrationPermissionKeys.MaintainPolicies],
|
|
623
|
+
// },
|
|
624
|
+
// },
|
|
625
|
+
]);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
//#endregion
|
|
630
|
+
//#region ---- Permission Definition Provider ----
|
|
631
|
+
/**
|
|
632
|
+
* Registers Administration root + Security & Access section capabilities.
|
|
633
|
+
*/
|
|
634
|
+
class AXMPermissionDefinitionProvider {
|
|
635
|
+
async define(context) {
|
|
636
|
+
const keys = AXPAdministrationPermissionKeys;
|
|
637
|
+
const groups = AXPCommonPermissionGroupKeys;
|
|
638
|
+
const P = '@common:administration.permissions';
|
|
639
|
+
const G = '@common:permission-groups';
|
|
640
|
+
context.addGroup(groups.Administration.Root, `${G}.administration.title`, `${G}.administration.description`, { order: 120 });
|
|
641
|
+
context
|
|
642
|
+
.addGroup(groups.Administration.SecurityAccess, `${G}.administration-security-access.title`, `${G}.administration-security-access.description`, { parent: groups.Administration.Root, order: 10 })
|
|
643
|
+
.addPermission(keys.Access, `${P}.access.title`, `${P}.access.description`)
|
|
644
|
+
.endPermission()
|
|
645
|
+
.addPermission(keys.MaintainUsersAndRoles, `${P}.maintain-users-and-roles.title`, `${P}.maintain-users-and-roles.description`)
|
|
646
|
+
.endPermission()
|
|
647
|
+
.addPermission(keys.ManageSessions, `${P}.manage-sessions.title`, `${P}.manage-sessions.description`)
|
|
648
|
+
.endPermission()
|
|
649
|
+
.addPermission(keys.MaintainPolicies, `${P}.maintain-policies.title`, `${P}.maintain-policies.description`)
|
|
650
|
+
.endPermission()
|
|
651
|
+
.endGroup();
|
|
652
|
+
}
|
|
653
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPermissionDefinitionProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
654
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPermissionDefinitionProvider }); }
|
|
655
|
+
}
|
|
656
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMPermissionDefinitionProvider, decorators: [{
|
|
657
|
+
type: Injectable
|
|
658
|
+
}] });
|
|
659
|
+
|
|
660
|
+
// TODO: Refactor to generic provider (do it later)
|
|
661
|
+
//
|
|
662
|
+
// Move this logic to a generic AXPEntityCreateSearchCommandProvider in libs/platform/layout/entity.
|
|
663
|
+
// The generic provider should:
|
|
664
|
+
// - Inject AXPEntityDefinitionRegistryService and AXPSessionService
|
|
665
|
+
// - Override search(text) to build commands dynamically from registry
|
|
666
|
+
// - Filter entities: only aggregate roots (commands?.create exists AND !parentKey)
|
|
667
|
+
// - Filter by permission: sessionService.authorize() for both Create and Management
|
|
668
|
+
// Permission convention: `${module}:Permission:${entity.name}.Create` or `.Management`
|
|
669
|
+
// - Use createEntityCommandOptions() with Entity:Create command (same pattern as below)
|
|
670
|
+
// - Register in entity module as AXP_SEARCH_PROVIDER
|
|
671
|
+
// - Remove this and other module-specific search command providers that only add entity-create commands
|
|
672
|
+
class AXMSearchCommandProvider extends AXPSearchCommandProvider {
|
|
673
|
+
constructor() {
|
|
674
|
+
super(...arguments);
|
|
675
|
+
this.commands = [
|
|
676
|
+
createEntitySearchCommand({
|
|
677
|
+
id: 'create-security-user',
|
|
678
|
+
entityKey: `${RootConfig.module.name}.${RootConfig.entities.users.name}`,
|
|
679
|
+
entityInfo: { name: RootConfig.entities.users.name, module: RootConfig.module.name, title: RootConfig.entities.users.title },
|
|
680
|
+
titleKey: '@security-management:users.permissions.create.title',
|
|
681
|
+
descriptionKey: '@security-management:users.permissions.create.description',
|
|
682
|
+
icon: RootConfig.entities.users.icon,
|
|
683
|
+
}),
|
|
684
|
+
createEntitySearchCommand({
|
|
685
|
+
id: 'create-security-role',
|
|
686
|
+
entityKey: `${RootConfig.module.name}.${RootConfig.entities.roles.name}`,
|
|
687
|
+
entityInfo: { name: RootConfig.entities.roles.name, module: RootConfig.module.name, title: RootConfig.entities.roles.title },
|
|
688
|
+
titleKey: '@security-management:roles.permissions.create.title',
|
|
689
|
+
descriptionKey: '@security-management:roles.permissions.create.description',
|
|
690
|
+
icon: RootConfig.entities.roles.icon,
|
|
691
|
+
}),
|
|
692
|
+
];
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
class AXMSecurityManagementModule {
|
|
697
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMSecurityManagementModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
698
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: AXMSecurityManagementModule, imports: [AXMPermissionsModule] }); }
|
|
699
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMSecurityManagementModule, providers: [
|
|
700
|
+
// Module-specific providers (not part of manifest system)
|
|
701
|
+
{
|
|
702
|
+
provide: AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER,
|
|
703
|
+
useClass: AXMSecurityManagementEvaluatorScopeProvider,
|
|
704
|
+
multi: true,
|
|
705
|
+
},
|
|
706
|
+
// Module Manifest Provider
|
|
707
|
+
{
|
|
708
|
+
provide: AXP_MODULE_MANIFEST_PROVIDER,
|
|
709
|
+
useValue: SecurityManagementManifest,
|
|
710
|
+
multi: true,
|
|
711
|
+
},
|
|
712
|
+
provideLazyProvider(AXP_PAGE_COMPONENT_PROVIDER, () => Promise.resolve().then(function () { return index; }).then((m) => m.AXMRolePermissionsPageComponentProvider)),
|
|
713
|
+
provideLazyProvider(AXP_PAGE_COMPONENT_PROVIDER, () => import('./acorex-platform-module-security-management-client-index-DOHWBEzM.mjs').then((m) => m.AXMUserRolesPageComponentProvider)),
|
|
714
|
+
// Provider references (now using tokens)
|
|
715
|
+
{
|
|
716
|
+
provide: AXP_PERMISSION_DEFINITION_PROVIDER,
|
|
717
|
+
useClass: AXMPermissionDefinitionProvider,
|
|
718
|
+
multi: true,
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
provide: AXP_MENU_PROVIDER,
|
|
722
|
+
useClass: AXMMenuProvider,
|
|
723
|
+
multi: true,
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
provide: AXP_SEARCH_PROVIDER,
|
|
727
|
+
useClass: AXMSearchCommandProvider,
|
|
728
|
+
multi: true,
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
provide: AXP_FEATURE_DEFINITION_PROVIDER,
|
|
732
|
+
useClass: AXMSecurityManagementFeatureDefinitionProvider,
|
|
733
|
+
multi: true,
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
provide: AXP_STATUS_PROVIDERS,
|
|
737
|
+
useClass: AXMUserStatusProvider,
|
|
738
|
+
multi: true,
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
provide: AXP_STATUS_PROVIDERS,
|
|
742
|
+
useClass: AXMUserAccessStateProvider,
|
|
743
|
+
multi: true,
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
provide: AXP_STATUS_PROVIDERS,
|
|
747
|
+
useClass: AXMUserEffectiveStateProvider,
|
|
748
|
+
multi: true,
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
provide: AXP_CONTEXT_DEFINITION_PROVIDER,
|
|
752
|
+
useClass: AXMSecurityManagementContextDefinitionProvider,
|
|
753
|
+
multi: true,
|
|
754
|
+
},
|
|
755
|
+
provideCommandSetups([
|
|
756
|
+
{
|
|
757
|
+
key: 'Session:Terminate',
|
|
758
|
+
command: () => import('./acorex-platform-module-security-management-client-terminate-session.command-dsU7scOK.mjs').then((c) => c.AXPTerminateSessionCommand),
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
key: 'SecurityManagement:User:SendPasswordResetEmail',
|
|
762
|
+
command: () => import('./acorex-platform-module-security-management-client-send-password-reset-email.command-CoQgWdya.mjs').then((c) => c.AXPSendPasswordResetEmailCommand),
|
|
763
|
+
},
|
|
764
|
+
]),
|
|
765
|
+
], imports: [AXMPermissionsModule] }); }
|
|
766
|
+
}
|
|
767
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMSecurityManagementModule, decorators: [{
|
|
768
|
+
type: NgModule,
|
|
769
|
+
args: [{
|
|
770
|
+
imports: [
|
|
771
|
+
AXMPermissionsModule,
|
|
772
|
+
],
|
|
773
|
+
exports: [],
|
|
774
|
+
declarations: [],
|
|
775
|
+
providers: [
|
|
776
|
+
// Module-specific providers (not part of manifest system)
|
|
777
|
+
{
|
|
778
|
+
provide: AXP_EXPRESSION_EVALUATOR_SCOPE_PROVIDER,
|
|
779
|
+
useClass: AXMSecurityManagementEvaluatorScopeProvider,
|
|
780
|
+
multi: true,
|
|
781
|
+
},
|
|
782
|
+
// Module Manifest Provider
|
|
783
|
+
{
|
|
784
|
+
provide: AXP_MODULE_MANIFEST_PROVIDER,
|
|
785
|
+
useValue: SecurityManagementManifest,
|
|
786
|
+
multi: true,
|
|
787
|
+
},
|
|
788
|
+
provideLazyProvider(AXP_PAGE_COMPONENT_PROVIDER, () => Promise.resolve().then(function () { return index; }).then((m) => m.AXMRolePermissionsPageComponentProvider)),
|
|
789
|
+
provideLazyProvider(AXP_PAGE_COMPONENT_PROVIDER, () => import('./acorex-platform-module-security-management-client-index-DOHWBEzM.mjs').then((m) => m.AXMUserRolesPageComponentProvider)),
|
|
790
|
+
// Provider references (now using tokens)
|
|
791
|
+
{
|
|
792
|
+
provide: AXP_PERMISSION_DEFINITION_PROVIDER,
|
|
793
|
+
useClass: AXMPermissionDefinitionProvider,
|
|
794
|
+
multi: true,
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
provide: AXP_MENU_PROVIDER,
|
|
798
|
+
useClass: AXMMenuProvider,
|
|
799
|
+
multi: true,
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
provide: AXP_SEARCH_PROVIDER,
|
|
803
|
+
useClass: AXMSearchCommandProvider,
|
|
804
|
+
multi: true,
|
|
805
|
+
},
|
|
806
|
+
{
|
|
807
|
+
provide: AXP_FEATURE_DEFINITION_PROVIDER,
|
|
808
|
+
useClass: AXMSecurityManagementFeatureDefinitionProvider,
|
|
809
|
+
multi: true,
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
provide: AXP_STATUS_PROVIDERS,
|
|
813
|
+
useClass: AXMUserStatusProvider,
|
|
814
|
+
multi: true,
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
provide: AXP_STATUS_PROVIDERS,
|
|
818
|
+
useClass: AXMUserAccessStateProvider,
|
|
819
|
+
multi: true,
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
provide: AXP_STATUS_PROVIDERS,
|
|
823
|
+
useClass: AXMUserEffectiveStateProvider,
|
|
824
|
+
multi: true,
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
provide: AXP_CONTEXT_DEFINITION_PROVIDER,
|
|
828
|
+
useClass: AXMSecurityManagementContextDefinitionProvider,
|
|
829
|
+
multi: true,
|
|
830
|
+
},
|
|
831
|
+
provideCommandSetups([
|
|
832
|
+
{
|
|
833
|
+
key: 'Session:Terminate',
|
|
834
|
+
command: () => import('./acorex-platform-module-security-management-client-terminate-session.command-dsU7scOK.mjs').then((c) => c.AXPTerminateSessionCommand),
|
|
835
|
+
},
|
|
836
|
+
{
|
|
837
|
+
key: 'SecurityManagement:User:SendPasswordResetEmail',
|
|
838
|
+
command: () => import('./acorex-platform-module-security-management-client-send-password-reset-email.command-CoQgWdya.mjs').then((c) => c.AXPSendPasswordResetEmailCommand),
|
|
839
|
+
},
|
|
840
|
+
]),
|
|
841
|
+
],
|
|
842
|
+
}]
|
|
843
|
+
}] });
|
|
844
|
+
|
|
845
|
+
// #region ---------------- Imports ----------------
|
|
846
|
+
// #endregion
|
|
847
|
+
class AXPPermissionsEditorComponent extends AXBasePageComponent {
|
|
848
|
+
// #endregion
|
|
849
|
+
// #region ---------------- Constructor ----------------
|
|
850
|
+
constructor() {
|
|
851
|
+
super();
|
|
852
|
+
// #region ---------------- Class Properties ----------------
|
|
853
|
+
/** Service for fetching permission groups and definitions */
|
|
854
|
+
this.permissionService = inject(AXPPermissionDefinitionService);
|
|
855
|
+
/** Translation service for resolving permission labels during search */
|
|
856
|
+
this.translationService = inject(AXTranslationService);
|
|
857
|
+
/** Currently selected permission group */
|
|
858
|
+
this.selectedGroup = signal(null, /* @ts-ignore */
|
|
859
|
+
...(ngDevMode ? [{ debugName: "selectedGroup" }] : /* istanbul ignore next */ []));
|
|
860
|
+
/** Current search query */
|
|
861
|
+
this.searchQuery = signal('', /* @ts-ignore */
|
|
862
|
+
...(ngDevMode ? [{ debugName: "searchQuery" }] : /* istanbul ignore next */ []));
|
|
863
|
+
/** All available permission groups */
|
|
864
|
+
this.groups = signal([], /* @ts-ignore */
|
|
865
|
+
...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
|
|
866
|
+
/** Initial permissions to be selected - exposed as signal input */
|
|
867
|
+
this.initialPermissions = input([], /* @ts-ignore */
|
|
868
|
+
...(ngDevMode ? [{ debugName: "initialPermissions" }] : /* istanbul ignore next */ []));
|
|
869
|
+
/** Whether the editor is in read-only mode */
|
|
870
|
+
this.readOnly = input(false, /* @ts-ignore */
|
|
871
|
+
...(ngDevMode ? [{ debugName: "readOnly" }] : /* istanbul ignore next */ []));
|
|
872
|
+
/** Whether popup footer actions (Cancel / Select) are shown */
|
|
873
|
+
this.showActions = input(false, /* @ts-ignore */
|
|
874
|
+
...(ngDevMode ? [{ debugName: "showActions" }] : /* istanbul ignore next */ []));
|
|
875
|
+
/** When true, fills the host (detail page) without the dialog max-height cap. */
|
|
876
|
+
this.fillHeight = input(false, /* @ts-ignore */
|
|
877
|
+
...(ngDevMode ? [{ debugName: "fillHeight" }] : /* istanbul ignore next */ []));
|
|
878
|
+
this.hostClass = computed(() => this.fillHeight()
|
|
879
|
+
? 'axm-permissions-editor axm-permissions-editor--fill-height flex h-full min-h-0 flex-col overflow-hidden'
|
|
880
|
+
: 'axm-permissions-editor axm-permissions-editor--dialog flex h-full max-h-[75vh] min-h-0 flex-col overflow-hidden p-4 gap-3', /* @ts-ignore */
|
|
881
|
+
...(ngDevMode ? [{ debugName: "hostClass" }] : /* istanbul ignore next */ []));
|
|
882
|
+
/** List of selected permission names */
|
|
883
|
+
this.selectedPermissions = signal([], /* @ts-ignore */
|
|
884
|
+
...(ngDevMode ? [{ debugName: "selectedPermissions" }] : /* istanbul ignore next */ []));
|
|
885
|
+
/** Event emitted when permissions selection changes */
|
|
886
|
+
this.permissionsChanged = output();
|
|
887
|
+
/** Event emitted when permissions are applied/selected */
|
|
888
|
+
this.permissionsSelected = output();
|
|
889
|
+
/** Whether permission groups are being loaded */
|
|
890
|
+
this.isEditorLoading = signal(true, /* @ts-ignore */
|
|
891
|
+
...(ngDevMode ? [{ debugName: "isEditorLoading" }] : /* istanbul ignore next */ []));
|
|
892
|
+
/**
|
|
893
|
+
* Computed: Left-nav roots only (groups without a parent), sorted by title.
|
|
894
|
+
*/
|
|
895
|
+
this.groupsWithBadge = computed(() => {
|
|
896
|
+
const roots = this.groups().filter((group) => !group.parent);
|
|
897
|
+
const result = roots.map((group) => ({
|
|
898
|
+
...group,
|
|
899
|
+
badge: this.getBadge(group) || '',
|
|
900
|
+
}));
|
|
901
|
+
return result.sort((a, b) => this.compareByTitle(a.title, b.title, a.name, b.name));
|
|
902
|
+
}, /* @ts-ignore */
|
|
903
|
+
...(ngDevMode ? [{ debugName: "groupsWithBadge" }] : /* istanbul ignore next */ []));
|
|
904
|
+
/**
|
|
905
|
+
* Computed: Right-panel sections for the selected root (or one flat block).
|
|
906
|
+
*/
|
|
907
|
+
this.selectedSections = computed(() => {
|
|
908
|
+
const selected = this.selectedGroup();
|
|
909
|
+
if (!selected) {
|
|
910
|
+
return [];
|
|
911
|
+
}
|
|
912
|
+
return this.buildPanelSections(selected, null);
|
|
913
|
+
}, /* @ts-ignore */
|
|
914
|
+
...(ngDevMode ? [{ debugName: "selectedSections" }] : /* istanbul ignore next */ []));
|
|
915
|
+
/** Sidebar group placeholders for the loading skeleton */
|
|
916
|
+
this.groupSkeletonPlaceholders = computed(() => [
|
|
917
|
+
'w-4/5',
|
|
918
|
+
'w-3/5',
|
|
919
|
+
'w-4/5',
|
|
920
|
+
'w-3/5',
|
|
921
|
+
'w-4/5',
|
|
922
|
+
'w-3/5',
|
|
923
|
+
'w-4/5',
|
|
924
|
+
], /* @ts-ignore */
|
|
925
|
+
...(ngDevMode ? [{ debugName: "groupSkeletonPlaceholders" }] : /* istanbul ignore next */ []));
|
|
926
|
+
/** Permission group placeholders (parent + child rows) for the loading skeleton */
|
|
927
|
+
this.permissionGroupSkeletonPlaceholders = computed(() => [
|
|
928
|
+
{ childCount: 4 },
|
|
929
|
+
{ childCount: 4 },
|
|
930
|
+
{ childCount: 2 },
|
|
931
|
+
{ childCount: 4 },
|
|
932
|
+
], /* @ts-ignore */
|
|
933
|
+
...(ngDevMode ? [{ debugName: "permissionGroupSkeletonPlaceholders" }] : /* istanbul ignore next */ []));
|
|
934
|
+
/** Child row placeholders for a permission group skeleton */
|
|
935
|
+
this.childSkeletonPlaceholders = (count) => Array.from({ length: count }, (_, index) => index);
|
|
936
|
+
/** Whether the user is actively searching permissions */
|
|
937
|
+
this.isSearchActive = computed(() => !isEmpty(trim(this.searchQuery())), /* @ts-ignore */
|
|
938
|
+
...(ngDevMode ? [{ debugName: "isSearchActive" }] : /* istanbul ignore next */ []));
|
|
939
|
+
/** Grouped search results across all root permission groups */
|
|
940
|
+
this.searchResultsGrouped = computed(() => {
|
|
941
|
+
const normalizedQuery = trim(this.searchQuery()).toLowerCase();
|
|
942
|
+
if (!normalizedQuery) {
|
|
943
|
+
return [];
|
|
944
|
+
}
|
|
945
|
+
return this.groupsWithBadge()
|
|
946
|
+
.map((group) => ({
|
|
947
|
+
group,
|
|
948
|
+
sections: this.buildPanelSections(group, normalizedQuery),
|
|
949
|
+
}))
|
|
950
|
+
.filter((result) => !isEmpty(result.sections));
|
|
951
|
+
}, /* @ts-ignore */
|
|
952
|
+
...(ngDevMode ? [{ debugName: "searchResultsGrouped" }] : /* istanbul ignore next */ []));
|
|
953
|
+
/** Whether the active search returned any permission groups */
|
|
954
|
+
this.hasSearchResults = computed(() => !isEmpty(this.searchResultsGrouped()), /* @ts-ignore */
|
|
955
|
+
...(ngDevMode ? [{ debugName: "hasSearchResults" }] : /* istanbul ignore next */ []));
|
|
956
|
+
setupPopupFooterKeyboardShortcuts(() => {
|
|
957
|
+
if (!this.isShowActions()) {
|
|
958
|
+
return [];
|
|
959
|
+
}
|
|
960
|
+
return [
|
|
961
|
+
{
|
|
962
|
+
title: '@general:actions.cancel.title',
|
|
963
|
+
command: 'cancel',
|
|
964
|
+
handler: () => this.handleCancelClick(),
|
|
965
|
+
},
|
|
966
|
+
{
|
|
967
|
+
title: '@general:actions.select.title',
|
|
968
|
+
command: 'select',
|
|
969
|
+
handler: () => this.handleSelectClick(),
|
|
970
|
+
},
|
|
971
|
+
];
|
|
972
|
+
}, { id: 'permissions-editor-popup', when: () => this.isShowActions() });
|
|
973
|
+
}
|
|
974
|
+
// #endregion
|
|
975
|
+
// #region ---------------- Computed Properties ----------------
|
|
976
|
+
/**
|
|
977
|
+
* Reads a signal input or a plain value assigned by AXPopupService data binding.
|
|
978
|
+
*/
|
|
979
|
+
readBoundValue(value, fallback) {
|
|
980
|
+
if (typeof value === 'function') {
|
|
981
|
+
return value();
|
|
982
|
+
}
|
|
983
|
+
return (value ?? fallback);
|
|
984
|
+
}
|
|
985
|
+
/** Whether popup footer actions are visible (supports popup data and input binding). */
|
|
986
|
+
isShowActions() {
|
|
987
|
+
return this.readBoundValue(this.showActions, false);
|
|
988
|
+
}
|
|
989
|
+
/** Whether the editor is read-only (supports popup data and input binding). */
|
|
990
|
+
isReadOnlyMode() {
|
|
991
|
+
return this.readBoundValue(this.readOnly, false);
|
|
992
|
+
}
|
|
993
|
+
/** Initial permission selection (supports popup data and input binding). */
|
|
994
|
+
getInitialPermissions() {
|
|
995
|
+
return this.readBoundValue(this.initialPermissions, []);
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Recursively maps permissions to include selection state and children.
|
|
999
|
+
* Sorted alphabetically by translated title.
|
|
1000
|
+
*/
|
|
1001
|
+
mapPermissions(items) {
|
|
1002
|
+
const mapped = (items || []).map((item) => {
|
|
1003
|
+
const children = item.children;
|
|
1004
|
+
return {
|
|
1005
|
+
...item,
|
|
1006
|
+
isSelected: this.isSelected(item.name),
|
|
1007
|
+
children: children?.length ? this.mapPermissions(children) : null,
|
|
1008
|
+
};
|
|
1009
|
+
});
|
|
1010
|
+
return mapped.sort((a, b) => this.compareByTitle(a.title, b.title, a.name, b.name));
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Locale-aware label comparison using the resolved translation when available.
|
|
1014
|
+
*/
|
|
1015
|
+
compareByTitle(titleA, titleB, tieBreakerA = titleA, tieBreakerB = titleB) {
|
|
1016
|
+
const labelA = (this.translationService.translateSync(titleA) || titleA).toLocaleLowerCase();
|
|
1017
|
+
const labelB = (this.translationService.translateSync(titleB) || titleB).toLocaleLowerCase();
|
|
1018
|
+
const byLabel = labelA.localeCompare(labelB);
|
|
1019
|
+
if (byLabel !== 0) {
|
|
1020
|
+
return byLabel;
|
|
1021
|
+
}
|
|
1022
|
+
return tieBreakerA.localeCompare(tieBreakerB);
|
|
1023
|
+
}
|
|
1024
|
+
// #endregion
|
|
1025
|
+
// #region ---------------- Utility Methods ----------------
|
|
1026
|
+
/**
|
|
1027
|
+
* Drops empty roots/sections after session feature filtering in
|
|
1028
|
+
* {@link AXPPermissionDefinitionService}. Keeps a root only when it or a child
|
|
1029
|
+
* section still has at least one permission.
|
|
1030
|
+
*/
|
|
1031
|
+
filterGroupsByEnabledModules(groups) {
|
|
1032
|
+
const withPermissions = new Set(groups.filter((group) => (group.permissions?.length ?? 0) > 0).map((group) => group.name));
|
|
1033
|
+
const parentsOfKept = new Set(groups
|
|
1034
|
+
.filter((group) => withPermissions.has(group.name) && group.parent)
|
|
1035
|
+
.map((group) => group.parent));
|
|
1036
|
+
return groups.filter((group) => withPermissions.has(group.name) || parentsOfKept.has(group.name));
|
|
1037
|
+
}
|
|
1038
|
+
/** Permission keys still visible after edition/feature filtering. */
|
|
1039
|
+
collectAvailablePermissionNames(groups) {
|
|
1040
|
+
const names = new Set();
|
|
1041
|
+
const walk = (permissions) => {
|
|
1042
|
+
for (const permission of permissions) {
|
|
1043
|
+
names.add(permission.name);
|
|
1044
|
+
if (permission.children?.length) {
|
|
1045
|
+
walk(permission.children);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
for (const group of groups) {
|
|
1050
|
+
walk(group.permissions ?? []);
|
|
1051
|
+
}
|
|
1052
|
+
return names;
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Child section groups under a root, sorted alphabetically by translated title.
|
|
1056
|
+
*/
|
|
1057
|
+
getChildSections(root) {
|
|
1058
|
+
return this.groups()
|
|
1059
|
+
.filter((group) => group.parent === root.name)
|
|
1060
|
+
.sort((a, b) => this.compareByTitle(a.title, b.title, a.name, b.name));
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Builds right-panel sections for a root. When `normalizedQuery` is set, keeps
|
|
1064
|
+
* only matching permissions/sections (and sections whose title matches).
|
|
1065
|
+
*/
|
|
1066
|
+
buildPanelSections(root, normalizedQuery) {
|
|
1067
|
+
const children = this.getChildSections(root);
|
|
1068
|
+
const sections = [];
|
|
1069
|
+
const rootMatches = !!normalizedQuery && this.matchesGroup(root, normalizedQuery);
|
|
1070
|
+
if (children.length === 0) {
|
|
1071
|
+
const permissions = normalizedQuery
|
|
1072
|
+
? this.getFilteredGroupPermissions(root, normalizedQuery)
|
|
1073
|
+
: (root.permissions ?? []);
|
|
1074
|
+
if (!isEmpty(permissions)) {
|
|
1075
|
+
sections.push({
|
|
1076
|
+
name: root.name,
|
|
1077
|
+
title: null,
|
|
1078
|
+
items: this.mapPermissions(permissions),
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
return sections;
|
|
1082
|
+
}
|
|
1083
|
+
const rootPermissions = !normalizedQuery || rootMatches
|
|
1084
|
+
? (root.permissions ?? [])
|
|
1085
|
+
: this.getFilteredGroupPermissions(root, normalizedQuery);
|
|
1086
|
+
if (!isEmpty(rootPermissions)) {
|
|
1087
|
+
sections.push({
|
|
1088
|
+
name: `${root.name}__root`,
|
|
1089
|
+
title: null,
|
|
1090
|
+
items: this.mapPermissions(rootPermissions),
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
for (const child of children) {
|
|
1094
|
+
const sectionMatches = !!normalizedQuery && this.matchesGroup(child, normalizedQuery);
|
|
1095
|
+
const permissions = !normalizedQuery || rootMatches || sectionMatches
|
|
1096
|
+
? (child.permissions ?? [])
|
|
1097
|
+
: this.getFilteredGroupPermissions(child, normalizedQuery);
|
|
1098
|
+
if (isEmpty(permissions)) {
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
sections.push({
|
|
1102
|
+
name: child.name,
|
|
1103
|
+
title: child.title,
|
|
1104
|
+
items: this.mapPermissions(permissions),
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
return sections;
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Recursively collects all permission names (parents and children) from an array.
|
|
1111
|
+
*/
|
|
1112
|
+
collectAllPermissionNames(permissions) {
|
|
1113
|
+
let names = [];
|
|
1114
|
+
for (const item of permissions) {
|
|
1115
|
+
names.push(item.name);
|
|
1116
|
+
if (item.children?.length) {
|
|
1117
|
+
names = names.concat(this.collectAllPermissionNames(item.children));
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
return names;
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Returns the badge count for a root (own permissions + child section permissions).
|
|
1124
|
+
*/
|
|
1125
|
+
getBadge(group) {
|
|
1126
|
+
const ownNames = this.collectAllPermissionNames(group.permissions || []);
|
|
1127
|
+
const childNames = this.getChildSections(group).flatMap((child) => this.collectAllPermissionNames(child.permissions || []));
|
|
1128
|
+
const allNames = [...ownNames, ...childNames];
|
|
1129
|
+
const selectedPermissions = this.selectedPermissions();
|
|
1130
|
+
const count = allNames.filter((name) => selectedPermissions.includes(name)).length;
|
|
1131
|
+
return count.toString();
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Checks if a permission is selected.
|
|
1135
|
+
*/
|
|
1136
|
+
isSelected(name) {
|
|
1137
|
+
return this.selectedPermissions().includes(name);
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Checks if a permission is selected.
|
|
1141
|
+
*/
|
|
1142
|
+
badgeToNumber(value) {
|
|
1143
|
+
return Number(value);
|
|
1144
|
+
}
|
|
1145
|
+
/** Looks up a left-nav root group by tab key (group name). */
|
|
1146
|
+
getGroupByKey(key) {
|
|
1147
|
+
if (!key) {
|
|
1148
|
+
return undefined;
|
|
1149
|
+
}
|
|
1150
|
+
return this.groupsWithBadge().find((group) => group.name === key);
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Returns true when searchable text contains the query.
|
|
1154
|
+
*/
|
|
1155
|
+
matchesSearchText(text, normalizedQuery) {
|
|
1156
|
+
if (!text) {
|
|
1157
|
+
return false;
|
|
1158
|
+
}
|
|
1159
|
+
const translated = this.translationService.translateSync(text) || text;
|
|
1160
|
+
return (translated.toLowerCase().includes(normalizedQuery) ||
|
|
1161
|
+
text.toLowerCase().includes(normalizedQuery));
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Returns true when a permission title or description matches the query.
|
|
1165
|
+
*/
|
|
1166
|
+
matchesPermission(permission, normalizedQuery) {
|
|
1167
|
+
return (this.matchesSearchText(permission.title, normalizedQuery) ||
|
|
1168
|
+
this.matchesSearchText(permission.description, normalizedQuery));
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Returns true when a permission group title or description matches the query.
|
|
1172
|
+
*/
|
|
1173
|
+
matchesGroup(group, normalizedQuery) {
|
|
1174
|
+
return (this.matchesSearchText(group.title, normalizedQuery) ||
|
|
1175
|
+
this.matchesSearchText(group.description, normalizedQuery));
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Filters permissions while keeping ancestor nodes for matching descendants.
|
|
1179
|
+
*/
|
|
1180
|
+
filterPermissions(items, normalizedQuery) {
|
|
1181
|
+
const result = [];
|
|
1182
|
+
for (const item of items) {
|
|
1183
|
+
const filteredChildren = this.filterPermissions(item.children ?? [], normalizedQuery);
|
|
1184
|
+
const selfMatches = this.matchesPermission(item, normalizedQuery);
|
|
1185
|
+
if (selfMatches || !isEmpty(filteredChildren)) {
|
|
1186
|
+
result.push({
|
|
1187
|
+
...item,
|
|
1188
|
+
children: filteredChildren,
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return result;
|
|
1193
|
+
}
|
|
1194
|
+
/**
|
|
1195
|
+
* Returns filtered permissions for a group, or all permissions when the group itself matches.
|
|
1196
|
+
*/
|
|
1197
|
+
getFilteredGroupPermissions(group, normalizedQuery) {
|
|
1198
|
+
const permissions = group.permissions ?? [];
|
|
1199
|
+
if (this.matchesGroup(group, normalizedQuery)) {
|
|
1200
|
+
return permissions;
|
|
1201
|
+
}
|
|
1202
|
+
return this.filterPermissions(permissions, normalizedQuery);
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Recursively collects all descendant permission names for a single item (including itself).
|
|
1206
|
+
*/
|
|
1207
|
+
collectPermissionNames(item) {
|
|
1208
|
+
let names = [item.name];
|
|
1209
|
+
if (item.children) {
|
|
1210
|
+
for (const child of item.children) {
|
|
1211
|
+
names = names.concat(this.collectPermissionNames(child));
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
return names;
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Gets the current selected permissions.
|
|
1218
|
+
*/
|
|
1219
|
+
getSelectedPermissions() {
|
|
1220
|
+
return this.selectedPermissions();
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Sets the selected permissions.
|
|
1224
|
+
*/
|
|
1225
|
+
setSelectedPermissions(permissions) {
|
|
1226
|
+
this.selectedPermissions.set(permissions || []);
|
|
1227
|
+
this.permissionsChanged.emit(this.selectedPermissions());
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Applies/selects the current permissions.
|
|
1231
|
+
*/
|
|
1232
|
+
selectPermissions() {
|
|
1233
|
+
this.permissionsSelected.emit(this.selectedPermissions());
|
|
1234
|
+
}
|
|
1235
|
+
// #endregion
|
|
1236
|
+
// #region ---------------- Lifecycle Methods ----------------
|
|
1237
|
+
/**
|
|
1238
|
+
* Initializes the component and loads permission groups.
|
|
1239
|
+
* Filters groups to only show permissions from modules enabled for the current tenant.
|
|
1240
|
+
*/
|
|
1241
|
+
async ngOnInit() {
|
|
1242
|
+
super.ngOnInit();
|
|
1243
|
+
try {
|
|
1244
|
+
const allGroups = await this.permissionService.getGroups();
|
|
1245
|
+
const filteredGroups = this.filterGroupsByEnabledModules(allGroups);
|
|
1246
|
+
this.groups.set(filteredGroups);
|
|
1247
|
+
// Select first left-nav root in the same title-sorted order as the sidebar
|
|
1248
|
+
const firstRoot = [...filteredGroups]
|
|
1249
|
+
.filter((group) => !group.parent)
|
|
1250
|
+
.sort((a, b) => this.compareByTitle(a.title, b.title, a.name, b.name))[0];
|
|
1251
|
+
if (firstRoot) {
|
|
1252
|
+
this.selectedGroup.set(firstRoot);
|
|
1253
|
+
}
|
|
1254
|
+
// Drop grants for permissions not available in this edition (e.g. platform caps in Safetyminder).
|
|
1255
|
+
const available = this.collectAvailablePermissionNames(filteredGroups);
|
|
1256
|
+
const initial = this.getInitialPermissions().filter((name) => available.has(name));
|
|
1257
|
+
this.selectedPermissions.set(initial);
|
|
1258
|
+
this.permissionsChanged.emit(initial);
|
|
1259
|
+
}
|
|
1260
|
+
finally {
|
|
1261
|
+
this.isEditorLoading.set(false);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
// #endregion
|
|
1265
|
+
// #region ---------------- UI Handlers ----------------
|
|
1266
|
+
/**
|
|
1267
|
+
* Handles group tab/menu item click.
|
|
1268
|
+
*/
|
|
1269
|
+
handleMenuItemClick(item) {
|
|
1270
|
+
this.selectedGroup.set(item);
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Updates the search query when the search box value changes.
|
|
1274
|
+
*/
|
|
1275
|
+
handleSearchChanged(event) {
|
|
1276
|
+
this.searchQuery.set(event.value ?? '');
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Handles permission checkbox toggle (checks/unchecks all children recursively).
|
|
1280
|
+
*/
|
|
1281
|
+
onPermissionToggle(item, e) {
|
|
1282
|
+
// Don't allow changes in read-only mode
|
|
1283
|
+
if (this.isReadOnlyMode()) {
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
const names = this.collectPermissionNames(item);
|
|
1287
|
+
if (e.value) {
|
|
1288
|
+
// Add all names (parent + children), avoiding duplicates
|
|
1289
|
+
const set = new Set([...this.selectedPermissions(), ...names]);
|
|
1290
|
+
this.selectedPermissions.set(Array.from(set));
|
|
1291
|
+
}
|
|
1292
|
+
else {
|
|
1293
|
+
// Remove all names (parent + children)
|
|
1294
|
+
this.selectedPermissions.set(this.selectedPermissions().filter((name) => !names.includes(name)));
|
|
1295
|
+
}
|
|
1296
|
+
this.permissionsChanged.emit(this.selectedPermissions());
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Closes the popup without applying permission changes.
|
|
1300
|
+
*/
|
|
1301
|
+
handleCancelClick() {
|
|
1302
|
+
this.close();
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Applies the current selection and closes the popup.
|
|
1306
|
+
*/
|
|
1307
|
+
handleSelectClick() {
|
|
1308
|
+
const permissions = this.getSelectedPermissions();
|
|
1309
|
+
this.permissionsSelected.emit(permissions);
|
|
1310
|
+
this.close(permissions);
|
|
1311
|
+
}
|
|
1312
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPPermissionsEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1313
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPPermissionsEditorComponent, isStandalone: true, selector: "axm-permissions-editor", inputs: { initialPermissions: { classPropertyName: "initialPermissions", publicName: "initialPermissions", isSignal: true, isRequired: false, transformFunction: null }, readOnly: { classPropertyName: "readOnly", publicName: "readOnly", isSignal: true, isRequired: false, transformFunction: null }, showActions: { classPropertyName: "showActions", publicName: "showActions", isSignal: true, isRequired: false, transformFunction: null }, fillHeight: { classPropertyName: "fillHeight", publicName: "fillHeight", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { permissionsChanged: "permissionsChanged", permissionsSelected: "permissionsSelected" }, host: { properties: { "class": "hostClass()", "attr.aria-busy": "isEditorLoading() ? \"true\" : null" } }, usesInheritance: true, ngImport: i0, template: "<div class=\"__frame\">\n @if (isEditorLoading()) {\n <!-- ----- Skeleton while permission groups load ----- -->\n <div class=\"__body flex min-h-0 flex-1 overflow-hidden\">\n <div class=\"__groups p-4 w-2/5\" aria-hidden=\"true\">\n <div class=\"flex flex-col gap-3 h-full\">\n @for (widthClass of groupSkeletonPlaceholders(); track $index) {\n <div class=\"flex items-center py-2\">\n <div class=\"h-5 rounded-md overflow-hidden\" [class]=\"widthClass\">\n <ax-skeleton\n [animated]=\"true\"\n style=\"display: block; width: 100%; height: 100%\"\n ></ax-skeleton>\n </div>\n </div>\n }\n </div>\n </div>\n\n <div\n class=\"__permissions flex w-3/5 flex-col overflow-hidden border-s\"\n aria-hidden=\"true\"\n >\n <div class=\"__header p-4 border-b border-lightest\">\n <div class=\"h-6 w-48 rounded-md overflow-hidden\">\n <ax-skeleton\n [animated]=\"true\"\n style=\"display: block; width: 100%; height: 100%\"\n ></ax-skeleton>\n </div>\n </div>\n\n <div class=\"__content flex flex-col gap-1 p-4\">\n @for (group of permissionGroupSkeletonPlaceholders(); track $index) {\n <div class=\"__skeleton-permission-group\">\n <div class=\"flex items-center gap-2 py-1\">\n <div class=\"h-4 w-4 shrink-0 rounded-sm overflow-hidden\">\n <ax-skeleton\n [animated]=\"true\"\n style=\"display: block; width: 100%; height: 100%\"\n ></ax-skeleton>\n </div>\n <div class=\"h-4 w-40 max-w-full rounded-md overflow-hidden\">\n <ax-skeleton\n [animated]=\"true\"\n style=\"display: block; width: 100%; height: 100%\"\n ></ax-skeleton>\n </div>\n <div class=\"h-3 w-3 shrink-0 rounded-full overflow-hidden\">\n <ax-skeleton\n [animated]=\"true\"\n style=\"display: block; width: 100%; height: 100%\"\n ></ax-skeleton>\n </div>\n </div>\n\n <div class=\"__children p-2 mt-1 ms-1\">\n @for (\n childIndex of childSkeletonPlaceholders(group.childCount);\n track childIndex\n ) {\n <div class=\"flex items-center gap-2 py-1 ps-2\">\n <div class=\"h-4 w-4 shrink-0 rounded-sm overflow-hidden\">\n <ax-skeleton\n [animated]=\"true\"\n style=\"display: block; width: 100%; height: 100%\"\n ></ax-skeleton>\n </div>\n <div class=\"h-4 w-32 max-w-full rounded-md overflow-hidden\">\n <ax-skeleton\n [animated]=\"true\"\n style=\"display: block; width: 100%; height: 100%\"\n ></ax-skeleton>\n </div>\n <div class=\"h-3 w-3 shrink-0 rounded-full overflow-hidden\">\n <ax-skeleton\n [animated]=\"true\"\n style=\"display: block; width: 100%; height: 100%\"\n ></ax-skeleton>\n </div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n }\n <!-- ----- Loaded editor: search + group/permission columns ----- -->\n @else {\n <div class=\"__search px-4 pt-4 pb-3 border-b border-border-lightest\">\n <ax-search-box\n [axAutoFocus]\n [ngModel]=\"searchQuery()\"\n (onValueChanged)=\"handleSearchChanged($event)\"\n [placeholder]=\"\n '@security-management:permissions-feature.components.permissions-editor.search.placeholder'\n | translate\n | async\n \"\n >\n <ax-clear-button></ax-clear-button>\n </ax-search-box>\n </div>\n\n <div class=\"__body flex min-h-0 flex-1 overflow-hidden\">\n @if (isSearchActive()) {\n <!-- ----- Search results fill both columns ----- -->\n <div\n class=\"__permissions __permissions--search flex min-h-0 flex-1 flex-col overflow-hidden\"\n >\n @if (hasSearchResults()) {\n <div class=\"__search-results flex min-h-0 flex-1 flex-col overflow-y-auto\">\n @for (result of searchResultsGrouped(); track result.group.name) {\n <section class=\"__search-group shrink-0\">\n <div\n class=\"__header shrink-0 p-4 border-b border-border-lightest\"\n >\n <span class=\"h6\">{{\n result.group.title | translate | async\n }}</span>\n </div>\n\n <div class=\"__content flex flex-col gap-4 p-4\">\n @for (section of result.sections; track section.name) {\n <section class=\"__section\">\n @if (section.title) {\n <div class=\"__section-header\">\n <span class=\"__section-title\">{{\n section.title | translate | async\n }}</span>\n </div>\n }\n <ul class=\"__list\">\n <ng-container\n *ngTemplateOutlet=\"\n recursiveList;\n context: { $implicit: section.items }\n \"\n ></ng-container>\n </ul>\n </section>\n }\n </div>\n </section>\n }\n </div>\n }\n <!-- ----- Empty search results ----- -->\n @else {\n <div class=\"__empty flex flex-1 items-center justify-center p-8\">\n <axp-state-message\n icon=\"fa-light fa-search\"\n [title]=\"\n '@security-management:permissions-feature.components.permissions-editor.empty-states.no-results.title'\n \"\n [description]=\"\n '@security-management:permissions-feature.components.permissions-editor.empty-states.no-results.description'\n \"\n >\n </axp-state-message>\n </div>\n }\n </div>\n }\n <!-- ----- Browse mode: groups nav + permissions panel ----- -->\n @else {\n <div class=\"__groups w-2/5\">\n <ax-tabs location=\"start\" look=\"with-line-color\" class=\"__group-tabs\">\n @for (group of groupsWithBadge(); track group.name) {\n <ax-tab-item\n [active]=\"group.name === selectedGroup()?.name\"\n [key]=\"group.name\"\n [text]=\"(group.title | translate | async)!\"\n [headerTemplate]=\"groupTabHeader\"\n (onClick)=\"handleMenuItemClick(group)\"\n >\n </ax-tab-item>\n }\n </ax-tabs>\n </div>\n\n <div class=\"__permissions flex w-3/5 flex-col overflow-hidden border-s border-border-lightest\">\n <div class=\"__header shrink-0 p-4 border-b border-border-lightest font-semibold\">\n <span>{{ $safeNavigationMigration(selectedGroup()?.title)! | translate | async }}</span>\n </div>\n\n <div class=\"__content flex flex-col gap-4 p-4\">\n @for (section of selectedSections(); track section.name) {\n <section class=\"__section\">\n @if (section.title) {\n <div class=\"__section-header\">\n <span class=\"__section-title\">{{\n section.title | translate | async\n }}</span>\n </div>\n }\n <ul class=\"__list\">\n <ng-container\n *ngTemplateOutlet=\"\n recursiveList;\n context: { $implicit: section.items }\n \"\n ></ng-container>\n </ul>\n </section>\n }\n </div>\n </div>\n }\n </div>\n }\n</div>\n\n@if (isShowActions()) {\n <!-- ----- Dialog footer actions ----- -->\n <ax-footer>\n <ax-suffix>\n <ax-button\n look=\"solid\"\n [text]=\"'@general:actions.cancel.title' | translate | async\"\n (onClick)=\"handleCancelClick()\"\n ></ax-button>\n <ax-button\n look=\"solid\"\n color=\"primary\"\n [text]=\"'@general:actions.select.title' | translate | async\"\n (onClick)=\"handleSelectClick()\"\n >\n <ax-prefix>\n <i class=\"fa-solid fa-check\"></i>\n </ax-prefix>\n </ax-button>\n </ax-suffix>\n </ax-footer>\n}\n\n<!-- ----- Left-nav group tab: title + description + badge ----- -->\n<ng-template #groupTabHeader let-ctx>\n @if (getGroupByKey(ctx.key); as group) {\n <div class=\"__group-tab\">\n <div class=\"__group-tab-main\">\n <div class=\"__group-tab-title\">{{ ctx.text }}</div>\n @if (group.description) {\n <div class=\"__group-tab-description\">\n {{ group.description | translate | async }}\n </div>\n }\n </div>\n @if (badgeToNumber(group.badge) > 0) {\n <ax-badge\n class=\"__group-tab-badge\"\n [color]=\"'primary'\"\n [look]=\"'twotone'\"\n [text]=\"group.badge\"\n ></ax-badge>\n }\n </div>\n }\n</ng-template>\n\n<ng-template #recursiveList let-items>\n @for (item of items; track $index) {\n <li class=\"__item\">\n <ax-check-box\n [ngModel]=\"item.isSelected\"\n [readonly]=\"isReadOnlyMode()\"\n (onValueChanged)=\"onPermissionToggle(item, $event)\"\n >\n <ax-label>\n <span class=\"__permission-label\">\n <span class=\"__permission-title\">{{\n item.title | translate | async\n }}</span>\n @if (item.description) {\n <span class=\"__permission-description\">{{\n item.description | translate | async\n }}</span>\n }\n </span>\n </ax-label>\n </ax-check-box>\n\n @if (item.children && item.children.length > 0) {\n <ul class=\"__children p-2 mt-1 ms-1\">\n <ng-container\n *ngTemplateOutlet=\"\n recursiveList;\n context: { $implicit: item.children }\n \"\n ></ng-container>\n </ul>\n }\n </li>\n }\n</ng-template>\n", styles: ["@layer properties;.axm-permissions-editor{display:flex;height:100%;max-height:75vh;min-height:0px;flex-direction:column;overflow:hidden}.axm-permissions-editor.--fill-height{max-height:none}.axm-permissions-editor .__frame{display:flex;min-height:0px;flex:1;flex-direction:column;overflow:hidden;border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-border-default);background-color:rgba(var(--ax-sys-color-lightest-surface))}.axm-permissions-editor .__search{flex-shrink:0}.axm-permissions-editor .__body{display:flex;min-height:0px;flex:1;overflow:hidden}.axm-permissions-editor .__groups{position:relative;display:flex;min-height:0px;flex-direction:column;overflow:hidden}.axm-permissions-editor .__groups ax-tabs.__group-tabs{height:100%;min-height:0px;width:100%;gap:0!important;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:rgba(var(--ax-sys-color-border-light-surface, 150 150 150),.7) transparent;scrollbar-gutter:auto}.axm-permissions-editor .__groups ax-tabs.__group-tabs::-webkit-scrollbar{width:6px}.axm-permissions-editor .__groups ax-tabs.__group-tabs::-webkit-scrollbar-track{background:transparent}.axm-permissions-editor .__groups ax-tabs.__group-tabs::-webkit-scrollbar-thumb{background-color:rgba(var(--ax-sys-color-border-light-surface, 150 150 150),.7);border-radius:999px;border:2px solid transparent;background-clip:content-box}.axm-permissions-editor .__groups ax-tabs.__group-tabs .ax-tabs-scroll-area{gap:0!important;width:100%;min-width:0;flex:none}.axm-permissions-editor .__groups ax-tabs.__group-tabs.ax-start,.axm-permissions-editor .__groups ax-tabs.__group-tabs.ax-end{align-items:stretch}.axm-permissions-editor .__groups ax-tabs.__group-tabs ax-tab-item{border-radius:0!important}.axm-permissions-editor .__groups ax-tabs.__group-tabs.ax-with-line.ax-start ax-tab-item,.axm-permissions-editor .__groups ax-tabs.__group-tabs.ax-with-line-color.ax-start ax-tab-item{align-items:flex-start;width:100%;padding-block:.375rem!important;padding-inline:.75rem!important}.axm-permissions-editor .__groups ax-tabs.__group-tabs .__group-tab{position:relative;display:flex;width:100%;align-items:flex-start;padding-inline-end:1.75rem}.axm-permissions-editor .__groups ax-tabs.__group-tabs .__group-tab .__group-tab-main{display:flex;min-width:0px;flex:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * .5)}.axm-permissions-editor .__groups ax-tabs.__group-tabs .__group-tab .__group-tab-title{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-leading: var(--leading-snug, 1.375);line-height:var(--leading-snug, 1.375);--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500)}.axm-permissions-editor .__groups ax-tabs.__group-tabs .__group-tab .__group-tab-description{font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: var(--leading-snug, 1.375);line-height:var(--leading-snug, 1.375);--tw-font-weight: var(--font-weight-normal, 400);font-weight:var(--font-weight-normal, 400);white-space:normal;color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axm-permissions-editor .__groups ax-tabs.__group-tabs .__group-tab .__group-tab-description{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axm-permissions-editor .__groups ax-tabs.__group-tabs .__group-tab .__group-tab-badge{position:absolute;inset-inline-end:0;top:.125rem}.axm-permissions-editor .__permissions{display:flex;min-height:0px;flex-direction:column;overflow:hidden}.axm-permissions-editor .__permissions .__header{flex-shrink:0}.axm-permissions-editor .__permissions .__header .__title{font-size:1rem;font-weight:500;line-height:1rem}.axm-permissions-editor .__permissions .__content{min-height:0px;flex:1;overflow-y:auto;overscroll-behavior:contain;padding-inline-start:1.25rem}.axm-permissions-editor .__permissions .__content .__section{display:flex;flex-direction:column;gap:var(--spacing, .25rem)}.axm-permissions-editor .__permissions .__content .__section .__section-header{margin-bottom:var(--spacing, .25rem);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-border-lightest-surface));padding-bottom:var(--spacing, .25rem)}.axm-permissions-editor .__permissions .__content .__section .__section-header .__section-title{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axm-permissions-editor .__permissions .__content .__section .__section-header .__section-title{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axm-permissions-editor .__permissions .__content .__list{width:100%}.axm-permissions-editor .__permissions .__content .__list .__item{padding-block:var(--spacing, .25rem);padding-inline-start:var(--spacing, .25rem)}.axm-permissions-editor .__permissions .__content .__list .__item ax-check-box{display:block;width:100%}.axm-permissions-editor .__permissions .__content .__list .__item ax-check-box .ax-checkbox-label{align-items:flex-start;width:100%}.axm-permissions-editor .__permissions .__content .__list .__item ax-check-box .ax-checkbox-start-side{flex:none;align-self:flex-start;padding-top:4px}.axm-permissions-editor .__permissions .__content .__list .__item ax-check-box .ax-checkbox-end-side{min-width:0;flex:1}.axm-permissions-editor .__permissions .__content .__list .__item .__permission-label{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * .5)}.axm-permissions-editor .__permissions .__content .__list .__item .__permission-label .__permission-title{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);line-height:calc(var(--spacing, .25rem) * 5)}.axm-permissions-editor .__permissions .__content .__list .__item .__permission-label .__permission-description{font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: var(--leading-snug, 1.375);line-height:var(--leading-snug, 1.375);--tw-font-weight: var(--font-weight-normal, 400);font-weight:var(--font-weight-normal, 400);white-space:normal;color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axm-permissions-editor .__permissions .__content .__list .__item .__permission-label .__permission-description{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axm-permissions-editor .__permissions .__content .__list .__item .__children{margin-inline-start:calc(var(--spacing, .25rem) * 2);margin-top:var(--spacing, .25rem);border-left-style:var(--tw-border-style);border-left-width:1px;--tw-border-style: dashed;border-style:dashed;border-color:rgba(var(--ax-sys-color-border-lighter-surface));padding-inline-start:calc(var(--spacing, .25rem) * 2)}.axm-permissions-editor .__permissions.__permissions--search{width:100%}.axm-permissions-editor .__permissions.__permissions--search .__search-results{min-height:0px;width:100%;flex:1;overflow-y:auto;overscroll-behavior:contain}.axm-permissions-editor .__permissions.__permissions--search .__search-group .__content{flex:none;overflow:visible;min-height:0}.axm-permissions-editor .__permissions.__permissions--search .__search-group:not(:last-child){border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface))}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-leading{syntax: \"*\"; inherits: false;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-leading: initial;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "ngmodule", type:
|
|
1314
|
+
// Angular
|
|
1315
|
+
CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AXCommonModule }, { kind: "directive", type: i3.AXAutoFocusDirective, selector: "[axAutoFocus]", inputs: ["axAutoFocus", "axAutoFocusTime"] }, { kind: "ngmodule", type:
|
|
1316
|
+
// ACoreX
|
|
1317
|
+
AXFormModule }, { kind: "ngmodule", type: AXTextBoxModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i4.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "size", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXLabelModule }, { kind: "component", type: i5.AXLabelComponent, selector: "ax-label", inputs: ["required", "for"], outputs: ["requiredChange"] }, { kind: "ngmodule", type: AXCheckBoxModule }, { kind: "component", type: i6.AXCheckBoxComponent, selector: "ax-check-box", inputs: ["disabled", "tabIndex", "readonly", "color", "value", "name", "id", "isLoading", "indeterminate"], outputs: ["onBlur", "onFocus", "valueChange", "onValueChanged"] }, { kind: "ngmodule", type: AXTabsModule }, { kind: "component", type: i7.AXTabsComponent, selector: "ax-tabs", inputs: ["look", "location", "fitParent", "minWidth", "content"], outputs: ["onActiveTabChanged"] }, { kind: "component", type: i7.AXTabItemComponent, selector: "ax-tab-item", inputs: ["disabled", "text", "key", "headerTemplate", "active"], outputs: ["disabledChange", "onClick", "onBlur", "onFocus", "activeChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i8.AXDecoratorClearButtonComponent, selector: "ax-clear-button", inputs: ["icon"] }, { kind: "component", type: i8.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i9.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXTooltipModule }, { kind: "ngmodule", type: AXSkeletonModule }, { kind: "component", type: i10.AXSkeletonComponent, selector: "ax-skeleton", inputs: ["animated"] }, { kind: "ngmodule", type: AXSearchBoxModule }, { kind: "component", type: i11.AXSearchBoxComponent, selector: "ax-search-box", inputs: ["disabled", "readonly", "tabIndex", "placeholder", "value", "state", "name", "id", "look", "class", "delayTime", "type", "autoSearch"], outputs: ["valueChange", "stateChange", "onValueChanged", "onBlur", "onFocus", "readonlyChange", "disabledChange", "onKeyDown", "onKeyUp", "onKeyPress"] }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "titleParams", "descriptionParams", "look"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i12.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.Eager, encapsulation: i0.ViewEncapsulation.None }); }
|
|
1318
|
+
}
|
|
1319
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPPermissionsEditorComponent, decorators: [{
|
|
1320
|
+
type: Component,
|
|
1321
|
+
args: [{ selector: 'axm-permissions-editor', imports: [
|
|
1322
|
+
// Angular
|
|
1323
|
+
CommonModule,
|
|
1324
|
+
FormsModule,
|
|
1325
|
+
AXCommonModule,
|
|
1326
|
+
// ACoreX
|
|
1327
|
+
AXFormModule,
|
|
1328
|
+
AXTextBoxModule,
|
|
1329
|
+
AXButtonModule,
|
|
1330
|
+
AXLabelModule,
|
|
1331
|
+
AXCheckBoxModule,
|
|
1332
|
+
AXTabsModule,
|
|
1333
|
+
AXDecoratorModule,
|
|
1334
|
+
AXTranslationModule,
|
|
1335
|
+
AXBadgeModule,
|
|
1336
|
+
AXCheckBoxModule,
|
|
1337
|
+
AXTooltipModule,
|
|
1338
|
+
AXSkeletonModule,
|
|
1339
|
+
AXSearchBoxModule,
|
|
1340
|
+
AXPStateMessageComponent,
|
|
1341
|
+
], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.Eager, host: {
|
|
1342
|
+
'[class]': 'hostClass()',
|
|
1343
|
+
'[attr.aria-busy]': 'isEditorLoading() ? "true" : null',
|
|
1344
|
+
}, template: "<div class=\"__frame\">\n @if (isEditorLoading()) {\n <!-- ----- Skeleton while permission groups load ----- -->\n <div class=\"__body flex min-h-0 flex-1 overflow-hidden\">\n <div class=\"__groups p-4 w-2/5\" aria-hidden=\"true\">\n <div class=\"flex flex-col gap-3 h-full\">\n @for (widthClass of groupSkeletonPlaceholders(); track $index) {\n <div class=\"flex items-center py-2\">\n <div class=\"h-5 rounded-md overflow-hidden\" [class]=\"widthClass\">\n <ax-skeleton\n [animated]=\"true\"\n style=\"display: block; width: 100%; height: 100%\"\n ></ax-skeleton>\n </div>\n </div>\n }\n </div>\n </div>\n\n <div\n class=\"__permissions flex w-3/5 flex-col overflow-hidden border-s\"\n aria-hidden=\"true\"\n >\n <div class=\"__header p-4 border-b border-lightest\">\n <div class=\"h-6 w-48 rounded-md overflow-hidden\">\n <ax-skeleton\n [animated]=\"true\"\n style=\"display: block; width: 100%; height: 100%\"\n ></ax-skeleton>\n </div>\n </div>\n\n <div class=\"__content flex flex-col gap-1 p-4\">\n @for (group of permissionGroupSkeletonPlaceholders(); track $index) {\n <div class=\"__skeleton-permission-group\">\n <div class=\"flex items-center gap-2 py-1\">\n <div class=\"h-4 w-4 shrink-0 rounded-sm overflow-hidden\">\n <ax-skeleton\n [animated]=\"true\"\n style=\"display: block; width: 100%; height: 100%\"\n ></ax-skeleton>\n </div>\n <div class=\"h-4 w-40 max-w-full rounded-md overflow-hidden\">\n <ax-skeleton\n [animated]=\"true\"\n style=\"display: block; width: 100%; height: 100%\"\n ></ax-skeleton>\n </div>\n <div class=\"h-3 w-3 shrink-0 rounded-full overflow-hidden\">\n <ax-skeleton\n [animated]=\"true\"\n style=\"display: block; width: 100%; height: 100%\"\n ></ax-skeleton>\n </div>\n </div>\n\n <div class=\"__children p-2 mt-1 ms-1\">\n @for (\n childIndex of childSkeletonPlaceholders(group.childCount);\n track childIndex\n ) {\n <div class=\"flex items-center gap-2 py-1 ps-2\">\n <div class=\"h-4 w-4 shrink-0 rounded-sm overflow-hidden\">\n <ax-skeleton\n [animated]=\"true\"\n style=\"display: block; width: 100%; height: 100%\"\n ></ax-skeleton>\n </div>\n <div class=\"h-4 w-32 max-w-full rounded-md overflow-hidden\">\n <ax-skeleton\n [animated]=\"true\"\n style=\"display: block; width: 100%; height: 100%\"\n ></ax-skeleton>\n </div>\n <div class=\"h-3 w-3 shrink-0 rounded-full overflow-hidden\">\n <ax-skeleton\n [animated]=\"true\"\n style=\"display: block; width: 100%; height: 100%\"\n ></ax-skeleton>\n </div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n }\n <!-- ----- Loaded editor: search + group/permission columns ----- -->\n @else {\n <div class=\"__search px-4 pt-4 pb-3 border-b border-border-lightest\">\n <ax-search-box\n [axAutoFocus]\n [ngModel]=\"searchQuery()\"\n (onValueChanged)=\"handleSearchChanged($event)\"\n [placeholder]=\"\n '@security-management:permissions-feature.components.permissions-editor.search.placeholder'\n | translate\n | async\n \"\n >\n <ax-clear-button></ax-clear-button>\n </ax-search-box>\n </div>\n\n <div class=\"__body flex min-h-0 flex-1 overflow-hidden\">\n @if (isSearchActive()) {\n <!-- ----- Search results fill both columns ----- -->\n <div\n class=\"__permissions __permissions--search flex min-h-0 flex-1 flex-col overflow-hidden\"\n >\n @if (hasSearchResults()) {\n <div class=\"__search-results flex min-h-0 flex-1 flex-col overflow-y-auto\">\n @for (result of searchResultsGrouped(); track result.group.name) {\n <section class=\"__search-group shrink-0\">\n <div\n class=\"__header shrink-0 p-4 border-b border-border-lightest\"\n >\n <span class=\"h6\">{{\n result.group.title | translate | async\n }}</span>\n </div>\n\n <div class=\"__content flex flex-col gap-4 p-4\">\n @for (section of result.sections; track section.name) {\n <section class=\"__section\">\n @if (section.title) {\n <div class=\"__section-header\">\n <span class=\"__section-title\">{{\n section.title | translate | async\n }}</span>\n </div>\n }\n <ul class=\"__list\">\n <ng-container\n *ngTemplateOutlet=\"\n recursiveList;\n context: { $implicit: section.items }\n \"\n ></ng-container>\n </ul>\n </section>\n }\n </div>\n </section>\n }\n </div>\n }\n <!-- ----- Empty search results ----- -->\n @else {\n <div class=\"__empty flex flex-1 items-center justify-center p-8\">\n <axp-state-message\n icon=\"fa-light fa-search\"\n [title]=\"\n '@security-management:permissions-feature.components.permissions-editor.empty-states.no-results.title'\n \"\n [description]=\"\n '@security-management:permissions-feature.components.permissions-editor.empty-states.no-results.description'\n \"\n >\n </axp-state-message>\n </div>\n }\n </div>\n }\n <!-- ----- Browse mode: groups nav + permissions panel ----- -->\n @else {\n <div class=\"__groups w-2/5\">\n <ax-tabs location=\"start\" look=\"with-line-color\" class=\"__group-tabs\">\n @for (group of groupsWithBadge(); track group.name) {\n <ax-tab-item\n [active]=\"group.name === selectedGroup()?.name\"\n [key]=\"group.name\"\n [text]=\"(group.title | translate | async)!\"\n [headerTemplate]=\"groupTabHeader\"\n (onClick)=\"handleMenuItemClick(group)\"\n >\n </ax-tab-item>\n }\n </ax-tabs>\n </div>\n\n <div class=\"__permissions flex w-3/5 flex-col overflow-hidden border-s border-border-lightest\">\n <div class=\"__header shrink-0 p-4 border-b border-border-lightest font-semibold\">\n <span>{{ $safeNavigationMigration(selectedGroup()?.title)! | translate | async }}</span>\n </div>\n\n <div class=\"__content flex flex-col gap-4 p-4\">\n @for (section of selectedSections(); track section.name) {\n <section class=\"__section\">\n @if (section.title) {\n <div class=\"__section-header\">\n <span class=\"__section-title\">{{\n section.title | translate | async\n }}</span>\n </div>\n }\n <ul class=\"__list\">\n <ng-container\n *ngTemplateOutlet=\"\n recursiveList;\n context: { $implicit: section.items }\n \"\n ></ng-container>\n </ul>\n </section>\n }\n </div>\n </div>\n }\n </div>\n }\n</div>\n\n@if (isShowActions()) {\n <!-- ----- Dialog footer actions ----- -->\n <ax-footer>\n <ax-suffix>\n <ax-button\n look=\"solid\"\n [text]=\"'@general:actions.cancel.title' | translate | async\"\n (onClick)=\"handleCancelClick()\"\n ></ax-button>\n <ax-button\n look=\"solid\"\n color=\"primary\"\n [text]=\"'@general:actions.select.title' | translate | async\"\n (onClick)=\"handleSelectClick()\"\n >\n <ax-prefix>\n <i class=\"fa-solid fa-check\"></i>\n </ax-prefix>\n </ax-button>\n </ax-suffix>\n </ax-footer>\n}\n\n<!-- ----- Left-nav group tab: title + description + badge ----- -->\n<ng-template #groupTabHeader let-ctx>\n @if (getGroupByKey(ctx.key); as group) {\n <div class=\"__group-tab\">\n <div class=\"__group-tab-main\">\n <div class=\"__group-tab-title\">{{ ctx.text }}</div>\n @if (group.description) {\n <div class=\"__group-tab-description\">\n {{ group.description | translate | async }}\n </div>\n }\n </div>\n @if (badgeToNumber(group.badge) > 0) {\n <ax-badge\n class=\"__group-tab-badge\"\n [color]=\"'primary'\"\n [look]=\"'twotone'\"\n [text]=\"group.badge\"\n ></ax-badge>\n }\n </div>\n }\n</ng-template>\n\n<ng-template #recursiveList let-items>\n @for (item of items; track $index) {\n <li class=\"__item\">\n <ax-check-box\n [ngModel]=\"item.isSelected\"\n [readonly]=\"isReadOnlyMode()\"\n (onValueChanged)=\"onPermissionToggle(item, $event)\"\n >\n <ax-label>\n <span class=\"__permission-label\">\n <span class=\"__permission-title\">{{\n item.title | translate | async\n }}</span>\n @if (item.description) {\n <span class=\"__permission-description\">{{\n item.description | translate | async\n }}</span>\n }\n </span>\n </ax-label>\n </ax-check-box>\n\n @if (item.children && item.children.length > 0) {\n <ul class=\"__children p-2 mt-1 ms-1\">\n <ng-container\n *ngTemplateOutlet=\"\n recursiveList;\n context: { $implicit: item.children }\n \"\n ></ng-container>\n </ul>\n }\n </li>\n }\n</ng-template>\n", styles: ["@layer properties;.axm-permissions-editor{display:flex;height:100%;max-height:75vh;min-height:0px;flex-direction:column;overflow:hidden}.axm-permissions-editor.--fill-height{max-height:none}.axm-permissions-editor .__frame{display:flex;min-height:0px;flex:1;flex-direction:column;overflow:hidden;border-radius:var(--radius-md, .375rem);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-border-default);background-color:rgba(var(--ax-sys-color-lightest-surface))}.axm-permissions-editor .__search{flex-shrink:0}.axm-permissions-editor .__body{display:flex;min-height:0px;flex:1;overflow:hidden}.axm-permissions-editor .__groups{position:relative;display:flex;min-height:0px;flex-direction:column;overflow:hidden}.axm-permissions-editor .__groups ax-tabs.__group-tabs{height:100%;min-height:0px;width:100%;gap:0!important;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:rgba(var(--ax-sys-color-border-light-surface, 150 150 150),.7) transparent;scrollbar-gutter:auto}.axm-permissions-editor .__groups ax-tabs.__group-tabs::-webkit-scrollbar{width:6px}.axm-permissions-editor .__groups ax-tabs.__group-tabs::-webkit-scrollbar-track{background:transparent}.axm-permissions-editor .__groups ax-tabs.__group-tabs::-webkit-scrollbar-thumb{background-color:rgba(var(--ax-sys-color-border-light-surface, 150 150 150),.7);border-radius:999px;border:2px solid transparent;background-clip:content-box}.axm-permissions-editor .__groups ax-tabs.__group-tabs .ax-tabs-scroll-area{gap:0!important;width:100%;min-width:0;flex:none}.axm-permissions-editor .__groups ax-tabs.__group-tabs.ax-start,.axm-permissions-editor .__groups ax-tabs.__group-tabs.ax-end{align-items:stretch}.axm-permissions-editor .__groups ax-tabs.__group-tabs ax-tab-item{border-radius:0!important}.axm-permissions-editor .__groups ax-tabs.__group-tabs.ax-with-line.ax-start ax-tab-item,.axm-permissions-editor .__groups ax-tabs.__group-tabs.ax-with-line-color.ax-start ax-tab-item{align-items:flex-start;width:100%;padding-block:.375rem!important;padding-inline:.75rem!important}.axm-permissions-editor .__groups ax-tabs.__group-tabs .__group-tab{position:relative;display:flex;width:100%;align-items:flex-start;padding-inline-end:1.75rem}.axm-permissions-editor .__groups ax-tabs.__group-tabs .__group-tab .__group-tab-main{display:flex;min-width:0px;flex:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * .5)}.axm-permissions-editor .__groups ax-tabs.__group-tabs .__group-tab .__group-tab-title{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-leading: var(--leading-snug, 1.375);line-height:var(--leading-snug, 1.375);--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500)}.axm-permissions-editor .__groups ax-tabs.__group-tabs .__group-tab .__group-tab-description{font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: var(--leading-snug, 1.375);line-height:var(--leading-snug, 1.375);--tw-font-weight: var(--font-weight-normal, 400);font-weight:var(--font-weight-normal, 400);white-space:normal;color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axm-permissions-editor .__groups ax-tabs.__group-tabs .__group-tab .__group-tab-description{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axm-permissions-editor .__groups ax-tabs.__group-tabs .__group-tab .__group-tab-badge{position:absolute;inset-inline-end:0;top:.125rem}.axm-permissions-editor .__permissions{display:flex;min-height:0px;flex-direction:column;overflow:hidden}.axm-permissions-editor .__permissions .__header{flex-shrink:0}.axm-permissions-editor .__permissions .__header .__title{font-size:1rem;font-weight:500;line-height:1rem}.axm-permissions-editor .__permissions .__content{min-height:0px;flex:1;overflow-y:auto;overscroll-behavior:contain;padding-inline-start:1.25rem}.axm-permissions-editor .__permissions .__content .__section{display:flex;flex-direction:column;gap:var(--spacing, .25rem)}.axm-permissions-editor .__permissions .__content .__section .__section-header{margin-bottom:var(--spacing, .25rem);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-border-lightest-surface));padding-bottom:var(--spacing, .25rem)}.axm-permissions-editor .__permissions .__content .__section .__section-header .__section-title{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axm-permissions-editor .__permissions .__content .__section .__section-header .__section-title{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axm-permissions-editor .__permissions .__content .__list{width:100%}.axm-permissions-editor .__permissions .__content .__list .__item{padding-block:var(--spacing, .25rem);padding-inline-start:var(--spacing, .25rem)}.axm-permissions-editor .__permissions .__content .__list .__item ax-check-box{display:block;width:100%}.axm-permissions-editor .__permissions .__content .__list .__item ax-check-box .ax-checkbox-label{align-items:flex-start;width:100%}.axm-permissions-editor .__permissions .__content .__list .__item ax-check-box .ax-checkbox-start-side{flex:none;align-self:flex-start;padding-top:4px}.axm-permissions-editor .__permissions .__content .__list .__item ax-check-box .ax-checkbox-end-side{min-width:0;flex:1}.axm-permissions-editor .__permissions .__content .__list .__item .__permission-label{display:flex;flex-direction:column;gap:calc(var(--spacing, .25rem) * .5)}.axm-permissions-editor .__permissions .__content .__list .__item .__permission-label .__permission-title{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);line-height:calc(var(--spacing, .25rem) * 5)}.axm-permissions-editor .__permissions .__content .__list .__item .__permission-label .__permission-description{font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: var(--leading-snug, 1.375);line-height:var(--leading-snug, 1.375);--tw-font-weight: var(--font-weight-normal, 400);font-weight:var(--font-weight-normal, 400);white-space:normal;color:rgba(var(--ax-sys-color-text-default))}@supports (color: color-mix(in lab,red,red)){.axm-permissions-editor .__permissions .__content .__list .__item .__permission-label .__permission-description{color:color-mix(in oklab,rgba(var(--ax-sys-color-text-default)) 80%,transparent)}}.axm-permissions-editor .__permissions .__content .__list .__item .__children{margin-inline-start:calc(var(--spacing, .25rem) * 2);margin-top:var(--spacing, .25rem);border-left-style:var(--tw-border-style);border-left-width:1px;--tw-border-style: dashed;border-style:dashed;border-color:rgba(var(--ax-sys-color-border-lighter-surface));padding-inline-start:calc(var(--spacing, .25rem) * 2)}.axm-permissions-editor .__permissions.__permissions--search{width:100%}.axm-permissions-editor .__permissions.__permissions--search .__search-results{min-height:0px;width:100%;flex:1;overflow-y:auto;overscroll-behavior:contain}.axm-permissions-editor .__permissions.__permissions--search .__search-group .__content{flex:none;overflow:visible;min-height:0}.axm-permissions-editor .__permissions.__permissions--search .__search-group:not(:last-child){border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:rgba(var(--ax-sys-color-lightest-surface))}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-leading{syntax: \"*\"; inherits: false;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-leading: initial;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
|
|
1345
|
+
}], ctorParameters: () => [], propDecorators: { initialPermissions: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialPermissions", required: false }] }], readOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readOnly", required: false }] }], showActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showActions", required: false }] }], fillHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "fillHeight", required: false }] }], permissionsChanged: [{ type: i0.Output, args: ["permissionsChanged"] }], permissionsSelected: [{ type: i0.Output, args: ["permissionsSelected"] }] } });
|
|
1346
|
+
|
|
1347
|
+
var permissionsEditor_component = /*#__PURE__*/Object.freeze({
|
|
1348
|
+
__proto__: null,
|
|
1349
|
+
AXPPermissionsEditorComponent: AXPPermissionsEditorComponent
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1352
|
+
//#region ----- Imports -----
|
|
1353
|
+
//#endregion
|
|
1354
|
+
//#region ----- Service -----
|
|
1355
|
+
/**
|
|
1356
|
+
* Loads and persists scoped RolePermission grants for a role.
|
|
1357
|
+
* Shared by the Assign Permissions dialog and the Role permissions detail page.
|
|
1358
|
+
*/
|
|
1359
|
+
class AXMRolePermissionGrantService {
|
|
1360
|
+
constructor() {
|
|
1361
|
+
//#region ----- Services & Dependencies -----
|
|
1362
|
+
this.entityRegistry = inject(AXPEntityDefinitionRegistryService);
|
|
1363
|
+
this.entityService = inject(AXPEntityService);
|
|
1364
|
+
this.sessionService = inject(AXPSessionService);
|
|
1365
|
+
this.entityEvents = inject(AXPEntityBroadcastService);
|
|
1366
|
+
}
|
|
1367
|
+
//#endregion
|
|
1368
|
+
//#region ----- Scope -----
|
|
1369
|
+
resolveSessionScope() {
|
|
1370
|
+
const tenantId = this.sessionService.tenant?.id;
|
|
1371
|
+
if (!tenantId) {
|
|
1372
|
+
return null;
|
|
1373
|
+
}
|
|
1374
|
+
return {
|
|
1375
|
+
tenantId,
|
|
1376
|
+
applicationId: this.sessionService.application?.id,
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
//#endregion
|
|
1380
|
+
//#region ----- Query -----
|
|
1381
|
+
rolePermissionData() {
|
|
1382
|
+
return this.entityService
|
|
1383
|
+
.withEntity(RootConfig.module.name, RootConfig.entities.rolePermission.name)
|
|
1384
|
+
.data();
|
|
1385
|
+
}
|
|
1386
|
+
async findGrant(roleId, tenantId, applicationId) {
|
|
1387
|
+
const result = await this.rolePermissionData().query({
|
|
1388
|
+
skip: 0,
|
|
1389
|
+
take: 50,
|
|
1390
|
+
filter: {
|
|
1391
|
+
logic: 'and',
|
|
1392
|
+
filters: [
|
|
1393
|
+
{ field: 'roleId', operator: { type: 'equal' }, value: roleId },
|
|
1394
|
+
{ field: 'scopeInfo.tenantId', operator: { type: 'equal' }, value: tenantId },
|
|
1395
|
+
],
|
|
1396
|
+
},
|
|
1397
|
+
});
|
|
1398
|
+
const items = (result?.items ?? []);
|
|
1399
|
+
if (items.length === 0) {
|
|
1400
|
+
return null;
|
|
1401
|
+
}
|
|
1402
|
+
if (applicationId) {
|
|
1403
|
+
const appMatch = items.find((item) => item.scopeInfo?.applicationId === applicationId);
|
|
1404
|
+
if (appMatch) {
|
|
1405
|
+
return appMatch;
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
const tenantWide = items.find((item) => !item.scopeInfo?.applicationId);
|
|
1409
|
+
return tenantWide ?? items[0] ?? null;
|
|
1410
|
+
}
|
|
1411
|
+
//#endregion
|
|
1412
|
+
//#region ----- Persist -----
|
|
1413
|
+
async saveGrant(options) {
|
|
1414
|
+
const existingState = options.existing?.stateInfo;
|
|
1415
|
+
if (existingState?.isSystem === true || existingState?.isImmutable === true) {
|
|
1416
|
+
throw new Error('System role permission grants are readonly.');
|
|
1417
|
+
}
|
|
1418
|
+
const entityKey = `${RootConfig.module.name}.${RootConfig.entities.rolePermission.name}`;
|
|
1419
|
+
const [moduleName, entityName] = entityKey.split('.');
|
|
1420
|
+
const entityRef = await this.entityRegistry.resolve(moduleName, entityName);
|
|
1421
|
+
const scopeInfo = {
|
|
1422
|
+
tenantId: options.tenantId,
|
|
1423
|
+
...(options.existing?.scopeInfo?.applicationId
|
|
1424
|
+
? { applicationId: options.existing.scopeInfo.applicationId }
|
|
1425
|
+
: options.applicationId
|
|
1426
|
+
? { applicationId: options.applicationId }
|
|
1427
|
+
: {}),
|
|
1428
|
+
};
|
|
1429
|
+
if (options.existing?.id) {
|
|
1430
|
+
const updatePayload = {
|
|
1431
|
+
...options.existing,
|
|
1432
|
+
roleId: options.roleId,
|
|
1433
|
+
permissions: options.permissions,
|
|
1434
|
+
scopeInfo,
|
|
1435
|
+
};
|
|
1436
|
+
return (entityRef.commands?.update?.execute)(updatePayload);
|
|
1437
|
+
}
|
|
1438
|
+
const createPayload = {
|
|
1439
|
+
roleId: options.roleId,
|
|
1440
|
+
permissions: options.permissions,
|
|
1441
|
+
scopeInfo,
|
|
1442
|
+
};
|
|
1443
|
+
return (entityRef.commands?.create?.execute)(createPayload);
|
|
1444
|
+
}
|
|
1445
|
+
notifyGrantModified(saved) {
|
|
1446
|
+
const entityKey = `${RootConfig.module.name}.${RootConfig.entities.rolePermission.name}`;
|
|
1447
|
+
dispatchEntityModifiedSideEffects(this.entityEvents, entityKey, saved);
|
|
1448
|
+
}
|
|
1449
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMRolePermissionGrantService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1450
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMRolePermissionGrantService, providedIn: 'root' }); }
|
|
1451
|
+
}
|
|
1452
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMRolePermissionGrantService, decorators: [{
|
|
1453
|
+
type: Injectable,
|
|
1454
|
+
args: [{ providedIn: 'root' }]
|
|
1455
|
+
}] });
|
|
1456
|
+
|
|
1457
|
+
//#endregion
|
|
1458
|
+
//#region ----- Command -----
|
|
1459
|
+
class AXMAssignPermissionsCommand {
|
|
1460
|
+
constructor() {
|
|
1461
|
+
//#region ----- Services & Dependencies -----
|
|
1462
|
+
this.popupService = inject(AXPopupService);
|
|
1463
|
+
this.translationService = inject(AXTranslationService);
|
|
1464
|
+
this.toastService = inject(AXToastService);
|
|
1465
|
+
this.settingsService = inject(AXPSettingsService);
|
|
1466
|
+
this.grantService = inject(AXMRolePermissionGrantService);
|
|
1467
|
+
}
|
|
1468
|
+
//#endregion
|
|
1469
|
+
//#region ----- Execute -----
|
|
1470
|
+
async execute(input) {
|
|
1471
|
+
try {
|
|
1472
|
+
const role = input.data;
|
|
1473
|
+
if (!role || !isString(role['id']) || isEmpty(role['id'])) {
|
|
1474
|
+
return {
|
|
1475
|
+
success: false,
|
|
1476
|
+
message: {
|
|
1477
|
+
text: await this.translationService.translateAsync('@general:messages.entity.invalid-data'),
|
|
1478
|
+
},
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
const roleId = role['id'];
|
|
1482
|
+
const scope = this.grantService.resolveSessionScope();
|
|
1483
|
+
if (!scope) {
|
|
1484
|
+
return {
|
|
1485
|
+
success: false,
|
|
1486
|
+
message: {
|
|
1487
|
+
text: await this.translationService.translateAsync('@general:messages.entity.invalid-data'),
|
|
1488
|
+
},
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
const existingGrant = await this.grantService.findGrant(roleId, scope.tenantId, scope.applicationId);
|
|
1492
|
+
const grantReadonly = existingGrant?.stateInfo?.isSystem === true || existingGrant?.stateInfo?.isImmutable === true;
|
|
1493
|
+
const component = await Promise.resolve().then(function () { return permissionsEditor_component; }).then((m) => m.AXPPermissionsEditorComponent);
|
|
1494
|
+
const editorTitle = await this.translationService.translateAsync('@security-management:permissions-feature.terms.permissions-editor');
|
|
1495
|
+
const roleTitle = this.resolveRoleTitle(role['title']);
|
|
1496
|
+
const initialPermissions = existingGrant?.permissions ?? [];
|
|
1497
|
+
const popupResult = await this.popupService.open(component, {
|
|
1498
|
+
title: roleTitle ? `${editorTitle} - ${roleTitle}` : editorTitle,
|
|
1499
|
+
size: 'md',
|
|
1500
|
+
data: {
|
|
1501
|
+
initialPermissions,
|
|
1502
|
+
showActions: !grantReadonly,
|
|
1503
|
+
readOnly: grantReadonly,
|
|
1504
|
+
},
|
|
1505
|
+
});
|
|
1506
|
+
const permissions = popupResult?.data;
|
|
1507
|
+
if (!isArray(permissions) || grantReadonly) {
|
|
1508
|
+
return { success: true, kind: 'object', data: null, metadata: { refresh: false } };
|
|
1509
|
+
}
|
|
1510
|
+
const saved = await this.grantService.saveGrant({
|
|
1511
|
+
existing: existingGrant,
|
|
1512
|
+
roleId,
|
|
1513
|
+
permissions: permissions,
|
|
1514
|
+
tenantId: scope.tenantId,
|
|
1515
|
+
applicationId: scope.applicationId,
|
|
1516
|
+
});
|
|
1517
|
+
await this.showSuccessToast();
|
|
1518
|
+
this.grantService.notifyGrantModified(saved);
|
|
1519
|
+
return {
|
|
1520
|
+
success: true,
|
|
1521
|
+
kind: 'object',
|
|
1522
|
+
data: permissions,
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
catch (error) {
|
|
1526
|
+
const message = error instanceof Error ? error.message : 'Failed to assign permissions.';
|
|
1527
|
+
return {
|
|
1528
|
+
success: false,
|
|
1529
|
+
message: { text: message },
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
//#endregion
|
|
1534
|
+
//#region ----- Utility Methods -----
|
|
1535
|
+
resolveRoleTitle(title) {
|
|
1536
|
+
if (!title) {
|
|
1537
|
+
return '';
|
|
1538
|
+
}
|
|
1539
|
+
const locale = this.translationService.getActiveLang?.() ?? 'en-US';
|
|
1540
|
+
if (isString(title)) {
|
|
1541
|
+
return resolveMultiLanguageString(title, locale).trim();
|
|
1542
|
+
}
|
|
1543
|
+
return resolveMultiLanguageString(String(title), locale).trim();
|
|
1544
|
+
}
|
|
1545
|
+
async showSuccessToast() {
|
|
1546
|
+
const enableOperationToasts = await this.settingsService.get(AXPCommonSettings.EnableOperationToasts);
|
|
1547
|
+
if (!enableOperationToasts) {
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
this.toastService.show({
|
|
1551
|
+
color: 'success',
|
|
1552
|
+
title: await this.translationService.translateAsync('@general:workflow.entity-modified-title'),
|
|
1553
|
+
content: await this.translationService.translateAsync('@general:workflow.entity-modified-body'),
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMAssignPermissionsCommand, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1557
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMAssignPermissionsCommand }); }
|
|
1558
|
+
}
|
|
1559
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMAssignPermissionsCommand, decorators: [{
|
|
1560
|
+
type: Injectable
|
|
1561
|
+
}] });
|
|
1562
|
+
|
|
1563
|
+
var assignPermissions_command = /*#__PURE__*/Object.freeze({
|
|
1564
|
+
__proto__: null,
|
|
1565
|
+
AXMAssignPermissionsCommand: AXMAssignPermissionsCommand
|
|
1566
|
+
});
|
|
1567
|
+
|
|
1568
|
+
/**
|
|
1569
|
+
* Resolves a persisted host entity id from wizard/details context (supports id, _id, string or number).
|
|
1570
|
+
*/
|
|
1571
|
+
function resolvePersistedEntityId(ctx) {
|
|
1572
|
+
if (!ctx) {
|
|
1573
|
+
return undefined;
|
|
1574
|
+
}
|
|
1575
|
+
const raw = ctx['id'] ?? ctx['_id'];
|
|
1576
|
+
if (typeof raw === 'string' && raw.length > 0) {
|
|
1577
|
+
return raw;
|
|
1578
|
+
}
|
|
1579
|
+
if (typeof raw === 'number' && !Number.isNaN(raw)) {
|
|
1580
|
+
return String(raw);
|
|
1581
|
+
}
|
|
1582
|
+
return undefined;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
//#endregion
|
|
1586
|
+
//#region ----- Constants -----
|
|
1587
|
+
/** Local command handled in execute(); reverts permissions to the loaded grant baseline. */
|
|
1588
|
+
const ROLE_PERMISSIONS_DISCARD_COMMAND = 'Role:Permissions:Discard';
|
|
1589
|
+
/** Local command handled in execute(); persists the scoped RolePermission grant. */
|
|
1590
|
+
const ROLE_PERMISSIONS_SAVE_COMMAND = 'Role:Permissions:Save';
|
|
1591
|
+
//#endregion
|
|
1592
|
+
//#region ----- Component -----
|
|
1593
|
+
/**
|
|
1594
|
+
* Role detail/create wizard page that edits scoped RolePermission grants.
|
|
1595
|
+
* Extends AXPPageLayoutBaseComponent for Save/Discard footer actions and dirty tracking.
|
|
1596
|
+
*/
|
|
1597
|
+
class AXMRolePermissionsPageComponent extends AXPPageLayoutBaseComponent {
|
|
1598
|
+
//#endregion
|
|
1599
|
+
//#region ----- Lifecycle -----
|
|
1600
|
+
constructor() {
|
|
1601
|
+
super();
|
|
1602
|
+
//#region ----- Inputs -----
|
|
1603
|
+
this.rootContext = input(/* @ts-ignore */
|
|
1604
|
+
...(ngDevMode ? [undefined, { debugName: "rootContext" }] : /* istanbul ignore next */ []));
|
|
1605
|
+
/** Optional page config set by the page-component-renderer. */
|
|
1606
|
+
this.pageConfig = input(/* @ts-ignore */
|
|
1607
|
+
...(ngDevMode ? [undefined, { debugName: "pageConfig" }] : /* istanbul ignore next */ []));
|
|
1608
|
+
//#endregion
|
|
1609
|
+
//#region ----- Services & Dependencies -----
|
|
1610
|
+
this.grantService = inject(AXMRolePermissionGrantService);
|
|
1611
|
+
this.hostPageLayout = inject(AXPPageLayoutBase, { optional: true, skipSelf: true });
|
|
1612
|
+
//#endregion
|
|
1613
|
+
//#region ----- State Signals -----
|
|
1614
|
+
this.loadedRoleId = signal(undefined, /* @ts-ignore */
|
|
1615
|
+
...(ngDevMode ? [{ debugName: "loadedRoleId" }] : /* istanbul ignore next */ []));
|
|
1616
|
+
this.existingGrant = signal(null, /* @ts-ignore */
|
|
1617
|
+
...(ngDevMode ? [{ debugName: "existingGrant" }] : /* istanbul ignore next */ []));
|
|
1618
|
+
this.baselinePermissions = signal([], /* @ts-ignore */
|
|
1619
|
+
...(ngDevMode ? [{ debugName: "baselinePermissions" }] : /* istanbul ignore next */ []));
|
|
1620
|
+
this.currentPermissions = signal([], /* @ts-ignore */
|
|
1621
|
+
...(ngDevMode ? [{ debugName: "currentPermissions" }] : /* istanbul ignore next */ []));
|
|
1622
|
+
this.isLoading = signal(true, /* @ts-ignore */
|
|
1623
|
+
...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
|
|
1624
|
+
/** Bumps when the editor should remount with a new initialPermissions snapshot (e.g. after discard). */
|
|
1625
|
+
this.editorResetToken = signal(0, /* @ts-ignore */
|
|
1626
|
+
...(ngDevMode ? [{ debugName: "editorResetToken" }] : /* istanbul ignore next */ []));
|
|
1627
|
+
//#endregion
|
|
1628
|
+
//#region ----- Computed Properties -----
|
|
1629
|
+
this.roleId = computed(() => resolvePersistedEntityId(this.rootContext()), /* @ts-ignore */
|
|
1630
|
+
...(ngDevMode ? [{ debugName: "roleId" }] : /* istanbul ignore next */ []));
|
|
1631
|
+
this.editorPermissions = computed(() => {
|
|
1632
|
+
// Depend on reset token so discard remounts the editor with baseline values.
|
|
1633
|
+
this.editorResetToken();
|
|
1634
|
+
return [...this.currentPermissions()];
|
|
1635
|
+
}, /* @ts-ignore */
|
|
1636
|
+
...(ngDevMode ? [{ debugName: "editorPermissions" }] : /* istanbul ignore next */ []));
|
|
1637
|
+
this.isDirty = computed(() => {
|
|
1638
|
+
return !this.arePermissionSetsEqual(this.baselinePermissions(), this.currentPermissions());
|
|
1639
|
+
}, /* @ts-ignore */
|
|
1640
|
+
...(ngDevMode ? [{ debugName: "isDirty" }] : /* istanbul ignore next */ []));
|
|
1641
|
+
/** System RolePermission grants are seeded and not editable in product/platform UI. */
|
|
1642
|
+
this.isGrantReadonly = computed(() => {
|
|
1643
|
+
const state = this.existingGrant()?.stateInfo;
|
|
1644
|
+
return state?.isSystem === true || state?.isImmutable === true;
|
|
1645
|
+
}, /* @ts-ignore */
|
|
1646
|
+
...(ngDevMode ? [{ debugName: "isGrantReadonly" }] : /* istanbul ignore next */ []));
|
|
1647
|
+
effect(() => {
|
|
1648
|
+
const id = this.roleId();
|
|
1649
|
+
// Avoid re-running when loadGrantForRole mutates its own loading/baseline signals.
|
|
1650
|
+
untracked(() => {
|
|
1651
|
+
void this.loadGrantForRole(id);
|
|
1652
|
+
});
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
//#endregion
|
|
1656
|
+
//#region ----- Unsaved Changes -----
|
|
1657
|
+
hasUnsavedChanges() {
|
|
1658
|
+
return this.isDirty();
|
|
1659
|
+
}
|
|
1660
|
+
//#endregion
|
|
1661
|
+
//#region ----- Page Actions -----
|
|
1662
|
+
async getPrimaryMenuItems() {
|
|
1663
|
+
const discardTitle = (await this.translateService.translateAsync('@general:actions.discard.title')) || 'Discard';
|
|
1664
|
+
const saveTitle = (await this.translateService.translateAsync('@general:actions.save.title')) || 'Save';
|
|
1665
|
+
const canEdit = !!this.roleId() && !this.isLoading() && !this.isGrantReadonly();
|
|
1666
|
+
const discardAction = {
|
|
1667
|
+
title: discardTitle,
|
|
1668
|
+
icon: 'fa-light fa-rotate-left',
|
|
1669
|
+
zone: 'footer',
|
|
1670
|
+
priority: 'secondary',
|
|
1671
|
+
color: 'default',
|
|
1672
|
+
visible: canEdit && this.isDirty(),
|
|
1673
|
+
command: {
|
|
1674
|
+
name: ROLE_PERMISSIONS_DISCARD_COMMAND,
|
|
1675
|
+
options: {},
|
|
1676
|
+
},
|
|
1677
|
+
};
|
|
1678
|
+
const saveAction = {
|
|
1679
|
+
title: saveTitle,
|
|
1680
|
+
icon: 'fa-light fa-floppy-disk',
|
|
1681
|
+
zone: 'footer',
|
|
1682
|
+
priority: 'primary',
|
|
1683
|
+
color: 'primary',
|
|
1684
|
+
visible: canEdit && this.isDirty(),
|
|
1685
|
+
command: {
|
|
1686
|
+
name: ROLE_PERMISSIONS_SAVE_COMMAND,
|
|
1687
|
+
options: {},
|
|
1688
|
+
},
|
|
1689
|
+
};
|
|
1690
|
+
return [discardAction, saveAction];
|
|
1691
|
+
}
|
|
1692
|
+
//#endregion
|
|
1693
|
+
//#region ----- Command Handling -----
|
|
1694
|
+
execute(command) {
|
|
1695
|
+
if (command.name === ROLE_PERMISSIONS_DISCARD_COMMAND) {
|
|
1696
|
+
this.handleDiscard();
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
if (command.name !== ROLE_PERMISSIONS_SAVE_COMMAND) {
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
return this.handleSaveExecute();
|
|
1703
|
+
}
|
|
1704
|
+
async handleSaveExecute() {
|
|
1705
|
+
const roleId = this.roleId();
|
|
1706
|
+
const scope = this.grantService.resolveSessionScope();
|
|
1707
|
+
if (!roleId || !scope) {
|
|
1708
|
+
return {
|
|
1709
|
+
success: false,
|
|
1710
|
+
message: {
|
|
1711
|
+
text: await this.translateService.translateAsync('@general:messages.entity.invalid-data'),
|
|
1712
|
+
},
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
try {
|
|
1716
|
+
const permissions = [...this.currentPermissions()];
|
|
1717
|
+
const saved = await this.grantService.saveGrant({
|
|
1718
|
+
existing: this.existingGrant(),
|
|
1719
|
+
roleId,
|
|
1720
|
+
permissions,
|
|
1721
|
+
tenantId: scope.tenantId,
|
|
1722
|
+
applicationId: scope.applicationId,
|
|
1723
|
+
});
|
|
1724
|
+
const nextGrant = {
|
|
1725
|
+
id: String(saved['id'] ?? this.existingGrant()?.id ?? ''),
|
|
1726
|
+
roleId,
|
|
1727
|
+
permissions,
|
|
1728
|
+
scopeInfo: saved['scopeInfo'] ?? this.existingGrant()?.scopeInfo,
|
|
1729
|
+
};
|
|
1730
|
+
this.existingGrant.set(nextGrant.id ? nextGrant : this.existingGrant());
|
|
1731
|
+
this.baselinePermissions.set([...permissions]);
|
|
1732
|
+
this.currentPermissions.set([...permissions]);
|
|
1733
|
+
this.grantService.notifyGrantModified(saved);
|
|
1734
|
+
this.recompute();
|
|
1735
|
+
return {
|
|
1736
|
+
success: true,
|
|
1737
|
+
kind: 'object',
|
|
1738
|
+
data: permissions,
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
catch (error) {
|
|
1742
|
+
const message = error instanceof Error ? error.message : 'Failed to save permissions.';
|
|
1743
|
+
return {
|
|
1744
|
+
success: false,
|
|
1745
|
+
message: { text: message },
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
//#endregion
|
|
1750
|
+
//#region ----- Event Handlers -----
|
|
1751
|
+
onPermissionsChanged(permissions) {
|
|
1752
|
+
if (this.arePermissionSetsEqual(this.currentPermissions(), permissions)) {
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
this.currentPermissions.set([...permissions]);
|
|
1756
|
+
this.recompute();
|
|
1757
|
+
}
|
|
1758
|
+
handleDiscard() {
|
|
1759
|
+
const baseline = [...this.baselinePermissions()];
|
|
1760
|
+
this.currentPermissions.set(baseline);
|
|
1761
|
+
this.editorResetToken.update((token) => token + 1);
|
|
1762
|
+
this.recompute();
|
|
1763
|
+
}
|
|
1764
|
+
//#endregion
|
|
1765
|
+
//#region ----- Wizard step context -----
|
|
1766
|
+
/**
|
|
1767
|
+
* Do not merge permissions into the Role create/update payload.
|
|
1768
|
+
* Grants live on SecurityManagement.RolePermission only.
|
|
1769
|
+
*/
|
|
1770
|
+
getWizardStepContext() {
|
|
1771
|
+
return null;
|
|
1772
|
+
}
|
|
1773
|
+
//#endregion
|
|
1774
|
+
//#region ----- Data Loading -----
|
|
1775
|
+
async loadGrantForRole(roleId) {
|
|
1776
|
+
if (!roleId) {
|
|
1777
|
+
this.loadedRoleId.set(undefined);
|
|
1778
|
+
this.existingGrant.set(null);
|
|
1779
|
+
this.baselinePermissions.set([]);
|
|
1780
|
+
this.currentPermissions.set([]);
|
|
1781
|
+
this.isLoading.set(false);
|
|
1782
|
+
this.recompute();
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
if (roleId === this.loadedRoleId() && !this.isLoading()) {
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
this.isLoading.set(true);
|
|
1789
|
+
this.recompute();
|
|
1790
|
+
const scope = this.grantService.resolveSessionScope();
|
|
1791
|
+
if (!scope) {
|
|
1792
|
+
this.loadedRoleId.set(roleId);
|
|
1793
|
+
this.existingGrant.set(null);
|
|
1794
|
+
this.baselinePermissions.set([]);
|
|
1795
|
+
this.currentPermissions.set([]);
|
|
1796
|
+
this.isLoading.set(false);
|
|
1797
|
+
this.recompute();
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
try {
|
|
1801
|
+
const grant = await this.grantService.findGrant(roleId, scope.tenantId, scope.applicationId);
|
|
1802
|
+
const permissions = [...(grant?.permissions ?? [])];
|
|
1803
|
+
this.loadedRoleId.set(roleId);
|
|
1804
|
+
this.existingGrant.set(grant);
|
|
1805
|
+
this.baselinePermissions.set(permissions);
|
|
1806
|
+
this.currentPermissions.set([...permissions]);
|
|
1807
|
+
this.editorResetToken.update((token) => token + 1);
|
|
1808
|
+
}
|
|
1809
|
+
catch {
|
|
1810
|
+
this.loadedRoleId.set(roleId);
|
|
1811
|
+
this.existingGrant.set(null);
|
|
1812
|
+
this.baselinePermissions.set([]);
|
|
1813
|
+
this.currentPermissions.set([]);
|
|
1814
|
+
}
|
|
1815
|
+
finally {
|
|
1816
|
+
this.isLoading.set(false);
|
|
1817
|
+
this.recompute();
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
//#endregion
|
|
1821
|
+
//#region ----- Utility Methods -----
|
|
1822
|
+
arePermissionSetsEqual(a, b) {
|
|
1823
|
+
return isEqual([...a].sort(), [...b].sort());
|
|
1824
|
+
}
|
|
1825
|
+
recompute() {
|
|
1826
|
+
super.recompute();
|
|
1827
|
+
const maybeLayout = this.hostPageLayout;
|
|
1828
|
+
maybeLayout?.recompute?.();
|
|
1829
|
+
}
|
|
1830
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMRolePermissionsPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1831
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXMRolePermissionsPageComponent, isStandalone: true, selector: "axm-role-permissions-page", inputs: { rootContext: { classPropertyName: "rootContext", publicName: "rootContext", isSignal: true, isRequired: false, transformFunction: null }, pageConfig: { classPropertyName: "pageConfig", publicName: "pageConfig", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "flex h-full min-h-0 flex-1 flex-col overflow-hidden" }, providers: [
|
|
1832
|
+
{
|
|
1833
|
+
provide: AXPPageLayoutBase,
|
|
1834
|
+
useExisting: AXMRolePermissionsPageComponent,
|
|
1835
|
+
},
|
|
1836
|
+
], usesInheritance: true, ngImport: i0, template: "@if (isLoading()) {\n <!-- ----- Loading grant while role permissions are fetched ----- -->\n <div class=\"flex h-full min-h-[20rem] items-center justify-center p-6\">\n <ax-loading></ax-loading>\n </div>\n}\n<!-- ----- Role must be persisted before grants can be edited ----- -->\n@else if (!roleId()) {\n <div class=\"flex h-full min-h-[20rem] items-center justify-center p-6\">\n <axp-state-message\n [title]=\"'@security-management:roles.entities.role.pages.permissions.save-role-first.title'\"\n [description]=\"'@security-management:roles.entities.role.pages.permissions.save-role-first.description'\"\n ></axp-state-message>\n </div>\n}\n<!-- ----- Full-page permissions editor for the scoped RolePermission grant ----- -->\n@else {\n <div class=\"flex h-full min-h-0 flex-1 flex-col overflow-hidden p-4\">\n <!-- ----- Remount editor when discard/load resets the selection baseline ----- -->\n @for (token of [editorResetToken()]; track token) {\n <axm-permissions-editor\n [initialPermissions]=\"editorPermissions()\"\n [readOnly]=\"isGrantReadonly()\"\n [fillHeight]=\"true\"\n (permissionsChanged)=\"onPermissionsChanged($event)\"\n ></axm-permissions-editor>\n }\n </div>\n}\n", dependencies: [{ kind: "component", type: AXPPermissionsEditorComponent, selector: "axm-permissions-editor", inputs: ["initialPermissions", "readOnly", "showActions", "fillHeight"], outputs: ["permissionsChanged", "permissionsSelected"] }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "titleParams", "descriptionParams", "look"] }, { kind: "ngmodule", type: AXLoadingModule }, { kind: "component", type: i1$1.AXLoadingComponent, selector: "ax-loading", inputs: ["visible", "type", "context"], outputs: ["visibleChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1837
|
+
}
|
|
1838
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMRolePermissionsPageComponent, decorators: [{
|
|
1839
|
+
type: Component,
|
|
1840
|
+
args: [{ selector: 'axm-role-permissions-page', standalone: true, imports: [AXPPermissionsEditorComponent, AXPStateMessageComponent, AXLoadingModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
1841
|
+
{
|
|
1842
|
+
provide: AXPPageLayoutBase,
|
|
1843
|
+
useExisting: AXMRolePermissionsPageComponent,
|
|
1844
|
+
},
|
|
1845
|
+
], host: {
|
|
1846
|
+
class: 'flex h-full min-h-0 flex-1 flex-col overflow-hidden',
|
|
1847
|
+
}, template: "@if (isLoading()) {\n <!-- ----- Loading grant while role permissions are fetched ----- -->\n <div class=\"flex h-full min-h-[20rem] items-center justify-center p-6\">\n <ax-loading></ax-loading>\n </div>\n}\n<!-- ----- Role must be persisted before grants can be edited ----- -->\n@else if (!roleId()) {\n <div class=\"flex h-full min-h-[20rem] items-center justify-center p-6\">\n <axp-state-message\n [title]=\"'@security-management:roles.entities.role.pages.permissions.save-role-first.title'\"\n [description]=\"'@security-management:roles.entities.role.pages.permissions.save-role-first.description'\"\n ></axp-state-message>\n </div>\n}\n<!-- ----- Full-page permissions editor for the scoped RolePermission grant ----- -->\n@else {\n <div class=\"flex h-full min-h-0 flex-1 flex-col overflow-hidden p-4\">\n <!-- ----- Remount editor when discard/load resets the selection baseline ----- -->\n @for (token of [editorResetToken()]; track token) {\n <axm-permissions-editor\n [initialPermissions]=\"editorPermissions()\"\n [readOnly]=\"isGrantReadonly()\"\n [fillHeight]=\"true\"\n (permissionsChanged)=\"onPermissionsChanged($event)\"\n ></axm-permissions-editor>\n }\n </div>\n}\n" }]
|
|
1848
|
+
}], ctorParameters: () => [], propDecorators: { rootContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootContext", required: false }] }], pageConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageConfig", required: false }] }] } });
|
|
1849
|
+
|
|
1850
|
+
var rolePermissionsPage_component = /*#__PURE__*/Object.freeze({
|
|
1851
|
+
__proto__: null,
|
|
1852
|
+
AXMRolePermissionsPageComponent: AXMRolePermissionsPageComponent
|
|
1853
|
+
});
|
|
1854
|
+
|
|
1855
|
+
//#endregion
|
|
1856
|
+
//#region ----- Provider -----
|
|
1857
|
+
class AXMRolePermissionsPageComponentProvider {
|
|
1858
|
+
async components() {
|
|
1859
|
+
return [
|
|
1860
|
+
{
|
|
1861
|
+
key: ROLE_PERMISSIONS_PAGE_COMPONENT_KEY,
|
|
1862
|
+
loader: () => Promise.resolve().then(function () { return rolePermissionsPage_component; }).then((m) => m.AXMRolePermissionsPageComponent),
|
|
1863
|
+
},
|
|
1864
|
+
];
|
|
1865
|
+
}
|
|
1866
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMRolePermissionsPageComponentProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1867
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMRolePermissionsPageComponentProvider }); }
|
|
1868
|
+
}
|
|
1869
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXMRolePermissionsPageComponentProvider, decorators: [{
|
|
1870
|
+
type: Injectable
|
|
1871
|
+
}] });
|
|
1872
|
+
|
|
1873
|
+
var index = /*#__PURE__*/Object.freeze({
|
|
1874
|
+
__proto__: null,
|
|
1875
|
+
AXMRolePermissionsPageComponent: AXMRolePermissionsPageComponent,
|
|
1876
|
+
AXMRolePermissionsPageComponentProvider: AXMRolePermissionsPageComponentProvider
|
|
1877
|
+
});
|
|
1878
|
+
|
|
1879
|
+
class AXPPermissionsWidgetViewComponent extends AXPValueWidgetComponent {
|
|
1880
|
+
constructor() {
|
|
1881
|
+
super(...arguments);
|
|
1882
|
+
this.permissionService = inject(AXPPermissionDefinitionService);
|
|
1883
|
+
this.commandService = inject(AXPCommandService);
|
|
1884
|
+
this.groups = signal([], /* @ts-ignore */
|
|
1885
|
+
...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
|
|
1886
|
+
this.loadGroupsEffect = effect(async () => {
|
|
1887
|
+
// Load groups when component initializes
|
|
1888
|
+
if (this.groups().length === 0) {
|
|
1889
|
+
const groupsData = await this.permissionService.getGroups();
|
|
1890
|
+
this.groups.set(groupsData);
|
|
1891
|
+
}
|
|
1892
|
+
}, /* @ts-ignore */
|
|
1893
|
+
...(ngDevMode ? [{ debugName: "loadGroupsEffect" }] : /* istanbul ignore next */ []));
|
|
1894
|
+
this.selectedPermissionsByGroup = computed(() => {
|
|
1895
|
+
const selected = new Set(this.getValue() ?? []);
|
|
1896
|
+
const result = {};
|
|
1897
|
+
for (const group of this.groups()) {
|
|
1898
|
+
const permPaths = [];
|
|
1899
|
+
const findPermissionPaths = (items, path = []) => {
|
|
1900
|
+
for (const item of items) {
|
|
1901
|
+
const currentPath = [...path, item];
|
|
1902
|
+
if (selected.has(item.name)) {
|
|
1903
|
+
const displayPath = currentPath.map(p => p.title).join(' > ');
|
|
1904
|
+
permPaths.push({
|
|
1905
|
+
group,
|
|
1906
|
+
path: currentPath,
|
|
1907
|
+
displayPath
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
if (item.children?.length) {
|
|
1911
|
+
findPermissionPaths(item.children, currentPath);
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
};
|
|
1915
|
+
findPermissionPaths(group.permissions);
|
|
1916
|
+
result[group.name] = permPaths;
|
|
1917
|
+
}
|
|
1918
|
+
return result;
|
|
1919
|
+
}, /* @ts-ignore */
|
|
1920
|
+
...(ngDevMode ? [{ debugName: "selectedPermissionsByGroup" }] : /* istanbul ignore next */ []));
|
|
1921
|
+
this.isEmpty = computed(() => {
|
|
1922
|
+
return Object.values(this.selectedPermissionsByGroup()).every(arr => !arr.length);
|
|
1923
|
+
}, /* @ts-ignore */
|
|
1924
|
+
...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
|
|
1925
|
+
}
|
|
1926
|
+
async openPermissionsEditor() {
|
|
1927
|
+
await this.commandService.execute('assign-permissions', {
|
|
1928
|
+
data: this.contextService.data(),
|
|
1929
|
+
entity: `${RootConfig.module.name}.${RootConfig.entities.roles.name}`,
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPPermissionsWidgetViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1933
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPPermissionsWidgetViewComponent, isStandalone: true, selector: "axm-permissions-widget-view", usesInheritance: true, ngImport: i0, template: `
|
|
1934
|
+
<!-- <axm-permissions-editor
|
|
1935
|
+
[initialPermissions]="getValue() || []"
|
|
1936
|
+
[readOnly]="true">
|
|
1937
|
+
</axm-permissions-editor> -->
|
|
1938
|
+
<div class="flex flex-col gap-2">
|
|
1939
|
+
@for (group of groups(); track group.name) {
|
|
1940
|
+
@if (selectedPermissionsByGroup()[group.name]?.length) {
|
|
1941
|
+
<div>
|
|
1942
|
+
<div class="text-xs font-semibold mb-2 text-muted">{{ group.title }}</div>
|
|
1943
|
+
<div class="flex gap-1 flex-wrap">
|
|
1944
|
+
@for (permPath of selectedPermissionsByGroup()[group.name]; track permPath.path[permPath.path.length - 1].name) {
|
|
1945
|
+
<ax-badge [text]="permPath.displayPath" color="primary" size="sm"></ax-badge>
|
|
1946
|
+
}
|
|
1947
|
+
</div>
|
|
1948
|
+
</div>
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
@if (isEmpty()) {
|
|
1952
|
+
<axp-state-message
|
|
1953
|
+
icon="fa-light fa-shield-check"
|
|
1954
|
+
[title]="'@security-management:permissions-feature.components.permissions-editor.empty-states.no-permissions.title'"
|
|
1955
|
+
[description]="'@security-management:permissions-feature.components.permissions-editor.empty-states.no-permissions.description'">
|
|
1956
|
+
</axp-state-message>
|
|
1957
|
+
}
|
|
1958
|
+
</div>
|
|
1959
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i9.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "titleParams", "descriptionParams", "look"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1960
|
+
}
|
|
1961
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPPermissionsWidgetViewComponent, decorators: [{
|
|
1962
|
+
type: Component,
|
|
1963
|
+
args: [{
|
|
1964
|
+
selector: 'axm-permissions-widget-view',
|
|
1965
|
+
template: `
|
|
1966
|
+
<!-- <axm-permissions-editor
|
|
1967
|
+
[initialPermissions]="getValue() || []"
|
|
1968
|
+
[readOnly]="true">
|
|
1969
|
+
</axm-permissions-editor> -->
|
|
1970
|
+
<div class="flex flex-col gap-2">
|
|
1971
|
+
@for (group of groups(); track group.name) {
|
|
1972
|
+
@if (selectedPermissionsByGroup()[group.name]?.length) {
|
|
1973
|
+
<div>
|
|
1974
|
+
<div class="text-xs font-semibold mb-2 text-muted">{{ group.title }}</div>
|
|
1975
|
+
<div class="flex gap-1 flex-wrap">
|
|
1976
|
+
@for (permPath of selectedPermissionsByGroup()[group.name]; track permPath.path[permPath.path.length - 1].name) {
|
|
1977
|
+
<ax-badge [text]="permPath.displayPath" color="primary" size="sm"></ax-badge>
|
|
1978
|
+
}
|
|
1979
|
+
</div>
|
|
1980
|
+
</div>
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
@if (isEmpty()) {
|
|
1984
|
+
<axp-state-message
|
|
1985
|
+
icon="fa-light fa-shield-check"
|
|
1986
|
+
[title]="'@security-management:permissions-feature.components.permissions-editor.empty-states.no-permissions.title'"
|
|
1987
|
+
[description]="'@security-management:permissions-feature.components.permissions-editor.empty-states.no-permissions.description'">
|
|
1988
|
+
</axp-state-message>
|
|
1989
|
+
}
|
|
1990
|
+
</div>
|
|
1991
|
+
`,
|
|
1992
|
+
standalone: true,
|
|
1993
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1994
|
+
imports: [AXBadgeModule, AXTranslationModule, AXPStateMessageComponent]
|
|
1995
|
+
}]
|
|
1996
|
+
}] });
|
|
1997
|
+
|
|
1998
|
+
var permissionsWidgetView_component = /*#__PURE__*/Object.freeze({
|
|
1999
|
+
__proto__: null,
|
|
2000
|
+
AXPPermissionsWidgetViewComponent: AXPPermissionsWidgetViewComponent
|
|
2001
|
+
});
|
|
2002
|
+
|
|
2003
|
+
class AXPPermissionsWidgetEditComponent extends AXPValueWidgetComponent {
|
|
2004
|
+
// #region ---------------- UI Handlers ----------------
|
|
2005
|
+
/**
|
|
2006
|
+
* Handles permissions changes from the editor.
|
|
2007
|
+
*/
|
|
2008
|
+
onPermissionsChanged(permissions) {
|
|
2009
|
+
this.setValue(permissions);
|
|
2010
|
+
}
|
|
2011
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPPermissionsWidgetEditComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
2012
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.8", type: AXPPermissionsWidgetEditComponent, isStandalone: true, selector: "axm-permissions-widget-edit", usesInheritance: true, ngImport: i0, template: `
|
|
2013
|
+
<axm-permissions-editor
|
|
2014
|
+
[initialPermissions]="getValue() || []"
|
|
2015
|
+
[readOnly]="false"
|
|
2016
|
+
(permissionsChanged)="onPermissionsChanged($event)">
|
|
2017
|
+
</axm-permissions-editor>
|
|
2018
|
+
`, isInline: true, dependencies: [{ kind: "component", type: AXPPermissionsEditorComponent, selector: "axm-permissions-editor", inputs: ["initialPermissions", "readOnly", "showActions", "fillHeight"], outputs: ["permissionsChanged", "permissionsSelected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2019
|
+
}
|
|
2020
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPPermissionsWidgetEditComponent, decorators: [{
|
|
2021
|
+
type: Component,
|
|
2022
|
+
args: [{
|
|
2023
|
+
selector: 'axm-permissions-widget-edit',
|
|
2024
|
+
template: `
|
|
2025
|
+
<axm-permissions-editor
|
|
2026
|
+
[initialPermissions]="getValue() || []"
|
|
2027
|
+
[readOnly]="false"
|
|
2028
|
+
(permissionsChanged)="onPermissionsChanged($event)">
|
|
2029
|
+
</axm-permissions-editor>
|
|
2030
|
+
`,
|
|
2031
|
+
standalone: true,
|
|
2032
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2033
|
+
imports: [
|
|
2034
|
+
AXPPermissionsEditorComponent
|
|
2035
|
+
]
|
|
2036
|
+
}]
|
|
2037
|
+
}] });
|
|
2038
|
+
|
|
2039
|
+
var permissionsWidgetEdit_component = /*#__PURE__*/Object.freeze({
|
|
2040
|
+
__proto__: null,
|
|
2041
|
+
AXPPermissionsWidgetEditComponent: AXPPermissionsWidgetEditComponent
|
|
2042
|
+
});
|
|
2043
|
+
|
|
2044
|
+
class AXPPermissionsWidgetColumnComponent extends AXPColumnWidgetComponent {
|
|
2045
|
+
constructor() {
|
|
2046
|
+
super(...arguments);
|
|
2047
|
+
this.count = computed(() => castArray(this.rawValue).length, /* @ts-ignore */
|
|
2048
|
+
...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
|
|
2049
|
+
this.commandService = inject(AXPCommandService);
|
|
2050
|
+
}
|
|
2051
|
+
async openPermissionsEditor() {
|
|
2052
|
+
await this.commandService.execute('assign-permissions', {
|
|
2053
|
+
data: this.rowData,
|
|
2054
|
+
entity: `${RootConfig.module.name}.${RootConfig.entities.roles.name}`,
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPPermissionsWidgetColumnComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
2058
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.8", type: AXPPermissionsWidgetColumnComponent, isStandalone: true, selector: "axm-permissions-widget-column", inputs: { rawValue: "rawValue", rowData: "rowData" }, usesInheritance: true, ngImport: i0, template: `
|
|
2059
|
+
<span class="cursor-pointer text-primary underline" (click)="openPermissionsEditor()">{{count()}} {{'@security-management:permissions-feature.terms.permissions' | translate | async}}</span>
|
|
2060
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i12.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2061
|
+
}
|
|
2062
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPPermissionsWidgetColumnComponent, decorators: [{
|
|
2063
|
+
type: Component,
|
|
2064
|
+
args: [{
|
|
2065
|
+
selector: 'axm-permissions-widget-column',
|
|
2066
|
+
template: `
|
|
2067
|
+
<span class="cursor-pointer text-primary underline" (click)="openPermissionsEditor()">{{count()}} {{'@security-management:permissions-feature.terms.permissions' | translate | async}}</span>
|
|
2068
|
+
`,
|
|
2069
|
+
standalone: true,
|
|
2070
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2071
|
+
imports: [CommonModule, AXTranslationModule],
|
|
2072
|
+
inputs: ['rawValue', 'rowData']
|
|
2073
|
+
}]
|
|
2074
|
+
}] });
|
|
2075
|
+
|
|
2076
|
+
var permissionsWidgetColumn_component = /*#__PURE__*/Object.freeze({
|
|
2077
|
+
__proto__: null,
|
|
2078
|
+
AXPPermissionsWidgetColumnComponent: AXPPermissionsWidgetColumnComponent
|
|
2079
|
+
});
|
|
2080
|
+
|
|
2081
|
+
// Permission Editor Components
|
|
2082
|
+
|
|
2083
|
+
/**
|
|
2084
|
+
* Generated bundle index. Do not edit.
|
|
2085
|
+
*/
|
|
2086
|
+
|
|
2087
|
+
export { AXMAssignPermissionsCommand as A, AXMDeviceSessionsService as a, AXMPermissionsModule as b, AXMRolePermissionGrantService as c, AXMRolePermissionsPageComponent as d, AXMRolePermissionsPageComponentProvider as e, AXMSecurityManagementModule as f, AXPPermissionsEditorComponent as g, AXPPermissionsWidget as h, AXPPermissionsWidgetColumnComponent as i, AXPPermissionsWidgetEditComponent as j, AXPPermissionsWidgetViewComponent as k, resolvePersistedEntityId as r };
|
|
2088
|
+
//# sourceMappingURL=acorex-platform-module-security-management-client-acorex-platform-module-security-management-client-jSDkyMMZ.mjs.map
|