@valtimo/access-control-management 13.35.0 → 13.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/valtimo-access-control-management.mjs +1558 -194
- package/fesm2022/valtimo-access-control-management.mjs.map +1 -1
- package/lib/access-control-management.module.d.ts +17 -11
- package/lib/components/access-control-form-editor-tab/access-control-form-editor-tab.component.d.ts +69 -0
- package/lib/components/condition-tree/condition-tree.component.d.ts +62 -0
- package/lib/components/editor/access-control-editor.component.d.ts +9 -3
- package/lib/components/overview/access-control-overview.component.d.ts +1 -0
- package/lib/components/permission-form/permission-form.component.d.ts +69 -0
- package/lib/components/role-metadata-modal/role-metadata-modal.component.d.ts +18 -5
- package/lib/constants/access-control-editor.test-ids.d.ts +19 -0
- package/lib/constants/access-control-labels.d.ts +3 -1
- package/lib/constants/index.d.ts +1 -0
- package/lib/models/access-control-editor-tab.model.d.ts +1 -0
- package/lib/models/index.d.ts +1 -0
- package/lib/models/pbac-editor.model.d.ts +10 -0
- package/lib/pipes/action-description.pipe.d.ts +10 -0
- package/lib/pipes/action-label.pipe.d.ts +10 -0
- package/lib/pipes/action-tag-type.pipe.d.ts +8 -0
- package/lib/pipes/index.d.ts +3 -0
- package/lib/services/access-control-form-editor.service.d.ts +37 -0
- package/lib/services/access-control.service.d.ts +4 -1
- package/lib/services/index.d.ts +1 -0
- package/lib/services/permission-schema-metadata.service.d.ts +19 -1
- package/lib/utils/condition-value.d.ts +17 -0
- package/lib/utils/index.d.ts +2 -0
- package/lib/utils/resource-name.d.ts +4 -0
- package/package.json +1 -1
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, EventEmitter, Output, Input, ChangeDetectionStrategy, Component, ViewChild, Pipe,
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
2
|
+
import { Injectable, EventEmitter, signal, Output, Input, ChangeDetectionStrategy, Component, ViewChild, Pipe, ViewChildren, NgModule } from '@angular/core';
|
|
3
|
+
import * as i1 from '@angular/forms';
|
|
4
|
+
import { Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
5
|
+
import * as i1$1 from '@valtimo/shared';
|
|
6
|
+
import { InterceptorSkipHeader, ROLE_ADMIN } from '@valtimo/shared';
|
|
7
|
+
import { BehaviorSubject, catchError, of, tap, switchMap, take, shareReplay, map, combineLatest, Subject, finalize, delay, Subscription, skip, filter } from 'rxjs';
|
|
6
8
|
import * as i2 from '@angular/common/http';
|
|
7
|
-
import * as
|
|
9
|
+
import * as i3 from '@angular/common';
|
|
8
10
|
import { CommonModule } from '@angular/common';
|
|
9
|
-
import * as
|
|
11
|
+
import * as i1$2 from '@ngx-translate/core';
|
|
10
12
|
import { TranslateModule } from '@ngx-translate/core';
|
|
11
|
-
import * as i3 from '@valtimo/components';
|
|
12
|
-
import { CARBON_CONSTANTS, ViewType, CarbonListComponent, EditorModule, OverflowMenuComponent, ConfirmationModalModule, RenderInPageHeaderDirective, OverflowMenuOptionComponent, OverflowMenuTriggerComponent, CarbonListModule } from '@valtimo/components';
|
|
13
|
-
import * as i1$
|
|
13
|
+
import * as i3$1 from '@valtimo/components';
|
|
14
|
+
import { CARBON_CONSTANTS, ViewType, CarbonListComponent, EditorModule, OverflowMenuComponent, PendingChangesComponent, pendingChangesGuard, ConfirmationModalModule, RenderInPageHeaderDirective, FitPageDirective, OverflowMenuOptionComponent, OverflowMenuTriggerComponent, CarbonListModule, SelectModule, TooltipIconModule } from '@valtimo/components';
|
|
15
|
+
import * as i1$3 from '@angular/router';
|
|
14
16
|
import { RouterModule } from '@angular/router';
|
|
15
17
|
import { AuthGuardService } from '@valtimo/security';
|
|
16
18
|
import * as i2$1 from 'carbon-components-angular';
|
|
17
|
-
import { ButtonModule, DropdownModule, IconModule, ModalModule, InputModule, LoadingModule, NotificationModule, TabsModule } from 'carbon-components-angular';
|
|
18
|
-
import
|
|
19
|
-
import { Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
20
|
-
import { Filter16, TrashCan16 } from '@carbon/icons';
|
|
19
|
+
import { ButtonModule, DropdownModule, IconModule, Tab, ModalModule, InputModule, LoadingModule, NotificationModule, TabsModule, ToggleModule, ContentSwitcherModule, AccordionModule, CheckboxModule, LayerModule, TagModule } from 'carbon-components-angular';
|
|
20
|
+
import { Add16, TrashCan16, Information16, Filter16 } from '@carbon/icons';
|
|
21
21
|
|
|
22
22
|
/*
|
|
23
23
|
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
@@ -37,6 +37,7 @@ import { Filter16, TrashCan16 } from '@carbon/icons';
|
|
|
37
37
|
var AccessControlEditorTab;
|
|
38
38
|
(function (AccessControlEditorTab) {
|
|
39
39
|
AccessControlEditorTab["SUMMARY"] = "summary";
|
|
40
|
+
AccessControlEditorTab["EDITOR"] = "editor";
|
|
40
41
|
AccessControlEditorTab["JSON_EDITOR"] = "jsonEditor";
|
|
41
42
|
})(AccessControlEditorTab || (AccessControlEditorTab = {}));
|
|
42
43
|
|
|
@@ -72,6 +73,22 @@ var AccessControlEditorTab;
|
|
|
72
73
|
* limitations under the License.
|
|
73
74
|
*/
|
|
74
75
|
|
|
76
|
+
/*
|
|
77
|
+
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
78
|
+
*
|
|
79
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
80
|
+
* you may not use this file except in compliance with the License.
|
|
81
|
+
* You may obtain a copy of the License at
|
|
82
|
+
*
|
|
83
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
84
|
+
*
|
|
85
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
86
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
87
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
88
|
+
* See the License for the specific language governing permissions and
|
|
89
|
+
* limitations under the License.
|
|
90
|
+
*/
|
|
91
|
+
|
|
75
92
|
/*
|
|
76
93
|
* Copyright 2015-2025 Ritense BV, the Netherlands.
|
|
77
94
|
*
|
|
@@ -104,6 +121,599 @@ var AccessControlEditorTab;
|
|
|
104
121
|
* limitations under the License.
|
|
105
122
|
*/
|
|
106
123
|
|
|
124
|
+
/*
|
|
125
|
+
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
126
|
+
*
|
|
127
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
128
|
+
* you may not use this file except in compliance with the License.
|
|
129
|
+
* You may obtain a copy of the License at
|
|
130
|
+
*
|
|
131
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
132
|
+
*
|
|
133
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
134
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
135
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
136
|
+
* See the License for the specific language governing permissions and
|
|
137
|
+
* limitations under the License.
|
|
138
|
+
*/
|
|
139
|
+
const ACCESS_CONTROL_EDITOR_TEST_IDS = {
|
|
140
|
+
addPermissionButton: 'accessControlEditorAddPermission',
|
|
141
|
+
permissionListItem: 'accessControlEditorPermissionListItem',
|
|
142
|
+
permissionCard: 'accessControlEditorPermissionCard',
|
|
143
|
+
removePermissionButton: 'accessControlEditorRemovePermission',
|
|
144
|
+
resourceTypeSelect: 'accessControlEditorResourceType',
|
|
145
|
+
actionsSelect: 'accessControlEditorActions',
|
|
146
|
+
contextToggle: 'accessControlEditorContextToggle',
|
|
147
|
+
contextResourceTypeSelect: 'accessControlEditorContextResourceType',
|
|
148
|
+
addConditionButton: 'accessControlEditorAddCondition',
|
|
149
|
+
removeConditionButton: 'accessControlEditorRemoveCondition',
|
|
150
|
+
conditionTypeSelect: 'accessControlEditorConditionType',
|
|
151
|
+
conditionFieldSelect: 'accessControlEditorConditionField',
|
|
152
|
+
conditionOperatorSelect: 'accessControlEditorConditionOperator',
|
|
153
|
+
conditionValueInput: 'accessControlEditorConditionValue',
|
|
154
|
+
conditionPathInput: 'accessControlEditorConditionPath',
|
|
155
|
+
conditionClazzInput: 'accessControlEditorConditionClazz',
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/*
|
|
159
|
+
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
160
|
+
*
|
|
161
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
162
|
+
* you may not use this file except in compliance with the License.
|
|
163
|
+
* You may obtain a copy of the License at
|
|
164
|
+
*
|
|
165
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
166
|
+
*
|
|
167
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
168
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
169
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
170
|
+
* See the License for the specific language governing permissions and
|
|
171
|
+
* limitations under the License.
|
|
172
|
+
*/
|
|
173
|
+
const OPERATOR_LABEL = {
|
|
174
|
+
'==': 'accessControl.overview.operators.eq',
|
|
175
|
+
'!=': 'accessControl.overview.operators.neq',
|
|
176
|
+
'>': 'accessControl.overview.operators.gt',
|
|
177
|
+
'>=': 'accessControl.overview.operators.gte',
|
|
178
|
+
'<': 'accessControl.overview.operators.lt',
|
|
179
|
+
'<=': 'accessControl.overview.operators.lte',
|
|
180
|
+
in: 'accessControl.overview.operators.in',
|
|
181
|
+
list_contains: 'accessControl.overview.operators.list_contains',
|
|
182
|
+
};
|
|
183
|
+
const NO_CONTEXT_RESOURCE_TYPE = 'com.ritense.authorization.NoContext';
|
|
184
|
+
// Each permission action is shown as a Carbon tag; every kind gets its own colour so they are
|
|
185
|
+
// distinguishable at a glance. The palette combines Carbon's built-in tag colours with the extended
|
|
186
|
+
// case-widget colours (orange, yellow, periwinkle, light-green, brown — registered globally in
|
|
187
|
+
// carbon.scss), avoiding high-contrast, outline and the grays. Related actions share a hue family:
|
|
188
|
+
// create/complete are greens, modify/inspect_modify are purples.
|
|
189
|
+
const ACTION_TAG_TYPE = {
|
|
190
|
+
view: 'blue',
|
|
191
|
+
view_list: 'cyan',
|
|
192
|
+
inspect: 'teal',
|
|
193
|
+
inspect_modify: 'periwinkle',
|
|
194
|
+
modify: 'purple',
|
|
195
|
+
create: 'green',
|
|
196
|
+
complete: 'light-green',
|
|
197
|
+
delete: 'red',
|
|
198
|
+
assign: 'orange',
|
|
199
|
+
assignable: 'yellow',
|
|
200
|
+
claim: 'magenta',
|
|
201
|
+
export: 'brown',
|
|
202
|
+
};
|
|
203
|
+
// Fallback colour for any action not listed above (an unmapped/custom action).
|
|
204
|
+
const DEFAULT_ACTION_TAG_TYPE = 'gray';
|
|
205
|
+
|
|
206
|
+
/*
|
|
207
|
+
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
208
|
+
*
|
|
209
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
210
|
+
* you may not use this file except in compliance with the License.
|
|
211
|
+
* You may obtain a copy of the License at
|
|
212
|
+
*
|
|
213
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
214
|
+
*
|
|
215
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
216
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
217
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
218
|
+
* See the License for the specific language governing permissions and
|
|
219
|
+
* limitations under the License.
|
|
220
|
+
*/
|
|
221
|
+
|
|
222
|
+
/*
|
|
223
|
+
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
224
|
+
*
|
|
225
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
226
|
+
* you may not use this file except in compliance with the License.
|
|
227
|
+
* You may obtain a copy of the License at
|
|
228
|
+
*
|
|
229
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
230
|
+
*
|
|
231
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
232
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
233
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
234
|
+
* See the License for the specific language governing permissions and
|
|
235
|
+
* limitations under the License.
|
|
236
|
+
*/
|
|
237
|
+
// A permission condition value is generically typed on the backend (a string, number, boolean,
|
|
238
|
+
// null, list or object). The form editor edits it through a single text field, so we need a
|
|
239
|
+
// lossless-as-possible bridge between the stored value and its text representation.
|
|
240
|
+
/**
|
|
241
|
+
* Render a stored condition value as editable text.
|
|
242
|
+
* - undefined -> empty string (no value set, e.g. a freshly-added condition)
|
|
243
|
+
* - null -> the literal "null" (so an intentional null reads as deliberate rather than an unfilled
|
|
244
|
+
* field, and still round-trips back to null through textToValue)
|
|
245
|
+
* - string -> the raw string (so '${currentUserId}' and 'someKey' show without quotes)
|
|
246
|
+
* - everything else (number, boolean, array, object) -> its JSON representation
|
|
247
|
+
*/
|
|
248
|
+
function valueToText(value) {
|
|
249
|
+
if (value === undefined)
|
|
250
|
+
return '';
|
|
251
|
+
if (value === null)
|
|
252
|
+
return 'null';
|
|
253
|
+
if (typeof value === 'string')
|
|
254
|
+
return value;
|
|
255
|
+
return JSON.stringify(value);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Parse editable text back into a stored condition value.
|
|
259
|
+
* - empty string -> null
|
|
260
|
+
* - valid JSON (number, boolean, null, array, object, quoted string) -> the parsed value
|
|
261
|
+
* - anything else -> the raw string (so 'view' or '${currentUserId}' stay strings)
|
|
262
|
+
*/
|
|
263
|
+
function textToValue(text) {
|
|
264
|
+
const trimmed = (text ?? '').trim();
|
|
265
|
+
if (trimmed === '')
|
|
266
|
+
return null;
|
|
267
|
+
try {
|
|
268
|
+
return JSON.parse(trimmed);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return text;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/*
|
|
276
|
+
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
277
|
+
*
|
|
278
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
279
|
+
* you may not use this file except in compliance with the License.
|
|
280
|
+
* You may obtain a copy of the License at
|
|
281
|
+
*
|
|
282
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
283
|
+
*
|
|
284
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
285
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
286
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
287
|
+
* See the License for the specific language governing permissions and
|
|
288
|
+
* limitations under the License.
|
|
289
|
+
*/
|
|
290
|
+
const SPECIAL_VALUE_PATTERN = /^\$\{([A-Za-z_$][A-Za-z0-9_$]*)\}$/;
|
|
291
|
+
function lowerFirst(s) {
|
|
292
|
+
return s.charAt(0).toLowerCase() + s.slice(1);
|
|
293
|
+
}
|
|
294
|
+
function upperFirst(s) {
|
|
295
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
296
|
+
}
|
|
297
|
+
function resourceTypeI18nKey(fqn) {
|
|
298
|
+
const lastSegment = fqn.split('.').pop();
|
|
299
|
+
if (!lastSegment)
|
|
300
|
+
return null;
|
|
301
|
+
return `accessControl.resourceTypes.${lowerFirst(lastSegment)}`;
|
|
302
|
+
}
|
|
303
|
+
function fieldI18nKey(resourceType, field) {
|
|
304
|
+
const resourceSegment = resourceType.split('.').pop();
|
|
305
|
+
if (!resourceSegment || !field)
|
|
306
|
+
return null;
|
|
307
|
+
const fieldSegment = field
|
|
308
|
+
.split('.')
|
|
309
|
+
.map((part, i) => (i === 0 ? part : upperFirst(part)))
|
|
310
|
+
.join('');
|
|
311
|
+
return `accessControl.overview.fields.${lowerFirst(resourceSegment)}.${fieldSegment}`;
|
|
312
|
+
}
|
|
313
|
+
function tryTranslate(translate, key) {
|
|
314
|
+
const translated = translate.instant(key);
|
|
315
|
+
return translated === key ? null : translated;
|
|
316
|
+
}
|
|
317
|
+
function formatResourceType(translate, fqn) {
|
|
318
|
+
const key = resourceTypeI18nKey(fqn);
|
|
319
|
+
const translated = key ? tryTranslate(translate, key) : null;
|
|
320
|
+
if (translated !== null)
|
|
321
|
+
return translated;
|
|
322
|
+
const segments = fqn.split('.');
|
|
323
|
+
return segments[segments.length - 1] || fqn;
|
|
324
|
+
}
|
|
325
|
+
function formatField(translate, resourceType, field) {
|
|
326
|
+
const key = fieldI18nKey(resourceType, field);
|
|
327
|
+
const translated = key ? tryTranslate(translate, key) : null;
|
|
328
|
+
if (translated !== null)
|
|
329
|
+
return translated;
|
|
330
|
+
return humanizeFieldPath(field);
|
|
331
|
+
}
|
|
332
|
+
function formatOperator(translate, operator) {
|
|
333
|
+
const key = OPERATOR_LABEL[operator];
|
|
334
|
+
return key ? translate.instant(key) : operator;
|
|
335
|
+
}
|
|
336
|
+
function formatValue(translate, value) {
|
|
337
|
+
if (value === null) {
|
|
338
|
+
return translate.instant('accessControl.overview.specialValues.null');
|
|
339
|
+
}
|
|
340
|
+
if (typeof value === 'string') {
|
|
341
|
+
if (value === '')
|
|
342
|
+
return translate.instant('accessControl.overview.specialValues.empty');
|
|
343
|
+
const match = SPECIAL_VALUE_PATTERN.exec(value);
|
|
344
|
+
if (match) {
|
|
345
|
+
const key = `accessControl.overview.specialValues.${match[1]}`;
|
|
346
|
+
const translated = tryTranslate(translate, key);
|
|
347
|
+
if (translated !== null)
|
|
348
|
+
return translated;
|
|
349
|
+
}
|
|
350
|
+
return value;
|
|
351
|
+
}
|
|
352
|
+
if (Array.isArray(value)) {
|
|
353
|
+
return value.map(item => formatValue(translate, item)).join(', ');
|
|
354
|
+
}
|
|
355
|
+
// Objects (and any other non-primitive) are shown as JSON rather than "[object Object]".
|
|
356
|
+
return JSON.stringify(value);
|
|
357
|
+
}
|
|
358
|
+
function humanizeFieldPath(field) {
|
|
359
|
+
return field
|
|
360
|
+
.split('.')
|
|
361
|
+
.map(part => part.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase())
|
|
362
|
+
.join(' ');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/*
|
|
366
|
+
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
367
|
+
*
|
|
368
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
369
|
+
* you may not use this file except in compliance with the License.
|
|
370
|
+
* You may obtain a copy of the License at
|
|
371
|
+
*
|
|
372
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
373
|
+
*
|
|
374
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
375
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
376
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
377
|
+
* See the License for the specific language governing permissions and
|
|
378
|
+
* limitations under the License.
|
|
379
|
+
*/
|
|
380
|
+
// The short (simple) name of a fully-qualified type, e.g. "CaseDefinition" for
|
|
381
|
+
// "com.ritense.case_.domain.definition.CaseDefinition".
|
|
382
|
+
function shortTypeName(fullyQualifiedName) {
|
|
383
|
+
if (!fullyQualifiedName)
|
|
384
|
+
return '';
|
|
385
|
+
return fullyQualifiedName.substring(fullyQualifiedName.lastIndexOf('.') + 1);
|
|
386
|
+
}
|
|
387
|
+
// A select item labelled "ShortName (fully.qualified.Name)", keeping the fully-qualified name as the
|
|
388
|
+
// stored id. Used for the resource-type, related-resource and value-type dropdowns so the readable
|
|
389
|
+
// name is shown while the exact technical type is what gets persisted.
|
|
390
|
+
function fqnSelectItem(fullyQualifiedName) {
|
|
391
|
+
return {
|
|
392
|
+
id: fullyQualifiedName,
|
|
393
|
+
text: `${shortTypeName(fullyQualifiedName)} (${fullyQualifiedName})`,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/*
|
|
398
|
+
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
399
|
+
*
|
|
400
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
401
|
+
* you may not use this file except in compliance with the License.
|
|
402
|
+
* You may obtain a copy of the License at
|
|
403
|
+
*
|
|
404
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
405
|
+
*
|
|
406
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
407
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
408
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
409
|
+
* See the License for the specific language governing permissions and
|
|
410
|
+
* limitations under the License.
|
|
411
|
+
*/
|
|
412
|
+
|
|
413
|
+
/*
|
|
414
|
+
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
415
|
+
*
|
|
416
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
417
|
+
* you may not use this file except in compliance with the License.
|
|
418
|
+
* You may obtain a copy of the License at
|
|
419
|
+
*
|
|
420
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
421
|
+
*
|
|
422
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
423
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
424
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
425
|
+
* See the License for the specific language governing permissions and
|
|
426
|
+
* limitations under the License.
|
|
427
|
+
*/
|
|
428
|
+
// Common Java value types offered in the "Value type" dropdown for JSON-field (expression)
|
|
429
|
+
// conditions. Users can still enter any other fully-qualified type manually; String is the default.
|
|
430
|
+
const VALUE_TYPE_OPTIONS = [
|
|
431
|
+
'java.lang.String',
|
|
432
|
+
'java.lang.Boolean',
|
|
433
|
+
'java.lang.Integer',
|
|
434
|
+
'java.lang.Long',
|
|
435
|
+
'java.lang.Double',
|
|
436
|
+
'java.math.BigDecimal',
|
|
437
|
+
'java.util.Collection',
|
|
438
|
+
'java.time.LocalDate',
|
|
439
|
+
'java.time.LocalDateTime',
|
|
440
|
+
];
|
|
441
|
+
const DEFAULT_VALUE_TYPE = 'java.lang.String';
|
|
442
|
+
class AccessControlFormEditorService {
|
|
443
|
+
constructor(fb) {
|
|
444
|
+
this.fb = fb;
|
|
445
|
+
this._resources = [];
|
|
446
|
+
this._resourceByType = {};
|
|
447
|
+
this._operatorKeys = [];
|
|
448
|
+
}
|
|
449
|
+
setRegistry(registry) {
|
|
450
|
+
this._resources = registry.resources;
|
|
451
|
+
this._resourceByType = registry.resources.reduce((acc, resource) => {
|
|
452
|
+
acc[resource.resourceType] = resource;
|
|
453
|
+
return acc;
|
|
454
|
+
}, {});
|
|
455
|
+
this._operatorKeys = registry.operators.map(operator => operator.key);
|
|
456
|
+
}
|
|
457
|
+
// ----- Form construction -----
|
|
458
|
+
buildPermissionsArray(permissions) {
|
|
459
|
+
return this.fb.array(permissions.map(permission => this.createPermissionGroup(permission)));
|
|
460
|
+
}
|
|
461
|
+
createPermissionGroup(permission) {
|
|
462
|
+
const conditions = permission?.conditions ?? [];
|
|
463
|
+
const contextConditions = permission?.contextConditions ?? [];
|
|
464
|
+
return this.fb.group({
|
|
465
|
+
resourceType: this.fb.control(permission?.resourceType ?? '', Validators.required),
|
|
466
|
+
actions: this.fb.control(this.normalizeActions(permission), minSelectedArrayValidator(1)),
|
|
467
|
+
conditions: this.fb.array(conditions.map(condition => this.createConditionGroup(condition))),
|
|
468
|
+
// Context is opt-in via a toggle. `hasContext` off means "no context at all" — nothing about
|
|
469
|
+
// context is serialized. On means the permission is scoped to the selected context resource,
|
|
470
|
+
// which may be a real related resource or the "No context" marker (itself a deliberate,
|
|
471
|
+
// distinct choice, as opposed to leaving context off entirely). The resource defaults to the
|
|
472
|
+
// marker so the dropdown always has a valid, non-empty value while the toggle is on.
|
|
473
|
+
hasContext: this.fb.control(!!permission?.contextResourceType),
|
|
474
|
+
contextResourceType: this.fb.control(permission?.contextResourceType || NO_CONTEXT_RESOURCE_TYPE),
|
|
475
|
+
contextConditions: this.fb.array(contextConditions.map(condition => this.createConditionGroup(condition))),
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
createConditionGroup(condition, type) {
|
|
479
|
+
const conditionType = condition?.type ?? type ?? 'field';
|
|
480
|
+
const nested = condition && condition.type === 'container' ? (condition.conditions ?? []) : [];
|
|
481
|
+
return this.fb.group({
|
|
482
|
+
type: this.fb.control(conditionType),
|
|
483
|
+
field: this.fb.control(this.conditionField(condition)),
|
|
484
|
+
operator: this.fb.control(this.conditionOperator(condition)),
|
|
485
|
+
value: this.fb.control(valueToText(this.conditionValue(condition))),
|
|
486
|
+
path: this.fb.control(condition && condition.type === 'expression' ? condition.path : ''),
|
|
487
|
+
clazz: this.fb.control(this.conditionClazz(condition)),
|
|
488
|
+
// Whether the value type is entered manually (a custom class) rather than picked from the
|
|
489
|
+
// preset dropdown. UI-only; not serialized.
|
|
490
|
+
clazzManual: this.fb.control(this.isCustomValueType(this.conditionClazz(condition))),
|
|
491
|
+
resourceType: this.fb.control(condition && condition.type === 'container' ? condition.resourceType : ''),
|
|
492
|
+
conditions: this.fb.array(nested.map(child => this.createConditionGroup(child))),
|
|
493
|
+
}, { validators: conditionValidator });
|
|
494
|
+
}
|
|
495
|
+
// ----- Serialization back to the PUT contract -----
|
|
496
|
+
serialize(permissionsArray) {
|
|
497
|
+
return permissionsArray.controls.map(control => this.serializePermission(control));
|
|
498
|
+
}
|
|
499
|
+
serializePermission(group) {
|
|
500
|
+
const result = {
|
|
501
|
+
resourceType: group.get('resourceType').value,
|
|
502
|
+
actions: group.get('actions').value ?? [],
|
|
503
|
+
conditions: this.serializeConditions(group.get('conditions')),
|
|
504
|
+
};
|
|
505
|
+
// Context is only written when the toggle is on. The selected resource may be a real related
|
|
506
|
+
// resource or the "No context" marker (a distinct, deliberate choice); either way it is
|
|
507
|
+
// persisted. Toggling context off drops the context fields entirely.
|
|
508
|
+
if (group.get('hasContext').value) {
|
|
509
|
+
result.contextResourceType = group.get('contextResourceType').value;
|
|
510
|
+
result.contextConditions = this.serializeConditions(group.get('contextConditions'));
|
|
511
|
+
}
|
|
512
|
+
return result;
|
|
513
|
+
}
|
|
514
|
+
serializeConditions(conditionsArray) {
|
|
515
|
+
return conditionsArray.controls.map(control => {
|
|
516
|
+
const group = control;
|
|
517
|
+
const type = group.get('type').value;
|
|
518
|
+
if (type === 'container') {
|
|
519
|
+
return {
|
|
520
|
+
type: 'container',
|
|
521
|
+
resourceType: group.get('resourceType').value,
|
|
522
|
+
conditions: this.serializeConditions(group.get('conditions')),
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
if (type === 'expression') {
|
|
526
|
+
return {
|
|
527
|
+
type: 'expression',
|
|
528
|
+
field: group.get('field').value,
|
|
529
|
+
path: group.get('path').value,
|
|
530
|
+
operator: group.get('operator').value,
|
|
531
|
+
value: textToValue(group.get('value').value),
|
|
532
|
+
clazz: group.get('clazz').value,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
type: 'field',
|
|
537
|
+
field: group.get('field').value,
|
|
538
|
+
operator: group.get('operator').value,
|
|
539
|
+
value: textToValue(group.get('value').value),
|
|
540
|
+
};
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
// ----- Registry-driven select options -----
|
|
544
|
+
// Resource types, fields and container targets are shown by their raw technical name (the exact
|
|
545
|
+
// value written to the permission JSON). Actions, operators and condition types instead use
|
|
546
|
+
// natural-language labels (actions fall back to the technical key when untranslated).
|
|
547
|
+
resourceTypeItems(include) {
|
|
548
|
+
// Shown as "ShortName (fully.qualified.Name)" — the readable short name up front, with the exact
|
|
549
|
+
// technical type it maps to in parentheses. The stored value (id) stays the fully-qualified name.
|
|
550
|
+
const items = this._resources.map(resource => ({
|
|
551
|
+
id: resource.resourceType,
|
|
552
|
+
text: `${resource.shortName || shortTypeName(resource.resourceType)} (${resource.resourceType})`,
|
|
553
|
+
}));
|
|
554
|
+
return this.sortByText(this.withIncluded(items, include));
|
|
555
|
+
}
|
|
556
|
+
// The context options are "No context" plus the resource types reachable from the permission's
|
|
557
|
+
// resource type via an entity mapper (the same set as the container-condition targets). "No
|
|
558
|
+
// context" maps to the backend NoContext marker.
|
|
559
|
+
contextResourceTypeItems(resourceType, include) {
|
|
560
|
+
const noContext = {
|
|
561
|
+
id: NO_CONTEXT_RESOURCE_TYPE,
|
|
562
|
+
translationKey: 'accessControl.overview.noContext',
|
|
563
|
+
};
|
|
564
|
+
// "No context" is added explicitly here, so the NoContext marker must never also be
|
|
565
|
+
// force-included among the container targets: doing so appends a second item with the same id
|
|
566
|
+
// (the marker is not a real target), which renders as a duplicate row and two selected
|
|
567
|
+
// checkmarks. Only real resources are kept visible via `include`.
|
|
568
|
+
const targetInclude = Array.isArray(include)
|
|
569
|
+
? include.filter(value => value !== NO_CONTEXT_RESOURCE_TYPE)
|
|
570
|
+
: include === NO_CONTEXT_RESOURCE_TYPE
|
|
571
|
+
? null
|
|
572
|
+
: include;
|
|
573
|
+
return [noContext, ...this.containerTargetItems(resourceType, targetInclude)];
|
|
574
|
+
}
|
|
575
|
+
actionItems(resourceType, include) {
|
|
576
|
+
// Actions come straight from the resource in the PBAC registry (the backend discovers every
|
|
577
|
+
// ResourceActionProvider on the classpath), so no separate action map is needed.
|
|
578
|
+
const actions = this._resourceByType[resourceType]?.actions ?? [];
|
|
579
|
+
const items = actions.map(action => ({
|
|
580
|
+
id: action,
|
|
581
|
+
text: action,
|
|
582
|
+
}));
|
|
583
|
+
return this.withIncluded(items, include);
|
|
584
|
+
}
|
|
585
|
+
fieldItems(resourceType, include) {
|
|
586
|
+
const resource = this._resourceByType[resourceType];
|
|
587
|
+
const names = new Set();
|
|
588
|
+
// Only string names are kept; a malformed registry entry must not render as "[object Object]".
|
|
589
|
+
for (const field of resource?.fields ?? []) {
|
|
590
|
+
if (typeof field?.name === 'string' && field.name)
|
|
591
|
+
names.add(field.name);
|
|
592
|
+
}
|
|
593
|
+
for (const alias of resource?.fieldAliases ?? []) {
|
|
594
|
+
if (typeof alias?.alias === 'string' && alias.alias)
|
|
595
|
+
names.add(alias.alias);
|
|
596
|
+
}
|
|
597
|
+
// Fields are shown by their technical name (e.g. "isFinal", "createdBy") since that is the
|
|
598
|
+
// exact value used in the permission condition.
|
|
599
|
+
const items = Array.from(names).map(name => ({ id: name, text: name }));
|
|
600
|
+
return this.sortByText(this.withIncluded(items, include));
|
|
601
|
+
}
|
|
602
|
+
containerTargetItems(resourceType, include) {
|
|
603
|
+
const targets = this._resourceByType[resourceType]?.containerTargets ?? [];
|
|
604
|
+
// Shown as "ShortName (fully.qualified.Name)", like the resource-type dropdown. The stored value
|
|
605
|
+
// (id) stays the fully-qualified name.
|
|
606
|
+
const items = targets.map(fqnSelectItem);
|
|
607
|
+
return this.sortByText(this.withIncluded(items, include));
|
|
608
|
+
}
|
|
609
|
+
operatorItems() {
|
|
610
|
+
// Operators are shown in natural language (e.g. "equals", "is greater than") via their
|
|
611
|
+
// translation key; the stored value stays the technical symbol (the item id).
|
|
612
|
+
return this._operatorKeys.map(key => ({
|
|
613
|
+
id: key,
|
|
614
|
+
translationKey: OPERATOR_LABEL[key],
|
|
615
|
+
}));
|
|
616
|
+
}
|
|
617
|
+
// Preset value types for JSON-field (expression) conditions, shown as "ShortName (fully.qualified
|
|
618
|
+
// .Name)". Any current custom value is kept selectable via `include`.
|
|
619
|
+
valueTypeItems(include) {
|
|
620
|
+
const items = VALUE_TYPE_OPTIONS.map(fqnSelectItem);
|
|
621
|
+
return this.withIncluded(items, include);
|
|
622
|
+
}
|
|
623
|
+
get defaultValueType() {
|
|
624
|
+
return DEFAULT_VALUE_TYPE;
|
|
625
|
+
}
|
|
626
|
+
// A value type is "custom" (i.e. entered manually) when it is set but not one of the presets.
|
|
627
|
+
isCustomValueType(clazz) {
|
|
628
|
+
return !!clazz && !VALUE_TYPE_OPTIONS.includes(clazz);
|
|
629
|
+
}
|
|
630
|
+
// ----- Helpers -----
|
|
631
|
+
normalizeActions(permission) {
|
|
632
|
+
if (permission?.actions?.length)
|
|
633
|
+
return [...permission.actions];
|
|
634
|
+
if (permission?.action)
|
|
635
|
+
return [permission.action];
|
|
636
|
+
return [];
|
|
637
|
+
}
|
|
638
|
+
conditionField(condition) {
|
|
639
|
+
if (condition && (condition.type === 'field' || condition.type === 'expression')) {
|
|
640
|
+
return condition.field;
|
|
641
|
+
}
|
|
642
|
+
return '';
|
|
643
|
+
}
|
|
644
|
+
conditionOperator(condition) {
|
|
645
|
+
if (condition && (condition.type === 'field' || condition.type === 'expression')) {
|
|
646
|
+
return condition.operator;
|
|
647
|
+
}
|
|
648
|
+
return '==';
|
|
649
|
+
}
|
|
650
|
+
conditionValue(condition) {
|
|
651
|
+
if (condition && (condition.type === 'field' || condition.type === 'expression')) {
|
|
652
|
+
return condition.value;
|
|
653
|
+
}
|
|
654
|
+
// A freshly-added field/expression row has no value yet; default it to null, which renders as
|
|
655
|
+
// the literal "null". The value field is required and may not be left empty — to express "no
|
|
656
|
+
// value" the user types null explicitly.
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
conditionClazz(condition) {
|
|
660
|
+
// Expression conditions carry a value type; default it to the preset so the dropdown starts on a
|
|
661
|
+
// valid selection. Other condition types don't use it.
|
|
662
|
+
if (condition && condition.type === 'expression') {
|
|
663
|
+
return condition.clazz || DEFAULT_VALUE_TYPE;
|
|
664
|
+
}
|
|
665
|
+
return '';
|
|
666
|
+
}
|
|
667
|
+
withIncluded(items, include) {
|
|
668
|
+
// Only genuine, non-empty strings are added. A truthy non-string (e.g. an object that somehow
|
|
669
|
+
// reached a field/value control) would otherwise be pushed as {id, text} and render as
|
|
670
|
+
// "[object Object]" — guard against that here rather than trusting the caller.
|
|
671
|
+
const values = (Array.isArray(include) ? include : [include]).filter((value) => typeof value === 'string' && !!value);
|
|
672
|
+
const result = [...items];
|
|
673
|
+
for (const value of values) {
|
|
674
|
+
if (!result.some(item => item.id === value))
|
|
675
|
+
result.push({ id: value, text: value });
|
|
676
|
+
}
|
|
677
|
+
return result;
|
|
678
|
+
}
|
|
679
|
+
sortByText(items) {
|
|
680
|
+
return [...items].sort((a, b) => (a.text ?? '').localeCompare(b.text ?? ''));
|
|
681
|
+
}
|
|
682
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlFormEditorService, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
683
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlFormEditorService }); }
|
|
684
|
+
}
|
|
685
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlFormEditorService, decorators: [{
|
|
686
|
+
type: Injectable
|
|
687
|
+
}], ctorParameters: () => [{ type: i1.FormBuilder }] });
|
|
688
|
+
function minSelectedArrayValidator(min) {
|
|
689
|
+
return (control) => {
|
|
690
|
+
const value = control.value;
|
|
691
|
+
return Array.isArray(value) && value.length >= min ? null : { minSelected: { min } };
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function conditionValidator(group) {
|
|
695
|
+
const type = group.get('type')?.value;
|
|
696
|
+
const errors = {};
|
|
697
|
+
if (type === 'container') {
|
|
698
|
+
if (!group.get('resourceType')?.value)
|
|
699
|
+
errors['resourceTypeRequired'] = true;
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
if (!group.get('field')?.value)
|
|
703
|
+
errors['fieldRequired'] = true;
|
|
704
|
+
// The value may not be left empty; "null" must be typed for an explicit null value.
|
|
705
|
+
if (!group.get('value')?.value)
|
|
706
|
+
errors['valueRequired'] = true;
|
|
707
|
+
if (type === 'expression') {
|
|
708
|
+
if (!group.get('path')?.value)
|
|
709
|
+
errors['pathRequired'] = true;
|
|
710
|
+
if (!group.get('clazz')?.value)
|
|
711
|
+
errors['clazzRequired'] = true;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return Object.keys(errors).length ? errors : null;
|
|
715
|
+
}
|
|
716
|
+
|
|
107
717
|
class AccessControlService {
|
|
108
718
|
get roleDtos$() {
|
|
109
719
|
return this.http.get(`${this.valtimoEndpointUri}v1/roles`);
|
|
@@ -113,7 +723,23 @@ class AccessControlService {
|
|
|
113
723
|
this.http = http;
|
|
114
724
|
this.roles$ = new BehaviorSubject([]);
|
|
115
725
|
this.loading$ = new BehaviorSubject(false);
|
|
116
|
-
this.
|
|
726
|
+
this.apiEndpointUri = this.configService.config.valtimoApi.endpointUri;
|
|
727
|
+
this.valtimoEndpointUri = `${this.apiEndpointUri}management/`;
|
|
728
|
+
}
|
|
729
|
+
// The roles known to the identity provider (e.g. Keycloak realm roles), optionally filtered by a
|
|
730
|
+
// name prefix. Used to let an admin pick an existing role when configuring access control instead
|
|
731
|
+
// of typing its key by hand. Degrades to an empty list (and stays silent) when the endpoint is
|
|
732
|
+
// unavailable — e.g. a deployment using a different IAM — so the caller can fall back to manual
|
|
733
|
+
// entry.
|
|
734
|
+
getExternalRoles(externalRoleNamePrefix) {
|
|
735
|
+
const params = externalRoleNamePrefix
|
|
736
|
+
? `?externalRoleNamePrefix=${encodeURIComponent(externalRoleNamePrefix)}`
|
|
737
|
+
: '';
|
|
738
|
+
return this.http
|
|
739
|
+
.get(`${this.apiEndpointUri}v1/external-role${params}`, {
|
|
740
|
+
headers: InterceptorSkipHeader,
|
|
741
|
+
})
|
|
742
|
+
.pipe(catchError(() => of([])));
|
|
117
743
|
}
|
|
118
744
|
addRole(role) {
|
|
119
745
|
return this.http.post(`${this.valtimoEndpointUri}v1/roles`, role);
|
|
@@ -163,16 +789,19 @@ class AccessControlService {
|
|
|
163
789
|
getPermissionSchema() {
|
|
164
790
|
return this.http.get(`${this.valtimoEndpointUri}v1/permissions/schema`);
|
|
165
791
|
}
|
|
792
|
+
getPbacRegistry() {
|
|
793
|
+
return this.http.get(`${this.valtimoEndpointUri}v1/pbac/registry`);
|
|
794
|
+
}
|
|
166
795
|
updateRole(roleKey, request) {
|
|
167
796
|
return this.http.put(`${this.valtimoEndpointUri}v1/roles/${roleKey}`, request);
|
|
168
797
|
}
|
|
169
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlService, deps: [{ token: i1.ConfigService }, { token: i2.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
798
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlService, deps: [{ token: i1$1.ConfigService }, { token: i2.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
170
799
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlService, providedIn: 'root' }); }
|
|
171
800
|
}
|
|
172
801
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlService, decorators: [{
|
|
173
802
|
type: Injectable,
|
|
174
803
|
args: [{ providedIn: 'root' }]
|
|
175
|
-
}], ctorParameters: () => [{ type: i1.ConfigService }, { type: i2.HttpClient }] });
|
|
804
|
+
}], ctorParameters: () => [{ type: i1$1.ConfigService }, { type: i2.HttpClient }] });
|
|
176
805
|
|
|
177
806
|
/*
|
|
178
807
|
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
@@ -194,14 +823,43 @@ class PermissionSchemaMetadataService {
|
|
|
194
823
|
this.accessControlService = accessControlService;
|
|
195
824
|
this._knownResourceTypes$ = new BehaviorSubject(new Set());
|
|
196
825
|
this._fieldsByResourceType$ = new BehaviorSubject({});
|
|
826
|
+
/**
|
|
827
|
+
* The full PBAC registry served by GET /api/management/v1/pbac/registry. This is the
|
|
828
|
+
* authoritative, purpose-built description of every resource type, its actions and fields,
|
|
829
|
+
* the available operators and condition types, the entity-mapper pairs that define which
|
|
830
|
+
* context resources a permission can be scoped to, and the configured roles. It drives both
|
|
831
|
+
* the read-only overview and the form-based editor.
|
|
832
|
+
*/
|
|
833
|
+
this.registry$ = this.accessControlService
|
|
834
|
+
.getPbacRegistry()
|
|
835
|
+
.pipe(tap(registry => {
|
|
836
|
+
this._knownResourceTypes$.next(this.extractKnownResourceTypes(registry));
|
|
837
|
+
this._fieldsByResourceType$.next(this.extractFieldsByResourceType(registry));
|
|
838
|
+
}), shareReplay({ bufferSize: 1, refCount: false }));
|
|
839
|
+
this.resources$ = this.registry$.pipe(map(registry => registry.resources));
|
|
840
|
+
this.resourceByType$ = this.resources$.pipe(map(resources => resources.reduce((acc, resource) => {
|
|
841
|
+
acc[resource.resourceType] = resource;
|
|
842
|
+
return acc;
|
|
843
|
+
}, {})));
|
|
844
|
+
/**
|
|
845
|
+
* Raw JSON schema served by GET /api/management/v1/permissions/schema. Drives validation and
|
|
846
|
+
* autocomplete in the Monaco JSON editor.
|
|
847
|
+
*/
|
|
197
848
|
this.schema$ = this.accessControlService
|
|
198
849
|
.getPermissionSchema()
|
|
199
|
-
.pipe(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
this.actionsByResourceType$ = this.
|
|
204
|
-
|
|
850
|
+
.pipe(shareReplay({ bufferSize: 1, refCount: false }));
|
|
851
|
+
// The available actions per resource type come straight from the PBAC registry. The backend
|
|
852
|
+
// discovers every ResourceActionProvider on the classpath — not only the ones registered as
|
|
853
|
+
// Spring beans — so each resource's action list is complete.
|
|
854
|
+
this.actionsByResourceType$ = this.resources$.pipe(map(resources => resources.reduce((acc, resource) => {
|
|
855
|
+
acc[resource.resourceType] = [...resource.actions];
|
|
856
|
+
return acc;
|
|
857
|
+
}, {})));
|
|
858
|
+
this.allResourceTypes$ = this.resources$.pipe(map(resources => resources.map(resource => resource.resourceType)));
|
|
859
|
+
this.operators$ = this.registry$.pipe(map(registry => registry.operators));
|
|
860
|
+
this.conditionTypes$ = this.registry$.pipe(map(registry => registry.conditionTypes));
|
|
861
|
+
this.entityMappers$ = this.registry$.pipe(map(registry => registry.entityMappers));
|
|
862
|
+
this.roles$ = this.registry$.pipe(map(registry => registry.roles));
|
|
205
863
|
}
|
|
206
864
|
isResourceTypeKnown(fqn) {
|
|
207
865
|
return this._knownResourceTypes$.value.has(fqn);
|
|
@@ -209,42 +867,18 @@ class PermissionSchemaMetadataService {
|
|
|
209
867
|
isFieldKnown(resourceType, field) {
|
|
210
868
|
return this._fieldsByResourceType$.value[resourceType]?.has(field) ?? false;
|
|
211
869
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const result = {};
|
|
215
|
-
for (const branch of branches) {
|
|
216
|
-
const resourceType = branch?.if?.properties?.resourceType?.const;
|
|
217
|
-
const actions = branch?.then?.properties?.action?.enum;
|
|
218
|
-
if (resourceType && Array.isArray(actions)) {
|
|
219
|
-
result[resourceType] = [...actions];
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return result;
|
|
870
|
+
extractKnownResourceTypes(registry) {
|
|
871
|
+
return new Set(registry.resources.map(resource => resource.resourceType));
|
|
223
872
|
}
|
|
224
|
-
|
|
225
|
-
const entries = schema?.items?.properties?.resourceType?.oneOf ?? [];
|
|
226
|
-
const result = new Set();
|
|
227
|
-
for (const entry of entries) {
|
|
228
|
-
if (entry?.const)
|
|
229
|
-
result.add(entry.const);
|
|
230
|
-
}
|
|
231
|
-
return result;
|
|
232
|
-
}
|
|
233
|
-
extractFieldsByResourceType(schema) {
|
|
234
|
-
const definitions = schema?.definitions ?? {};
|
|
873
|
+
extractFieldsByResourceType(registry) {
|
|
235
874
|
const result = {};
|
|
236
|
-
for (const
|
|
237
|
-
if (!key.startsWith('condList.'))
|
|
238
|
-
continue;
|
|
239
|
-
const resourceType = key.substring('condList.'.length);
|
|
875
|
+
for (const resource of registry.resources) {
|
|
240
876
|
const fields = new Set();
|
|
241
|
-
for (const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
result[resourceType] = fields;
|
|
877
|
+
for (const field of resource.fields)
|
|
878
|
+
fields.add(field.name);
|
|
879
|
+
for (const alias of resource.fieldAliases)
|
|
880
|
+
fields.add(alias.alias);
|
|
881
|
+
result[resource.resourceType] = fields;
|
|
248
882
|
}
|
|
249
883
|
return result;
|
|
250
884
|
}
|
|
@@ -337,56 +971,107 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
337
971
|
* limitations under the License.
|
|
338
972
|
*/
|
|
339
973
|
class RoleMetadataModalComponent {
|
|
974
|
+
set open(value) {
|
|
975
|
+
this._open = value;
|
|
976
|
+
// Fetch the selectable roles the first time the modal is opened.
|
|
977
|
+
if (value && !this._rolesRequested)
|
|
978
|
+
this.loadRoleKeyItems();
|
|
979
|
+
}
|
|
980
|
+
get open() {
|
|
981
|
+
return this._open;
|
|
982
|
+
}
|
|
340
983
|
set defaultKeyValue(value) {
|
|
341
984
|
this._defaultKeyValue = value;
|
|
342
985
|
this.setDefaultKeyValue(value);
|
|
343
986
|
}
|
|
344
987
|
get key() {
|
|
345
|
-
return this.form
|
|
988
|
+
return this.form.get('key');
|
|
346
989
|
}
|
|
347
|
-
constructor(fb) {
|
|
990
|
+
constructor(fb, accessControlService) {
|
|
348
991
|
this.fb = fb;
|
|
349
|
-
this.
|
|
992
|
+
this.accessControlService = accessControlService;
|
|
350
993
|
this.type = 'add';
|
|
994
|
+
// Role keys already configured; excluded from the picker so a role can't be added twice.
|
|
995
|
+
this.usedKeys = [];
|
|
351
996
|
this.closeEvent = new EventEmitter();
|
|
997
|
+
// Selectable role keys, fetched from the identity provider (e.g. Keycloak realm roles). The picker
|
|
998
|
+
// only lists these; a custom key is entered via manual mode.
|
|
999
|
+
this.$roleKeyItems = signal([]);
|
|
1000
|
+
this.$loadingRoles = signal(false);
|
|
1001
|
+
// Whether the key is typed manually rather than picked from the dropdown.
|
|
1002
|
+
this.$keyManual = signal(false);
|
|
352
1003
|
this.form = this.fb.group({
|
|
353
1004
|
key: this.fb.control('', Validators.required),
|
|
354
1005
|
});
|
|
1006
|
+
this._open = false;
|
|
1007
|
+
this._rolesRequested = false;
|
|
1008
|
+
}
|
|
1009
|
+
ngOnInit() {
|
|
1010
|
+
// Editing starts in manual mode so the existing key is shown for renaming; adding starts on the
|
|
1011
|
+
// role picker.
|
|
1012
|
+
this.$keyManual.set(this.type === 'edit');
|
|
355
1013
|
}
|
|
356
1014
|
onCancel() {
|
|
357
1015
|
this.closeEvent.emit(null);
|
|
358
1016
|
this.resetForm();
|
|
359
1017
|
}
|
|
360
1018
|
onConfirm() {
|
|
361
|
-
if (
|
|
1019
|
+
if (this.form.invalid) {
|
|
362
1020
|
return;
|
|
363
1021
|
}
|
|
364
1022
|
this.closeEvent.emit({ roleKey: this.key.value });
|
|
365
1023
|
this.resetForm();
|
|
366
1024
|
}
|
|
1025
|
+
// Switches between picking a role and typing one. Returning to the picker drops a value that isn't
|
|
1026
|
+
// one of the listed roles, so a valid option must be chosen.
|
|
1027
|
+
onKeyManualToggle(manual) {
|
|
1028
|
+
this.$keyManual.set(manual);
|
|
1029
|
+
if (!manual && !this.$roleKeyItems().some(item => item.id === this.key.value)) {
|
|
1030
|
+
this.key.setValue('');
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
loadRoleKeyItems() {
|
|
1034
|
+
this._rolesRequested = true;
|
|
1035
|
+
this.$loadingRoles.set(true);
|
|
1036
|
+
// Only application roles (ROLE_*) are offered; roles already configured are filtered out (except
|
|
1037
|
+
// when editing, where the current key must remain selectable).
|
|
1038
|
+
this.accessControlService.getExternalRoles('ROLE_').subscribe(roles => {
|
|
1039
|
+
const used = this.usedKeys ?? [];
|
|
1040
|
+
this.$roleKeyItems.set(roles
|
|
1041
|
+
.filter(role => this.type === 'edit' || !used.includes(role))
|
|
1042
|
+
.map(role => ({ id: role, text: role })));
|
|
1043
|
+
// With no roles to pick from, fall back to manual entry so the field stays usable.
|
|
1044
|
+
if (this.type === 'add' && this.$roleKeyItems().length === 0)
|
|
1045
|
+
this.$keyManual.set(true);
|
|
1046
|
+
this.$loadingRoles.set(false);
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
367
1049
|
setDefaultKeyValue(value) {
|
|
368
1050
|
this.key.setValue(value);
|
|
369
1051
|
}
|
|
370
1052
|
resetForm() {
|
|
371
1053
|
setTimeout(() => {
|
|
372
1054
|
this.form.reset();
|
|
1055
|
+
this.$keyManual.set(this.type === 'edit');
|
|
373
1056
|
if (this.type === 'edit') {
|
|
374
1057
|
this.setDefaultKeyValue(this._defaultKeyValue);
|
|
375
1058
|
}
|
|
376
1059
|
}, CARBON_CONSTANTS.modalAnimationMs);
|
|
377
1060
|
}
|
|
378
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: RoleMetadataModalComponent, deps: [{ token: i1
|
|
379
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
1061
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: RoleMetadataModalComponent, deps: [{ token: i1.FormBuilder }, { token: AccessControlService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1062
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: RoleMetadataModalComponent, isStandalone: false, selector: "valtimo-role-metadata-modal", inputs: { type: "type", open: "open", defaultKeyValue: "defaultKeyValue", usedKeys: "usedKeys" }, outputs: { closeEvent: "closeEvent" }, ngImport: i0, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<cds-modal\n *ngIf=\"{\n isAdd: type === 'add',\n } as vars\"\n [open]=\"open\"\n showFooter=\"true\"\n [title]=\"vars.title\"\n valtimoCdsModal\n (close)=\"onCancel()\"\n>\n <cds-modal-header [cdsLayer]=\"1\" [showCloseButton]=\"true\" (closeSelect)=\"onCancel()\">\n <h3 cdsModalHeaderHeading>\n {{\n vars.isAdd\n ? ('accessControl.roles.add' | translate)\n : ('accessControl.roles.editRole' | translate)\n }}\n </h3>\n </cds-modal-header>\n\n <section cdsModalContent [cdsLayer]=\"1\">\n <form [formGroup]=\"form\">\n <div class=\"role-metadata-modal__field cds--form-item\">\n <label class=\"cds--label\">\n {{ 'accessControl.roles.name' | translate }} ({{ 'interface.required' | translate }})\n </label>\n\n @if ($keyManual()) {\n <input\n formControlName=\"key\"\n cdsText\n placeholder=\"{{ 'accessControl.roles.name' | translate }}\"\n [attr.modal-primary-focus]=\"true\"\n [invalid]=\"key.dirty && key.invalid\"\n />\n } @else {\n <v-select\n formControlName=\"key\"\n [appendInline]=\"false\"\n [clearable]=\"false\"\n [defaultSelectionId]=\"key.value || ''\"\n [dropUp]=\"false\"\n [invalid]=\"key.dirty && key.invalid\"\n [items]=\"$roleKeyItems()\"\n [loading]=\"$loadingRoles()\"\n [placeholder]=\"'accessControl.roles.selectRole' | translate\"\n ></v-select>\n }\n\n <button\n class=\"role-metadata-modal__mode-switch\"\n cdsButton=\"ghost\"\n size=\"sm\"\n type=\"button\"\n (click)=\"onKeyManualToggle(!$keyManual())\"\n >\n {{\n ($keyManual()\n ? 'accessControl.roles.chooseFromList'\n : 'accessControl.roles.enterManually'\n ) | translate\n }}\n </button>\n </div>\n </form>\n </section>\n\n <cds-modal-footer [cdsLayer]=\"1\">\n <button cdsButton=\"secondary\" (click)=\"onCancel()\">\n {{ 'interface.cancel' | translate }}\n </button>\n\n <button [disabled]=\"this.form.invalid\" (click)=\"onConfirm()\" cdsButton=\"primary\">\n {{ vars.isAdd ? ('interface.create' | translate) : ('interface.save' | translate) }}\n </button>\n </cds-modal-footer>\n</cds-modal>\n", styles: [".role-metadata-modal__field{display:flex;flex-direction:column;gap:.5rem}.role-metadata-modal__field v-select{display:block;width:100%}.role-metadata-modal__field input{width:100%}.role-metadata-modal__mode-switch{align-self:flex-start}\n"], dependencies: [{ kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$1.Button, selector: "[cdsButton], [ibmButton]", inputs: ["ibmButton", "cdsButton", "size", "skeleton", "iconOnly", "isExpressive"] }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "component", type: i2$1.Modal, selector: "cds-modal, ibm-modal", inputs: ["size", "theme", "ariaLabel", "open", "trigger", "hasScrollingContent"], outputs: ["overlaySelected", "close"] }, { kind: "component", type: i2$1.ModalHeader, selector: "cds-modal-header, ibm-modal-header", inputs: ["theme", "closeLabel", "showCloseButton"], outputs: ["closeSelect"] }, { kind: "component", type: i2$1.ModalFooter, selector: "cds-modal-footer, ibm-modal-footer" }, { kind: "directive", type: i2$1.ModalContent, selector: "[cdsModalContent], [ibmModalContent]", inputs: ["hasForm"] }, { kind: "directive", type: i2$1.ModalHeaderHeading, selector: "[cdsModalHeaderHeading], [ibmModalHeaderHeading]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i2$1.TextInput, selector: "[cdsText], [ibmText]", inputs: ["theme", "size", "invalid", "warn", "skeleton"] }, { kind: "component", type: i3$1.SelectComponent, selector: "v-select", inputs: ["items", "defaultSelection", "defaultSelectionId", "defaultSelectionIds", "disabled", "dropUp", "invalid", "multiple", "margin", "widthInPx", "notFoundText", "clearAllText", "clearText", "clearable", "name", "title", "titleTranslationKey", "clearSelectionSubject$", "tooltip", "required", "loading", "loadingText", "placeholder", "smallMargin", "carbonTheme", "appendInline", "warn", "warnText", "dataTestId"], outputs: ["selectedChange"] }, { kind: "directive", type: i2$1.LayerDirective, selector: "[cdsLayer], [ibmLayer]", inputs: ["ibmLayer", "cdsLayer"], exportAs: ["layer"] }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
380
1063
|
}
|
|
381
1064
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: RoleMetadataModalComponent, decorators: [{
|
|
382
1065
|
type: Component,
|
|
383
|
-
args: [{ standalone: false, selector: 'valtimo-role-metadata-modal', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<cds-modal\n *ngIf=\"{\n isAdd: type === 'add',\n } as vars\"\n [open]=\"open\"\n showFooter=\"true\"\n [title]=\"vars.title\"\n valtimoCdsModal\n (close)=\"onCancel()\"\n>\n <cds-modal-header [showCloseButton]=\"true\" (closeSelect)=\"onCancel()\">\n <h3 cdsModalHeaderHeading>\n {{\n vars.isAdd\n ? ('accessControl.roles.add' | translate)\n : ('accessControl.roles.editRole' | translate)\n }}\n </h3>\n </cds-modal-header>\n\n <section cdsModalContent>\n <form [formGroup]=\"form\">\n <cds-label
|
|
384
|
-
}], ctorParameters: () => [{ type: i1
|
|
1066
|
+
args: [{ standalone: false, selector: 'valtimo-role-metadata-modal', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<cds-modal\n *ngIf=\"{\n isAdd: type === 'add',\n } as vars\"\n [open]=\"open\"\n showFooter=\"true\"\n [title]=\"vars.title\"\n valtimoCdsModal\n (close)=\"onCancel()\"\n>\n <cds-modal-header [cdsLayer]=\"1\" [showCloseButton]=\"true\" (closeSelect)=\"onCancel()\">\n <h3 cdsModalHeaderHeading>\n {{\n vars.isAdd\n ? ('accessControl.roles.add' | translate)\n : ('accessControl.roles.editRole' | translate)\n }}\n </h3>\n </cds-modal-header>\n\n <section cdsModalContent [cdsLayer]=\"1\">\n <form [formGroup]=\"form\">\n <div class=\"role-metadata-modal__field cds--form-item\">\n <label class=\"cds--label\">\n {{ 'accessControl.roles.name' | translate }} ({{ 'interface.required' | translate }})\n </label>\n\n @if ($keyManual()) {\n <input\n formControlName=\"key\"\n cdsText\n placeholder=\"{{ 'accessControl.roles.name' | translate }}\"\n [attr.modal-primary-focus]=\"true\"\n [invalid]=\"key.dirty && key.invalid\"\n />\n } @else {\n <v-select\n formControlName=\"key\"\n [appendInline]=\"false\"\n [clearable]=\"false\"\n [defaultSelectionId]=\"key.value || ''\"\n [dropUp]=\"false\"\n [invalid]=\"key.dirty && key.invalid\"\n [items]=\"$roleKeyItems()\"\n [loading]=\"$loadingRoles()\"\n [placeholder]=\"'accessControl.roles.selectRole' | translate\"\n ></v-select>\n }\n\n <button\n class=\"role-metadata-modal__mode-switch\"\n cdsButton=\"ghost\"\n size=\"sm\"\n type=\"button\"\n (click)=\"onKeyManualToggle(!$keyManual())\"\n >\n {{\n ($keyManual()\n ? 'accessControl.roles.chooseFromList'\n : 'accessControl.roles.enterManually'\n ) | translate\n }}\n </button>\n </div>\n </form>\n </section>\n\n <cds-modal-footer [cdsLayer]=\"1\">\n <button cdsButton=\"secondary\" (click)=\"onCancel()\">\n {{ 'interface.cancel' | translate }}\n </button>\n\n <button [disabled]=\"this.form.invalid\" (click)=\"onConfirm()\" cdsButton=\"primary\">\n {{ vars.isAdd ? ('interface.create' | translate) : ('interface.save' | translate) }}\n </button>\n </cds-modal-footer>\n</cds-modal>\n", styles: [".role-metadata-modal__field{display:flex;flex-direction:column;gap:.5rem}.role-metadata-modal__field v-select{display:block;width:100%}.role-metadata-modal__field input{width:100%}.role-metadata-modal__mode-switch{align-self:flex-start}\n"] }]
|
|
1067
|
+
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type: AccessControlService }], propDecorators: { type: [{
|
|
385
1068
|
type: Input
|
|
386
|
-
}],
|
|
1069
|
+
}], open: [{
|
|
387
1070
|
type: Input
|
|
388
1071
|
}], defaultKeyValue: [{
|
|
389
1072
|
type: Input
|
|
1073
|
+
}], usedKeys: [{
|
|
1074
|
+
type: Input
|
|
390
1075
|
}], closeEvent: [{
|
|
391
1076
|
type: Output
|
|
392
1077
|
}] } });
|
|
@@ -414,7 +1099,7 @@ class DeleteRoleModalComponent {
|
|
|
414
1099
|
this.deleteEvent.emit(roles);
|
|
415
1100
|
}
|
|
416
1101
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DeleteRoleModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
417
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: DeleteRoleModalComponent, isStandalone: false, selector: "valtimo-delete-role-modal", inputs: { deleteRowKeys: "deleteRowKeys", showDeleteModal$: "showDeleteModal$" }, outputs: { deleteEvent: "deleteEvent" }, ngImport: i0, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<valtimo-confirmation-modal\n confirmButtonTextTranslationKey=\"interface.delete\"\n confirmButtonType=\"danger\"\n contentTranslationKey=\"accessControl.roles.deleteModalContent\"\n [outputOnConfirm]=\"deleteRowKeys\"\n [showModalSubject$]=\"showDeleteModal$\"\n titleTranslationKey=\"interface.delete\"\n (confirmEvent)=\"onDelete($event)\"\n></valtimo-confirmation-modal>\n", dependencies: [{ kind: "component", type: i3.ConfirmationModalComponent, selector: "valtimo-confirmation-modal", inputs: ["titleTranslationKey", "title", "content", "contentTranslationKey", "confirmButtonText", "confirmButtonTextTranslationKey", "confirmButtonType", "showOptionalButton", "optionalButtonText", "optionalButtonTextTranslationKey", "optionalButtonType", "cancelButtonText", "cancelButtonTextTranslationKey", "cancelButtonType", "showModalSubject$", "outputOnConfirm", "outputOnOptional", "spacerAfterCancelButton"], outputs: ["confirmEvent", "optionalEvent", "cancelEvent"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1102
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: DeleteRoleModalComponent, isStandalone: false, selector: "valtimo-delete-role-modal", inputs: { deleteRowKeys: "deleteRowKeys", showDeleteModal$: "showDeleteModal$" }, outputs: { deleteEvent: "deleteEvent" }, ngImport: i0, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<valtimo-confirmation-modal\n confirmButtonTextTranslationKey=\"interface.delete\"\n confirmButtonType=\"danger\"\n contentTranslationKey=\"accessControl.roles.deleteModalContent\"\n [outputOnConfirm]=\"deleteRowKeys\"\n [showModalSubject$]=\"showDeleteModal$\"\n titleTranslationKey=\"interface.delete\"\n (confirmEvent)=\"onDelete($event)\"\n></valtimo-confirmation-modal>\n", dependencies: [{ kind: "component", type: i3$1.ConfirmationModalComponent, selector: "valtimo-confirmation-modal", inputs: ["titleTranslationKey", "title", "content", "contentTranslationKey", "confirmButtonText", "confirmButtonTextTranslationKey", "confirmButtonType", "showOptionalButton", "optionalButtonText", "optionalButtonTextTranslationKey", "optionalButtonType", "cancelButtonText", "cancelButtonTextTranslationKey", "cancelButtonType", "showModalSubject$", "outputOnConfirm", "outputOnOptional", "spacerAfterCancelButton"], outputs: ["confirmEvent", "optionalEvent", "cancelEvent"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
418
1103
|
}
|
|
419
1104
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: DeleteRoleModalComponent, decorators: [{
|
|
420
1105
|
type: Component,
|
|
@@ -479,11 +1164,11 @@ class ExportRoleModalComponent {
|
|
|
479
1164
|
});
|
|
480
1165
|
}
|
|
481
1166
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ExportRoleModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
482
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: ExportRoleModalComponent, isStandalone: false, selector: "valtimo-export-role-modal", inputs: { open: "open", exportRowKeys: "exportRowKeys", reset$: "reset$", disabled: "disabled" }, outputs: { exportEvent: "exportEvent", closeEvent: "closeEvent" }, ngImport: i0, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<cds-modal\n *ngIf=\"{title: ('interface.export' | translate), selectedType: selectedType$ | async} as vars\"\n valtimoCdsModal\n [open]=\"open\"\n size=\"sm\"\n showFooter=\"true\"\n [title]=\"vars.title\"\n (close)=\"onCancel()\"\n>\n <cds-modal-header [showCloseButton]=\"true\" (closeSelect)=\"onCancel()\">\n <h3 cdsModalHeaderHeading>\n {{ vars.title }}\n </h3>\n </cds-modal-header>\n\n <section cdsModalContent>\n <div class=\"export-buttons\">\n <button [disabled]=\"disabled\" cdsButton=\"secondary\" (click)=\"selectType('unified')\">\n {{ 'accessControl.roles.singleExport' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"document\" size=\"16\"></svg>\n </button>\n\n <button [disabled]=\"disabled\" cdsButton=\"secondary\" (click)=\"selectType('separate')\">\n {{ 'accessControl.roles.multipleExport' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"copy--file\" size=\"16\"></svg>\n </button>\n </div>\n </section>\n\n <cds-modal-footer>\n <button [disabled]=\"disabled\" cdsButton=\"secondary\" (click)=\"onCancel()\">\n {{ 'interface.cancel' | translate }}\n </button>\n\n <button\n [disabled]=\"!vars.selectedType || disabled\"\n cdsButton=\"primary\"\n (click)=\"onConfirm(vars.selectedType)\"\n >\n {{ vars.title }}\n </button>\n </cds-modal-footer>\n</cds-modal>\n", styles: [".export-buttons{display:grid;width:100%;grid-template-columns:1fr 1fr;column-gap:16px}.export-buttons button{width:100%}\n/*!\n * Copyright 2015-2025 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"], dependencies: [{ kind: "directive", type:
|
|
1167
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: ExportRoleModalComponent, isStandalone: false, selector: "valtimo-export-role-modal", inputs: { open: "open", exportRowKeys: "exportRowKeys", reset$: "reset$", disabled: "disabled" }, outputs: { exportEvent: "exportEvent", closeEvent: "closeEvent" }, ngImport: i0, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<cds-modal\n *ngIf=\"{title: ('interface.export' | translate), selectedType: selectedType$ | async} as vars\"\n valtimoCdsModal\n [open]=\"open\"\n size=\"sm\"\n showFooter=\"true\"\n [title]=\"vars.title\"\n (close)=\"onCancel()\"\n>\n <cds-modal-header [cdsLayer]=\"1\" [showCloseButton]=\"true\" (closeSelect)=\"onCancel()\">\n <h3 cdsModalHeaderHeading>\n {{ vars.title }}\n </h3>\n </cds-modal-header>\n\n <section cdsModalContent [cdsLayer]=\"1\">\n <div class=\"export-buttons\">\n <button [disabled]=\"disabled\" cdsButton=\"secondary\" (click)=\"selectType('unified')\">\n {{ 'accessControl.roles.singleExport' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"document\" size=\"16\"></svg>\n </button>\n\n <button [disabled]=\"disabled\" cdsButton=\"secondary\" (click)=\"selectType('separate')\">\n {{ 'accessControl.roles.multipleExport' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"copy--file\" size=\"16\"></svg>\n </button>\n </div>\n </section>\n\n <cds-modal-footer [cdsLayer]=\"1\">\n <button [disabled]=\"disabled\" cdsButton=\"secondary\" (click)=\"onCancel()\">\n {{ 'interface.cancel' | translate }}\n </button>\n\n <button\n [disabled]=\"!vars.selectedType || disabled\"\n cdsButton=\"primary\"\n (click)=\"onConfirm(vars.selectedType)\"\n >\n {{ vars.title }}\n </button>\n </cds-modal-footer>\n</cds-modal>\n", styles: [".export-buttons{display:grid;width:100%;grid-template-columns:1fr 1fr;column-gap:16px}.export-buttons button{width:100%}\n/*!\n * Copyright 2015-2025 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"], dependencies: [{ kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$1.Button, selector: "[cdsButton], [ibmButton]", inputs: ["ibmButton", "cdsButton", "size", "skeleton", "iconOnly", "isExpressive"] }, { kind: "component", type: i2$1.Modal, selector: "cds-modal, ibm-modal", inputs: ["size", "theme", "ariaLabel", "open", "trigger", "hasScrollingContent"], outputs: ["overlaySelected", "close"] }, { kind: "component", type: i2$1.ModalHeader, selector: "cds-modal-header, ibm-modal-header", inputs: ["theme", "closeLabel", "showCloseButton"], outputs: ["closeSelect"] }, { kind: "component", type: i2$1.ModalFooter, selector: "cds-modal-footer, ibm-modal-footer" }, { kind: "directive", type: i2$1.ModalContent, selector: "[cdsModalContent], [ibmModalContent]", inputs: ["hasForm"] }, { kind: "directive", type: i2$1.ModalHeaderHeading, selector: "[cdsModalHeaderHeading], [ibmModalHeaderHeading]" }, { kind: "directive", type: i2$1.IconDirective, selector: "[cdsIcon], [ibmIcon]", inputs: ["ibmIcon", "cdsIcon", "size", "title", "ariaLabel", "ariaLabelledBy", "ariaHidden", "isFocusable"] }, { kind: "directive", type: i2$1.LayerDirective, selector: "[cdsLayer], [ibmLayer]", inputs: ["ibmLayer", "cdsLayer"], exportAs: ["layer"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
483
1168
|
}
|
|
484
1169
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ExportRoleModalComponent, decorators: [{
|
|
485
1170
|
type: Component,
|
|
486
|
-
args: [{ standalone: false, selector: 'valtimo-export-role-modal', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<cds-modal\n *ngIf=\"{title: ('interface.export' | translate), selectedType: selectedType$ | async} as vars\"\n valtimoCdsModal\n [open]=\"open\"\n size=\"sm\"\n showFooter=\"true\"\n [title]=\"vars.title\"\n (close)=\"onCancel()\"\n>\n <cds-modal-header [showCloseButton]=\"true\" (closeSelect)=\"onCancel()\">\n <h3 cdsModalHeaderHeading>\n {{ vars.title }}\n </h3>\n </cds-modal-header>\n\n <section cdsModalContent>\n <div class=\"export-buttons\">\n <button [disabled]=\"disabled\" cdsButton=\"secondary\" (click)=\"selectType('unified')\">\n {{ 'accessControl.roles.singleExport' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"document\" size=\"16\"></svg>\n </button>\n\n <button [disabled]=\"disabled\" cdsButton=\"secondary\" (click)=\"selectType('separate')\">\n {{ 'accessControl.roles.multipleExport' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"copy--file\" size=\"16\"></svg>\n </button>\n </div>\n </section>\n\n <cds-modal-footer>\n <button [disabled]=\"disabled\" cdsButton=\"secondary\" (click)=\"onCancel()\">\n {{ 'interface.cancel' | translate }}\n </button>\n\n <button\n [disabled]=\"!vars.selectedType || disabled\"\n cdsButton=\"primary\"\n (click)=\"onConfirm(vars.selectedType)\"\n >\n {{ vars.title }}\n </button>\n </cds-modal-footer>\n</cds-modal>\n", styles: [".export-buttons{display:grid;width:100%;grid-template-columns:1fr 1fr;column-gap:16px}.export-buttons button{width:100%}\n/*!\n * Copyright 2015-2025 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"] }]
|
|
1171
|
+
args: [{ standalone: false, selector: 'valtimo-export-role-modal', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<cds-modal\n *ngIf=\"{title: ('interface.export' | translate), selectedType: selectedType$ | async} as vars\"\n valtimoCdsModal\n [open]=\"open\"\n size=\"sm\"\n showFooter=\"true\"\n [title]=\"vars.title\"\n (close)=\"onCancel()\"\n>\n <cds-modal-header [cdsLayer]=\"1\" [showCloseButton]=\"true\" (closeSelect)=\"onCancel()\">\n <h3 cdsModalHeaderHeading>\n {{ vars.title }}\n </h3>\n </cds-modal-header>\n\n <section cdsModalContent [cdsLayer]=\"1\">\n <div class=\"export-buttons\">\n <button [disabled]=\"disabled\" cdsButton=\"secondary\" (click)=\"selectType('unified')\">\n {{ 'accessControl.roles.singleExport' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"document\" size=\"16\"></svg>\n </button>\n\n <button [disabled]=\"disabled\" cdsButton=\"secondary\" (click)=\"selectType('separate')\">\n {{ 'accessControl.roles.multipleExport' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"copy--file\" size=\"16\"></svg>\n </button>\n </div>\n </section>\n\n <cds-modal-footer [cdsLayer]=\"1\">\n <button [disabled]=\"disabled\" cdsButton=\"secondary\" (click)=\"onCancel()\">\n {{ 'interface.cancel' | translate }}\n </button>\n\n <button\n [disabled]=\"!vars.selectedType || disabled\"\n cdsButton=\"primary\"\n (click)=\"onConfirm(vars.selectedType)\"\n >\n {{ vars.title }}\n </button>\n </cds-modal-footer>\n</cds-modal>\n", styles: [".export-buttons{display:grid;width:100%;grid-template-columns:1fr 1fr;column-gap:16px}.export-buttons button{width:100%}\n/*!\n * Copyright 2015-2025 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"] }]
|
|
487
1172
|
}], propDecorators: { open: [{
|
|
488
1173
|
type: Input
|
|
489
1174
|
}], exportRowKeys: [{
|
|
@@ -526,6 +1211,8 @@ class AccessControlOverviewComponent {
|
|
|
526
1211
|
},
|
|
527
1212
|
];
|
|
528
1213
|
this.roles$ = this.accessControlService.roles$;
|
|
1214
|
+
// Keys of already-configured roles, used to keep them out of the add-role picker.
|
|
1215
|
+
this.existingRoleKeys$ = this.roles$.pipe(map(roles => roles.map(role => role.roleKey)));
|
|
529
1216
|
this.loading$ = this.accessControlService.loading$;
|
|
530
1217
|
this.skeleton$ = new BehaviorSubject(false);
|
|
531
1218
|
this.showAddModal$ = new BehaviorSubject(false);
|
|
@@ -584,21 +1271,176 @@ class AccessControlOverviewComponent {
|
|
|
584
1271
|
enableSkeleton() {
|
|
585
1272
|
this.skeleton$.next(true);
|
|
586
1273
|
}
|
|
587
|
-
disableSkeleton() {
|
|
588
|
-
this.skeleton$.next(false);
|
|
1274
|
+
disableSkeleton() {
|
|
1275
|
+
this.skeleton$.next(false);
|
|
1276
|
+
}
|
|
1277
|
+
setSelectedRoleKeys() {
|
|
1278
|
+
this.selectedRowKeys$.next(this.carbonList.selectedItems.map((role) => role.roleKey));
|
|
1279
|
+
}
|
|
1280
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlOverviewComponent, deps: [{ token: AccessControlService }, { token: AccessControlExportService }, { token: i1$3.Router }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1281
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: AccessControlOverviewComponent, isStandalone: false, selector: "ng-component", viewQueries: [{ propertyName: "carbonList", first: true, predicate: CarbonListComponent, descendants: true }], ngImport: i0, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<valtimo-carbon-list\n [header]=\"false\"\n [items]=\"roles$ | async\"\n [fields]=\"fields\"\n [loading]=\"loading$ | async\"\n [showSelectionColumn]=\"true\"\n (rowClicked)=\"onRowClick($event)\"\n>\n <ng-container header> {{ 'accessControl.roles.title' | translate }} </ng-container>\n\n <div carbonToolbarContent>\n <ng-container [ngTemplateOutlet]=\"addRoleButton\"></ng-container>\n </div>\n\n <ng-container carbonToolbarActions>\n <button cdsButton=\"primary\" (click)=\"showDeleteModal()\">\n {{ 'interface.delete' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"trash-can\" size=\"16\"></svg>\n </button>\n\n <button cdsButton=\"primary\" (click)=\"showExportModal()\">\n {{ 'interface.export' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"download\" size=\"16\"></svg>\n </button>\n </ng-container>\n\n <valtimo-no-results\n [action]=\"addRoleButton\"\n description=\"{{ 'accessControl.roles.noResults.description' | translate }}\"\n title=\"{{ 'accessControl.roles.noResults.title' | translate }}\"\n ></valtimo-no-results>\n</valtimo-carbon-list>\n\n<valtimo-role-metadata-modal\n [open]=\"showAddModal$ | async\"\n [usedKeys]=\"existingRoleKeys$ | async\"\n (closeEvent)=\"onAdd($event)\"\n></valtimo-role-metadata-modal>\n\n<ng-container *ngIf=\"{selectedRowKeys: selectedRowKeys$ | async} as obs\">\n <valtimo-delete-role-modal\n [deleteRowKeys]=\"obs.selectedRowKeys\"\n [showDeleteModal$]=\"showDeleteModal$\"\n (deleteEvent)=\"onDelete($event)\"\n >\n </valtimo-delete-role-modal>\n\n <valtimo-export-role-modal\n [disabled]=\"exportDisabled$ | async\"\n [exportRowKeys]=\"obs.selectedRowKeys\"\n [open]=\"showExportModal$ | async\"\n [reset$]=\"resetExportType$\"\n (exportEvent)=\"onExport($event)\"\n (closeEvent)=\"closeExportModal()\"\n >\n </valtimo-export-role-modal>\n</ng-container>\n\n<ng-template #addRoleButton>\n <button cdsButton=\"primary\" (click)=\"openAddModal()\">\n {{ 'accessControl.roles.add' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"add\" size=\"16\"></svg>\n </button>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2$1.Button, selector: "[cdsButton], [ibmButton]", inputs: ["ibmButton", "cdsButton", "size", "skeleton", "iconOnly", "isExpressive"] }, { kind: "directive", type: i2$1.IconDirective, selector: "[cdsIcon], [ibmIcon]", inputs: ["ibmIcon", "cdsIcon", "size", "title", "ariaLabel", "ariaLabelledBy", "ariaHidden", "isFocusable"] }, { kind: "component", type: i3$1.CarbonListComponent, selector: "valtimo-carbon-list", inputs: ["items", "fields", "tableTranslations", "paginatorConfig", "pagination", "loading", "skeletonRowCount", "actions", "actionItems", "showActionItems", "header", "hideColumnHeader", "initialSortState", "sortState", "isSearchable", "enableSingleSelection", "lastColumnTemplate", "paginationIdentifier", "showSelectionColumn", "striped", "hideToolbar", "lockedTooltipTranslationKey", "movingRowsEnabled", "dragAndDrop", "dragAndDropDisabled"], outputs: ["rowClicked", "paginationClicked", "paginationSet", "search", "sortChanged", "moveRow", "itemsReordered"] }, { kind: "component", type: i3$1.CarbonNoResultsComponent, selector: "valtimo-no-results", inputs: ["action", "description", "illustration", "title", "smallPadding", "collapseVertically", "alwaysRenderVertically"] }, { kind: "component", type: RoleMetadataModalComponent, selector: "valtimo-role-metadata-modal", inputs: ["type", "open", "defaultKeyValue", "usedKeys"], outputs: ["closeEvent"] }, { kind: "component", type: DeleteRoleModalComponent, selector: "valtimo-delete-role-modal", inputs: ["deleteRowKeys", "showDeleteModal$"], outputs: ["deleteEvent"] }, { kind: "component", type: ExportRoleModalComponent, selector: "valtimo-export-role-modal", inputs: ["open", "exportRowKeys", "reset$", "disabled"], outputs: ["exportEvent", "closeEvent"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1282
|
+
}
|
|
1283
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlOverviewComponent, decorators: [{
|
|
1284
|
+
type: Component,
|
|
1285
|
+
args: [{ standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<valtimo-carbon-list\n [header]=\"false\"\n [items]=\"roles$ | async\"\n [fields]=\"fields\"\n [loading]=\"loading$ | async\"\n [showSelectionColumn]=\"true\"\n (rowClicked)=\"onRowClick($event)\"\n>\n <ng-container header> {{ 'accessControl.roles.title' | translate }} </ng-container>\n\n <div carbonToolbarContent>\n <ng-container [ngTemplateOutlet]=\"addRoleButton\"></ng-container>\n </div>\n\n <ng-container carbonToolbarActions>\n <button cdsButton=\"primary\" (click)=\"showDeleteModal()\">\n {{ 'interface.delete' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"trash-can\" size=\"16\"></svg>\n </button>\n\n <button cdsButton=\"primary\" (click)=\"showExportModal()\">\n {{ 'interface.export' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"download\" size=\"16\"></svg>\n </button>\n </ng-container>\n\n <valtimo-no-results\n [action]=\"addRoleButton\"\n description=\"{{ 'accessControl.roles.noResults.description' | translate }}\"\n title=\"{{ 'accessControl.roles.noResults.title' | translate }}\"\n ></valtimo-no-results>\n</valtimo-carbon-list>\n\n<valtimo-role-metadata-modal\n [open]=\"showAddModal$ | async\"\n [usedKeys]=\"existingRoleKeys$ | async\"\n (closeEvent)=\"onAdd($event)\"\n></valtimo-role-metadata-modal>\n\n<ng-container *ngIf=\"{selectedRowKeys: selectedRowKeys$ | async} as obs\">\n <valtimo-delete-role-modal\n [deleteRowKeys]=\"obs.selectedRowKeys\"\n [showDeleteModal$]=\"showDeleteModal$\"\n (deleteEvent)=\"onDelete($event)\"\n >\n </valtimo-delete-role-modal>\n\n <valtimo-export-role-modal\n [disabled]=\"exportDisabled$ | async\"\n [exportRowKeys]=\"obs.selectedRowKeys\"\n [open]=\"showExportModal$ | async\"\n [reset$]=\"resetExportType$\"\n (exportEvent)=\"onExport($event)\"\n (closeEvent)=\"closeExportModal()\"\n >\n </valtimo-export-role-modal>\n</ng-container>\n\n<ng-template #addRoleButton>\n <button cdsButton=\"primary\" (click)=\"openAddModal()\">\n {{ 'accessControl.roles.add' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"add\" size=\"16\"></svg>\n </button>\n</ng-template>\n" }]
|
|
1286
|
+
}], ctorParameters: () => [{ type: AccessControlService }, { type: AccessControlExportService }, { type: i1$3.Router }], propDecorators: { carbonList: [{
|
|
1287
|
+
type: ViewChild,
|
|
1288
|
+
args: [CarbonListComponent]
|
|
1289
|
+
}] } });
|
|
1290
|
+
|
|
1291
|
+
/*
|
|
1292
|
+
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
1293
|
+
*
|
|
1294
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
1295
|
+
* you may not use this file except in compliance with the License.
|
|
1296
|
+
* You may obtain a copy of the License at
|
|
1297
|
+
*
|
|
1298
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
1299
|
+
*
|
|
1300
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1301
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
1302
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1303
|
+
* See the License for the specific language governing permissions and
|
|
1304
|
+
* limitations under the License.
|
|
1305
|
+
*/
|
|
1306
|
+
class ConditionTreeComponent {
|
|
1307
|
+
// Carbon only provides three layer tokens, and they alternate (layer 0 and 2 share a surface
|
|
1308
|
+
// colour, layer 1 is the contrasting one). To keep every nesting level visually distinct from
|
|
1309
|
+
// the one it sits on, the condition rows alternate between layer 1 and 2 as they nest. The
|
|
1310
|
+
// permission card sits on layer 0, so the first row level is layer 1.
|
|
1311
|
+
get rowLayerLevel() {
|
|
1312
|
+
return this.depth % 2 === 0 ? 1 : 2;
|
|
1313
|
+
}
|
|
1314
|
+
// Inputs use the layer that contrasts with their row's surface, so the field background always
|
|
1315
|
+
// stands out from the row it sits on (layer 1 ↔ layer 2).
|
|
1316
|
+
get inputLayerLevel() {
|
|
1317
|
+
return this.rowLayerLevel === 1 ? 2 : 1;
|
|
1318
|
+
}
|
|
1319
|
+
constructor(formEditorService, iconService) {
|
|
1320
|
+
this.formEditorService = formEditorService;
|
|
1321
|
+
this.iconService = iconService;
|
|
1322
|
+
this.resourceType = null;
|
|
1323
|
+
this.disabled = false;
|
|
1324
|
+
this.depth = 0;
|
|
1325
|
+
this.fieldItems = [];
|
|
1326
|
+
this.containerTargetItems = [];
|
|
1327
|
+
this.operatorItems = [];
|
|
1328
|
+
// Preset value types for JSON-field conditions (String, Boolean, …). The dropdown only ever holds
|
|
1329
|
+
// presets — custom types are entered via manual mode — so this list is stable.
|
|
1330
|
+
this.valueTypeItems = [];
|
|
1331
|
+
// Condition types are shown in natural language. "Related resource" (container) is only offered
|
|
1332
|
+
// when the resource can be related to another resource (see recomputeItems).
|
|
1333
|
+
this.typeItems = [];
|
|
1334
|
+
this.testIds = ACCESS_CONTROL_EDITOR_TEST_IDS;
|
|
1335
|
+
this.iconService.registerAll([Add16, TrashCan16]);
|
|
1336
|
+
}
|
|
1337
|
+
ngOnInit() {
|
|
1338
|
+
this.operatorItems = this.formEditorService.operatorItems();
|
|
1339
|
+
this.valueTypeItems = this.formEditorService.valueTypeItems();
|
|
1340
|
+
this.recomputeItems();
|
|
1341
|
+
}
|
|
1342
|
+
ngOnChanges(changes) {
|
|
1343
|
+
if (changes['resourceType'])
|
|
1344
|
+
this.recomputeItems();
|
|
1345
|
+
}
|
|
1346
|
+
groupAt(index) {
|
|
1347
|
+
return this.conditions.at(index);
|
|
1348
|
+
}
|
|
1349
|
+
asFormGroup(control) {
|
|
1350
|
+
return control;
|
|
1351
|
+
}
|
|
1352
|
+
typeOf(index) {
|
|
1353
|
+
return this.groupAt(index).get('type').value;
|
|
1354
|
+
}
|
|
1355
|
+
nestedConditions(index) {
|
|
1356
|
+
return this.groupAt(index).get('conditions');
|
|
1357
|
+
}
|
|
1358
|
+
containerResourceType(index) {
|
|
1359
|
+
return this.groupAt(index).get('resourceType').value || null;
|
|
1360
|
+
}
|
|
1361
|
+
containerResourceTypeAt(index) {
|
|
1362
|
+
return this.groupAt(index).get('resourceType').value || '';
|
|
1363
|
+
}
|
|
1364
|
+
fieldValueAt(index) {
|
|
1365
|
+
return this.groupAt(index).get('field').value || '';
|
|
1366
|
+
}
|
|
1367
|
+
operatorValueAt(index) {
|
|
1368
|
+
return this.groupAt(index).get('operator').value || '';
|
|
1369
|
+
}
|
|
1370
|
+
clazzValueAt(index) {
|
|
1371
|
+
return this.groupAt(index).get('clazz').value || '';
|
|
1372
|
+
}
|
|
1373
|
+
// Whether the value type is entered manually (a custom class) rather than picked from the preset
|
|
1374
|
+
// dropdown.
|
|
1375
|
+
clazzManualAt(index) {
|
|
1376
|
+
return !!this.groupAt(index).get('clazzManual').value;
|
|
1377
|
+
}
|
|
1378
|
+
addCondition() {
|
|
1379
|
+
this.conditions.push(this.formEditorService.createConditionGroup(undefined, 'field'));
|
|
1380
|
+
}
|
|
1381
|
+
removeCondition(index) {
|
|
1382
|
+
this.conditions.removeAt(index);
|
|
1383
|
+
}
|
|
1384
|
+
// Toggles the value-type input between the preset dropdown and manual entry. Switching back to the
|
|
1385
|
+
// dropdown with nothing entered yet falls back to the default type.
|
|
1386
|
+
onClazzManualToggle(index, manual) {
|
|
1387
|
+
const group = this.groupAt(index);
|
|
1388
|
+
group.get('clazzManual').setValue(manual);
|
|
1389
|
+
// The dropdown only lists presets, so entering it with an empty or custom value falls back to
|
|
1390
|
+
// the default preset rather than leaving the dropdown with nothing selected.
|
|
1391
|
+
const clazz = group.get('clazz').value;
|
|
1392
|
+
if (!manual && (!clazz || this.formEditorService.isCustomValueType(clazz))) {
|
|
1393
|
+
group.get('clazz').setValue(this.formEditorService.defaultValueType);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
onTypeChange(index) {
|
|
1397
|
+
const group = this.groupAt(index);
|
|
1398
|
+
// Switching to a JSON field (expression) defaults the value type to a preset so its dropdown
|
|
1399
|
+
// starts on a valid, selected option.
|
|
1400
|
+
if (group.get('type').value === 'expression' && !group.get('clazz').value) {
|
|
1401
|
+
group.get('clazz').setValue(this.formEditorService.defaultValueType);
|
|
1402
|
+
group.get('clazzManual').setValue(false);
|
|
1403
|
+
}
|
|
1404
|
+
this.recomputeItems();
|
|
1405
|
+
}
|
|
1406
|
+
recomputeItems() {
|
|
1407
|
+
const resourceType = this.resourceType ?? '';
|
|
1408
|
+
this.fieldItems = this.formEditorService.fieldItems(resourceType, this.collectFieldValues());
|
|
1409
|
+
this.containerTargetItems = this.formEditorService.containerTargetItems(resourceType, this.collectContainerTargets());
|
|
1410
|
+
// Only offer the "related resource" (container) type when the resource actually has related
|
|
1411
|
+
// resources to point at; otherwise a container condition could never be completed.
|
|
1412
|
+
const types = this.containerTargetItems.length
|
|
1413
|
+
? ['field', 'expression', 'container']
|
|
1414
|
+
: ['field', 'expression'];
|
|
1415
|
+
this.typeItems = types.map(type => ({
|
|
1416
|
+
id: type,
|
|
1417
|
+
translationKey: `accessControl.editor.conditionTypes.${type}`,
|
|
1418
|
+
}));
|
|
1419
|
+
}
|
|
1420
|
+
collectFieldValues() {
|
|
1421
|
+
return this.conditions.controls
|
|
1422
|
+
.map(control => control.get('field').value)
|
|
1423
|
+
.filter((value) => !!value);
|
|
589
1424
|
}
|
|
590
|
-
|
|
591
|
-
this.
|
|
1425
|
+
collectContainerTargets() {
|
|
1426
|
+
return this.conditions.controls
|
|
1427
|
+
.map(control => control.get('resourceType').value)
|
|
1428
|
+
.filter((value) => !!value);
|
|
592
1429
|
}
|
|
593
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type:
|
|
594
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: AccessControlOverviewComponent, isStandalone: false, selector: "ng-component", viewQueries: [{ propertyName: "carbonList", first: true, predicate: CarbonListComponent, descendants: true }], ngImport: i0, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<valtimo-carbon-list\n [header]=\"false\"\n [items]=\"roles$ | async\"\n [fields]=\"fields\"\n [loading]=\"loading$ | async\"\n [showSelectionColumn]=\"true\"\n (rowClicked)=\"onRowClick($event)\"\n>\n <ng-container header> {{ 'accessControl.roles.title' | translate }} </ng-container>\n\n <div carbonToolbarContent>\n <ng-container [ngTemplateOutlet]=\"addRoleButton\"></ng-container>\n </div>\n\n <ng-container carbonToolbarActions>\n <button cdsButton=\"primary\" (click)=\"showDeleteModal()\">\n {{ 'interface.delete' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"trash-can\" size=\"16\"></svg>\n </button>\n\n <button cdsButton=\"primary\" (click)=\"showExportModal()\">\n {{ 'interface.export' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"download\" size=\"16\"></svg>\n </button>\n </ng-container>\n\n <valtimo-no-results\n [action]=\"addRoleButton\"\n description=\"{{ 'accessControl.roles.noResults.description' | translate }}\"\n title=\"{{ 'accessControl.roles.noResults.title' | translate }}\"\n ></valtimo-no-results>\n</valtimo-carbon-list>\n\n<valtimo-role-metadata-modal\n [open]=\"showAddModal$ | async\"\n (closeEvent)=\"onAdd($event)\"\n></valtimo-role-metadata-modal>\n\n<ng-container *ngIf=\"{selectedRowKeys: selectedRowKeys$ | async} as obs\">\n <valtimo-delete-role-modal\n [deleteRowKeys]=\"obs.selectedRowKeys\"\n [showDeleteModal$]=\"showDeleteModal$\"\n (deleteEvent)=\"onDelete($event)\"\n >\n </valtimo-delete-role-modal>\n\n <valtimo-export-role-modal\n [disabled]=\"exportDisabled$ | async\"\n [exportRowKeys]=\"obs.selectedRowKeys\"\n [open]=\"showExportModal$ | async\"\n [reset$]=\"resetExportType$\"\n (exportEvent)=\"onExport($event)\"\n (closeEvent)=\"closeExportModal()\"\n >\n </valtimo-export-role-modal>\n</ng-container>\n\n<ng-template #addRoleButton>\n <button cdsButton=\"primary\" (click)=\"openAddModal()\">\n {{ 'accessControl.roles.add' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"add\" size=\"16\"></svg>\n </button>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2$1.Button, selector: "[cdsButton], [ibmButton]", inputs: ["ibmButton", "cdsButton", "size", "skeleton", "iconOnly", "isExpressive"] }, { kind: "directive", type: i2$1.IconDirective, selector: "[cdsIcon], [ibmIcon]", inputs: ["ibmIcon", "cdsIcon", "size", "title", "ariaLabel", "ariaLabelledBy", "ariaHidden", "isFocusable"] }, { kind: "component", type: i3.CarbonListComponent, selector: "valtimo-carbon-list", inputs: ["items", "fields", "tableTranslations", "paginatorConfig", "pagination", "loading", "skeletonRowCount", "actions", "actionItems", "showActionItems", "header", "hideColumnHeader", "initialSortState", "sortState", "isSearchable", "enableSingleSelection", "lastColumnTemplate", "paginationIdentifier", "showSelectionColumn", "striped", "hideToolbar", "lockedTooltipTranslationKey", "movingRowsEnabled", "dragAndDrop", "dragAndDropDisabled"], outputs: ["rowClicked", "paginationClicked", "paginationSet", "search", "sortChanged", "moveRow", "itemsReordered"] }, { kind: "component", type: i3.CarbonNoResultsComponent, selector: "valtimo-no-results", inputs: ["action", "description", "illustration", "title", "smallPadding", "collapseVertically", "alwaysRenderVertically"] }, { kind: "component", type: RoleMetadataModalComponent, selector: "valtimo-role-metadata-modal", inputs: ["open", "type", "defaultKeyValue"], outputs: ["closeEvent"] }, { kind: "component", type: DeleteRoleModalComponent, selector: "valtimo-delete-role-modal", inputs: ["deleteRowKeys", "showDeleteModal$"], outputs: ["deleteEvent"] }, { kind: "component", type: ExportRoleModalComponent, selector: "valtimo-export-role-modal", inputs: ["open", "exportRowKeys", "reset$", "disabled"], outputs: ["exportEvent", "closeEvent"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i4$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1430
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ConditionTreeComponent, deps: [{ token: AccessControlFormEditorService }, { token: i2$1.IconService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1431
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: ConditionTreeComponent, isStandalone: false, selector: "valtimo-condition-tree", inputs: { conditions: "conditions", resourceType: "resourceType", disabled: "disabled", depth: "depth" }, usesOnChanges: true, ngImport: i0, template: "<!--\n ~ Copyright 2015-2026 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<div class=\"condition-tree\" [class.condition-tree--nested]=\"depth > 0\">\n @for (control of conditions.controls; track control; let i = $index) {\n <div class=\"condition-tree__row\" [cdsLayer]=\"rowLayerLevel\" [formGroup]=\"asFormGroup(control)\">\n <div class=\"condition-tree__content\">\n <v-select\n class=\"condition-tree__type\"\n formControlName=\"type\"\n [cdsLayer]=\"inputLayerLevel\"\n [clearable]=\"false\"\n [dataTestId]=\"testIds.conditionTypeSelect\"\n [defaultSelectionId]=\"typeOf(i)\"\n [dropUp]=\"false\"\n [items]=\"typeItems\"\n [tooltip]=\"'accessControl.editor.tooltips.conditionType' | translate\"\n titleTranslationKey=\"accessControl.editor.conditionType\"\n (selectedChange)=\"onTypeChange(i)\"\n ></v-select>\n\n @switch (typeOf(i)) {\n @case ('container') {\n <v-select\n formControlName=\"resourceType\"\n [cdsLayer]=\"inputLayerLevel\"\n [dataTestId]=\"testIds.conditionFieldSelect\"\n [defaultSelectionId]=\"containerResourceTypeAt(i)\"\n [dropUp]=\"false\"\n [invalid]=\"\n !!asFormGroup(control).errors?.['resourceTypeRequired'] &&\n !!asFormGroup(control).get('resourceType')?.touched\n \"\n [items]=\"containerTargetItems\"\n [placeholder]=\"'accessControl.editor.relatedResourcePlaceholder' | translate\"\n [required]=\"true\"\n [tooltip]=\"'accessControl.editor.tooltips.relatedResource' | translate\"\n titleTranslationKey=\"accessControl.editor.relatedResource\"\n ></v-select>\n\n <div class=\"condition-tree__nested\">\n <valtimo-condition-tree\n [conditions]=\"nestedConditions(i)\"\n [depth]=\"depth + 1\"\n [disabled]=\"disabled\"\n [resourceType]=\"containerResourceType(i)\"\n ></valtimo-condition-tree>\n </div>\n }\n\n @default {\n <div class=\"condition-tree__fields\">\n <v-select\n class=\"condition-tree__field\"\n formControlName=\"field\"\n [cdsLayer]=\"inputLayerLevel\"\n [dataTestId]=\"testIds.conditionFieldSelect\"\n [defaultSelectionId]=\"fieldValueAt(i)\"\n [dropUp]=\"false\"\n [invalid]=\"\n !!asFormGroup(control).errors?.['fieldRequired'] &&\n !!asFormGroup(control).get('field')?.touched\n \"\n [items]=\"fieldItems\"\n [placeholder]=\"'accessControl.editor.fieldPlaceholder' | translate\"\n [required]=\"true\"\n [tooltip]=\"'accessControl.editor.tooltips.field' | translate\"\n titleTranslationKey=\"accessControl.editor.field\"\n ></v-select>\n\n <v-select\n class=\"condition-tree__operator\"\n formControlName=\"operator\"\n [cdsLayer]=\"inputLayerLevel\"\n [clearable]=\"false\"\n [dataTestId]=\"testIds.conditionOperatorSelect\"\n [defaultSelectionId]=\"operatorValueAt(i)\"\n [dropUp]=\"false\"\n [items]=\"operatorItems\"\n [tooltip]=\"'accessControl.editor.tooltips.operator' | translate\"\n titleTranslationKey=\"accessControl.editor.operator\"\n ></v-select>\n\n <div class=\"condition-tree__value cds--form-item\" [cdsLayer]=\"inputLayerLevel\">\n <label class=\"cds--label label-with-tooltip\">\n {{ 'accessControl.editor.value' | translate }} ({{\n 'interface.required' | translate\n }})\n <v-tooltip-icon\n [tooltip]=\"'accessControl.editor.tooltips.value' | translate\"\n ></v-tooltip-icon>\n </label>\n <input\n cdsText\n [attr.data-test-id]=\"testIds.conditionValueInput\"\n [class.cds--text-input--invalid]=\"\n !!asFormGroup(control).errors?.['valueRequired'] &&\n !!asFormGroup(control).get('value')?.touched\n \"\n [placeholder]=\"'accessControl.editor.valuePlaceholder' | translate\"\n formControlName=\"value\"\n />\n </div>\n </div>\n\n @if (typeOf(i) === 'expression') {\n <div class=\"condition-tree__fields\">\n <div class=\"condition-tree__value cds--form-item\" [cdsLayer]=\"inputLayerLevel\">\n <label class=\"cds--label label-with-tooltip\">\n {{ 'accessControl.editor.path' | translate }} ({{\n 'interface.required' | translate\n }})\n <v-tooltip-icon\n [tooltip]=\"'accessControl.editor.tooltips.path' | translate\"\n ></v-tooltip-icon>\n </label>\n <input\n cdsText\n [attr.data-test-id]=\"testIds.conditionPathInput\"\n [class.cds--text-input--invalid]=\"\n !!asFormGroup(control).errors?.['pathRequired'] &&\n !!asFormGroup(control).get('path')?.touched\n \"\n [placeholder]=\"'accessControl.editor.pathPlaceholder' | translate\"\n formControlName=\"path\"\n />\n </div>\n\n <div class=\"condition-tree__value cds--form-item\" [cdsLayer]=\"inputLayerLevel\">\n <label class=\"cds--label label-with-tooltip\">\n {{ 'accessControl.editor.clazz' | translate }} ({{\n 'interface.required' | translate\n }})\n <v-tooltip-icon\n [tooltip]=\"'accessControl.editor.tooltips.clazz' | translate\"\n ></v-tooltip-icon>\n </label>\n\n @if (clazzManualAt(i)) {\n <input\n cdsText\n [attr.data-test-id]=\"testIds.conditionClazzInput\"\n [class.cds--text-input--invalid]=\"\n !!asFormGroup(control).errors?.['clazzRequired'] &&\n !!asFormGroup(control).get('clazz')?.touched\n \"\n [placeholder]=\"'accessControl.editor.clazzPlaceholder' | translate\"\n formControlName=\"clazz\"\n />\n } @else {\n <v-select\n formControlName=\"clazz\"\n [cdsLayer]=\"inputLayerLevel\"\n [clearable]=\"false\"\n [dataTestId]=\"testIds.conditionClazzInput\"\n [defaultSelectionId]=\"clazzValueAt(i)\"\n [dropUp]=\"false\"\n [invalid]=\"\n !!asFormGroup(control).errors?.['clazzRequired'] &&\n !!asFormGroup(control).get('clazz')?.touched\n \"\n [items]=\"valueTypeItems\"\n ></v-select>\n }\n\n <button\n class=\"condition-tree__mode-switch\"\n cdsButton=\"ghost\"\n size=\"sm\"\n type=\"button\"\n [disabled]=\"disabled\"\n (click)=\"onClazzManualToggle(i, !clazzManualAt(i))\"\n >\n {{\n (clazzManualAt(i)\n ? 'accessControl.editor.valueTypeFromList'\n : 'accessControl.editor.valueTypeManual'\n ) | translate\n }}\n </button>\n </div>\n </div>\n }\n }\n }\n </div>\n\n <button\n class=\"condition-tree__remove\"\n cdsButton=\"ghost\"\n [attr.data-test-id]=\"testIds.removeConditionButton\"\n [disabled]=\"disabled\"\n [iconOnly]=\"true\"\n (click)=\"removeCondition(i)\"\n >\n <svg cdsIcon=\"trash-can\" size=\"16\"></svg>\n </button>\n </div>\n }\n\n <button\n class=\"condition-tree__add\"\n cdsButton=\"ghost\"\n size=\"sm\"\n [attr.data-test-id]=\"testIds.addConditionButton\"\n [disabled]=\"disabled\"\n (click)=\"addCondition()\"\n >\n {{ 'accessControl.editor.addCondition' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"add\" size=\"16\"></svg>\n </button>\n</div>\n", styles: [".condition-tree{display:flex;flex-direction:column;gap:.75rem}.condition-tree v-select{display:block;width:100%}.condition-tree--nested{border-left:2px solid var(--cds-border-subtle, #c6c6c6);margin-top:.5rem;padding-left:1rem}.condition-tree__row{background-color:var(--cds-layer);border:1px solid var(--cds-border-subtle);display:flex;flex-direction:row;align-items:stretch;gap:1rem;padding:.75rem}.condition-tree__content{display:flex;flex:1 1 auto;flex-direction:column;gap:.75rem;min-width:0}.condition-tree__remove{align-items:flex-start;align-self:stretch}.condition-tree__fields{display:flex;flex-direction:column;gap:.75rem}.condition-tree__value{width:100%}.condition-tree__mode-switch{align-self:flex-start;margin-top:.25rem}.condition-tree__nested{display:flex;flex-direction:column}.condition-tree__add{align-self:flex-start}.condition-tree .label-with-tooltip{align-items:center;display:inline-flex;gap:.25rem}\n/*!\n * Copyright 2015-2026 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"], dependencies: [{ kind: "directive", type: i2$1.Button, selector: "[cdsButton], [ibmButton]", inputs: ["ibmButton", "cdsButton", "size", "skeleton", "iconOnly", "isExpressive"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i2$1.TextInput, selector: "[cdsText], [ibmText]", inputs: ["theme", "size", "invalid", "warn", "skeleton"] }, { kind: "directive", type: i2$1.IconDirective, selector: "[cdsIcon], [ibmIcon]", inputs: ["ibmIcon", "cdsIcon", "size", "title", "ariaLabel", "ariaLabelledBy", "ariaHidden", "isFocusable"] }, { kind: "component", type: i3$1.SelectComponent, selector: "v-select", inputs: ["items", "defaultSelection", "defaultSelectionId", "defaultSelectionIds", "disabled", "dropUp", "invalid", "multiple", "margin", "widthInPx", "notFoundText", "clearAllText", "clearText", "clearable", "name", "title", "titleTranslationKey", "clearSelectionSubject$", "tooltip", "required", "loading", "loadingText", "placeholder", "smallMargin", "carbonTheme", "appendInline", "warn", "warnText", "dataTestId"], outputs: ["selectedChange"] }, { kind: "component", type: i3$1.TooltipIconComponent, selector: "v-tooltip-icon", inputs: ["tooltip", "disabled"] }, { kind: "directive", type: i2$1.LayerDirective, selector: "[cdsLayer], [ibmLayer]", inputs: ["ibmLayer", "cdsLayer"], exportAs: ["layer"] }, { kind: "component", type: ConditionTreeComponent, selector: "valtimo-condition-tree", inputs: ["conditions", "resourceType", "disabled", "depth"] }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }] }); }
|
|
595
1432
|
}
|
|
596
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type:
|
|
1433
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ConditionTreeComponent, decorators: [{
|
|
597
1434
|
type: Component,
|
|
598
|
-
args: [{ standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<valtimo-carbon-list\n [header]=\"false\"\n [items]=\"roles$ | async\"\n [fields]=\"fields\"\n [loading]=\"loading$ | async\"\n [showSelectionColumn]=\"true\"\n (rowClicked)=\"onRowClick($event)\"\n>\n <ng-container header> {{ 'accessControl.roles.title' | translate }} </ng-container>\n\n <div carbonToolbarContent>\n <ng-container [ngTemplateOutlet]=\"addRoleButton\"></ng-container>\n </div>\n\n <ng-container carbonToolbarActions>\n <button cdsButton=\"primary\" (click)=\"showDeleteModal()\">\n {{ 'interface.delete' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"trash-can\" size=\"16\"></svg>\n </button>\n\n <button cdsButton=\"primary\" (click)=\"showExportModal()\">\n {{ 'interface.export' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"download\" size=\"16\"></svg>\n </button>\n </ng-container>\n\n <valtimo-no-results\n [action]=\"addRoleButton\"\n description=\"{{ 'accessControl.roles.noResults.description' | translate }}\"\n title=\"{{ 'accessControl.roles.noResults.title' | translate }}\"\n ></valtimo-no-results>\n</valtimo-carbon-list>\n\n<valtimo-role-metadata-modal\n [open]=\"showAddModal$ | async\"\n (closeEvent)=\"onAdd($event)\"\n></valtimo-role-metadata-modal>\n\n<ng-container *ngIf=\"{selectedRowKeys: selectedRowKeys$ | async} as obs\">\n <valtimo-delete-role-modal\n [deleteRowKeys]=\"obs.selectedRowKeys\"\n [showDeleteModal$]=\"showDeleteModal$\"\n (deleteEvent)=\"onDelete($event)\"\n >\n </valtimo-delete-role-modal>\n\n <valtimo-export-role-modal\n [disabled]=\"exportDisabled$ | async\"\n [exportRowKeys]=\"obs.selectedRowKeys\"\n [open]=\"showExportModal$ | async\"\n [reset$]=\"resetExportType$\"\n (exportEvent)=\"onExport($event)\"\n (closeEvent)=\"closeExportModal()\"\n >\n </valtimo-export-role-modal>\n</ng-container>\n\n<ng-template #addRoleButton>\n <button cdsButton=\"primary\" (click)=\"openAddModal()\">\n {{ 'accessControl.roles.add' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"add\" size=\"16\"></svg>\n </button>\n</ng-template>\n" }]
|
|
599
|
-
}], ctorParameters: () => [{ type:
|
|
600
|
-
type:
|
|
601
|
-
|
|
1435
|
+
args: [{ standalone: false, selector: 'valtimo-condition-tree', template: "<!--\n ~ Copyright 2015-2026 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<div class=\"condition-tree\" [class.condition-tree--nested]=\"depth > 0\">\n @for (control of conditions.controls; track control; let i = $index) {\n <div class=\"condition-tree__row\" [cdsLayer]=\"rowLayerLevel\" [formGroup]=\"asFormGroup(control)\">\n <div class=\"condition-tree__content\">\n <v-select\n class=\"condition-tree__type\"\n formControlName=\"type\"\n [cdsLayer]=\"inputLayerLevel\"\n [clearable]=\"false\"\n [dataTestId]=\"testIds.conditionTypeSelect\"\n [defaultSelectionId]=\"typeOf(i)\"\n [dropUp]=\"false\"\n [items]=\"typeItems\"\n [tooltip]=\"'accessControl.editor.tooltips.conditionType' | translate\"\n titleTranslationKey=\"accessControl.editor.conditionType\"\n (selectedChange)=\"onTypeChange(i)\"\n ></v-select>\n\n @switch (typeOf(i)) {\n @case ('container') {\n <v-select\n formControlName=\"resourceType\"\n [cdsLayer]=\"inputLayerLevel\"\n [dataTestId]=\"testIds.conditionFieldSelect\"\n [defaultSelectionId]=\"containerResourceTypeAt(i)\"\n [dropUp]=\"false\"\n [invalid]=\"\n !!asFormGroup(control).errors?.['resourceTypeRequired'] &&\n !!asFormGroup(control).get('resourceType')?.touched\n \"\n [items]=\"containerTargetItems\"\n [placeholder]=\"'accessControl.editor.relatedResourcePlaceholder' | translate\"\n [required]=\"true\"\n [tooltip]=\"'accessControl.editor.tooltips.relatedResource' | translate\"\n titleTranslationKey=\"accessControl.editor.relatedResource\"\n ></v-select>\n\n <div class=\"condition-tree__nested\">\n <valtimo-condition-tree\n [conditions]=\"nestedConditions(i)\"\n [depth]=\"depth + 1\"\n [disabled]=\"disabled\"\n [resourceType]=\"containerResourceType(i)\"\n ></valtimo-condition-tree>\n </div>\n }\n\n @default {\n <div class=\"condition-tree__fields\">\n <v-select\n class=\"condition-tree__field\"\n formControlName=\"field\"\n [cdsLayer]=\"inputLayerLevel\"\n [dataTestId]=\"testIds.conditionFieldSelect\"\n [defaultSelectionId]=\"fieldValueAt(i)\"\n [dropUp]=\"false\"\n [invalid]=\"\n !!asFormGroup(control).errors?.['fieldRequired'] &&\n !!asFormGroup(control).get('field')?.touched\n \"\n [items]=\"fieldItems\"\n [placeholder]=\"'accessControl.editor.fieldPlaceholder' | translate\"\n [required]=\"true\"\n [tooltip]=\"'accessControl.editor.tooltips.field' | translate\"\n titleTranslationKey=\"accessControl.editor.field\"\n ></v-select>\n\n <v-select\n class=\"condition-tree__operator\"\n formControlName=\"operator\"\n [cdsLayer]=\"inputLayerLevel\"\n [clearable]=\"false\"\n [dataTestId]=\"testIds.conditionOperatorSelect\"\n [defaultSelectionId]=\"operatorValueAt(i)\"\n [dropUp]=\"false\"\n [items]=\"operatorItems\"\n [tooltip]=\"'accessControl.editor.tooltips.operator' | translate\"\n titleTranslationKey=\"accessControl.editor.operator\"\n ></v-select>\n\n <div class=\"condition-tree__value cds--form-item\" [cdsLayer]=\"inputLayerLevel\">\n <label class=\"cds--label label-with-tooltip\">\n {{ 'accessControl.editor.value' | translate }} ({{\n 'interface.required' | translate\n }})\n <v-tooltip-icon\n [tooltip]=\"'accessControl.editor.tooltips.value' | translate\"\n ></v-tooltip-icon>\n </label>\n <input\n cdsText\n [attr.data-test-id]=\"testIds.conditionValueInput\"\n [class.cds--text-input--invalid]=\"\n !!asFormGroup(control).errors?.['valueRequired'] &&\n !!asFormGroup(control).get('value')?.touched\n \"\n [placeholder]=\"'accessControl.editor.valuePlaceholder' | translate\"\n formControlName=\"value\"\n />\n </div>\n </div>\n\n @if (typeOf(i) === 'expression') {\n <div class=\"condition-tree__fields\">\n <div class=\"condition-tree__value cds--form-item\" [cdsLayer]=\"inputLayerLevel\">\n <label class=\"cds--label label-with-tooltip\">\n {{ 'accessControl.editor.path' | translate }} ({{\n 'interface.required' | translate\n }})\n <v-tooltip-icon\n [tooltip]=\"'accessControl.editor.tooltips.path' | translate\"\n ></v-tooltip-icon>\n </label>\n <input\n cdsText\n [attr.data-test-id]=\"testIds.conditionPathInput\"\n [class.cds--text-input--invalid]=\"\n !!asFormGroup(control).errors?.['pathRequired'] &&\n !!asFormGroup(control).get('path')?.touched\n \"\n [placeholder]=\"'accessControl.editor.pathPlaceholder' | translate\"\n formControlName=\"path\"\n />\n </div>\n\n <div class=\"condition-tree__value cds--form-item\" [cdsLayer]=\"inputLayerLevel\">\n <label class=\"cds--label label-with-tooltip\">\n {{ 'accessControl.editor.clazz' | translate }} ({{\n 'interface.required' | translate\n }})\n <v-tooltip-icon\n [tooltip]=\"'accessControl.editor.tooltips.clazz' | translate\"\n ></v-tooltip-icon>\n </label>\n\n @if (clazzManualAt(i)) {\n <input\n cdsText\n [attr.data-test-id]=\"testIds.conditionClazzInput\"\n [class.cds--text-input--invalid]=\"\n !!asFormGroup(control).errors?.['clazzRequired'] &&\n !!asFormGroup(control).get('clazz')?.touched\n \"\n [placeholder]=\"'accessControl.editor.clazzPlaceholder' | translate\"\n formControlName=\"clazz\"\n />\n } @else {\n <v-select\n formControlName=\"clazz\"\n [cdsLayer]=\"inputLayerLevel\"\n [clearable]=\"false\"\n [dataTestId]=\"testIds.conditionClazzInput\"\n [defaultSelectionId]=\"clazzValueAt(i)\"\n [dropUp]=\"false\"\n [invalid]=\"\n !!asFormGroup(control).errors?.['clazzRequired'] &&\n !!asFormGroup(control).get('clazz')?.touched\n \"\n [items]=\"valueTypeItems\"\n ></v-select>\n }\n\n <button\n class=\"condition-tree__mode-switch\"\n cdsButton=\"ghost\"\n size=\"sm\"\n type=\"button\"\n [disabled]=\"disabled\"\n (click)=\"onClazzManualToggle(i, !clazzManualAt(i))\"\n >\n {{\n (clazzManualAt(i)\n ? 'accessControl.editor.valueTypeFromList'\n : 'accessControl.editor.valueTypeManual'\n ) | translate\n }}\n </button>\n </div>\n </div>\n }\n }\n }\n </div>\n\n <button\n class=\"condition-tree__remove\"\n cdsButton=\"ghost\"\n [attr.data-test-id]=\"testIds.removeConditionButton\"\n [disabled]=\"disabled\"\n [iconOnly]=\"true\"\n (click)=\"removeCondition(i)\"\n >\n <svg cdsIcon=\"trash-can\" size=\"16\"></svg>\n </button>\n </div>\n }\n\n <button\n class=\"condition-tree__add\"\n cdsButton=\"ghost\"\n size=\"sm\"\n [attr.data-test-id]=\"testIds.addConditionButton\"\n [disabled]=\"disabled\"\n (click)=\"addCondition()\"\n >\n {{ 'accessControl.editor.addCondition' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"add\" size=\"16\"></svg>\n </button>\n</div>\n", styles: [".condition-tree{display:flex;flex-direction:column;gap:.75rem}.condition-tree v-select{display:block;width:100%}.condition-tree--nested{border-left:2px solid var(--cds-border-subtle, #c6c6c6);margin-top:.5rem;padding-left:1rem}.condition-tree__row{background-color:var(--cds-layer);border:1px solid var(--cds-border-subtle);display:flex;flex-direction:row;align-items:stretch;gap:1rem;padding:.75rem}.condition-tree__content{display:flex;flex:1 1 auto;flex-direction:column;gap:.75rem;min-width:0}.condition-tree__remove{align-items:flex-start;align-self:stretch}.condition-tree__fields{display:flex;flex-direction:column;gap:.75rem}.condition-tree__value{width:100%}.condition-tree__mode-switch{align-self:flex-start;margin-top:.25rem}.condition-tree__nested{display:flex;flex-direction:column}.condition-tree__add{align-self:flex-start}.condition-tree .label-with-tooltip{align-items:center;display:inline-flex;gap:.25rem}\n/*!\n * Copyright 2015-2026 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"] }]
|
|
1436
|
+
}], ctorParameters: () => [{ type: AccessControlFormEditorService }, { type: i2$1.IconService }], propDecorators: { conditions: [{
|
|
1437
|
+
type: Input
|
|
1438
|
+
}], resourceType: [{
|
|
1439
|
+
type: Input
|
|
1440
|
+
}], disabled: [{
|
|
1441
|
+
type: Input
|
|
1442
|
+
}], depth: [{
|
|
1443
|
+
type: Input
|
|
602
1444
|
}] } });
|
|
603
1445
|
|
|
604
1446
|
/*
|
|
@@ -616,17 +1458,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
616
1458
|
* See the License for the specific language governing permissions and
|
|
617
1459
|
* limitations under the License.
|
|
618
1460
|
*/
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
const
|
|
1461
|
+
// Renders a permission action key (e.g. "view_list") as its natural-language label, falling back to
|
|
1462
|
+
// the raw key when there is no translation. Impure so the label follows runtime language changes.
|
|
1463
|
+
class ActionLabelPipe {
|
|
1464
|
+
constructor(translateService) {
|
|
1465
|
+
this.translateService = translateService;
|
|
1466
|
+
}
|
|
1467
|
+
transform(action) {
|
|
1468
|
+
if (!action)
|
|
1469
|
+
return '';
|
|
1470
|
+
const key = `accessControl.actions.${action}`;
|
|
1471
|
+
const translated = this.translateService.instant(key);
|
|
1472
|
+
return translated === key ? action : translated;
|
|
1473
|
+
}
|
|
1474
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ActionLabelPipe, deps: [{ token: i1$2.TranslateService }], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
1475
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: ActionLabelPipe, isStandalone: true, name: "actionLabel", pure: false }); }
|
|
1476
|
+
}
|
|
1477
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ActionLabelPipe, decorators: [{
|
|
1478
|
+
type: Pipe,
|
|
1479
|
+
args: [{
|
|
1480
|
+
standalone: true,
|
|
1481
|
+
name: 'actionLabel',
|
|
1482
|
+
pure: false,
|
|
1483
|
+
}]
|
|
1484
|
+
}], ctorParameters: () => [{ type: i1$2.TranslateService }] });
|
|
630
1485
|
|
|
631
1486
|
/*
|
|
632
1487
|
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
@@ -643,6 +1498,31 @@ const NO_CONTEXT_RESOURCE_TYPE = 'com.ritense.authorization.NoContext';
|
|
|
643
1498
|
* See the License for the specific language governing permissions and
|
|
644
1499
|
* limitations under the License.
|
|
645
1500
|
*/
|
|
1501
|
+
// Resolves a short, human description for a permission action (e.g. "view_list") from
|
|
1502
|
+
// accessControl.actionDescriptions.<action>. Returns an empty string when no description is
|
|
1503
|
+
// configured, so callers can hide the tooltip. Impure so it follows runtime language changes.
|
|
1504
|
+
class ActionDescriptionPipe {
|
|
1505
|
+
constructor(translateService) {
|
|
1506
|
+
this.translateService = translateService;
|
|
1507
|
+
}
|
|
1508
|
+
transform(action) {
|
|
1509
|
+
if (!action)
|
|
1510
|
+
return '';
|
|
1511
|
+
const key = `accessControl.actionDescriptions.${action}`;
|
|
1512
|
+
const translated = this.translateService.instant(key);
|
|
1513
|
+
return translated === key ? '' : translated;
|
|
1514
|
+
}
|
|
1515
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ActionDescriptionPipe, deps: [{ token: i1$2.TranslateService }], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
1516
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: ActionDescriptionPipe, isStandalone: true, name: "actionDescription", pure: false }); }
|
|
1517
|
+
}
|
|
1518
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ActionDescriptionPipe, decorators: [{
|
|
1519
|
+
type: Pipe,
|
|
1520
|
+
args: [{
|
|
1521
|
+
standalone: true,
|
|
1522
|
+
name: 'actionDescription',
|
|
1523
|
+
pure: false,
|
|
1524
|
+
}]
|
|
1525
|
+
}], ctorParameters: () => [{ type: i1$2.TranslateService }] });
|
|
646
1526
|
|
|
647
1527
|
/*
|
|
648
1528
|
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
@@ -659,79 +1539,215 @@ const NO_CONTEXT_RESOURCE_TYPE = 'com.ritense.authorization.NoContext';
|
|
|
659
1539
|
* See the License for the specific language governing permissions and
|
|
660
1540
|
* limitations under the License.
|
|
661
1541
|
*/
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
}
|
|
675
|
-
function fieldI18nKey(resourceType, field) {
|
|
676
|
-
const resourceSegment = resourceType.split('.').pop();
|
|
677
|
-
if (!resourceSegment || !field)
|
|
678
|
-
return null;
|
|
679
|
-
const fieldSegment = field
|
|
680
|
-
.split('.')
|
|
681
|
-
.map((part, i) => (i === 0 ? part : upperFirst(part)))
|
|
682
|
-
.join('');
|
|
683
|
-
return `accessControl.overview.fields.${lowerFirst(resourceSegment)}.${fieldSegment}`;
|
|
684
|
-
}
|
|
685
|
-
function tryTranslate(translate, key) {
|
|
686
|
-
const translated = translate.instant(key);
|
|
687
|
-
return translated === key ? null : translated;
|
|
688
|
-
}
|
|
689
|
-
function formatResourceType(translate, fqn) {
|
|
690
|
-
const key = resourceTypeI18nKey(fqn);
|
|
691
|
-
const translated = key ? tryTranslate(translate, key) : null;
|
|
692
|
-
if (translated !== null)
|
|
693
|
-
return translated;
|
|
694
|
-
const segments = fqn.split('.');
|
|
695
|
-
return segments[segments.length - 1] || fqn;
|
|
696
|
-
}
|
|
697
|
-
function formatField(translate, resourceType, field) {
|
|
698
|
-
const key = fieldI18nKey(resourceType, field);
|
|
699
|
-
const translated = key ? tryTranslate(translate, key) : null;
|
|
700
|
-
if (translated !== null)
|
|
701
|
-
return translated;
|
|
702
|
-
return humanizeFieldPath(field);
|
|
703
|
-
}
|
|
704
|
-
function formatOperator(translate, operator) {
|
|
705
|
-
const key = OPERATOR_LABEL[operator];
|
|
706
|
-
return key ? translate.instant(key) : operator;
|
|
1542
|
+
// Maps a permission action key (e.g. "create") to a tag colour, so each kind of action is shown with
|
|
1543
|
+
// its own distinct colour. The colour may be a Carbon built-in or one of the extended case-widget
|
|
1544
|
+
// colours registered globally in carbon.scss (e.g. "orange", "periwinkle"); the latter are cast to
|
|
1545
|
+
// TagType so they satisfy the cds-tag input. Falls back to a neutral colour for unknown actions.
|
|
1546
|
+
class ActionTagTypePipe {
|
|
1547
|
+
transform(action) {
|
|
1548
|
+
if (!action)
|
|
1549
|
+
return DEFAULT_ACTION_TAG_TYPE;
|
|
1550
|
+
return (ACTION_TAG_TYPE[action] ?? DEFAULT_ACTION_TAG_TYPE);
|
|
1551
|
+
}
|
|
1552
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ActionTagTypePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
1553
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: ActionTagTypePipe, isStandalone: true, name: "actionTagType" }); }
|
|
707
1554
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
1555
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ActionTagTypePipe, decorators: [{
|
|
1556
|
+
type: Pipe,
|
|
1557
|
+
args: [{
|
|
1558
|
+
standalone: true,
|
|
1559
|
+
name: 'actionTagType',
|
|
1560
|
+
}]
|
|
1561
|
+
}] });
|
|
1562
|
+
|
|
1563
|
+
/*
|
|
1564
|
+
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
1565
|
+
*
|
|
1566
|
+
* Licensed under EUPL, Version 1.2 (the "License");
|
|
1567
|
+
* you may not use this file except in compliance with the License.
|
|
1568
|
+
* You may obtain a copy of the License at
|
|
1569
|
+
*
|
|
1570
|
+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
|
|
1571
|
+
*
|
|
1572
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1573
|
+
* distributed under the License is distributed on an "AS IS" basis,
|
|
1574
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1575
|
+
* See the License for the specific language governing permissions and
|
|
1576
|
+
* limitations under the License.
|
|
1577
|
+
*/
|
|
1578
|
+
class PermissionFormComponent {
|
|
1579
|
+
constructor(formEditorService, iconService, changeDetectorRef) {
|
|
1580
|
+
this.formEditorService = formEditorService;
|
|
1581
|
+
this.iconService = iconService;
|
|
1582
|
+
this.changeDetectorRef = changeDetectorRef;
|
|
1583
|
+
this.index = 0;
|
|
1584
|
+
this.disabled = false;
|
|
1585
|
+
this.roleKey = null;
|
|
1586
|
+
this.removeEvent = new EventEmitter();
|
|
1587
|
+
this.resourceTypeItems = [];
|
|
1588
|
+
this.actionItems = [];
|
|
1589
|
+
this.contextResourceTypeItems = [];
|
|
1590
|
+
// Whether the resource type has any related resources to scope to — gates the "Specific context"
|
|
1591
|
+
// option in the context switcher.
|
|
1592
|
+
this.hasContextTargets = false;
|
|
1593
|
+
// The form is rendered (but visually hidden) from the start so the wrapped comboboxes can
|
|
1594
|
+
// initialize and fill in their preselected values. A spinner is shown until that has happened,
|
|
1595
|
+
// after which the prefilled form is revealed.
|
|
1596
|
+
this.$loading = signal(true);
|
|
1597
|
+
// The permission is shown as a three-section accordion. Only one section is open at a time;
|
|
1598
|
+
// "Resource & actions" is open by default.
|
|
1599
|
+
this.$openSection = signal('resourceActions');
|
|
1600
|
+
// Controls the "remove permission" confirmation modal. Removal only happens on confirm, and even
|
|
1601
|
+
// then it just drops the permission from the editable list — nothing is persisted until the whole
|
|
1602
|
+
// set of permissions is saved.
|
|
1603
|
+
this.showRemoveModal$ = new BehaviorSubject(false);
|
|
1604
|
+
this.testIds = ACCESS_CONTROL_EDITOR_TEST_IDS;
|
|
1605
|
+
this._subscriptions = new Subscription();
|
|
1606
|
+
this.iconService.registerAll([Information16, TrashCan16]);
|
|
711
1607
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
1608
|
+
get resourceTypeValue() {
|
|
1609
|
+
return this.group.get('resourceType').value;
|
|
1610
|
+
}
|
|
1611
|
+
// The short (simple) class name of the resource type, e.g. "CaseDefinition" for
|
|
1612
|
+
// "com.ritense.case_.domain.definition.CaseDefinition". Shown as the preview title, with the full
|
|
1613
|
+
// technical name as a subtitle beneath it.
|
|
1614
|
+
get resourceShortName() {
|
|
1615
|
+
return shortTypeName(this.resourceTypeValue);
|
|
1616
|
+
}
|
|
1617
|
+
get contextResourceTypeValue() {
|
|
1618
|
+
return this.group.get('contextResourceType').value;
|
|
1619
|
+
}
|
|
1620
|
+
// Whether context scoping is enabled (the toggle). Off means no context is written at all.
|
|
1621
|
+
get hasContext() {
|
|
1622
|
+
return this.group.get('hasContext').value;
|
|
1623
|
+
}
|
|
1624
|
+
// True when a real context resource is selected (i.e. anything other than the "No context"
|
|
1625
|
+
// marker). Gates the context condition tree — the marker has no fields to build conditions on.
|
|
1626
|
+
get hasContextResource() {
|
|
1627
|
+
const value = this.contextResourceTypeValue;
|
|
1628
|
+
return !!value && value !== NO_CONTEXT_RESOURCE_TYPE;
|
|
1629
|
+
}
|
|
1630
|
+
// The three-way context choice, derived from the form: 'none' (context is ignored), 'noContext'
|
|
1631
|
+
// (only applies when there is no context) or 'specific' (restricted to a context resource).
|
|
1632
|
+
get contextMode() {
|
|
1633
|
+
if (!this.hasContext)
|
|
1634
|
+
return 'none';
|
|
1635
|
+
return this.hasContextResource ? 'specific' : 'noContext';
|
|
1636
|
+
}
|
|
1637
|
+
get actionsControl() {
|
|
1638
|
+
return this.group.get('actions');
|
|
1639
|
+
}
|
|
1640
|
+
get selectedActions() {
|
|
1641
|
+
return this.actionsControl.value ?? [];
|
|
1642
|
+
}
|
|
1643
|
+
get conditionsArray() {
|
|
1644
|
+
return this.group.get('conditions');
|
|
1645
|
+
}
|
|
1646
|
+
get contextConditionsArray() {
|
|
1647
|
+
return this.group.get('contextConditions');
|
|
1648
|
+
}
|
|
1649
|
+
ngOnInit() {
|
|
1650
|
+
this.resourceTypeItems = this.formEditorService.resourceTypeItems(this.resourceTypeValue);
|
|
1651
|
+
this.recomputeActionItems();
|
|
1652
|
+
this.recomputeContextResourceTypeItems();
|
|
1653
|
+
this._subscriptions.add(this.group.get('resourceType').valueChanges.subscribe(() => {
|
|
1654
|
+
this.recomputeActionItems();
|
|
1655
|
+
this.recomputeContextResourceTypeItems();
|
|
1656
|
+
}));
|
|
1657
|
+
}
|
|
1658
|
+
ngAfterViewInit() {
|
|
1659
|
+
// The child comboboxes flush their preselected values in a setTimeout scheduled during their
|
|
1660
|
+
// own view initialization (child hooks run first). This setTimeout therefore runs after them,
|
|
1661
|
+
// at which point the form is prefilled and can be revealed.
|
|
1662
|
+
this._revealTimeoutId = setTimeout(() => {
|
|
1663
|
+
this.$loading.set(false);
|
|
1664
|
+
this.changeDetectorRef.detectChanges();
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
ngOnDestroy() {
|
|
1668
|
+
this._subscriptions.unsubscribe();
|
|
1669
|
+
this.showRemoveModal$.complete();
|
|
1670
|
+
if (this._revealTimeoutId !== undefined)
|
|
1671
|
+
clearTimeout(this._revealTimeoutId);
|
|
1672
|
+
}
|
|
1673
|
+
// Applies a context-switcher choice to the form. 'none' clears context entirely; 'noContext'
|
|
1674
|
+
// scopes the permission to the "no context" marker; 'specific' scopes it to a real context
|
|
1675
|
+
// resource (keeping the current one, or defaulting to the first available target).
|
|
1676
|
+
onContextModeChange(mode) {
|
|
1677
|
+
if (this.disabled || mode === this.contextMode)
|
|
1678
|
+
return;
|
|
1679
|
+
const hasContext = this.group.get('hasContext');
|
|
1680
|
+
const contextResourceType = this.group.get('contextResourceType');
|
|
1681
|
+
if (mode === 'none') {
|
|
1682
|
+
hasContext.setValue(false);
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
hasContext.setValue(true);
|
|
1686
|
+
if (mode === 'noContext') {
|
|
1687
|
+
contextResourceType.setValue(NO_CONTEXT_RESOURCE_TYPE);
|
|
1688
|
+
}
|
|
1689
|
+
else if (!this.hasContextResource) {
|
|
1690
|
+
const firstTarget = this.formEditorService.containerTargetItems(this.resourceTypeValue)[0]
|
|
1691
|
+
?.id;
|
|
1692
|
+
contextResourceType.setValue(firstTarget ?? NO_CONTEXT_RESOURCE_TYPE);
|
|
721
1693
|
}
|
|
722
|
-
return value;
|
|
723
1694
|
}
|
|
724
|
-
|
|
725
|
-
|
|
1695
|
+
onActionToggle(action, checked) {
|
|
1696
|
+
const actions = new Set(this.selectedActions);
|
|
1697
|
+
if (checked) {
|
|
1698
|
+
actions.add(action);
|
|
1699
|
+
}
|
|
1700
|
+
else {
|
|
1701
|
+
actions.delete(action);
|
|
1702
|
+
}
|
|
1703
|
+
this.actionsControl.setValue([...actions]);
|
|
1704
|
+
this.actionsControl.markAsTouched();
|
|
1705
|
+
}
|
|
1706
|
+
onSectionToggle(section, event) {
|
|
1707
|
+
// Only one section is open at a time: opening one collapses the others via the [expanded]
|
|
1708
|
+
// bindings; collapsing the open one leaves all sections closed.
|
|
1709
|
+
this.$openSection.set(event?.expanded ? section : null);
|
|
1710
|
+
}
|
|
1711
|
+
openRemoveModal() {
|
|
1712
|
+
this.showRemoveModal$.next(true);
|
|
1713
|
+
}
|
|
1714
|
+
onRemove() {
|
|
1715
|
+
this.removeEvent.emit();
|
|
1716
|
+
}
|
|
1717
|
+
recomputeActionItems() {
|
|
1718
|
+
this.actionItems = this.formEditorService.actionItems(this.resourceTypeValue, this.actionsControl.value ?? []);
|
|
1719
|
+
}
|
|
1720
|
+
// The context choices depend on the selected resource type, so recompute on change. The "specific
|
|
1721
|
+
// context" dropdown lists only real target resources ("no context" is its own switcher mode); if
|
|
1722
|
+
// the selected resource is no longer a valid target, fall back to the "no context" marker.
|
|
1723
|
+
recomputeContextResourceTypeItems() {
|
|
1724
|
+
const validTargets = this.formEditorService.containerTargetItems(this.resourceTypeValue);
|
|
1725
|
+
this.hasContextTargets = validTargets.length > 0;
|
|
1726
|
+
const current = this.contextResourceTypeValue;
|
|
1727
|
+
if (current &&
|
|
1728
|
+
current !== NO_CONTEXT_RESOURCE_TYPE &&
|
|
1729
|
+
!validTargets.some(target => target.id === current)) {
|
|
1730
|
+
this.group.get('contextResourceType').setValue(NO_CONTEXT_RESOURCE_TYPE);
|
|
1731
|
+
}
|
|
1732
|
+
this.contextResourceTypeItems = this.formEditorService.containerTargetItems(this.resourceTypeValue, this.hasContextResource ? this.contextResourceTypeValue : null);
|
|
726
1733
|
}
|
|
727
|
-
|
|
728
|
-
}
|
|
729
|
-
function humanizeFieldPath(field) {
|
|
730
|
-
return field
|
|
731
|
-
.split('.')
|
|
732
|
-
.map(part => part.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase())
|
|
733
|
-
.join(' ');
|
|
1734
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: PermissionFormComponent, deps: [{ token: AccessControlFormEditorService }, { token: i2$1.IconService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1735
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: PermissionFormComponent, isStandalone: false, selector: "valtimo-permission-form", inputs: { group: "group", index: "index", disabled: "disabled", roleKey: "roleKey" }, outputs: { removeEvent: "removeEvent" }, ngImport: i0, template: "<!--\n ~ Copyright 2015-2026 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<div\n class=\"permission-form\"\n [attr.data-test-id]=\"testIds.permissionCard\"\n [cdsLayer]=\"0\"\n [formGroup]=\"group\"\n>\n @if ($loading()) {\n <div class=\"permission-form__loading\">\n <cds-loading [isActive]=\"true\" [overlay]=\"false\" size=\"sm\"></cds-loading>\n </div>\n }\n\n <div class=\"permission-form__body\" [class.permission-form__body--hidden]=\"$loading()\">\n <!-- Header block: the permission preview followed by an editor-level note, separated from the\n editable sections below by a subtle divider. -->\n <div class=\"permission-form__header\">\n <!-- A larger preview of the permission (the same information shown in the sidebar list), so\n the rule being edited is recognisable at the top of the panel. The full resource type is\n shown here (the sidebar only shows the short name). -->\n <div class=\"permission-form__preview\">\n <div class=\"permission-form__preview-heading\">\n <h4 class=\"permission-form__preview-title\">\n @if (resourceTypeValue) {\n {{ resourceShortName }}\n } @else {\n {{ 'accessControl.editor.newPermission' | translate }}\n }\n </h4>\n\n @if (resourceTypeValue) {\n <span class=\"permission-form__preview-subtitle\">{{ resourceTypeValue }}</span>\n }\n </div>\n\n @if (conditionsArray.length || hasContext) {\n <div class=\"permission-form__preview-row\">\n <span class=\"permission-form__preview-label\">{{\n 'accessControl.editor.preview.includes' | translate\n }}</span>\n\n @if (conditionsArray.length) {\n <cds-tag size=\"sm\" type=\"high-contrast\">{{\n 'accessControl.editor.hasConditions' | translate\n }}</cds-tag>\n }\n @if (hasContext) {\n <cds-tag size=\"sm\" type=\"high-contrast\">{{\n 'accessControl.editor.hasContext' | translate\n }}</cds-tag>\n }\n </div>\n }\n\n @if (selectedActions.length) {\n <div class=\"permission-form__preview-row\">\n <span class=\"permission-form__preview-label\">{{\n 'accessControl.editor.preview.actions' | translate\n }}</span>\n\n @for (action of selectedActions; track action) {\n <cds-tag size=\"sm\" [type]=\"action | actionTagType\">{{\n action | actionLabel\n }}</cds-tag>\n }\n </div>\n }\n </div>\n\n <!-- Editor-level note: applies to the role selected on this page, and only system-supported\n options can be selected. -->\n <ng-container\n *ngTemplateOutlet=\"\n infoNote;\n context: {\n $implicit: resourceShortName\n ? ('accessControl.editor.permissionsHint'\n | translate: {resource: resourceShortName, role: roleKey})\n : ('accessControl.editor.permissionsHintNew' | translate: {role: roleKey}),\n }\n \"\n ></ng-container>\n </div>\n\n <cds-accordion class=\"permission-form__accordion\">\n <cds-accordion-item\n [expanded]=\"$openSection() === 'resourceActions'\"\n [title]=\"'accessControl.editor.resourceAndActions' | translate\"\n (selected)=\"onSectionToggle('resourceActions', $event)\"\n >\n <div class=\"permission-form__section\">\n <ng-container\n *ngTemplateOutlet=\"\n infoNote;\n context: {$implicit: 'accessControl.editor.resourceActionsHint' | translate}\n \"\n ></ng-container>\n\n <div class=\"permission-form__row\">\n <v-select\n class=\"permission-form__resource-type\"\n formControlName=\"resourceType\"\n [cdsLayer]=\"1\"\n [dataTestId]=\"testIds.resourceTypeSelect\"\n [defaultSelectionId]=\"resourceTypeValue\"\n [dropUp]=\"false\"\n [invalid]=\"group.get('resourceType')!.invalid && group.get('resourceType')!.touched\"\n [items]=\"resourceTypeItems\"\n [placeholder]=\"'accessControl.editor.resourceTypePlaceholder' | translate\"\n [required]=\"true\"\n [tooltip]=\"'accessControl.editor.tooltips.resourceType' | translate\"\n titleTranslationKey=\"accessControl.editor.resourceType\"\n ></v-select>\n\n <div class=\"permission-form__actions-field\">\n <label class=\"cds--label label-with-tooltip\">\n {{ 'accessControl.editor.actions' | translate }} ({{\n 'interface.required' | translate\n }})\n <v-tooltip-icon\n [tooltip]=\"'accessControl.editor.tooltips.actions' | translate\"\n ></v-tooltip-icon>\n </label>\n\n @if (actionItems.length) {\n <div\n class=\"permission-form__actions-grid\"\n [attr.data-test-id]=\"testIds.actionsSelect\"\n [class.permission-form__actions-grid--invalid]=\"\n actionsControl.invalid && actionsControl.touched\n \"\n >\n @for (item of actionItems; track item.id) {\n <div class=\"permission-form__action\">\n <cds-checkbox\n [checked]=\"selectedActions.includes(item.id)\"\n [disabled]=\"disabled\"\n (checkedChange)=\"onActionToggle(item.id, $event)\"\n >\n {{ item.id | actionLabel }}\n </cds-checkbox>\n\n @if (item.id | actionDescription; as description) {\n <v-tooltip-icon [tooltip]=\"description\"></v-tooltip-icon>\n }\n </div>\n }\n </div>\n } @else {\n <p class=\"permission-form__hint\">\n {{ 'accessControl.editor.noActions' | translate }}\n </p>\n }\n </div>\n </div>\n </div>\n </cds-accordion-item>\n\n <cds-accordion-item\n [class.permission-form__section-configured]=\"conditionsArray.length > 0\"\n [expanded]=\"$openSection() === 'conditions'\"\n [title]=\"'accessControl.editor.conditions' | translate\"\n (selected)=\"onSectionToggle('conditions', $event)\"\n >\n <div class=\"permission-form__section\">\n <ng-container\n *ngTemplateOutlet=\"\n infoNote;\n context: {$implicit: 'accessControl.editor.conditionsHint' | translate}\n \"\n ></ng-container>\n\n <valtimo-condition-tree\n [conditions]=\"conditionsArray\"\n [disabled]=\"disabled\"\n [resourceType]=\"resourceTypeValue\"\n ></valtimo-condition-tree>\n </div>\n </cds-accordion-item>\n\n <cds-accordion-item\n [class.permission-form__section-configured]=\"hasContext\"\n [expanded]=\"$openSection() === 'context'\"\n [title]=\"'accessControl.editor.contextSection' | translate\"\n (selected)=\"onSectionToggle('context', $event)\"\n >\n <div class=\"permission-form__section\">\n <ng-container\n *ngTemplateOutlet=\"\n infoNote;\n context: {$implicit: 'accessControl.editor.contextHint' | translate}\n \"\n ></ng-container>\n\n <cds-content-switcher\n class=\"permission-form__context-switcher\"\n [attr.data-test-id]=\"testIds.contextToggle\"\n [class.permission-form__context-switcher--disabled]=\"disabled\"\n size=\"sm\"\n >\n <button\n cdsContentOption\n type=\"button\"\n [active]=\"contextMode === 'none'\"\n (selected)=\"onContextModeChange('none')\"\n >\n {{ 'accessControl.editor.contextModeNone' | translate }}\n </button>\n <button\n cdsContentOption\n type=\"button\"\n [active]=\"contextMode === 'noContext'\"\n (selected)=\"onContextModeChange('noContext')\"\n >\n {{ 'accessControl.editor.contextModeNoContext' | translate }}\n </button>\n @if (hasContextTargets) {\n <button\n cdsContentOption\n type=\"button\"\n [active]=\"contextMode === 'specific'\"\n (selected)=\"onContextModeChange('specific')\"\n >\n {{ 'accessControl.editor.contextModeSpecific' | translate }}\n </button>\n }\n </cds-content-switcher>\n\n @switch (contextMode) {\n @case ('noContext') {\n <cds-inline-notification\n class=\"permission-form__notification\"\n [notificationObj]=\"{\n type: 'info',\n message: 'accessControl.editor.noContextHint' | translate,\n lowContrast: true,\n showClose: false,\n }\"\n ></cds-inline-notification>\n }\n @case ('specific') {\n <v-select\n class=\"permission-form__resource-type\"\n formControlName=\"contextResourceType\"\n [cdsLayer]=\"1\"\n [clearable]=\"false\"\n [dataTestId]=\"testIds.contextResourceTypeSelect\"\n [defaultSelectionId]=\"contextResourceTypeValue || ''\"\n [dropUp]=\"false\"\n [items]=\"contextResourceTypeItems\"\n [placeholder]=\"'accessControl.editor.contextResourceTypePlaceholder' | translate\"\n [tooltip]=\"'accessControl.editor.tooltips.contextResourceType' | translate\"\n titleTranslationKey=\"accessControl.editor.contextResourceType\"\n ></v-select>\n\n <valtimo-condition-tree\n [conditions]=\"contextConditionsArray\"\n [disabled]=\"disabled\"\n [resourceType]=\"contextResourceTypeValue\"\n ></valtimo-condition-tree>\n }\n }\n </div>\n </cds-accordion-item>\n </cds-accordion>\n\n <div class=\"permission-form__footer\">\n <button\n cdsButton=\"danger--ghost\"\n size=\"sm\"\n [attr.data-test-id]=\"testIds.removePermissionButton\"\n [disabled]=\"disabled\"\n (click)=\"openRemoveModal()\"\n >\n {{ 'accessControl.editor.removePermission' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"trash-can\" size=\"16\"></svg>\n </button>\n </div>\n </div>\n\n <valtimo-confirmation-modal\n confirmButtonTextTranslationKey=\"accessControl.editor.removePermission\"\n confirmButtonType=\"danger\"\n contentTranslationKey=\"accessControl.editor.removePermissionModal.content\"\n titleTranslationKey=\"accessControl.editor.removePermissionModal.title\"\n [showModalSubject$]=\"showRemoveModal$\"\n (confirmEvent)=\"onRemove()\"\n ></valtimo-confirmation-modal>\n\n <!-- Lightweight inline hint: a blue information icon with italic secondary text. The message is\n passed in via context so the icon and styling are defined in a single place. -->\n <ng-template #infoNote let-message>\n <p class=\"permission-form__info-note\">\n <svg class=\"permission-form__info-icon\" cdsIcon=\"information\" size=\"16\"></svg>\n\n <span>{{ message }}</span>\n </p>\n </ng-template>\n</div>\n", styles: [".permission-form{display:flex;flex-direction:column}.permission-form v-select{display:block;width:100%}.permission-form__loading{align-items:center;display:flex;justify-content:center;padding:1.5rem 0}.permission-form__body{display:flex;flex-direction:column;gap:1rem}.permission-form__body--hidden{display:none}.permission-form__accordion{width:100%}.permission-form__header{border-bottom:1px solid var(--cds-border-subtle, #e0e0e0);display:flex;flex-direction:column;gap:1rem;padding-bottom:1.5rem}.permission-form__preview{display:flex;flex-direction:column;gap:.5rem}.permission-form__preview-heading{display:flex;flex-direction:column;gap:.125rem}.permission-form__preview-title{font-size:1.25rem;font-weight:400;line-height:1.4;margin:0;overflow-wrap:anywhere}.permission-form__preview-subtitle{color:var(--cds-text-secondary, #525252);font-size:.75rem;font-weight:400;line-height:1.34;overflow-wrap:anywhere}.permission-form__preview-row{align-items:center;display:flex;flex-wrap:wrap;gap:.25rem}.permission-form__preview-row ::ng-deep .cds--tag{margin:0;max-width:none}.permission-form__preview-label{color:var(--cds-text-secondary, #525252);font-size:.875rem;font-weight:600;padding-right:.5rem}.permission-form__row{display:flex;flex-direction:column;gap:1rem}.permission-form__context-switcher{display:block;width:100%}.permission-form__context-switcher--disabled{opacity:.5;pointer-events:none}.permission-form__actions-field{display:flex;flex-direction:column;gap:.5rem}.permission-form__actions-grid{display:grid;gap:.25rem 1rem;grid-template-columns:repeat(auto-fill,minmax(10rem,1fr))}.permission-form__actions-grid--invalid{border-left:2px solid var(--cds-support-error, #da1e28);margin-left:-.75rem;padding-left:calc(.75rem - 2px)}.permission-form__action{align-items:center;display:flex;gap:.25rem}.permission-form .label-with-tooltip{align-items:center;display:inline-flex;gap:.25rem}.permission-form__section{display:flex;flex-direction:column;gap:.75rem}.permission-form__hint{color:var(--cds-text-secondary, #525252);font-size:.875rem;margin:0}.permission-form__info-note{align-items:flex-start;color:var(--cds-text-secondary, #525252);display:flex;font-size:.875rem;gap:.375rem;line-height:1.34;margin-bottom:8px}.permission-form__info-icon{color:var(--cds-support-info, #0043ce);flex-shrink:0;margin-top:.0625rem}.permission-form__footer{display:flex;justify-content:flex-end;padding-top:.25rem}:host ::ng-deep .permission-form__accordion .cds--accordion__item:first-of-type{border-top:0}:host ::ng-deep .permission-form__accordion .cds--accordion__content{padding:1rem 1rem 1.5rem}:host ::ng-deep .permission-form__accordion .permission-form__section-configured .cds--accordion__title:after{background:var(--cds-support-info, #0043ce);border-radius:50%;content:\"\";display:inline-block;height:.5rem;margin-left:.5rem;vertical-align:middle;width:.5rem}:host ::ng-deep .permission-form__notification.cds--inline-notification{inline-size:100%;margin-block-end:0;max-inline-size:100%;min-inline-size:0}\n/*!\n * Copyright 2015-2026 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"], dependencies: [{ kind: "directive", type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2$1.Button, selector: "[cdsButton], [ibmButton]", inputs: ["ibmButton", "cdsButton", "size", "skeleton", "iconOnly", "isExpressive"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i2$1.IconDirective, selector: "[cdsIcon], [ibmIcon]", inputs: ["ibmIcon", "cdsIcon", "size", "title", "ariaLabel", "ariaLabelledBy", "ariaHidden", "isFocusable"] }, { kind: "component", type: i3$1.ConfirmationModalComponent, selector: "valtimo-confirmation-modal", inputs: ["titleTranslationKey", "title", "content", "contentTranslationKey", "confirmButtonText", "confirmButtonTextTranslationKey", "confirmButtonType", "showOptionalButton", "optionalButtonText", "optionalButtonTextTranslationKey", "optionalButtonType", "cancelButtonText", "cancelButtonTextTranslationKey", "cancelButtonType", "showModalSubject$", "outputOnConfirm", "outputOnOptional", "spacerAfterCancelButton"], outputs: ["confirmEvent", "optionalEvent", "cancelEvent"] }, { kind: "component", type: i2$1.Loading, selector: "cds-loading, ibm-loading", inputs: ["title", "isActive", "size", "overlay"] }, { kind: "component", type: i2$1.Notification, selector: "cds-notification, cds-inline-notification, ibm-notification, ibm-inline-notification", inputs: ["notificationObj"] }, { kind: "component", type: i3$1.SelectComponent, selector: "v-select", inputs: ["items", "defaultSelection", "defaultSelectionId", "defaultSelectionIds", "disabled", "dropUp", "invalid", "multiple", "margin", "widthInPx", "notFoundText", "clearAllText", "clearText", "clearable", "name", "title", "titleTranslationKey", "clearSelectionSubject$", "tooltip", "required", "loading", "loadingText", "placeholder", "smallMargin", "carbonTheme", "appendInline", "warn", "warnText", "dataTestId"], outputs: ["selectedChange"] }, { kind: "component", type: i3$1.TooltipIconComponent, selector: "v-tooltip-icon", inputs: ["tooltip", "disabled"] }, { kind: "component", type: i2$1.ContentSwitcher, selector: "cds-content-switcher, ibm-content-switcher", inputs: ["ariaLabel", "size"], outputs: ["selected"] }, { kind: "directive", type: i2$1.ContentSwitcherOption, selector: "[cdsContentOption], [ibmContentOption]", inputs: ["active", "name"], outputs: ["selected", "onClick", "onFocus"] }, { kind: "component", type: i2$1.Accordion, selector: "cds-accordion, ibm-accordion", inputs: ["align", "size", "skeleton"] }, { kind: "component", type: i2$1.AccordionItem, selector: "cds-accordion-item, ibm-accordion-item", inputs: ["title", "context", "id", "skeleton", "expanded", "disabled"], outputs: ["selected"] }, { kind: "component", type: i2$1.Checkbox, selector: "cds-checkbox, ibm-checkbox", inputs: ["disabled", "skeleton", "hideLabel", "name", "id", "required", "value", "ariaLabel", "ariaLabelledby", "indeterminate", "checked"], outputs: ["click", "checkedChange", "indeterminateChange"] }, { kind: "directive", type: i2$1.LayerDirective, selector: "[cdsLayer], [ibmLayer]", inputs: ["ibmLayer", "cdsLayer"], exportAs: ["layer"] }, { kind: "component", type: i2$1.Tag, selector: "cds-tag, ibm-tag", inputs: ["type", "size", "class", "skeleton"] }, { kind: "component", type: ConditionTreeComponent, selector: "valtimo-condition-tree", inputs: ["conditions", "resourceType", "disabled", "depth"] }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }, { kind: "pipe", type: ActionLabelPipe, name: "actionLabel" }, { kind: "pipe", type: ActionDescriptionPipe, name: "actionDescription" }, { kind: "pipe", type: ActionTagTypePipe, name: "actionTagType" }] }); }
|
|
734
1736
|
}
|
|
1737
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: PermissionFormComponent, decorators: [{
|
|
1738
|
+
type: Component,
|
|
1739
|
+
args: [{ standalone: false, selector: 'valtimo-permission-form', template: "<!--\n ~ Copyright 2015-2026 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<div\n class=\"permission-form\"\n [attr.data-test-id]=\"testIds.permissionCard\"\n [cdsLayer]=\"0\"\n [formGroup]=\"group\"\n>\n @if ($loading()) {\n <div class=\"permission-form__loading\">\n <cds-loading [isActive]=\"true\" [overlay]=\"false\" size=\"sm\"></cds-loading>\n </div>\n }\n\n <div class=\"permission-form__body\" [class.permission-form__body--hidden]=\"$loading()\">\n <!-- Header block: the permission preview followed by an editor-level note, separated from the\n editable sections below by a subtle divider. -->\n <div class=\"permission-form__header\">\n <!-- A larger preview of the permission (the same information shown in the sidebar list), so\n the rule being edited is recognisable at the top of the panel. The full resource type is\n shown here (the sidebar only shows the short name). -->\n <div class=\"permission-form__preview\">\n <div class=\"permission-form__preview-heading\">\n <h4 class=\"permission-form__preview-title\">\n @if (resourceTypeValue) {\n {{ resourceShortName }}\n } @else {\n {{ 'accessControl.editor.newPermission' | translate }}\n }\n </h4>\n\n @if (resourceTypeValue) {\n <span class=\"permission-form__preview-subtitle\">{{ resourceTypeValue }}</span>\n }\n </div>\n\n @if (conditionsArray.length || hasContext) {\n <div class=\"permission-form__preview-row\">\n <span class=\"permission-form__preview-label\">{{\n 'accessControl.editor.preview.includes' | translate\n }}</span>\n\n @if (conditionsArray.length) {\n <cds-tag size=\"sm\" type=\"high-contrast\">{{\n 'accessControl.editor.hasConditions' | translate\n }}</cds-tag>\n }\n @if (hasContext) {\n <cds-tag size=\"sm\" type=\"high-contrast\">{{\n 'accessControl.editor.hasContext' | translate\n }}</cds-tag>\n }\n </div>\n }\n\n @if (selectedActions.length) {\n <div class=\"permission-form__preview-row\">\n <span class=\"permission-form__preview-label\">{{\n 'accessControl.editor.preview.actions' | translate\n }}</span>\n\n @for (action of selectedActions; track action) {\n <cds-tag size=\"sm\" [type]=\"action | actionTagType\">{{\n action | actionLabel\n }}</cds-tag>\n }\n </div>\n }\n </div>\n\n <!-- Editor-level note: applies to the role selected on this page, and only system-supported\n options can be selected. -->\n <ng-container\n *ngTemplateOutlet=\"\n infoNote;\n context: {\n $implicit: resourceShortName\n ? ('accessControl.editor.permissionsHint'\n | translate: {resource: resourceShortName, role: roleKey})\n : ('accessControl.editor.permissionsHintNew' | translate: {role: roleKey}),\n }\n \"\n ></ng-container>\n </div>\n\n <cds-accordion class=\"permission-form__accordion\">\n <cds-accordion-item\n [expanded]=\"$openSection() === 'resourceActions'\"\n [title]=\"'accessControl.editor.resourceAndActions' | translate\"\n (selected)=\"onSectionToggle('resourceActions', $event)\"\n >\n <div class=\"permission-form__section\">\n <ng-container\n *ngTemplateOutlet=\"\n infoNote;\n context: {$implicit: 'accessControl.editor.resourceActionsHint' | translate}\n \"\n ></ng-container>\n\n <div class=\"permission-form__row\">\n <v-select\n class=\"permission-form__resource-type\"\n formControlName=\"resourceType\"\n [cdsLayer]=\"1\"\n [dataTestId]=\"testIds.resourceTypeSelect\"\n [defaultSelectionId]=\"resourceTypeValue\"\n [dropUp]=\"false\"\n [invalid]=\"group.get('resourceType')!.invalid && group.get('resourceType')!.touched\"\n [items]=\"resourceTypeItems\"\n [placeholder]=\"'accessControl.editor.resourceTypePlaceholder' | translate\"\n [required]=\"true\"\n [tooltip]=\"'accessControl.editor.tooltips.resourceType' | translate\"\n titleTranslationKey=\"accessControl.editor.resourceType\"\n ></v-select>\n\n <div class=\"permission-form__actions-field\">\n <label class=\"cds--label label-with-tooltip\">\n {{ 'accessControl.editor.actions' | translate }} ({{\n 'interface.required' | translate\n }})\n <v-tooltip-icon\n [tooltip]=\"'accessControl.editor.tooltips.actions' | translate\"\n ></v-tooltip-icon>\n </label>\n\n @if (actionItems.length) {\n <div\n class=\"permission-form__actions-grid\"\n [attr.data-test-id]=\"testIds.actionsSelect\"\n [class.permission-form__actions-grid--invalid]=\"\n actionsControl.invalid && actionsControl.touched\n \"\n >\n @for (item of actionItems; track item.id) {\n <div class=\"permission-form__action\">\n <cds-checkbox\n [checked]=\"selectedActions.includes(item.id)\"\n [disabled]=\"disabled\"\n (checkedChange)=\"onActionToggle(item.id, $event)\"\n >\n {{ item.id | actionLabel }}\n </cds-checkbox>\n\n @if (item.id | actionDescription; as description) {\n <v-tooltip-icon [tooltip]=\"description\"></v-tooltip-icon>\n }\n </div>\n }\n </div>\n } @else {\n <p class=\"permission-form__hint\">\n {{ 'accessControl.editor.noActions' | translate }}\n </p>\n }\n </div>\n </div>\n </div>\n </cds-accordion-item>\n\n <cds-accordion-item\n [class.permission-form__section-configured]=\"conditionsArray.length > 0\"\n [expanded]=\"$openSection() === 'conditions'\"\n [title]=\"'accessControl.editor.conditions' | translate\"\n (selected)=\"onSectionToggle('conditions', $event)\"\n >\n <div class=\"permission-form__section\">\n <ng-container\n *ngTemplateOutlet=\"\n infoNote;\n context: {$implicit: 'accessControl.editor.conditionsHint' | translate}\n \"\n ></ng-container>\n\n <valtimo-condition-tree\n [conditions]=\"conditionsArray\"\n [disabled]=\"disabled\"\n [resourceType]=\"resourceTypeValue\"\n ></valtimo-condition-tree>\n </div>\n </cds-accordion-item>\n\n <cds-accordion-item\n [class.permission-form__section-configured]=\"hasContext\"\n [expanded]=\"$openSection() === 'context'\"\n [title]=\"'accessControl.editor.contextSection' | translate\"\n (selected)=\"onSectionToggle('context', $event)\"\n >\n <div class=\"permission-form__section\">\n <ng-container\n *ngTemplateOutlet=\"\n infoNote;\n context: {$implicit: 'accessControl.editor.contextHint' | translate}\n \"\n ></ng-container>\n\n <cds-content-switcher\n class=\"permission-form__context-switcher\"\n [attr.data-test-id]=\"testIds.contextToggle\"\n [class.permission-form__context-switcher--disabled]=\"disabled\"\n size=\"sm\"\n >\n <button\n cdsContentOption\n type=\"button\"\n [active]=\"contextMode === 'none'\"\n (selected)=\"onContextModeChange('none')\"\n >\n {{ 'accessControl.editor.contextModeNone' | translate }}\n </button>\n <button\n cdsContentOption\n type=\"button\"\n [active]=\"contextMode === 'noContext'\"\n (selected)=\"onContextModeChange('noContext')\"\n >\n {{ 'accessControl.editor.contextModeNoContext' | translate }}\n </button>\n @if (hasContextTargets) {\n <button\n cdsContentOption\n type=\"button\"\n [active]=\"contextMode === 'specific'\"\n (selected)=\"onContextModeChange('specific')\"\n >\n {{ 'accessControl.editor.contextModeSpecific' | translate }}\n </button>\n }\n </cds-content-switcher>\n\n @switch (contextMode) {\n @case ('noContext') {\n <cds-inline-notification\n class=\"permission-form__notification\"\n [notificationObj]=\"{\n type: 'info',\n message: 'accessControl.editor.noContextHint' | translate,\n lowContrast: true,\n showClose: false,\n }\"\n ></cds-inline-notification>\n }\n @case ('specific') {\n <v-select\n class=\"permission-form__resource-type\"\n formControlName=\"contextResourceType\"\n [cdsLayer]=\"1\"\n [clearable]=\"false\"\n [dataTestId]=\"testIds.contextResourceTypeSelect\"\n [defaultSelectionId]=\"contextResourceTypeValue || ''\"\n [dropUp]=\"false\"\n [items]=\"contextResourceTypeItems\"\n [placeholder]=\"'accessControl.editor.contextResourceTypePlaceholder' | translate\"\n [tooltip]=\"'accessControl.editor.tooltips.contextResourceType' | translate\"\n titleTranslationKey=\"accessControl.editor.contextResourceType\"\n ></v-select>\n\n <valtimo-condition-tree\n [conditions]=\"contextConditionsArray\"\n [disabled]=\"disabled\"\n [resourceType]=\"contextResourceTypeValue\"\n ></valtimo-condition-tree>\n }\n }\n </div>\n </cds-accordion-item>\n </cds-accordion>\n\n <div class=\"permission-form__footer\">\n <button\n cdsButton=\"danger--ghost\"\n size=\"sm\"\n [attr.data-test-id]=\"testIds.removePermissionButton\"\n [disabled]=\"disabled\"\n (click)=\"openRemoveModal()\"\n >\n {{ 'accessControl.editor.removePermission' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"trash-can\" size=\"16\"></svg>\n </button>\n </div>\n </div>\n\n <valtimo-confirmation-modal\n confirmButtonTextTranslationKey=\"accessControl.editor.removePermission\"\n confirmButtonType=\"danger\"\n contentTranslationKey=\"accessControl.editor.removePermissionModal.content\"\n titleTranslationKey=\"accessControl.editor.removePermissionModal.title\"\n [showModalSubject$]=\"showRemoveModal$\"\n (confirmEvent)=\"onRemove()\"\n ></valtimo-confirmation-modal>\n\n <!-- Lightweight inline hint: a blue information icon with italic secondary text. The message is\n passed in via context so the icon and styling are defined in a single place. -->\n <ng-template #infoNote let-message>\n <p class=\"permission-form__info-note\">\n <svg class=\"permission-form__info-icon\" cdsIcon=\"information\" size=\"16\"></svg>\n\n <span>{{ message }}</span>\n </p>\n </ng-template>\n</div>\n", styles: [".permission-form{display:flex;flex-direction:column}.permission-form v-select{display:block;width:100%}.permission-form__loading{align-items:center;display:flex;justify-content:center;padding:1.5rem 0}.permission-form__body{display:flex;flex-direction:column;gap:1rem}.permission-form__body--hidden{display:none}.permission-form__accordion{width:100%}.permission-form__header{border-bottom:1px solid var(--cds-border-subtle, #e0e0e0);display:flex;flex-direction:column;gap:1rem;padding-bottom:1.5rem}.permission-form__preview{display:flex;flex-direction:column;gap:.5rem}.permission-form__preview-heading{display:flex;flex-direction:column;gap:.125rem}.permission-form__preview-title{font-size:1.25rem;font-weight:400;line-height:1.4;margin:0;overflow-wrap:anywhere}.permission-form__preview-subtitle{color:var(--cds-text-secondary, #525252);font-size:.75rem;font-weight:400;line-height:1.34;overflow-wrap:anywhere}.permission-form__preview-row{align-items:center;display:flex;flex-wrap:wrap;gap:.25rem}.permission-form__preview-row ::ng-deep .cds--tag{margin:0;max-width:none}.permission-form__preview-label{color:var(--cds-text-secondary, #525252);font-size:.875rem;font-weight:600;padding-right:.5rem}.permission-form__row{display:flex;flex-direction:column;gap:1rem}.permission-form__context-switcher{display:block;width:100%}.permission-form__context-switcher--disabled{opacity:.5;pointer-events:none}.permission-form__actions-field{display:flex;flex-direction:column;gap:.5rem}.permission-form__actions-grid{display:grid;gap:.25rem 1rem;grid-template-columns:repeat(auto-fill,minmax(10rem,1fr))}.permission-form__actions-grid--invalid{border-left:2px solid var(--cds-support-error, #da1e28);margin-left:-.75rem;padding-left:calc(.75rem - 2px)}.permission-form__action{align-items:center;display:flex;gap:.25rem}.permission-form .label-with-tooltip{align-items:center;display:inline-flex;gap:.25rem}.permission-form__section{display:flex;flex-direction:column;gap:.75rem}.permission-form__hint{color:var(--cds-text-secondary, #525252);font-size:.875rem;margin:0}.permission-form__info-note{align-items:flex-start;color:var(--cds-text-secondary, #525252);display:flex;font-size:.875rem;gap:.375rem;line-height:1.34;margin-bottom:8px}.permission-form__info-icon{color:var(--cds-support-info, #0043ce);flex-shrink:0;margin-top:.0625rem}.permission-form__footer{display:flex;justify-content:flex-end;padding-top:.25rem}:host ::ng-deep .permission-form__accordion .cds--accordion__item:first-of-type{border-top:0}:host ::ng-deep .permission-form__accordion .cds--accordion__content{padding:1rem 1rem 1.5rem}:host ::ng-deep .permission-form__accordion .permission-form__section-configured .cds--accordion__title:after{background:var(--cds-support-info, #0043ce);border-radius:50%;content:\"\";display:inline-block;height:.5rem;margin-left:.5rem;vertical-align:middle;width:.5rem}:host ::ng-deep .permission-form__notification.cds--inline-notification{inline-size:100%;margin-block-end:0;max-inline-size:100%;min-inline-size:0}\n/*!\n * Copyright 2015-2026 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"] }]
|
|
1740
|
+
}], ctorParameters: () => [{ type: AccessControlFormEditorService }, { type: i2$1.IconService }, { type: i0.ChangeDetectorRef }], propDecorators: { group: [{
|
|
1741
|
+
type: Input
|
|
1742
|
+
}], index: [{
|
|
1743
|
+
type: Input
|
|
1744
|
+
}], disabled: [{
|
|
1745
|
+
type: Input
|
|
1746
|
+
}], roleKey: [{
|
|
1747
|
+
type: Input
|
|
1748
|
+
}], removeEvent: [{
|
|
1749
|
+
type: Output
|
|
1750
|
+
}] } });
|
|
735
1751
|
|
|
736
1752
|
/*
|
|
737
1753
|
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
@@ -748,6 +1764,250 @@ function humanizeFieldPath(field) {
|
|
|
748
1764
|
* See the License for the specific language governing permissions and
|
|
749
1765
|
* limitations under the License.
|
|
750
1766
|
*/
|
|
1767
|
+
class AccessControlFormEditorTabComponent {
|
|
1768
|
+
constructor(formEditorService, metadataService, iconService, changeDetectorRef) {
|
|
1769
|
+
this.formEditorService = formEditorService;
|
|
1770
|
+
this.metadataService = metadataService;
|
|
1771
|
+
this.iconService = iconService;
|
|
1772
|
+
this.changeDetectorRef = changeDetectorRef;
|
|
1773
|
+
this.model = null;
|
|
1774
|
+
this.disabled = false;
|
|
1775
|
+
// The role being edited on this page; surfaced in the permission-form's editor note.
|
|
1776
|
+
this.roleKey = null;
|
|
1777
|
+
this.validEvent = new EventEmitter();
|
|
1778
|
+
this.valueChangeEvent = new EventEmitter();
|
|
1779
|
+
this.permissionsArray = null;
|
|
1780
|
+
this.ready = false;
|
|
1781
|
+
// Index of the permission shown in the detail panel. Null when there are no permissions.
|
|
1782
|
+
this.$selectedIndex = signal(null);
|
|
1783
|
+
this.testIds = ACCESS_CONTROL_EDITOR_TEST_IDS;
|
|
1784
|
+
// Armed by the parent right before a save (see prepareSelectionReopen): a one-shot instruction to
|
|
1785
|
+
// re-open a specific permission on the next rebuild. Null otherwise, so ordinary (re)builds — the
|
|
1786
|
+
// initial load and tab switches — simply select the first permission.
|
|
1787
|
+
this._pendingReselect = null;
|
|
1788
|
+
this.iconService.registerAll([Add16]);
|
|
1789
|
+
}
|
|
1790
|
+
ngOnChanges(changes) {
|
|
1791
|
+
if (changes['model']) {
|
|
1792
|
+
this.rebuild();
|
|
1793
|
+
}
|
|
1794
|
+
else if (changes['disabled']) {
|
|
1795
|
+
this.applyDisabled();
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
ngOnDestroy() {
|
|
1799
|
+
this._valueChangesSubscription?.unsubscribe();
|
|
1800
|
+
}
|
|
1801
|
+
// ----- Sidebar item display -----
|
|
1802
|
+
// The sidebar shows the resource's short (simple) name, e.g. "CaseTab" for
|
|
1803
|
+
// "com.ritense.case.domain.CaseTab". The full technical name is still what is edited and stored.
|
|
1804
|
+
shortName(control) {
|
|
1805
|
+
const resourceType = control.get('resourceType').value;
|
|
1806
|
+
return shortTypeName(resourceType);
|
|
1807
|
+
}
|
|
1808
|
+
actionsOf(control) {
|
|
1809
|
+
return control.get('actions').value ?? [];
|
|
1810
|
+
}
|
|
1811
|
+
// Whether the permission has any conditions / a scoped context. Surfaced as status tags in the
|
|
1812
|
+
// sidebar so a rule that carries extra configuration is recognisable at a glance (e.g. two
|
|
1813
|
+
// entries for the same resource differ by their conditions).
|
|
1814
|
+
hasConditions(control) {
|
|
1815
|
+
const conditions = control.get('conditions');
|
|
1816
|
+
return (conditions?.length ?? 0) > 0;
|
|
1817
|
+
}
|
|
1818
|
+
hasContext(control) {
|
|
1819
|
+
return !!control.get('hasContext').value;
|
|
1820
|
+
}
|
|
1821
|
+
// The sidebar always lists permissions alphabetically by their (short) resource name, with
|
|
1822
|
+
// not-yet-named new permissions kept at the bottom. The original array index is preserved so
|
|
1823
|
+
// selection and serialization stay tied to the underlying form-array order.
|
|
1824
|
+
orderedPermissions() {
|
|
1825
|
+
if (!this.permissionsArray)
|
|
1826
|
+
return [];
|
|
1827
|
+
return this.permissionsArray.controls
|
|
1828
|
+
.map((control, index) => ({ control, index }))
|
|
1829
|
+
.sort((a, b) => {
|
|
1830
|
+
const nameA = this.shortName(a.control);
|
|
1831
|
+
const nameB = this.shortName(b.control);
|
|
1832
|
+
if (!nameA)
|
|
1833
|
+
return nameB ? 1 : 0;
|
|
1834
|
+
if (!nameB)
|
|
1835
|
+
return -1;
|
|
1836
|
+
return nameA.localeCompare(nameB);
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
// ----- Selection -----
|
|
1840
|
+
selectPermission(index) {
|
|
1841
|
+
this.$selectedIndex.set(index);
|
|
1842
|
+
}
|
|
1843
|
+
// Returns the selected permission as a single-element array so the template can render it with a
|
|
1844
|
+
// keyed @for: tracking by the FormGroup reference recreates the detail form (and re-runs its
|
|
1845
|
+
// combobox prefill) whenever the selection changes.
|
|
1846
|
+
selectedControls() {
|
|
1847
|
+
const index = this.$selectedIndex();
|
|
1848
|
+
if (index === null || !this.permissionsArray || index >= this.permissionsArray.length) {
|
|
1849
|
+
return [];
|
|
1850
|
+
}
|
|
1851
|
+
return [this.permissionsArray.at(index)];
|
|
1852
|
+
}
|
|
1853
|
+
addPermission() {
|
|
1854
|
+
if (!this.permissionsArray)
|
|
1855
|
+
return;
|
|
1856
|
+
const group = this.formEditorService.createPermissionGroup();
|
|
1857
|
+
this.permissionsArray.push(group);
|
|
1858
|
+
this.$selectedIndex.set(this.permissionsArray.length - 1);
|
|
1859
|
+
this.changeDetectorRef.markForCheck();
|
|
1860
|
+
this.scrollNewPermissionIntoView();
|
|
1861
|
+
}
|
|
1862
|
+
// The new permission is appended at the bottom of the sidebar list; once it has rendered, scroll
|
|
1863
|
+
// the list so the freshly-added (and now selected) item is brought into view.
|
|
1864
|
+
scrollNewPermissionIntoView() {
|
|
1865
|
+
setTimeout(() => {
|
|
1866
|
+
const list = this._permissionList?.nativeElement;
|
|
1867
|
+
if (list)
|
|
1868
|
+
list.scrollTo({ top: list.scrollHeight, behavior: 'smooth' });
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
// After a save reloads the model and the previously-open rule is reselected, scroll that rule
|
|
1872
|
+
// into view in the (alphabetically sorted) sidebar so it is visible without manual scrolling.
|
|
1873
|
+
scrollSelectedIntoView() {
|
|
1874
|
+
setTimeout(() => {
|
|
1875
|
+
const selected = this._permissionList?.nativeElement.querySelector('.form-editor__item--selected');
|
|
1876
|
+
selected?.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
removePermission(index) {
|
|
1880
|
+
if (!this.permissionsArray)
|
|
1881
|
+
return;
|
|
1882
|
+
// After removal, select the permission shown directly above the deleted one in the
|
|
1883
|
+
// (alphabetical) sidebar; if it was the first, fall back to the new first. The neighbour is
|
|
1884
|
+
// captured by reference before removal so we can find its new array index afterwards.
|
|
1885
|
+
const ordered = this.orderedPermissions();
|
|
1886
|
+
const position = ordered.findIndex(item => item.index === index);
|
|
1887
|
+
const neighbour = position > 0 ? ordered[position - 1].control : (ordered[position + 1]?.control ?? null);
|
|
1888
|
+
this.permissionsArray.removeAt(index);
|
|
1889
|
+
const neighbourIndex = neighbour ? this.permissionsArray.controls.indexOf(neighbour) : -1;
|
|
1890
|
+
// No neighbour left means the list is now empty → clear the selection so the empty state shows.
|
|
1891
|
+
this.$selectedIndex.set(neighbourIndex === -1 ? null : neighbourIndex);
|
|
1892
|
+
this.changeDetectorRef.markForCheck();
|
|
1893
|
+
}
|
|
1894
|
+
rebuild() {
|
|
1895
|
+
this.metadataService.registry$.pipe(take(1)).subscribe(registry => {
|
|
1896
|
+
this.formEditorService.setRegistry(registry);
|
|
1897
|
+
this.buildForm();
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
buildForm() {
|
|
1901
|
+
// Every (re)build selects the first permission, EXCEPT right after a save: the parent arms a
|
|
1902
|
+
// one-shot reselect (prepareSelectionReopen) so the rule that was open at save time is re-opened
|
|
1903
|
+
// once the saved model reloads. This is deliberately not preserved across ordinary reloads such
|
|
1904
|
+
// as switching tabs — those always start at the first permission.
|
|
1905
|
+
const reselect = this._pendingReselect;
|
|
1906
|
+
this._pendingReselect = null;
|
|
1907
|
+
this.permissionsArray = this.formEditorService.buildPermissionsArray(this.parsePermissions());
|
|
1908
|
+
this.$selectedIndex.set(this.resolveSelectedIndex(reselect));
|
|
1909
|
+
this.applyDisabled();
|
|
1910
|
+
this._valueChangesSubscription?.unsubscribe();
|
|
1911
|
+
this._valueChangesSubscription = this.permissionsArray.valueChanges.subscribe(() => this.emitState());
|
|
1912
|
+
this.ready = true;
|
|
1913
|
+
this.emitState();
|
|
1914
|
+
this.changeDetectorRef.markForCheck();
|
|
1915
|
+
// After a post-save reselect, bring the re-opened rule into view in the sidebar.
|
|
1916
|
+
if (reselect)
|
|
1917
|
+
this.scrollSelectedIntoView();
|
|
1918
|
+
}
|
|
1919
|
+
// Called by the parent immediately before a save: captures the open permission so that, once the
|
|
1920
|
+
// saved model reloads and the form rebuilds, the same rule is re-opened. One-shot — consumed by
|
|
1921
|
+
// the next buildForm.
|
|
1922
|
+
prepareSelectionReopen() {
|
|
1923
|
+
this._pendingReselect = this.selectedPermissionKeys();
|
|
1924
|
+
}
|
|
1925
|
+
// Identity of the currently-selected permission. Null when nothing is selected.
|
|
1926
|
+
selectedPermissionKeys() {
|
|
1927
|
+
const index = this.$selectedIndex();
|
|
1928
|
+
if (index === null || !this.permissionsArray || index >= this.permissionsArray.length) {
|
|
1929
|
+
return null;
|
|
1930
|
+
}
|
|
1931
|
+
return this.permissionKeys(this.permissionsArray.at(index));
|
|
1932
|
+
}
|
|
1933
|
+
// Finds the rebuilt permission matching the previously-open one. Prefers an exact (full) match;
|
|
1934
|
+
// falls back to a looser match on resource type + actions + context (so a permission whose
|
|
1935
|
+
// conditions the backend normalized on save is still re-opened), then to the first permission.
|
|
1936
|
+
resolveSelectedIndex(previous) {
|
|
1937
|
+
if (!this.permissionsArray || this.permissionsArray.length === 0)
|
|
1938
|
+
return null;
|
|
1939
|
+
// Default to the permission shown at the top of the (alphabetically sorted) sidebar, not the
|
|
1940
|
+
// raw form-array index 0 — the array order follows the stored JSON, which need not be
|
|
1941
|
+
// alphabetical.
|
|
1942
|
+
const firstListedIndex = this.orderedPermissions()[0]?.index ?? 0;
|
|
1943
|
+
if (!previous)
|
|
1944
|
+
return firstListedIndex;
|
|
1945
|
+
const keys = this.permissionsArray.controls.map(control => this.permissionKeys(control));
|
|
1946
|
+
const fullMatch = keys.findIndex(key => key.full === previous.full);
|
|
1947
|
+
if (fullMatch !== -1)
|
|
1948
|
+
return fullMatch;
|
|
1949
|
+
const partialMatch = keys.findIndex(key => key.partial === previous.partial);
|
|
1950
|
+
return partialMatch !== -1 ? partialMatch : firstListedIndex;
|
|
1951
|
+
}
|
|
1952
|
+
// Two identity keys for a permission: the full serialized permission (exact match) and a partial
|
|
1953
|
+
// key of just resource type + sorted actions + context resource (order-independent, condition-
|
|
1954
|
+
// agnostic).
|
|
1955
|
+
permissionKeys(group) {
|
|
1956
|
+
const serialized = this.formEditorService.serializePermission(group);
|
|
1957
|
+
const partial = JSON.stringify([
|
|
1958
|
+
serialized.resourceType,
|
|
1959
|
+
[...(serialized.actions ?? [])].sort(),
|
|
1960
|
+
serialized.contextResourceType ?? null,
|
|
1961
|
+
]);
|
|
1962
|
+
return { full: JSON.stringify(serialized), partial };
|
|
1963
|
+
}
|
|
1964
|
+
applyDisabled() {
|
|
1965
|
+
if (!this.permissionsArray)
|
|
1966
|
+
return;
|
|
1967
|
+
if (this.disabled && this.permissionsArray.enabled) {
|
|
1968
|
+
this.permissionsArray.disable({ emitEvent: false });
|
|
1969
|
+
}
|
|
1970
|
+
else if (!this.disabled && this.permissionsArray.disabled) {
|
|
1971
|
+
this.permissionsArray.enable({ emitEvent: false });
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
emitState() {
|
|
1975
|
+
if (!this.permissionsArray)
|
|
1976
|
+
return;
|
|
1977
|
+
this.valueChangeEvent.emit(JSON.stringify(this.formEditorService.serialize(this.permissionsArray)));
|
|
1978
|
+
this.validEvent.emit(this.permissionsArray.valid);
|
|
1979
|
+
}
|
|
1980
|
+
parsePermissions() {
|
|
1981
|
+
if (!this.model?.value)
|
|
1982
|
+
return [];
|
|
1983
|
+
try {
|
|
1984
|
+
const parsed = JSON.parse(this.model.value);
|
|
1985
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
1986
|
+
}
|
|
1987
|
+
catch {
|
|
1988
|
+
return [];
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlFormEditorTabComponent, deps: [{ token: AccessControlFormEditorService }, { token: PermissionSchemaMetadataService }, { token: i2$1.IconService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1992
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: AccessControlFormEditorTabComponent, isStandalone: false, selector: "valtimo-access-control-form-editor-tab", inputs: { model: "model", disabled: "disabled", roleKey: "roleKey" }, outputs: { validEvent: "validEvent", valueChangeEvent: "valueChangeEvent" }, providers: [AccessControlFormEditorService], viewQueries: [{ propertyName: "_permissionList", first: true, predicate: ["permissionList"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!--\n ~ Copyright 2015-2026 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<!-- fitPage sizes this container to the viewport so the sidebar and panel each scroll on their own. -->\n<div *ngIf=\"ready && permissionsArray as permissions\" class=\"form-editor\" fitPage>\n <aside class=\"form-editor__sidebar\">\n <div class=\"form-editor__sidebar-header\">\n <span class=\"form-editor__sidebar-title\">{{\n 'accessControl.editor.permissions' | translate\n }}</span>\n\n <button\n cdsButton=\"primary\"\n size=\"sm\"\n [attr.data-test-id]=\"testIds.addPermissionButton\"\n [disabled]=\"!!disabled\"\n (click)=\"addPermission()\"\n >\n {{ 'accessControl.editor.newPermission' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"add\" size=\"16\"></svg>\n </button>\n </div>\n\n <ul class=\"form-editor__list\" #permissionList>\n @for (item of orderedPermissions(); track item.control) {\n <li class=\"form-editor__list-item\">\n <button\n class=\"form-editor__item\"\n type=\"button\"\n [attr.data-test-id]=\"testIds.permissionListItem\"\n [class.form-editor__item--selected]=\"$selectedIndex() === item.index\"\n (click)=\"selectPermission(item.index)\"\n >\n <span class=\"form-editor__item-name\" [title]=\"shortName(item.control)\">\n @if (shortName(item.control)) {\n {{ shortName(item.control) }}\n } @else {\n {{ 'accessControl.editor.newPermission' | translate }}\n }\n </span>\n\n @if (hasConditions(item.control) || hasContext(item.control)) {\n <span class=\"form-editor__item-status\">\n <span class=\"form-editor__item-label\">{{\n 'accessControl.editor.preview.includes' | translate\n }}</span>\n\n @if (hasConditions(item.control)) {\n <cds-tag size=\"sm\" type=\"high-contrast\">{{\n 'accessControl.editor.hasConditions' | translate\n }}</cds-tag>\n }\n @if (hasContext(item.control)) {\n <cds-tag size=\"sm\" type=\"high-contrast\">{{\n 'accessControl.editor.hasContext' | translate\n }}</cds-tag>\n }\n </span>\n }\n\n @if (actionsOf(item.control).length) {\n <span class=\"form-editor__item-tags\">\n <span class=\"form-editor__item-label\">{{\n 'accessControl.editor.preview.actions' | translate\n }}</span>\n\n @for (action of actionsOf(item.control); track action) {\n <cds-tag size=\"sm\" [type]=\"action | actionTagType\">{{\n action | actionLabel\n }}</cds-tag>\n }\n </span>\n }\n </button>\n </li>\n }\n </ul>\n </aside>\n\n <section class=\"form-editor__panel\">\n @for (group of selectedControls(); track group) {\n <valtimo-permission-form\n [disabled]=\"!!disabled\"\n [group]=\"$any(group)\"\n [index]=\"$selectedIndex() ?? 0\"\n [roleKey]=\"roleKey\"\n (removeEvent)=\"removePermission($selectedIndex() ?? 0)\"\n ></valtimo-permission-form>\n } @empty {\n <div class=\"form-editor__empty\">\n <h4 class=\"form-editor__empty-title\">\n {{ 'accessControl.editor.empty.title' | translate }}\n </h4>\n <p class=\"form-editor__empty-message\">\n {{ 'accessControl.editor.empty.message' | translate }}\n </p>\n\n <button cdsButton=\"primary\" size=\"sm\" [disabled]=\"!!disabled\" (click)=\"addPermission()\">\n {{ 'accessControl.editor.addPermission' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"add\" size=\"16\"></svg>\n </button>\n </div>\n }\n </section>\n</div>\n", styles: [".form-editor{display:flex;border:1px solid var(--cds-border-subtle, #e0e0e0);overflow:hidden}.form-editor__sidebar{background:var(--cds-layer-01, #f4f4f4);border-right:1px solid var(--cds-border-subtle, #e0e0e0);display:flex;flex:0 0 22rem;flex-direction:column;min-height:0}.form-editor__sidebar-header{align-items:center;border-bottom:1px solid var(--cds-border-subtle, #e0e0e0);display:flex;flex:0 0 auto;justify-content:space-between;padding:12px}.form-editor__sidebar-title{color:var(--cds-text-secondary, #525252);font-size:14px;line-height:24px;font-weight:600}.form-editor__list{flex:1 1 auto;list-style:none;margin:0;min-height:0;overflow-y:auto;padding:0}.form-editor__list-item{margin:0}.form-editor__item{background:transparent;border:0;border-left:3px solid transparent;cursor:pointer;display:flex;flex-direction:column;gap:.375rem;padding:.75rem 1rem;text-align:left;width:100%}.form-editor__item:hover{background:var(--cds-layer-hover-01, #e8e8e8)}.form-editor__item:focus-visible{outline:2px solid var(--cds-focus, #0f62fe);outline-offset:-2px}.form-editor__item--selected{background:var(--cds-layer-selected-01, #e0e0e0);border-left-color:var(--cds-border-interactive, #0f62fe)}.form-editor__item--selected:hover{background:var(--cds-layer-selected-hover-01, #d1d1d1)}.form-editor__item-name{color:var(--cds-text-primary, #161616);font-size:.875rem;font-weight:600;line-height:1.29;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.form-editor__item-status{align-items:center;display:flex;flex-wrap:wrap;gap:.25rem}.form-editor__item-status ::ng-deep .cds--tag{margin:0;max-width:none;box-shadow:0 0 0 1px var(--cds-layer-01)}.form-editor__item-tags{align-items:center;display:flex;flex-wrap:wrap;gap:.25rem}.form-editor__item-tags ::ng-deep .cds--tag{margin:0;max-width:none;box-shadow:0 0 0 1px var(--cds-layer-01)}.form-editor__item-label{color:var(--cds-text-secondary, #525252);font-size:.75rem;font-weight:600;padding-right:.25rem}.form-editor__panel{flex:1 1 auto;min-width:0;overflow-y:auto;padding:1rem 1.5rem 1.5rem}.form-editor__empty{align-items:center;border:1px dashed var(--cds-border-strong, #8d8d8d);display:flex;flex-direction:column;gap:.5rem;margin:2rem auto;max-width:30rem;padding:2rem;text-align:center}.form-editor__empty-title{margin:0}.form-editor__empty-message{color:var(--cds-text-secondary, #525252);margin:0 0 .5rem}:host ::ng-deep .cds--checkbox-label-text{color:var(--cds-text-primary)}\n/*!\n * Copyright 2015-2026 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"], dependencies: [{ kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$1.Button, selector: "[cdsButton], [ibmButton]", inputs: ["ibmButton", "cdsButton", "size", "skeleton", "iconOnly", "isExpressive"] }, { kind: "directive", type: i2$1.IconDirective, selector: "[cdsIcon], [ibmIcon]", inputs: ["ibmIcon", "cdsIcon", "size", "title", "ariaLabel", "ariaLabelledBy", "ariaHidden", "isFocusable"] }, { kind: "directive", type: i3$1.FitPageDirective, selector: "[fitPage]", inputs: ["spaceAdjustment", "fitPageDisabled", "disableOverflow", "bottomMargin"] }, { kind: "component", type: i2$1.Tag, selector: "cds-tag, ibm-tag", inputs: ["type", "size", "class", "skeleton"] }, { kind: "component", type: PermissionFormComponent, selector: "valtimo-permission-form", inputs: ["group", "index", "disabled", "roleKey"], outputs: ["removeEvent"] }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }, { kind: "pipe", type: ActionLabelPipe, name: "actionLabel" }, { kind: "pipe", type: ActionTagTypePipe, name: "actionTagType" }] }); }
|
|
1993
|
+
}
|
|
1994
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlFormEditorTabComponent, decorators: [{
|
|
1995
|
+
type: Component,
|
|
1996
|
+
args: [{ standalone: false, selector: 'valtimo-access-control-form-editor-tab', providers: [AccessControlFormEditorService], template: "<!--\n ~ Copyright 2015-2026 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<!-- fitPage sizes this container to the viewport so the sidebar and panel each scroll on their own. -->\n<div *ngIf=\"ready && permissionsArray as permissions\" class=\"form-editor\" fitPage>\n <aside class=\"form-editor__sidebar\">\n <div class=\"form-editor__sidebar-header\">\n <span class=\"form-editor__sidebar-title\">{{\n 'accessControl.editor.permissions' | translate\n }}</span>\n\n <button\n cdsButton=\"primary\"\n size=\"sm\"\n [attr.data-test-id]=\"testIds.addPermissionButton\"\n [disabled]=\"!!disabled\"\n (click)=\"addPermission()\"\n >\n {{ 'accessControl.editor.newPermission' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"add\" size=\"16\"></svg>\n </button>\n </div>\n\n <ul class=\"form-editor__list\" #permissionList>\n @for (item of orderedPermissions(); track item.control) {\n <li class=\"form-editor__list-item\">\n <button\n class=\"form-editor__item\"\n type=\"button\"\n [attr.data-test-id]=\"testIds.permissionListItem\"\n [class.form-editor__item--selected]=\"$selectedIndex() === item.index\"\n (click)=\"selectPermission(item.index)\"\n >\n <span class=\"form-editor__item-name\" [title]=\"shortName(item.control)\">\n @if (shortName(item.control)) {\n {{ shortName(item.control) }}\n } @else {\n {{ 'accessControl.editor.newPermission' | translate }}\n }\n </span>\n\n @if (hasConditions(item.control) || hasContext(item.control)) {\n <span class=\"form-editor__item-status\">\n <span class=\"form-editor__item-label\">{{\n 'accessControl.editor.preview.includes' | translate\n }}</span>\n\n @if (hasConditions(item.control)) {\n <cds-tag size=\"sm\" type=\"high-contrast\">{{\n 'accessControl.editor.hasConditions' | translate\n }}</cds-tag>\n }\n @if (hasContext(item.control)) {\n <cds-tag size=\"sm\" type=\"high-contrast\">{{\n 'accessControl.editor.hasContext' | translate\n }}</cds-tag>\n }\n </span>\n }\n\n @if (actionsOf(item.control).length) {\n <span class=\"form-editor__item-tags\">\n <span class=\"form-editor__item-label\">{{\n 'accessControl.editor.preview.actions' | translate\n }}</span>\n\n @for (action of actionsOf(item.control); track action) {\n <cds-tag size=\"sm\" [type]=\"action | actionTagType\">{{\n action | actionLabel\n }}</cds-tag>\n }\n </span>\n }\n </button>\n </li>\n }\n </ul>\n </aside>\n\n <section class=\"form-editor__panel\">\n @for (group of selectedControls(); track group) {\n <valtimo-permission-form\n [disabled]=\"!!disabled\"\n [group]=\"$any(group)\"\n [index]=\"$selectedIndex() ?? 0\"\n [roleKey]=\"roleKey\"\n (removeEvent)=\"removePermission($selectedIndex() ?? 0)\"\n ></valtimo-permission-form>\n } @empty {\n <div class=\"form-editor__empty\">\n <h4 class=\"form-editor__empty-title\">\n {{ 'accessControl.editor.empty.title' | translate }}\n </h4>\n <p class=\"form-editor__empty-message\">\n {{ 'accessControl.editor.empty.message' | translate }}\n </p>\n\n <button cdsButton=\"primary\" size=\"sm\" [disabled]=\"!!disabled\" (click)=\"addPermission()\">\n {{ 'accessControl.editor.addPermission' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"add\" size=\"16\"></svg>\n </button>\n </div>\n }\n </section>\n</div>\n", styles: [".form-editor{display:flex;border:1px solid var(--cds-border-subtle, #e0e0e0);overflow:hidden}.form-editor__sidebar{background:var(--cds-layer-01, #f4f4f4);border-right:1px solid var(--cds-border-subtle, #e0e0e0);display:flex;flex:0 0 22rem;flex-direction:column;min-height:0}.form-editor__sidebar-header{align-items:center;border-bottom:1px solid var(--cds-border-subtle, #e0e0e0);display:flex;flex:0 0 auto;justify-content:space-between;padding:12px}.form-editor__sidebar-title{color:var(--cds-text-secondary, #525252);font-size:14px;line-height:24px;font-weight:600}.form-editor__list{flex:1 1 auto;list-style:none;margin:0;min-height:0;overflow-y:auto;padding:0}.form-editor__list-item{margin:0}.form-editor__item{background:transparent;border:0;border-left:3px solid transparent;cursor:pointer;display:flex;flex-direction:column;gap:.375rem;padding:.75rem 1rem;text-align:left;width:100%}.form-editor__item:hover{background:var(--cds-layer-hover-01, #e8e8e8)}.form-editor__item:focus-visible{outline:2px solid var(--cds-focus, #0f62fe);outline-offset:-2px}.form-editor__item--selected{background:var(--cds-layer-selected-01, #e0e0e0);border-left-color:var(--cds-border-interactive, #0f62fe)}.form-editor__item--selected:hover{background:var(--cds-layer-selected-hover-01, #d1d1d1)}.form-editor__item-name{color:var(--cds-text-primary, #161616);font-size:.875rem;font-weight:600;line-height:1.29;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.form-editor__item-status{align-items:center;display:flex;flex-wrap:wrap;gap:.25rem}.form-editor__item-status ::ng-deep .cds--tag{margin:0;max-width:none;box-shadow:0 0 0 1px var(--cds-layer-01)}.form-editor__item-tags{align-items:center;display:flex;flex-wrap:wrap;gap:.25rem}.form-editor__item-tags ::ng-deep .cds--tag{margin:0;max-width:none;box-shadow:0 0 0 1px var(--cds-layer-01)}.form-editor__item-label{color:var(--cds-text-secondary, #525252);font-size:.75rem;font-weight:600;padding-right:.25rem}.form-editor__panel{flex:1 1 auto;min-width:0;overflow-y:auto;padding:1rem 1.5rem 1.5rem}.form-editor__empty{align-items:center;border:1px dashed var(--cds-border-strong, #8d8d8d);display:flex;flex-direction:column;gap:.5rem;margin:2rem auto;max-width:30rem;padding:2rem;text-align:center}.form-editor__empty-title{margin:0}.form-editor__empty-message{color:var(--cds-text-secondary, #525252);margin:0 0 .5rem}:host ::ng-deep .cds--checkbox-label-text{color:var(--cds-text-primary)}\n/*!\n * Copyright 2015-2026 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"] }]
|
|
1997
|
+
}], ctorParameters: () => [{ type: AccessControlFormEditorService }, { type: PermissionSchemaMetadataService }, { type: i2$1.IconService }, { type: i0.ChangeDetectorRef }], propDecorators: { _permissionList: [{
|
|
1998
|
+
type: ViewChild,
|
|
1999
|
+
args: ['permissionList']
|
|
2000
|
+
}], model: [{
|
|
2001
|
+
type: Input
|
|
2002
|
+
}], disabled: [{
|
|
2003
|
+
type: Input
|
|
2004
|
+
}], roleKey: [{
|
|
2005
|
+
type: Input
|
|
2006
|
+
}], validEvent: [{
|
|
2007
|
+
type: Output
|
|
2008
|
+
}], valueChangeEvent: [{
|
|
2009
|
+
type: Output
|
|
2010
|
+
}] } });
|
|
751
2011
|
|
|
752
2012
|
/*
|
|
753
2013
|
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
@@ -890,8 +2150,8 @@ class AccessControlJsonEditorTabComponent {
|
|
|
890
2150
|
return [];
|
|
891
2151
|
}
|
|
892
2152
|
}
|
|
893
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlJsonEditorTabComponent, deps: [{ token: i1$
|
|
894
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: AccessControlJsonEditorTabComponent, isStandalone: true, selector: "valtimo-access-control-json-editor-tab", inputs: { disabled: "disabled", model: "model" }, outputs: { validEvent: "validEvent", valueChangeEvent: "valueChangeEvent" }, usesOnChanges: true, ngImport: i0, template: "<!--\n ~ Copyright 2015-2026 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<div\n *ngIf=\"{\n filteredModel: filteredModel$ | async,\n editorDisabled: editorDisabled$ | async,\n permissionSchema: permissionSchema$ | async,\n } as obs\"\n class=\"json-editor-tab\"\n>\n <div class=\"json-editor-tab__toolbar\">\n <v-overflow-menu placement=\"bottom-end\" [closeOnSelect]=\"false\">\n <button\n overflowTrigger\n cdsButton=\"ghost\"\n class=\"json-editor-tab__filter-trigger\"\n [class.json-editor-tab__filter-trigger--active]=\"isFilterActive$ | async\"\n [iconOnly]=\"true\"\n >\n <svg cdsIcon=\"filter\" size=\"16\"></svg>\n </button>\n\n <div overflowPane>\n <ng-container *ngTemplateOutlet=\"filterPane\"></ng-container>\n </div>\n </v-overflow-menu>\n </div>\n\n <valtimo-editor\n *ngIf=\"obs.permissionSchema\"\n [disabled]=\"obs.editorDisabled || disabled\"\n [fitPage]=\"true\"\n [jsonSchema]=\"obs.permissionSchema\"\n [model]=\"obs.filteredModel\"\n (validEvent)=\"onValid($event)\"\n (valueChangeEvent)=\"onValueChange($event)\"\n ></valtimo-editor>\n</div>\n\n<ng-template #filterPane>\n <form class=\"json-editor-tab__filter\" (click)=\"$event.stopImmediatePropagation()\">\n <cds-dropdown\n [appendInline]=\"true\"\n [label]=\"'accessControl.filter.resourceType' | translate\"\n [placeholder]=\"'accessControl.filter.allResourceTypes' | translate\"\n (selected)=\"onResourceTypeSelected($event)\"\n >\n <cds-dropdown-list [items]=\"resourceTypeItems$ | async\"></cds-dropdown-list>\n </cds-dropdown>\n\n <cds-dropdown\n [appendInline]=\"true\"\n [label]=\"'accessControl.filter.action' | translate\"\n [placeholder]=\"'accessControl.filter.allActions' | translate\"\n (selected)=\"onActionSelected($event)\"\n >\n <cds-dropdown-list [items]=\"actionItems$ | async\"></cds-dropdown-list>\n </cds-dropdown>\n\n <button cdsButton=\"tertiary\" type=\"button\" (click)=\"onClearFilter()\">\n {{ 'interface.clear' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"trash-can\" size=\"16\"></svg>\n </button>\n </form>\n</ng-template>\n", styles: [":host{display:block;height:100%}.json-editor-tab{display:flex;flex-direction:column;height:100%}.json-editor-tab__toolbar{display:flex;justify-content:flex-end
|
|
2153
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlJsonEditorTabComponent, deps: [{ token: i1$3.ActivatedRoute }, { token: i2$1.IconService }, { token: PermissionSchemaMetadataService }, { token: i1$3.Router }, { token: i1$2.TranslateService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2154
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.20", type: AccessControlJsonEditorTabComponent, isStandalone: true, selector: "valtimo-access-control-json-editor-tab", inputs: { disabled: "disabled", model: "model" }, outputs: { validEvent: "validEvent", valueChangeEvent: "valueChangeEvent" }, usesOnChanges: true, ngImport: i0, template: "<!--\n ~ Copyright 2015-2026 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<div\n *ngIf=\"{\n filteredModel: filteredModel$ | async,\n editorDisabled: editorDisabled$ | async,\n permissionSchema: permissionSchema$ | async,\n } as obs\"\n class=\"json-editor-tab\"\n>\n <div class=\"json-editor-tab__toolbar\">\n <v-overflow-menu placement=\"bottom-end\" [closeOnSelect]=\"false\">\n <button\n overflowTrigger\n cdsButton=\"ghost\"\n class=\"json-editor-tab__filter-trigger\"\n [class.json-editor-tab__filter-trigger--active]=\"isFilterActive$ | async\"\n [iconOnly]=\"true\"\n >\n <svg cdsIcon=\"filter\" size=\"16\"></svg>\n </button>\n\n <div overflowPane>\n <ng-container *ngTemplateOutlet=\"filterPane\"></ng-container>\n </div>\n </v-overflow-menu>\n </div>\n\n <valtimo-editor\n *ngIf=\"obs.permissionSchema\"\n [disabled]=\"obs.editorDisabled || disabled\"\n [fitPage]=\"true\"\n [jsonSchema]=\"obs.permissionSchema\"\n [model]=\"obs.filteredModel\"\n (validEvent)=\"onValid($event)\"\n (valueChangeEvent)=\"onValueChange($event)\"\n ></valtimo-editor>\n</div>\n\n<ng-template #filterPane>\n <form class=\"json-editor-tab__filter\" (click)=\"$event.stopImmediatePropagation()\">\n <cds-dropdown\n [appendInline]=\"true\"\n [label]=\"'accessControl.filter.resourceType' | translate\"\n [placeholder]=\"'accessControl.filter.allResourceTypes' | translate\"\n (selected)=\"onResourceTypeSelected($event)\"\n >\n <cds-dropdown-list [items]=\"resourceTypeItems$ | async\"></cds-dropdown-list>\n </cds-dropdown>\n\n <cds-dropdown\n [appendInline]=\"true\"\n [label]=\"'accessControl.filter.action' | translate\"\n [placeholder]=\"'accessControl.filter.allActions' | translate\"\n (selected)=\"onActionSelected($event)\"\n >\n <cds-dropdown-list [items]=\"actionItems$ | async\"></cds-dropdown-list>\n </cds-dropdown>\n\n <button cdsButton=\"tertiary\" type=\"button\" (click)=\"onClearFilter()\">\n {{ 'interface.clear' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"trash-can\" size=\"16\"></svg>\n </button>\n </form>\n</ng-template>\n", styles: [":host{display:block;height:100%}.json-editor-tab{display:flex;flex-direction:column;height:100%}.json-editor-tab__toolbar{display:flex;justify-content:flex-end}.json-editor-tab__filter-trigger{position:relative}.json-editor-tab__filter-trigger--active:after{content:\"\";position:absolute;top:.375rem;right:.375rem;width:.5rem;height:.5rem;border-radius:50%;background-color:var(--cds-support-info, #0f62fe)}.json-editor-tab__filter{display:flex;flex-direction:column;gap:1rem;padding:1rem;min-width:16rem}\n/*!\n * Copyright 2015-2026 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2$1.Button, selector: "[cdsButton], [ibmButton]", inputs: ["ibmButton", "cdsButton", "size", "skeleton", "iconOnly", "isExpressive"] }, { kind: "ngmodule", type: DropdownModule }, { kind: "component", type: i2$1.Dropdown, selector: "cds-dropdown, ibm-dropdown", inputs: ["id", "label", "hideLabel", "helperText", "placeholder", "displayValue", "clearText", "size", "type", "theme", "disabled", "readonly", "skeleton", "inline", "disableArrowKeys", "invalid", "invalidText", "warn", "warnText", "appendInline", "scrollableContainer", "itemValueKey", "selectionFeedback", "menuButtonLabel", "selectedLabel", "dropUp", "fluid"], outputs: ["selected", "onClose", "close"] }, { kind: "component", type: i2$1.DropdownList, selector: "cds-dropdown-list, ibm-dropdown-list", inputs: ["ariaLabel", "items", "listTpl", "type", "showTitles"], outputs: ["select", "scroll", "blurIntent"] }, { kind: "ngmodule", type: EditorModule }, { kind: "component", type: i3$1.EditorComponent, selector: "valtimo-editor", inputs: ["editorOptions", "model", "disabled", "formatOnLoad", "widthPx", "heightPx", "heightStyle", "jsonSchema", "fitPage"], outputs: ["validEvent", "valueChangeEvent"] }, { kind: "ngmodule", type: IconModule }, { kind: "directive", type: i2$1.IconDirective, selector: "[cdsIcon], [ibmIcon]", inputs: ["ibmIcon", "cdsIcon", "size", "title", "ariaLabel", "ariaLabelledBy", "ariaHidden", "isFocusable"] }, { kind: "component", type: OverflowMenuComponent, selector: "v-overflow-menu", inputs: ["open", "placement", "menuWidth", "offsetX", "offsetY", "closeOnSelect", "useHostAsReference", "portalToBody"], outputs: ["openChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
895
2155
|
}
|
|
896
2156
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlJsonEditorTabComponent, decorators: [{
|
|
897
2157
|
type: Component,
|
|
@@ -903,8 +2163,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
903
2163
|
EditorModule,
|
|
904
2164
|
IconModule,
|
|
905
2165
|
OverflowMenuComponent,
|
|
906
|
-
], template: "<!--\n ~ Copyright 2015-2026 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<div\n *ngIf=\"{\n filteredModel: filteredModel$ | async,\n editorDisabled: editorDisabled$ | async,\n permissionSchema: permissionSchema$ | async,\n } as obs\"\n class=\"json-editor-tab\"\n>\n <div class=\"json-editor-tab__toolbar\">\n <v-overflow-menu placement=\"bottom-end\" [closeOnSelect]=\"false\">\n <button\n overflowTrigger\n cdsButton=\"ghost\"\n class=\"json-editor-tab__filter-trigger\"\n [class.json-editor-tab__filter-trigger--active]=\"isFilterActive$ | async\"\n [iconOnly]=\"true\"\n >\n <svg cdsIcon=\"filter\" size=\"16\"></svg>\n </button>\n\n <div overflowPane>\n <ng-container *ngTemplateOutlet=\"filterPane\"></ng-container>\n </div>\n </v-overflow-menu>\n </div>\n\n <valtimo-editor\n *ngIf=\"obs.permissionSchema\"\n [disabled]=\"obs.editorDisabled || disabled\"\n [fitPage]=\"true\"\n [jsonSchema]=\"obs.permissionSchema\"\n [model]=\"obs.filteredModel\"\n (validEvent)=\"onValid($event)\"\n (valueChangeEvent)=\"onValueChange($event)\"\n ></valtimo-editor>\n</div>\n\n<ng-template #filterPane>\n <form class=\"json-editor-tab__filter\" (click)=\"$event.stopImmediatePropagation()\">\n <cds-dropdown\n [appendInline]=\"true\"\n [label]=\"'accessControl.filter.resourceType' | translate\"\n [placeholder]=\"'accessControl.filter.allResourceTypes' | translate\"\n (selected)=\"onResourceTypeSelected($event)\"\n >\n <cds-dropdown-list [items]=\"resourceTypeItems$ | async\"></cds-dropdown-list>\n </cds-dropdown>\n\n <cds-dropdown\n [appendInline]=\"true\"\n [label]=\"'accessControl.filter.action' | translate\"\n [placeholder]=\"'accessControl.filter.allActions' | translate\"\n (selected)=\"onActionSelected($event)\"\n >\n <cds-dropdown-list [items]=\"actionItems$ | async\"></cds-dropdown-list>\n </cds-dropdown>\n\n <button cdsButton=\"tertiary\" type=\"button\" (click)=\"onClearFilter()\">\n {{ 'interface.clear' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"trash-can\" size=\"16\"></svg>\n </button>\n </form>\n</ng-template>\n", styles: [":host{display:block;height:100%}.json-editor-tab{display:flex;flex-direction:column;height:100%}.json-editor-tab__toolbar{display:flex;justify-content:flex-end
|
|
907
|
-
}], ctorParameters: () => [{ type: i1$
|
|
2166
|
+
], template: "<!--\n ~ Copyright 2015-2026 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<div\n *ngIf=\"{\n filteredModel: filteredModel$ | async,\n editorDisabled: editorDisabled$ | async,\n permissionSchema: permissionSchema$ | async,\n } as obs\"\n class=\"json-editor-tab\"\n>\n <div class=\"json-editor-tab__toolbar\">\n <v-overflow-menu placement=\"bottom-end\" [closeOnSelect]=\"false\">\n <button\n overflowTrigger\n cdsButton=\"ghost\"\n class=\"json-editor-tab__filter-trigger\"\n [class.json-editor-tab__filter-trigger--active]=\"isFilterActive$ | async\"\n [iconOnly]=\"true\"\n >\n <svg cdsIcon=\"filter\" size=\"16\"></svg>\n </button>\n\n <div overflowPane>\n <ng-container *ngTemplateOutlet=\"filterPane\"></ng-container>\n </div>\n </v-overflow-menu>\n </div>\n\n <valtimo-editor\n *ngIf=\"obs.permissionSchema\"\n [disabled]=\"obs.editorDisabled || disabled\"\n [fitPage]=\"true\"\n [jsonSchema]=\"obs.permissionSchema\"\n [model]=\"obs.filteredModel\"\n (validEvent)=\"onValid($event)\"\n (valueChangeEvent)=\"onValueChange($event)\"\n ></valtimo-editor>\n</div>\n\n<ng-template #filterPane>\n <form class=\"json-editor-tab__filter\" (click)=\"$event.stopImmediatePropagation()\">\n <cds-dropdown\n [appendInline]=\"true\"\n [label]=\"'accessControl.filter.resourceType' | translate\"\n [placeholder]=\"'accessControl.filter.allResourceTypes' | translate\"\n (selected)=\"onResourceTypeSelected($event)\"\n >\n <cds-dropdown-list [items]=\"resourceTypeItems$ | async\"></cds-dropdown-list>\n </cds-dropdown>\n\n <cds-dropdown\n [appendInline]=\"true\"\n [label]=\"'accessControl.filter.action' | translate\"\n [placeholder]=\"'accessControl.filter.allActions' | translate\"\n (selected)=\"onActionSelected($event)\"\n >\n <cds-dropdown-list [items]=\"actionItems$ | async\"></cds-dropdown-list>\n </cds-dropdown>\n\n <button cdsButton=\"tertiary\" type=\"button\" (click)=\"onClearFilter()\">\n {{ 'interface.clear' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"trash-can\" size=\"16\"></svg>\n </button>\n </form>\n</ng-template>\n", styles: [":host{display:block;height:100%}.json-editor-tab{display:flex;flex-direction:column;height:100%}.json-editor-tab__toolbar{display:flex;justify-content:flex-end}.json-editor-tab__filter-trigger{position:relative}.json-editor-tab__filter-trigger--active:after{content:\"\";position:absolute;top:.375rem;right:.375rem;width:.5rem;height:.5rem;border-radius:50%;background-color:var(--cds-support-info, #0f62fe)}.json-editor-tab__filter{display:flex;flex-direction:column;gap:1rem;padding:1rem;min-width:16rem}\n/*!\n * Copyright 2015-2026 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"] }]
|
|
2167
|
+
}], ctorParameters: () => [{ type: i1$3.ActivatedRoute }, { type: i2$1.IconService }, { type: PermissionSchemaMetadataService }, { type: i1$3.Router }, { type: i1$2.TranslateService }], propDecorators: { disabled: [{
|
|
908
2168
|
type: Input
|
|
909
2169
|
}], model: [{
|
|
910
2170
|
type: Input
|
|
@@ -938,7 +2198,7 @@ class ResourceTypeLabelPipe {
|
|
|
938
2198
|
return '';
|
|
939
2199
|
return formatResourceType(this.translateService, fqn);
|
|
940
2200
|
}
|
|
941
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ResourceTypeLabelPipe, deps: [{ token:
|
|
2201
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ResourceTypeLabelPipe, deps: [{ token: i1$2.TranslateService }], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
942
2202
|
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: ResourceTypeLabelPipe, isStandalone: true, name: "resourceTypeLabel" }); }
|
|
943
2203
|
}
|
|
944
2204
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: ResourceTypeLabelPipe, decorators: [{
|
|
@@ -947,7 +2207,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
947
2207
|
standalone: true,
|
|
948
2208
|
name: 'resourceTypeLabel',
|
|
949
2209
|
}]
|
|
950
|
-
}], ctorParameters: () => [{ type:
|
|
2210
|
+
}], ctorParameters: () => [{ type: i1$2.TranslateService }] });
|
|
951
2211
|
|
|
952
2212
|
/*
|
|
953
2213
|
* Copyright 2015-2026 Ritense BV, the Netherlands.
|
|
@@ -1066,13 +2326,13 @@ class AccessControlOverviewTabComponent {
|
|
|
1066
2326
|
customResource: !this.metadataService.isResourceTypeKnown(condition.resourceType),
|
|
1067
2327
|
};
|
|
1068
2328
|
}
|
|
1069
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlOverviewTabComponent, deps: [{ token: PermissionSchemaMetadataService }, { token:
|
|
1070
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: AccessControlOverviewTabComponent, isStandalone: true, selector: "valtimo-access-control-overview-tab", inputs: { roleKey: "roleKey", permissions: "permissions" }, ngImport: i0, template: "<!--\n ~ Copyright 2015-2026 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<div *ngIf=\"overview$ | async as overview\" class=\"ac\">\n @for (resource of overview; track resource.resourceType) {\n <section class=\"ac__group\">\n <h4 class=\"ac__heading\">\n @if (roleKey) {\n <a\n class=\"ac__heading-link\"\n [routerLink]=\"['/access-control', roleKey, 'json-editor']\"\n [queryParams]=\"{filterResourceType: resource.resourceType}\"\n >\n {{ resource.resourceType | resourceTypeLabel }}\n <span class=\"ac__fqn\">({{ resource.resourceType }})</span>\n </a>\n } @else {\n {{ resource.resourceType | resourceTypeLabel }}\n <span class=\"ac__fqn\">({{ resource.resourceType }})</span>\n }\n </h4>\n\n @for (entry of resource.actions; track entry.action) {\n <div class=\"ac__action\">\n @if (entry.grants.length) {\n <span class=\"ac__connective\">{{ 'accessControl.overview.can' | translate }}</span>\n @if (roleKey) {\n <a\n class=\"ac__action-link\"\n [routerLink]=\"['/access-control', roleKey, 'json-editor']\"\n [queryParams]=\"{\n filterResourceType: resource.resourceType,\n filterAction: entry.action,\n }\"\n >\n <span class=\"ac__action-name\">{{\n 'accessControl.actions.' + entry.action | translate\n }}</span>\n </a>\n } @else {\n <span class=\"ac__action-name\">{{\n 'accessControl.actions.' + entry.action | translate\n }}</span>\n }\n\n @for (perm of entry.grants; track $index; let isFirst = $first) {\n @if (!isFirst) {\n <div class=\"ac__or ac__connective\">{{\n 'accessControl.overview.or' | translate\n }}</div>\n }\n <div class=\"ac__grant\">\n @if (perm.conditions?.length) {\n <div class=\"ac__connective\">{{\n 'accessControl.overview.when' | translate\n }}</div>\n <ng-container\n *ngTemplateOutlet=\"\n conditionsTpl;\n context: {\n conditions: perm.conditions,\n resourceType: perm.resourceType,\n }\n \"\n ></ng-container>\n } @else if (!perm.contextResourceType) {\n <div class=\"ac__connective\">{{\n 'accessControl.overview.unconditional' | translate\n }}</div>\n }\n\n @if (perm.contextResourceType) {\n @if (isNoContext(perm.contextResourceType)) {\n <div class=\"ac__connective\">{{\n 'accessControl.overview.noContext' | translate\n }}</div>\n } @else {\n <div>\n <span class=\"ac__connective\">{{\n 'accessControl.overview.inContextOf' | translate\n }}</span>\n <span class=\"ac__resource-type\">{{\n perm.contextResourceType | resourceTypeLabel\n }}</span>\n @if (perm.contextConditions?.length) {\n <span class=\"ac__connective\">{{\n 'accessControl.overview.where' | translate\n }}</span>\n }\n </div>\n @if (perm.contextConditions?.length) {\n <ng-container\n *ngTemplateOutlet=\"\n conditionsTpl;\n context: {\n conditions: perm.contextConditions,\n resourceType: perm.contextResourceType,\n }\n \"\n ></ng-container>\n }\n }\n }\n </div>\n }\n } @else {\n <span class=\"ac__no-access\">{{\n 'accessControl.overview.cannot' | translate\n }}</span>\n @if (roleKey) {\n <a\n class=\"ac__action-link\"\n [routerLink]=\"['/access-control', roleKey, 'json-editor']\"\n [queryParams]=\"{\n filterResourceType: resource.resourceType,\n filterAction: entry.action,\n }\"\n >\n <span class=\"ac__action-name\">{{\n 'accessControl.actions.' + entry.action | translate\n }}</span>\n </a>\n } @else {\n <span class=\"ac__action-name\">{{\n 'accessControl.actions.' + entry.action | translate\n }}</span>\n }\n }\n </div>\n }\n </section>\n }\n</div>\n\n<ng-template #conditionsTpl let-conditions=\"conditions\" let-resourceType=\"resourceType\">\n <div class=\"ac__conditions\">\n @for (\n condition of formatConditions(conditions, resourceType);\n track $index;\n let isFirst = $first\n ) {\n <div class=\"ac__condition\">\n @if (!isFirst) {\n <span class=\"ac__connective\">{{ 'accessControl.overview.and' | translate }}</span>\n }\n @switch (condition.kind) {\n @case ('field') {\n <span>{{ condition.field }}</span>\n @if (condition.customField) {\n <span class=\"ac__connective\">{{\n 'accessControl.overview.customField' | translate\n }}</span>\n }\n <span class=\"ac__operator\">{{ condition.operator }}</span>\n <span>{{ condition.value }}</span>\n }\n\n @case ('expression') {\n <span>{{ condition.field }}</span>\n @if (condition.customField) {\n <span class=\"ac__connective\">{{\n 'accessControl.overview.customField' | translate\n }}</span>\n }\n <span class=\"ac__connective\">{{\n 'accessControl.overview.atJsonPath' | translate\n }}</span>\n <code class=\"ac__path\" [title]=\"condition.clazz\">{{ condition.path }}</code>\n <span class=\"ac__operator\">{{ condition.operator }}</span>\n <span>{{ condition.value }}</span>\n }\n\n @case ('container') {\n <span class=\"ac__connective\">{{\n 'accessControl.overview.relatedResource' | translate\n }}</span>\n <span class=\"ac__resource-type\">{{\n condition.resourceType | resourceTypeLabel\n }}</span>\n @if (condition.customResource) {\n <span class=\"ac__connective\">{{\n 'accessControl.overview.customResource' | translate\n }}</span>\n }\n <span class=\"ac__connective\">{{\n 'accessControl.overview.where' | translate\n }}</span>\n <ng-container\n *ngTemplateOutlet=\"\n conditionsTpl;\n context: {\n conditions: condition.conditions,\n resourceType: condition.resourceType,\n }\n \"\n ></ng-container>\n }\n }\n </div>\n }\n </div>\n</ng-template>\n", styles: [".ac{display:flex;flex-direction:column;gap:2rem;padding:1rem}.ac__group{display:flex;flex-direction:column;gap:.75rem}.ac__heading{border-bottom:1px solid var(--cds-border-subtle, #c6c6c6);margin:0 0 .5rem;padding-bottom:.25rem}.ac__heading-link{color:inherit;text-decoration:none}.ac__heading-link:hover,.ac__heading-link:focus-visible{text-decoration:underline}.ac__action-link{color:inherit;text-decoration:none}.ac__action-link:hover,.ac__action-link:focus-visible{text-decoration:underline}.ac__fqn{color:var(--cds-text-secondary, #525252);font-family:var(--cds-code-01-font-family, monospace);font-size:.875rem;font-weight:400;margin-left:.5rem}.ac__action{line-height:1.6}.ac__action-name,.ac__resource-type{font-weight:600}.ac__operator{font-style:italic}.ac__connective{color:var(--cds-text-secondary, #525252);font-style:italic}.ac__no-access{color:var(--cds-text-error, #da1e28);font-style:italic}.ac__grant,.ac__or,.ac__conditions{padding-left:1.5rem}.ac__path{background-color:var(--cds-layer-accent, #e0e0e0);border-radius:2px;font-family:var(--cds-code-01-font-family, monospace);padding:0 .25rem}\n/*!\n * Copyright 2015-2026 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i4$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: ResourceTypeLabelPipe, name: "resourceTypeLabel" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
2329
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlOverviewTabComponent, deps: [{ token: PermissionSchemaMetadataService }, { token: i1$2.TranslateService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2330
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: AccessControlOverviewTabComponent, isStandalone: true, selector: "valtimo-access-control-overview-tab", inputs: { roleKey: "roleKey", permissions: "permissions" }, ngImport: i0, template: "<!--\n ~ Copyright 2015-2026 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<div *ngIf=\"overview$ | async as overview\" class=\"ac\">\n @for (resource of overview; track resource.resourceType) {\n <section class=\"ac__group\">\n <h4 class=\"ac__heading\">\n @if (roleKey) {\n <a\n class=\"ac__heading-link\"\n [routerLink]=\"['/access-control', roleKey, 'json-editor']\"\n [queryParams]=\"{filterResourceType: resource.resourceType}\"\n >\n {{ resource.resourceType | resourceTypeLabel }}\n <span class=\"ac__fqn\">({{ resource.resourceType }})</span>\n </a>\n } @else {\n {{ resource.resourceType | resourceTypeLabel }}\n <span class=\"ac__fqn\">({{ resource.resourceType }})</span>\n }\n </h4>\n\n @for (entry of resource.actions; track entry.action) {\n <div class=\"ac__action\">\n @if (entry.grants.length) {\n <span class=\"ac__connective\">{{ 'accessControl.overview.can' | translate }}</span>\n @if (roleKey) {\n <a\n class=\"ac__action-link\"\n [routerLink]=\"['/access-control', roleKey, 'json-editor']\"\n [queryParams]=\"{\n filterResourceType: resource.resourceType,\n filterAction: entry.action,\n }\"\n >\n <span class=\"ac__action-name\">{{\n 'accessControl.actions.' + entry.action | translate\n }}</span>\n </a>\n } @else {\n <span class=\"ac__action-name\">{{\n 'accessControl.actions.' + entry.action | translate\n }}</span>\n }\n\n @for (perm of entry.grants; track $index; let isFirst = $first) {\n @if (!isFirst) {\n <div class=\"ac__or ac__connective\">{{\n 'accessControl.overview.or' | translate\n }}</div>\n }\n <div class=\"ac__grant\">\n @if (perm.conditions?.length) {\n <div class=\"ac__connective\">{{\n 'accessControl.overview.when' | translate\n }}</div>\n <ng-container\n *ngTemplateOutlet=\"\n conditionsTpl;\n context: {\n conditions: perm.conditions,\n resourceType: perm.resourceType,\n }\n \"\n ></ng-container>\n } @else if (!perm.contextResourceType) {\n <div class=\"ac__connective\">{{\n 'accessControl.overview.unconditional' | translate\n }}</div>\n }\n\n @if (perm.contextResourceType) {\n @if (isNoContext(perm.contextResourceType)) {\n <div class=\"ac__connective\">{{\n 'accessControl.overview.noContext' | translate\n }}</div>\n } @else {\n <div>\n <span class=\"ac__connective\">{{\n 'accessControl.overview.inContextOf' | translate\n }}</span>\n <span class=\"ac__resource-type\">{{\n perm.contextResourceType | resourceTypeLabel\n }}</span>\n @if (perm.contextConditions?.length) {\n <span class=\"ac__connective\">{{\n 'accessControl.overview.where' | translate\n }}</span>\n }\n </div>\n @if (perm.contextConditions?.length) {\n <ng-container\n *ngTemplateOutlet=\"\n conditionsTpl;\n context: {\n conditions: perm.contextConditions,\n resourceType: perm.contextResourceType,\n }\n \"\n ></ng-container>\n }\n }\n }\n </div>\n }\n } @else {\n <span class=\"ac__no-access\">{{\n 'accessControl.overview.cannot' | translate\n }}</span>\n @if (roleKey) {\n <a\n class=\"ac__action-link\"\n [routerLink]=\"['/access-control', roleKey, 'json-editor']\"\n [queryParams]=\"{\n filterResourceType: resource.resourceType,\n filterAction: entry.action,\n }\"\n >\n <span class=\"ac__action-name\">{{\n 'accessControl.actions.' + entry.action | translate\n }}</span>\n </a>\n } @else {\n <span class=\"ac__action-name\">{{\n 'accessControl.actions.' + entry.action | translate\n }}</span>\n }\n }\n </div>\n }\n </section>\n }\n</div>\n\n<ng-template #conditionsTpl let-conditions=\"conditions\" let-resourceType=\"resourceType\">\n <div class=\"ac__conditions\">\n @for (\n condition of formatConditions(conditions, resourceType);\n track $index;\n let isFirst = $first\n ) {\n <div class=\"ac__condition\">\n @if (!isFirst) {\n <span class=\"ac__connective\">{{ 'accessControl.overview.and' | translate }}</span>\n }\n @switch (condition.kind) {\n @case ('field') {\n <span>{{ condition.field }}</span>\n @if (condition.customField) {\n <span class=\"ac__connective\">{{\n 'accessControl.overview.customField' | translate\n }}</span>\n }\n <span class=\"ac__operator\">{{ condition.operator }}</span>\n <span>{{ condition.value }}</span>\n }\n\n @case ('expression') {\n <span>{{ condition.field }}</span>\n @if (condition.customField) {\n <span class=\"ac__connective\">{{\n 'accessControl.overview.customField' | translate\n }}</span>\n }\n <span class=\"ac__connective\">{{\n 'accessControl.overview.atJsonPath' | translate\n }}</span>\n <code class=\"ac__path\" [title]=\"condition.clazz\">{{ condition.path }}</code>\n <span class=\"ac__operator\">{{ condition.operator }}</span>\n <span>{{ condition.value }}</span>\n }\n\n @case ('container') {\n <span class=\"ac__connective\">{{\n 'accessControl.overview.relatedResource' | translate\n }}</span>\n <span class=\"ac__resource-type\">{{\n condition.resourceType | resourceTypeLabel\n }}</span>\n @if (condition.customResource) {\n <span class=\"ac__connective\">{{\n 'accessControl.overview.customResource' | translate\n }}</span>\n }\n <span class=\"ac__connective\">{{\n 'accessControl.overview.where' | translate\n }}</span>\n <ng-container\n *ngTemplateOutlet=\"\n conditionsTpl;\n context: {\n conditions: condition.conditions,\n resourceType: condition.resourceType,\n }\n \"\n ></ng-container>\n }\n }\n </div>\n }\n </div>\n</ng-template>\n", styles: [".ac{display:flex;flex-direction:column;gap:2rem;padding:1rem}.ac__group{display:flex;flex-direction:column;gap:.75rem}.ac__heading{border-bottom:1px solid var(--cds-border-subtle, #c6c6c6);margin:0 0 .5rem;padding-bottom:.25rem}.ac__heading-link{color:inherit;text-decoration:none}.ac__heading-link:hover,.ac__heading-link:focus-visible{text-decoration:underline}.ac__action-link{color:inherit;text-decoration:none}.ac__action-link:hover,.ac__action-link:focus-visible{text-decoration:underline}.ac__fqn{color:var(--cds-text-secondary, #525252);font-family:var(--cds-code-01-font-family, monospace);font-size:.875rem;font-weight:400;margin-left:.5rem}.ac__action{line-height:1.6}.ac__action-name,.ac__resource-type{font-weight:600}.ac__operator{font-style:italic}.ac__connective{color:var(--cds-text-secondary, #525252);font-style:italic}.ac__no-access{color:var(--cds-text-error, #da1e28);font-style:italic}.ac__grant,.ac__or,.ac__conditions{padding-left:1.5rem}.ac__path{background-color:var(--cds-layer-accent, #e0e0e0);border-radius:2px;font-family:var(--cds-code-01-font-family, monospace);padding:0 .25rem}\n/*!\n * Copyright 2015-2026 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$3.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }, { kind: "pipe", type: ResourceTypeLabelPipe, name: "resourceTypeLabel" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
1071
2331
|
}
|
|
1072
2332
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlOverviewTabComponent, decorators: [{
|
|
1073
2333
|
type: Component,
|
|
1074
2334
|
args: [{ standalone: true, selector: 'valtimo-access-control-overview-tab', changeDetection: ChangeDetectionStrategy.OnPush, preserveWhitespaces: true, imports: [CommonModule, RouterModule, TranslateModule, ResourceTypeLabelPipe], template: "<!--\n ~ Copyright 2015-2026 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<div *ngIf=\"overview$ | async as overview\" class=\"ac\">\n @for (resource of overview; track resource.resourceType) {\n <section class=\"ac__group\">\n <h4 class=\"ac__heading\">\n @if (roleKey) {\n <a\n class=\"ac__heading-link\"\n [routerLink]=\"['/access-control', roleKey, 'json-editor']\"\n [queryParams]=\"{filterResourceType: resource.resourceType}\"\n >\n {{ resource.resourceType | resourceTypeLabel }}\n <span class=\"ac__fqn\">({{ resource.resourceType }})</span>\n </a>\n } @else {\n {{ resource.resourceType | resourceTypeLabel }}\n <span class=\"ac__fqn\">({{ resource.resourceType }})</span>\n }\n </h4>\n\n @for (entry of resource.actions; track entry.action) {\n <div class=\"ac__action\">\n @if (entry.grants.length) {\n <span class=\"ac__connective\">{{ 'accessControl.overview.can' | translate }}</span>\n @if (roleKey) {\n <a\n class=\"ac__action-link\"\n [routerLink]=\"['/access-control', roleKey, 'json-editor']\"\n [queryParams]=\"{\n filterResourceType: resource.resourceType,\n filterAction: entry.action,\n }\"\n >\n <span class=\"ac__action-name\">{{\n 'accessControl.actions.' + entry.action | translate\n }}</span>\n </a>\n } @else {\n <span class=\"ac__action-name\">{{\n 'accessControl.actions.' + entry.action | translate\n }}</span>\n }\n\n @for (perm of entry.grants; track $index; let isFirst = $first) {\n @if (!isFirst) {\n <div class=\"ac__or ac__connective\">{{\n 'accessControl.overview.or' | translate\n }}</div>\n }\n <div class=\"ac__grant\">\n @if (perm.conditions?.length) {\n <div class=\"ac__connective\">{{\n 'accessControl.overview.when' | translate\n }}</div>\n <ng-container\n *ngTemplateOutlet=\"\n conditionsTpl;\n context: {\n conditions: perm.conditions,\n resourceType: perm.resourceType,\n }\n \"\n ></ng-container>\n } @else if (!perm.contextResourceType) {\n <div class=\"ac__connective\">{{\n 'accessControl.overview.unconditional' | translate\n }}</div>\n }\n\n @if (perm.contextResourceType) {\n @if (isNoContext(perm.contextResourceType)) {\n <div class=\"ac__connective\">{{\n 'accessControl.overview.noContext' | translate\n }}</div>\n } @else {\n <div>\n <span class=\"ac__connective\">{{\n 'accessControl.overview.inContextOf' | translate\n }}</span>\n <span class=\"ac__resource-type\">{{\n perm.contextResourceType | resourceTypeLabel\n }}</span>\n @if (perm.contextConditions?.length) {\n <span class=\"ac__connective\">{{\n 'accessControl.overview.where' | translate\n }}</span>\n }\n </div>\n @if (perm.contextConditions?.length) {\n <ng-container\n *ngTemplateOutlet=\"\n conditionsTpl;\n context: {\n conditions: perm.contextConditions,\n resourceType: perm.contextResourceType,\n }\n \"\n ></ng-container>\n }\n }\n }\n </div>\n }\n } @else {\n <span class=\"ac__no-access\">{{\n 'accessControl.overview.cannot' | translate\n }}</span>\n @if (roleKey) {\n <a\n class=\"ac__action-link\"\n [routerLink]=\"['/access-control', roleKey, 'json-editor']\"\n [queryParams]=\"{\n filterResourceType: resource.resourceType,\n filterAction: entry.action,\n }\"\n >\n <span class=\"ac__action-name\">{{\n 'accessControl.actions.' + entry.action | translate\n }}</span>\n </a>\n } @else {\n <span class=\"ac__action-name\">{{\n 'accessControl.actions.' + entry.action | translate\n }}</span>\n }\n }\n </div>\n }\n </section>\n }\n</div>\n\n<ng-template #conditionsTpl let-conditions=\"conditions\" let-resourceType=\"resourceType\">\n <div class=\"ac__conditions\">\n @for (\n condition of formatConditions(conditions, resourceType);\n track $index;\n let isFirst = $first\n ) {\n <div class=\"ac__condition\">\n @if (!isFirst) {\n <span class=\"ac__connective\">{{ 'accessControl.overview.and' | translate }}</span>\n }\n @switch (condition.kind) {\n @case ('field') {\n <span>{{ condition.field }}</span>\n @if (condition.customField) {\n <span class=\"ac__connective\">{{\n 'accessControl.overview.customField' | translate\n }}</span>\n }\n <span class=\"ac__operator\">{{ condition.operator }}</span>\n <span>{{ condition.value }}</span>\n }\n\n @case ('expression') {\n <span>{{ condition.field }}</span>\n @if (condition.customField) {\n <span class=\"ac__connective\">{{\n 'accessControl.overview.customField' | translate\n }}</span>\n }\n <span class=\"ac__connective\">{{\n 'accessControl.overview.atJsonPath' | translate\n }}</span>\n <code class=\"ac__path\" [title]=\"condition.clazz\">{{ condition.path }}</code>\n <span class=\"ac__operator\">{{ condition.operator }}</span>\n <span>{{ condition.value }}</span>\n }\n\n @case ('container') {\n <span class=\"ac__connective\">{{\n 'accessControl.overview.relatedResource' | translate\n }}</span>\n <span class=\"ac__resource-type\">{{\n condition.resourceType | resourceTypeLabel\n }}</span>\n @if (condition.customResource) {\n <span class=\"ac__connective\">{{\n 'accessControl.overview.customResource' | translate\n }}</span>\n }\n <span class=\"ac__connective\">{{\n 'accessControl.overview.where' | translate\n }}</span>\n <ng-container\n *ngTemplateOutlet=\"\n conditionsTpl;\n context: {\n conditions: condition.conditions,\n resourceType: condition.resourceType,\n }\n \"\n ></ng-container>\n }\n }\n </div>\n }\n </div>\n</ng-template>\n", styles: [".ac{display:flex;flex-direction:column;gap:2rem;padding:1rem}.ac__group{display:flex;flex-direction:column;gap:.75rem}.ac__heading{border-bottom:1px solid var(--cds-border-subtle, #c6c6c6);margin:0 0 .5rem;padding-bottom:.25rem}.ac__heading-link{color:inherit;text-decoration:none}.ac__heading-link:hover,.ac__heading-link:focus-visible{text-decoration:underline}.ac__action-link{color:inherit;text-decoration:none}.ac__action-link:hover,.ac__action-link:focus-visible{text-decoration:underline}.ac__fqn{color:var(--cds-text-secondary, #525252);font-family:var(--cds-code-01-font-family, monospace);font-size:.875rem;font-weight:400;margin-left:.5rem}.ac__action{line-height:1.6}.ac__action-name,.ac__resource-type{font-weight:600}.ac__operator{font-style:italic}.ac__connective{color:var(--cds-text-secondary, #525252);font-style:italic}.ac__no-access{color:var(--cds-text-error, #da1e28);font-style:italic}.ac__grant,.ac__or,.ac__conditions{padding-left:1.5rem}.ac__path{background-color:var(--cds-layer-accent, #e0e0e0);border-radius:2px;font-family:var(--cds-code-01-font-family, monospace);padding:0 .25rem}\n/*!\n * Copyright 2015-2026 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"] }]
|
|
1075
|
-
}], ctorParameters: () => [{ type: PermissionSchemaMetadataService }, { type:
|
|
2335
|
+
}], ctorParameters: () => [{ type: PermissionSchemaMetadataService }, { type: i1$2.TranslateService }], propDecorators: { roleKey: [{
|
|
1076
2336
|
type: Input
|
|
1077
2337
|
}], permissions: [{
|
|
1078
2338
|
type: Input
|
|
@@ -1093,8 +2353,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1093
2353
|
* See the License for the specific language governing permissions and
|
|
1094
2354
|
* limitations under the License.
|
|
1095
2355
|
*/
|
|
1096
|
-
class AccessControlEditorComponent {
|
|
2356
|
+
class AccessControlEditorComponent extends PendingChangesComponent {
|
|
1097
2357
|
constructor(accessControlService, route, pageTitleService, router, globalNotificationService, translateService, accessControlExportService, pageHeaderService) {
|
|
2358
|
+
super();
|
|
1098
2359
|
this.accessControlService = accessControlService;
|
|
1099
2360
|
this.route = route;
|
|
1100
2361
|
this.pageTitleService = pageTitleService;
|
|
@@ -1113,16 +2374,19 @@ class AccessControlEditorComponent {
|
|
|
1113
2374
|
this.showEditModal$ = new BehaviorSubject(false);
|
|
1114
2375
|
this.selectedRowKeys$ = new BehaviorSubject(null);
|
|
1115
2376
|
this.compactMode$ = this.pageHeaderService.compactMode$;
|
|
1116
|
-
this.$activeTab = signal(AccessControlEditorTab.
|
|
2377
|
+
this.$activeTab = signal(AccessControlEditorTab.EDITOR);
|
|
1117
2378
|
this.AccessControlEditorTab = AccessControlEditorTab;
|
|
1118
2379
|
this._updatedModelValue$ = new BehaviorSubject('');
|
|
2380
|
+
// The form's serialized value captured on (re)load. Edits that differ from it mark the editor
|
|
2381
|
+
// dirty so the leave-page guard can warn about unsaved changes.
|
|
2382
|
+
this._pendingBaseline = null;
|
|
1119
2383
|
}
|
|
1120
2384
|
ngOnInit() {
|
|
1121
2385
|
this.restoreActiveTabFromUrl();
|
|
1122
|
-
this.getPermissions();
|
|
1123
2386
|
this.openRoleKeySubscription();
|
|
1124
2387
|
}
|
|
1125
2388
|
ngOnDestroy() {
|
|
2389
|
+
this.pendingChanges = false;
|
|
1126
2390
|
this.pageTitleService.enableReset();
|
|
1127
2391
|
this._roleKeySubscription?.unsubscribe();
|
|
1128
2392
|
}
|
|
@@ -1130,9 +2394,50 @@ class AccessControlEditorComponent {
|
|
|
1130
2394
|
this.saveDisabled$.next(valid === false);
|
|
1131
2395
|
}
|
|
1132
2396
|
onValueChange(value) {
|
|
2397
|
+
// The first emission after a (re)load is the editor's baseline. Comparison is done on the
|
|
2398
|
+
// normalized (whitespace-insensitive) JSON so the JSON editor's format-on-load reflow is not
|
|
2399
|
+
// mistaken for an edit; only a genuine content change marks the editor dirty. Each tab keeps
|
|
2400
|
+
// its own baseline (the editor reloads from the saved model per tab), so the JSON and visual
|
|
2401
|
+
// editors track their unsaved state independently.
|
|
2402
|
+
const normalized = this.normalizeJson(value);
|
|
2403
|
+
if (this._pendingBaseline === null) {
|
|
2404
|
+
this._pendingBaseline = normalized;
|
|
2405
|
+
this.pendingChanges = false;
|
|
2406
|
+
}
|
|
2407
|
+
else {
|
|
2408
|
+
this.pendingChanges = normalized !== this._pendingBaseline;
|
|
2409
|
+
}
|
|
1133
2410
|
this._updatedModelValue$.next(value);
|
|
1134
2411
|
}
|
|
2412
|
+
// Canonicalizes a JSON string by dropping insignificant whitespace, so reformatting (such as the
|
|
2413
|
+
// JSON editor's format-on-load) does not register as a change. Invalid JSON is compared as-is.
|
|
2414
|
+
normalizeJson(value) {
|
|
2415
|
+
try {
|
|
2416
|
+
return JSON.stringify(JSON.parse(value));
|
|
2417
|
+
}
|
|
2418
|
+
catch {
|
|
2419
|
+
return value;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
onCancelRedirect() {
|
|
2423
|
+
this.syncTabHeaders();
|
|
2424
|
+
}
|
|
2425
|
+
// The Carbon tab header highlights the clicked tab immediately; if the leave-page prompt is
|
|
2426
|
+
// cancelled, restore the highlight to the tab that is actually still active.
|
|
2427
|
+
syncTabHeaders() {
|
|
2428
|
+
const order = [
|
|
2429
|
+
AccessControlEditorTab.EDITOR,
|
|
2430
|
+
AccessControlEditorTab.SUMMARY,
|
|
2431
|
+
AccessControlEditorTab.JSON_EDITOR,
|
|
2432
|
+
];
|
|
2433
|
+
this._tabs?.forEach((tab, index) => {
|
|
2434
|
+
tab.active = order[index] === this.$activeTab();
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
1135
2437
|
updatePermissions() {
|
|
2438
|
+
// Preserve the open rule across the reload that follows this save (a one-shot; only after an
|
|
2439
|
+
// explicit save, never on an ordinary tab open).
|
|
2440
|
+
this._formEditorTab?.prepareSelectionReopen();
|
|
1136
2441
|
this.disableEditor();
|
|
1137
2442
|
this.disableSave();
|
|
1138
2443
|
this.disableMore();
|
|
@@ -1188,18 +2493,30 @@ class AccessControlEditorComponent {
|
|
|
1188
2493
|
this.$activeTab.set(tab);
|
|
1189
2494
|
return;
|
|
1190
2495
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
2496
|
+
this.router.navigate(this.segmentsForTab(tab, roleKey));
|
|
2497
|
+
}
|
|
2498
|
+
segmentsForTab(tab, roleKey) {
|
|
2499
|
+
switch (tab) {
|
|
2500
|
+
case AccessControlEditorTab.SUMMARY:
|
|
2501
|
+
return ['/access-control', roleKey, 'summary'];
|
|
2502
|
+
case AccessControlEditorTab.JSON_EDITOR:
|
|
2503
|
+
return ['/access-control', roleKey, 'json-editor'];
|
|
2504
|
+
default:
|
|
2505
|
+
return ['/access-control', roleKey];
|
|
2506
|
+
}
|
|
1195
2507
|
}
|
|
1196
2508
|
restoreActiveTabFromUrl() {
|
|
1197
2509
|
const url = this.route.snapshot.url;
|
|
1198
2510
|
const lastSegment = url[url.length - 1]?.path;
|
|
2511
|
+
if (lastSegment === 'summary') {
|
|
2512
|
+
this.$activeTab.set(AccessControlEditorTab.SUMMARY);
|
|
2513
|
+
return;
|
|
2514
|
+
}
|
|
1199
2515
|
if (lastSegment === 'json-editor') {
|
|
1200
2516
|
this.$activeTab.set(AccessControlEditorTab.JSON_EDITOR);
|
|
1201
2517
|
return;
|
|
1202
2518
|
}
|
|
2519
|
+
// The base URL (no tab segment) opens the visual editor — the first and default tab.
|
|
1203
2520
|
const params = this.route.snapshot.queryParamMap;
|
|
1204
2521
|
if (params.get('filterResourceType') || params.get('filterAction')) {
|
|
1205
2522
|
this.$activeTab.set(AccessControlEditorTab.JSON_EDITOR);
|
|
@@ -1225,21 +2542,11 @@ class AccessControlEditorComponent {
|
|
|
1225
2542
|
}))
|
|
1226
2543
|
.subscribe();
|
|
1227
2544
|
}
|
|
1228
|
-
getPermissions() {
|
|
1229
|
-
this.route.params
|
|
1230
|
-
.pipe(tap(params => {
|
|
1231
|
-
this.pageTitleService.setCustomPageTitle(params?.id);
|
|
1232
|
-
this.roleKey$.next(params?.id ?? null);
|
|
1233
|
-
this.selectedRowKeys$.next([params?.id]);
|
|
1234
|
-
}), switchMap(params => this.accessControlService.getRolePermissions(params.id)))
|
|
1235
|
-
.subscribe(permissions => {
|
|
1236
|
-
this.enableMore();
|
|
1237
|
-
this.enableSave();
|
|
1238
|
-
this.enableEditor();
|
|
1239
|
-
this.setModel(permissions);
|
|
1240
|
-
});
|
|
1241
|
-
}
|
|
1242
2545
|
setModel(permissions) {
|
|
2546
|
+
// A freshly loaded or saved model is the new clean baseline; the next value emission recaptures
|
|
2547
|
+
// it and any prior unsaved-changes flag is cleared.
|
|
2548
|
+
this._pendingBaseline = null;
|
|
2549
|
+
this.pendingChanges = false;
|
|
1243
2550
|
const roleKey = this.roleKey$.value ?? 'unknown';
|
|
1244
2551
|
this.model$.next({
|
|
1245
2552
|
value: JSON.stringify(permissions),
|
|
@@ -1275,13 +2582,19 @@ class AccessControlEditorComponent {
|
|
|
1275
2582
|
type: 'success',
|
|
1276
2583
|
});
|
|
1277
2584
|
}
|
|
1278
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlEditorComponent, deps: [{ token: AccessControlService }, { token: i1$
|
|
1279
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
2585
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlEditorComponent, deps: [{ token: AccessControlService }, { token: i1$3.ActivatedRoute }, { token: i3$1.PageTitleService }, { token: i1$3.Router }, { token: i1$1.GlobalNotificationService }, { token: i1$2.TranslateService }, { token: AccessControlExportService }, { token: i3$1.PageHeaderService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2586
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: AccessControlEditorComponent, isStandalone: false, selector: "ng-component", viewQueries: [{ propertyName: "_formEditorTab", first: true, predicate: AccessControlFormEditorTabComponent, descendants: true }, { propertyName: "_tabs", predicate: Tab, descendants: true }], usesInheritance: true, ngImport: i0, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<ng-container *ngIf=\"model$ | async as model; else loading\">\n <!-- The headers only switch the active tab; the tab content is rendered below (gated by the\n active-tab signal) instead of inside the Carbon tab panes. Carbon removes a pane's content\n as soon as another header is clicked, which would destroy the editor's unsaved form before\n the leave-page confirmation is resolved. -->\n <cds-tabs type=\"contained\">\n <cds-tab\n [active]=\"$activeTab() === AccessControlEditorTab.EDITOR\"\n [heading]=\"'accessControl.roles.tabs.editor' | translate\"\n (selected)=\"setActiveTab(AccessControlEditorTab.EDITOR)\"\n ></cds-tab>\n\n <cds-tab\n [active]=\"$activeTab() === AccessControlEditorTab.SUMMARY\"\n [heading]=\"'accessControl.roles.tabs.summary' | translate\"\n (selected)=\"setActiveTab(AccessControlEditorTab.SUMMARY)\"\n ></cds-tab>\n\n <cds-tab\n [active]=\"$activeTab() === AccessControlEditorTab.JSON_EDITOR\"\n [heading]=\"'accessControl.roles.tabs.jsonEditor' | translate\"\n (selected)=\"setActiveTab(AccessControlEditorTab.JSON_EDITOR)\"\n ></cds-tab>\n </cds-tabs>\n\n <!-- White surface for the tab content. The Carbon tab pane used to provide this; since the\n content is now rendered outside the panes, the wrapper supplies the layer background. -->\n <div class=\"access-control-editor__content\">\n @if ($activeTab() === AccessControlEditorTab.EDITOR) {\n <valtimo-access-control-form-editor-tab\n [disabled]=\"editorDisabled$ | async\"\n [model]=\"model\"\n [roleKey]=\"roleKey$ | async\"\n (validEvent)=\"onValid($event)\"\n (valueChangeEvent)=\"onValueChange($event)\"\n ></valtimo-access-control-form-editor-tab>\n }\n\n @if ($activeTab() === AccessControlEditorTab.SUMMARY) {\n <valtimo-access-control-overview-tab\n [permissions]=\"permissions$ | async\"\n [roleKey]=\"roleKey$ | async\"\n ></valtimo-access-control-overview-tab>\n }\n\n @if ($activeTab() === AccessControlEditorTab.JSON_EDITOR) {\n <valtimo-access-control-json-editor-tab\n [disabled]=\"editorDisabled$ | async\"\n [model]=\"model\"\n (validEvent)=\"onValid($event)\"\n (valueChangeEvent)=\"onValueChange($event)\"\n ></valtimo-access-control-json-editor-tab>\n }\n </div>\n</ng-container>\n\n<ng-container renderInPageHeader>\n <ng-template>\n <div\n *ngIf=\"{\n model: model$ | async,\n moreDisabled: moreDisabled$ | async,\n compactMode: compactMode$ | async,\n } as obs\"\n class=\"buttons-container\"\n >\n <v-overflow-menu class=\"overflow-button\" placement=\"bottom-end\">\n <v-overflow-menu-trigger overflowTrigger></v-overflow-menu-trigger>\n <v-overflow-menu-option [disabled]=\"obs.moreDisabled\" (selected)=\"showEditModal()\">{{\n 'accessControl.roles.editMetadata' | translate\n }}</v-overflow-menu-option>\n\n <v-overflow-menu-option [disabled]=\"obs.moreDisabled\" (selected)=\"exportPermissions()\">{{\n 'interface.export' | translate\n }}</v-overflow-menu-option>\n\n <v-overflow-menu-option\n [disabled]=\"obs.moreDisabled\"\n type=\"danger\"\n (selected)=\"showDeleteModal()\"\n >{{ 'interface.delete' | translate }}</v-overflow-menu-option\n >\n </v-overflow-menu>\n\n <button\n [disabled]=\"saveDisabled$ | async\"\n cdsButton=\"primary\"\n [size]=\"obs.compactMode ? 'sm' : 'md'\"\n (click)=\"updatePermissions()\"\n >\n {{ 'interface.save' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"save\" size=\"16\"></svg>\n </button>\n </div>\n </ng-template>\n</ng-container>\n\n<ng-container *ngIf=\"{selectedRowKeys: selectedRowKeys$ | async} as obs\">\n <valtimo-delete-role-modal\n [deleteRowKeys]=\"obs.selectedRowKeys\"\n [showDeleteModal$]=\"showDeleteModal$\"\n (deleteEvent)=\"onDelete($event)\"\n >\n </valtimo-delete-role-modal>\n\n <valtimo-role-metadata-modal\n [defaultKeyValue]=\"obs.selectedRowKeys[0]\"\n [open]=\"showEditModal$ | async\"\n type=\"edit\"\n (closeEvent)=\"onEdit(obs.selectedRowKeys[0], $event)\"\n ></valtimo-role-metadata-modal>\n</ng-container>\n\n<ng-template #loading>\n <div class=\"loading-container\">\n <cds-loading></cds-loading>\n </div>\n</ng-template>\n", styles: [".loading-container{display:flex;width:100%;justify-content:center}.overflow-button{margin-right:16px}.buttons-container{display:flex;flex-direction:row;align-items:center}.buttons-container button{height:min-content}:host ::ng-deep .cds--tab-content{background-color:var(--cds-layer);outline:none!important;padding:0}.access-control-editor__content{background-color:var(--cds-layer)}\n/*!\n * Copyright 2015-2025 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"], dependencies: [{ kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$1.Button, selector: "[cdsButton], [ibmButton]", inputs: ["ibmButton", "cdsButton", "size", "skeleton", "iconOnly", "isExpressive"] }, { kind: "directive", type: i2$1.IconDirective, selector: "[cdsIcon], [ibmIcon]", inputs: ["ibmIcon", "cdsIcon", "size", "title", "ariaLabel", "ariaLabelledBy", "ariaHidden", "isFocusable"] }, { kind: "directive", type: i3$1.RenderInPageHeaderDirective, selector: "[renderInPageHeader]", inputs: ["fullWidth"] }, { kind: "component", type: i2$1.Loading, selector: "cds-loading, ibm-loading", inputs: ["title", "isActive", "size", "overlay"] }, { kind: "component", type: i3$1.OverflowMenuComponent, selector: "v-overflow-menu", inputs: ["open", "placement", "menuWidth", "offsetX", "offsetY", "closeOnSelect", "useHostAsReference", "portalToBody"], outputs: ["openChange"] }, { kind: "component", type: i3$1.OverflowMenuOptionComponent, selector: "v-overflow-menu-option", inputs: ["disabled", "type", "testId", "optionId"], outputs: ["selected"] }, { kind: "component", type: i3$1.OverflowMenuTriggerComponent, selector: "v-overflow-menu-trigger", inputs: ["compact"] }, { kind: "component", type: i2$1.Tabs, selector: "cds-tabs, ibm-tabs", inputs: ["position", "cacheActive", "followFocus", "isNavigation", "ariaLabel", "ariaLabelledby", "type", "theme", "skeleton"] }, { kind: "component", type: i2$1.Tab, selector: "cds-tab, ibm-tab", inputs: ["heading", "title", "context", "active", "disabled", "tabIndex", "id", "cacheActive", "tabContent", "templateContext"], outputs: ["selected"] }, { kind: "component", type: AccessControlJsonEditorTabComponent, selector: "valtimo-access-control-json-editor-tab", inputs: ["disabled", "model"], outputs: ["validEvent", "valueChangeEvent"] }, { kind: "component", type: AccessControlOverviewTabComponent, selector: "valtimo-access-control-overview-tab", inputs: ["roleKey", "permissions"] }, { kind: "component", type: RoleMetadataModalComponent, selector: "valtimo-role-metadata-modal", inputs: ["type", "open", "defaultKeyValue", "usedKeys"], outputs: ["closeEvent"] }, { kind: "component", type: AccessControlFormEditorTabComponent, selector: "valtimo-access-control-form-editor-tab", inputs: ["model", "disabled", "roleKey"], outputs: ["validEvent", "valueChangeEvent"] }, { kind: "component", type: DeleteRoleModalComponent, selector: "valtimo-delete-role-modal", inputs: ["deleteRowKeys", "showDeleteModal$"], outputs: ["deleteEvent"] }, { kind: "pipe", type: i3.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$2.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1280
2587
|
}
|
|
1281
2588
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlEditorComponent, decorators: [{
|
|
1282
2589
|
type: Component,
|
|
1283
|
-
args: [{ standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<ng-container *ngIf=\"model$ | async as model; else loading\">\n <cds-tabs type=\"contained\">\n <cds-tab\n [active]=\"$activeTab() === AccessControlEditorTab.
|
|
1284
|
-
}], ctorParameters: () => [{ type: AccessControlService }, { type: i1$
|
|
2590
|
+
args: [{ standalone: false, changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n ~ Copyright 2015-2025 Ritense BV, the Netherlands.\n ~\n ~ Licensed under EUPL, Version 1.2 (the \"License\");\n ~ you may not use this file except in compliance with the License.\n ~ You may obtain a copy of the License at\n ~\n ~ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n ~\n ~ Unless required by applicable law or agreed to in writing, software\n ~ distributed under the License is distributed on an \"AS IS\" basis,\n ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n ~ See the License for the specific language governing permissions and\n ~ limitations under the License.\n -->\n\n<ng-container *ngIf=\"model$ | async as model; else loading\">\n <!-- The headers only switch the active tab; the tab content is rendered below (gated by the\n active-tab signal) instead of inside the Carbon tab panes. Carbon removes a pane's content\n as soon as another header is clicked, which would destroy the editor's unsaved form before\n the leave-page confirmation is resolved. -->\n <cds-tabs type=\"contained\">\n <cds-tab\n [active]=\"$activeTab() === AccessControlEditorTab.EDITOR\"\n [heading]=\"'accessControl.roles.tabs.editor' | translate\"\n (selected)=\"setActiveTab(AccessControlEditorTab.EDITOR)\"\n ></cds-tab>\n\n <cds-tab\n [active]=\"$activeTab() === AccessControlEditorTab.SUMMARY\"\n [heading]=\"'accessControl.roles.tabs.summary' | translate\"\n (selected)=\"setActiveTab(AccessControlEditorTab.SUMMARY)\"\n ></cds-tab>\n\n <cds-tab\n [active]=\"$activeTab() === AccessControlEditorTab.JSON_EDITOR\"\n [heading]=\"'accessControl.roles.tabs.jsonEditor' | translate\"\n (selected)=\"setActiveTab(AccessControlEditorTab.JSON_EDITOR)\"\n ></cds-tab>\n </cds-tabs>\n\n <!-- White surface for the tab content. The Carbon tab pane used to provide this; since the\n content is now rendered outside the panes, the wrapper supplies the layer background. -->\n <div class=\"access-control-editor__content\">\n @if ($activeTab() === AccessControlEditorTab.EDITOR) {\n <valtimo-access-control-form-editor-tab\n [disabled]=\"editorDisabled$ | async\"\n [model]=\"model\"\n [roleKey]=\"roleKey$ | async\"\n (validEvent)=\"onValid($event)\"\n (valueChangeEvent)=\"onValueChange($event)\"\n ></valtimo-access-control-form-editor-tab>\n }\n\n @if ($activeTab() === AccessControlEditorTab.SUMMARY) {\n <valtimo-access-control-overview-tab\n [permissions]=\"permissions$ | async\"\n [roleKey]=\"roleKey$ | async\"\n ></valtimo-access-control-overview-tab>\n }\n\n @if ($activeTab() === AccessControlEditorTab.JSON_EDITOR) {\n <valtimo-access-control-json-editor-tab\n [disabled]=\"editorDisabled$ | async\"\n [model]=\"model\"\n (validEvent)=\"onValid($event)\"\n (valueChangeEvent)=\"onValueChange($event)\"\n ></valtimo-access-control-json-editor-tab>\n }\n </div>\n</ng-container>\n\n<ng-container renderInPageHeader>\n <ng-template>\n <div\n *ngIf=\"{\n model: model$ | async,\n moreDisabled: moreDisabled$ | async,\n compactMode: compactMode$ | async,\n } as obs\"\n class=\"buttons-container\"\n >\n <v-overflow-menu class=\"overflow-button\" placement=\"bottom-end\">\n <v-overflow-menu-trigger overflowTrigger></v-overflow-menu-trigger>\n <v-overflow-menu-option [disabled]=\"obs.moreDisabled\" (selected)=\"showEditModal()\">{{\n 'accessControl.roles.editMetadata' | translate\n }}</v-overflow-menu-option>\n\n <v-overflow-menu-option [disabled]=\"obs.moreDisabled\" (selected)=\"exportPermissions()\">{{\n 'interface.export' | translate\n }}</v-overflow-menu-option>\n\n <v-overflow-menu-option\n [disabled]=\"obs.moreDisabled\"\n type=\"danger\"\n (selected)=\"showDeleteModal()\"\n >{{ 'interface.delete' | translate }}</v-overflow-menu-option\n >\n </v-overflow-menu>\n\n <button\n [disabled]=\"saveDisabled$ | async\"\n cdsButton=\"primary\"\n [size]=\"obs.compactMode ? 'sm' : 'md'\"\n (click)=\"updatePermissions()\"\n >\n {{ 'interface.save' | translate }}\n\n <svg class=\"cds--btn__icon\" cdsIcon=\"save\" size=\"16\"></svg>\n </button>\n </div>\n </ng-template>\n</ng-container>\n\n<ng-container *ngIf=\"{selectedRowKeys: selectedRowKeys$ | async} as obs\">\n <valtimo-delete-role-modal\n [deleteRowKeys]=\"obs.selectedRowKeys\"\n [showDeleteModal$]=\"showDeleteModal$\"\n (deleteEvent)=\"onDelete($event)\"\n >\n </valtimo-delete-role-modal>\n\n <valtimo-role-metadata-modal\n [defaultKeyValue]=\"obs.selectedRowKeys[0]\"\n [open]=\"showEditModal$ | async\"\n type=\"edit\"\n (closeEvent)=\"onEdit(obs.selectedRowKeys[0], $event)\"\n ></valtimo-role-metadata-modal>\n</ng-container>\n\n<ng-template #loading>\n <div class=\"loading-container\">\n <cds-loading></cds-loading>\n </div>\n</ng-template>\n", styles: [".loading-container{display:flex;width:100%;justify-content:center}.overflow-button{margin-right:16px}.buttons-container{display:flex;flex-direction:row;align-items:center}.buttons-container button{height:min-content}:host ::ng-deep .cds--tab-content{background-color:var(--cds-layer);outline:none!important;padding:0}.access-control-editor__content{background-color:var(--cds-layer)}\n/*!\n * Copyright 2015-2025 Ritense BV, the Netherlands.\n *\n * Licensed under EUPL, Version 1.2 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" basis,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"] }]
|
|
2591
|
+
}], ctorParameters: () => [{ type: AccessControlService }, { type: i1$3.ActivatedRoute }, { type: i3$1.PageTitleService }, { type: i1$3.Router }, { type: i1$1.GlobalNotificationService }, { type: i1$2.TranslateService }, { type: AccessControlExportService }, { type: i3$1.PageHeaderService }], propDecorators: { _tabs: [{
|
|
2592
|
+
type: ViewChildren,
|
|
2593
|
+
args: [Tab]
|
|
2594
|
+
}], _formEditorTab: [{
|
|
2595
|
+
type: ViewChild,
|
|
2596
|
+
args: [AccessControlFormEditorTabComponent]
|
|
2597
|
+
}] } });
|
|
1285
2598
|
|
|
1286
2599
|
/*
|
|
1287
2600
|
* Copyright 2015-2025 Ritense BV, the Netherlands.
|
|
@@ -1309,6 +2622,18 @@ const routes = [
|
|
|
1309
2622
|
path: 'access-control/:id',
|
|
1310
2623
|
component: AccessControlEditorComponent,
|
|
1311
2624
|
canActivate: [AuthGuardService],
|
|
2625
|
+
canDeactivate: [pendingChangesGuard],
|
|
2626
|
+
data: {
|
|
2627
|
+
title: 'Role details',
|
|
2628
|
+
roles: [ROLE_ADMIN],
|
|
2629
|
+
customPageTitle: true,
|
|
2630
|
+
},
|
|
2631
|
+
},
|
|
2632
|
+
{
|
|
2633
|
+
path: 'access-control/:id/summary',
|
|
2634
|
+
component: AccessControlEditorComponent,
|
|
2635
|
+
canActivate: [AuthGuardService],
|
|
2636
|
+
canDeactivate: [pendingChangesGuard],
|
|
1312
2637
|
data: {
|
|
1313
2638
|
title: 'Role details',
|
|
1314
2639
|
roles: [ROLE_ADMIN],
|
|
@@ -1319,6 +2644,7 @@ const routes = [
|
|
|
1319
2644
|
path: 'access-control/:id/json-editor',
|
|
1320
2645
|
component: AccessControlEditorComponent,
|
|
1321
2646
|
canActivate: [AuthGuardService],
|
|
2647
|
+
canDeactivate: [pendingChangesGuard],
|
|
1322
2648
|
data: {
|
|
1323
2649
|
title: 'Role details',
|
|
1324
2650
|
roles: [ROLE_ADMIN],
|
|
@@ -1328,7 +2654,7 @@ const routes = [
|
|
|
1328
2654
|
];
|
|
1329
2655
|
class AccessControlManagementRoutingModule {
|
|
1330
2656
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlManagementRoutingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
1331
|
-
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: AccessControlManagementRoutingModule, imports: [CommonModule, i1$
|
|
2657
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: AccessControlManagementRoutingModule, imports: [CommonModule, i1$3.RouterModule], exports: [RouterModule] }); }
|
|
1332
2658
|
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlManagementRoutingModule, imports: [CommonModule, RouterModule.forChild(routes), RouterModule] }); }
|
|
1333
2659
|
}
|
|
1334
2660
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlManagementRoutingModule, decorators: [{
|
|
@@ -1359,8 +2685,11 @@ class AccessControlManagementModule {
|
|
|
1359
2685
|
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: AccessControlManagementModule, declarations: [AccessControlOverviewComponent,
|
|
1360
2686
|
RoleMetadataModalComponent,
|
|
1361
2687
|
AccessControlEditorComponent,
|
|
2688
|
+
AccessControlFormEditorTabComponent,
|
|
2689
|
+
ConditionTreeComponent,
|
|
1362
2690
|
DeleteRoleModalComponent,
|
|
1363
|
-
ExportRoleModalComponent
|
|
2691
|
+
ExportRoleModalComponent,
|
|
2692
|
+
PermissionFormComponent], imports: [CommonModule,
|
|
1364
2693
|
AccessControlManagementRoutingModule,
|
|
1365
2694
|
ButtonModule,
|
|
1366
2695
|
FormsModule,
|
|
@@ -1371,6 +2700,7 @@ class AccessControlManagementModule {
|
|
|
1371
2700
|
IconModule,
|
|
1372
2701
|
ConfirmationModalModule,
|
|
1373
2702
|
RenderInPageHeaderDirective,
|
|
2703
|
+
FitPageDirective,
|
|
1374
2704
|
LoadingModule,
|
|
1375
2705
|
IconModule,
|
|
1376
2706
|
OverflowMenuComponent,
|
|
@@ -1378,9 +2708,20 @@ class AccessControlManagementModule {
|
|
|
1378
2708
|
OverflowMenuTriggerComponent,
|
|
1379
2709
|
NotificationModule,
|
|
1380
2710
|
CarbonListModule,
|
|
2711
|
+
SelectModule,
|
|
2712
|
+
TooltipIconModule,
|
|
1381
2713
|
TabsModule,
|
|
2714
|
+
ToggleModule,
|
|
2715
|
+
ContentSwitcherModule,
|
|
2716
|
+
AccordionModule,
|
|
2717
|
+
CheckboxModule,
|
|
2718
|
+
LayerModule,
|
|
2719
|
+
TagModule,
|
|
1382
2720
|
AccessControlJsonEditorTabComponent,
|
|
1383
|
-
AccessControlOverviewTabComponent
|
|
2721
|
+
AccessControlOverviewTabComponent,
|
|
2722
|
+
ActionLabelPipe,
|
|
2723
|
+
ActionDescriptionPipe,
|
|
2724
|
+
ActionTagTypePipe] }); }
|
|
1384
2725
|
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: AccessControlManagementModule, imports: [CommonModule,
|
|
1385
2726
|
AccessControlManagementRoutingModule,
|
|
1386
2727
|
ButtonModule,
|
|
@@ -1398,7 +2739,15 @@ class AccessControlManagementModule {
|
|
|
1398
2739
|
OverflowMenuTriggerComponent,
|
|
1399
2740
|
NotificationModule,
|
|
1400
2741
|
CarbonListModule,
|
|
2742
|
+
SelectModule,
|
|
2743
|
+
TooltipIconModule,
|
|
1401
2744
|
TabsModule,
|
|
2745
|
+
ToggleModule,
|
|
2746
|
+
ContentSwitcherModule,
|
|
2747
|
+
AccordionModule,
|
|
2748
|
+
CheckboxModule,
|
|
2749
|
+
LayerModule,
|
|
2750
|
+
TagModule,
|
|
1402
2751
|
AccessControlJsonEditorTabComponent,
|
|
1403
2752
|
AccessControlOverviewTabComponent] }); }
|
|
1404
2753
|
}
|
|
@@ -1409,8 +2758,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1409
2758
|
AccessControlOverviewComponent,
|
|
1410
2759
|
RoleMetadataModalComponent,
|
|
1411
2760
|
AccessControlEditorComponent,
|
|
2761
|
+
AccessControlFormEditorTabComponent,
|
|
2762
|
+
ConditionTreeComponent,
|
|
1412
2763
|
DeleteRoleModalComponent,
|
|
1413
2764
|
ExportRoleModalComponent,
|
|
2765
|
+
PermissionFormComponent,
|
|
1414
2766
|
],
|
|
1415
2767
|
imports: [
|
|
1416
2768
|
CommonModule,
|
|
@@ -1424,6 +2776,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1424
2776
|
IconModule,
|
|
1425
2777
|
ConfirmationModalModule,
|
|
1426
2778
|
RenderInPageHeaderDirective,
|
|
2779
|
+
FitPageDirective,
|
|
1427
2780
|
LoadingModule,
|
|
1428
2781
|
IconModule,
|
|
1429
2782
|
OverflowMenuComponent,
|
|
@@ -1431,9 +2784,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1431
2784
|
OverflowMenuTriggerComponent,
|
|
1432
2785
|
NotificationModule,
|
|
1433
2786
|
CarbonListModule,
|
|
2787
|
+
SelectModule,
|
|
2788
|
+
TooltipIconModule,
|
|
1434
2789
|
TabsModule,
|
|
2790
|
+
ToggleModule,
|
|
2791
|
+
ContentSwitcherModule,
|
|
2792
|
+
AccordionModule,
|
|
2793
|
+
CheckboxModule,
|
|
2794
|
+
LayerModule,
|
|
2795
|
+
TagModule,
|
|
1435
2796
|
AccessControlJsonEditorTabComponent,
|
|
1436
2797
|
AccessControlOverviewTabComponent,
|
|
2798
|
+
ActionLabelPipe,
|
|
2799
|
+
ActionDescriptionPipe,
|
|
2800
|
+
ActionTagTypePipe,
|
|
1437
2801
|
],
|
|
1438
2802
|
}]
|
|
1439
2803
|
}] });
|
|
@@ -1461,5 +2825,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
1461
2825
|
* Generated bundle index. Do not edit.
|
|
1462
2826
|
*/
|
|
1463
2827
|
|
|
1464
|
-
export { AccessControlEditorTab, AccessControlManagementModule, AccessControlService, PermissionSchemaMetadataService };
|
|
2828
|
+
export { AccessControlEditorTab, AccessControlFormEditorService, AccessControlManagementModule, AccessControlService, PermissionSchemaMetadataService };
|
|
1465
2829
|
//# sourceMappingURL=valtimo-access-control-management.mjs.map
|