@openziti/ziti-console-lib 1.5.2 → 1.5.3

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.
@@ -30,6 +30,8 @@ import * as i5$3 from '@angular/material/select';
30
30
  import { MatSelect, MatSelectModule } from '@angular/material/select';
31
31
  import * as i1$4 from '@angular/cdk/overlay';
32
32
  import { OverlayModule } from '@angular/cdk/overlay';
33
+ import * as i6$1 from '@angular/cdk/drag-drop';
34
+ import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
33
35
  import { catchError, map as map$1, distinctUntilChanged, debounceTime, skip, takeUntil, throttleTime, filter as filter$1 } from 'rxjs/operators';
34
36
  import * as i1$2 from 'ngx-logger';
35
37
  import { NgxLoggerLevel } from 'ngx-logger';
@@ -2914,9 +2916,13 @@ class DataTableFilterService {
2914
2916
  this.activatedRoute = activatedRoute;
2915
2917
  this.filters = [];
2916
2918
  this.currentPage = 1;
2919
+ /** Rows requested per page. Defaults to the legacy page size; the list-table's
2920
+ * rows-per-page control changes it via {@link changePageSize}. */
2921
+ this.pageSize = 50;
2917
2922
  this.filtersChanged = new BehaviorSubject(this.filters);
2918
2923
  this.filtering = new BehaviorSubject(false);
2919
2924
  this.pageChanged = new BehaviorSubject(this.currentPage);
2925
+ this.pageSizeChanged = new BehaviorSubject(this.pageSize);
2920
2926
  this.currentQueryParams = [];
2921
2927
  }
2922
2928
  updateFilter(filterObj) {
@@ -2962,6 +2968,11 @@ class DataTableFilterService {
2962
2968
  this.currentPage = page;
2963
2969
  this.pageChanged.next(page);
2964
2970
  }
2971
+ changePageSize(size) {
2972
+ this.pageSize = size;
2973
+ this.currentPage = 1;
2974
+ this.pageSizeChanged.next(size);
2975
+ }
2965
2976
  clearFilters() {
2966
2977
  this.filters = [];
2967
2978
  this.updateUrlParameters();
@@ -3510,6 +3521,14 @@ let ListPageServiceClass = class ListPageServiceClass {
3510
3521
  this.basePath = pathSegments[0];
3511
3522
  }
3512
3523
  });
3524
+ // The subscription above misses the NavigationEnd that already fired before
3525
+ // this root-provided service was lazily constructed (first page load), which
3526
+ // would leave basePath empty and send name links / openEditForm to "/<id>".
3527
+ // Seed it from the current route immediately.
3528
+ const currentConfig = this.router?.routerState?.snapshot?.root?.firstChild?.routeConfig;
3529
+ if (currentConfig?.path) {
3530
+ this.basePath = currentConfig.path.split('/')[0];
3531
+ }
3513
3532
  }
3514
3533
  /**
3515
3534
  * Root-provided page services survive route changes; restore baseline menus before each table init
@@ -3702,6 +3721,7 @@ class ListPageComponent {
3702
3721
  this.filterApplied = false;
3703
3722
  this.modalOpen = false;
3704
3723
  this.gridObj = {};
3724
+ this.tableObj = null;
3705
3725
  this.subscription = new Subscription();
3706
3726
  this.growlerSvc = inject(GrowlerService);
3707
3727
  this.managementPermissions = inject(ManagementPermissionsService);
@@ -3712,7 +3732,7 @@ class ListPageComponent {
3712
3732
  this.svc.refreshData = this.refreshData.bind(this);
3713
3733
  this.svc.resetMenusForInit();
3714
3734
  this.svc.initMenuActions();
3715
- this.columnDefs = this.svc.initTableColumns();
3735
+ this.columnDefs = this.loadTableColumns();
3716
3736
  this.svc.capturePermissionSnapshots();
3717
3737
  this.subscription.add(this.managementPermissions.stateVersion$.subscribe(() => {
3718
3738
  this.svc.rebuildMenusAndHeaders(this.managementPermissions);
@@ -3731,6 +3751,9 @@ class ListPageComponent {
3731
3751
  this.subscription.add(this.filterService.pageChanged.pipe(skip(1)).subscribe(page => {
3732
3752
  this.refreshData();
3733
3753
  }));
3754
+ this.subscription.add(this.filterService.pageSizeChanged.pipe(skip(1)).subscribe(() => {
3755
+ this.refreshData();
3756
+ }));
3734
3757
  this.subscription.add(this.consoleEvents.closeSideModal.subscribe((event) => {
3735
3758
  this.closeModal();
3736
3759
  }));
@@ -3751,7 +3774,8 @@ class ListPageComponent {
3751
3774
  const index = this.svc.tableHeaderActions.findIndex((a) => a.action === 'download-selected');
3752
3775
  const allow = this.managementPermissions.canRead(this.svc.resourceType);
3753
3776
  if (hasSelection && allow && index === -1) {
3754
- this.svc.tableHeaderActions.push({ name: 'Download Selected', action: 'download-selected' });
3777
+ // icon (SVG) is rendered by the new list-table's header menu; legacy ignores it
3778
+ this.svc.tableHeaderActions.push({ name: 'Download Selected', action: 'download-selected', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M8 2.5v7M5 6.6 8 9.6l3-3M3.2 12.6h9.6"/></svg>' });
3755
3779
  }
3756
3780
  if ((!hasSelection || !allow) && index !== -1) {
3757
3781
  this.svc.tableHeaderActions.splice(index, 1);
@@ -3782,7 +3806,7 @@ class ListPageComponent {
3782
3806
  }
3783
3807
  this.isLoading = true;
3784
3808
  sort = sort ? sort : this.svc.currentSort;
3785
- this.svc.getData(this.filterService.filters, sort, this.filterService.currentPage)
3809
+ this.svc.getData(this.filterService.filters, sort, this.filterService.currentPage, this.filterService.pageSize)
3786
3810
  .then((data) => {
3787
3811
  this.rowData = [];
3788
3812
  if (hardRefresh) {
@@ -3868,9 +3892,22 @@ class ListPageComponent {
3868
3892
  }
3869
3893
  return extensionFound;
3870
3894
  }
3895
+ /**
3896
+ * Column set loaded into the table on init. Defaults to the service's ag-grid
3897
+ * column defs (the legacy {@link DataTableComponent}). A page that renders the
3898
+ * new {@link ListTableComponent} overrides this to return the `ListColumn` set
3899
+ * instead, leaving every other page untouched.
3900
+ */
3901
+ loadTableColumns() {
3902
+ return this.svc.initTableColumns();
3903
+ }
3871
3904
  gridReady(event) {
3872
3905
  this.gridObj = event;
3873
3906
  }
3907
+ /** Ready hook for {@link ListTableComponent}; keeps the instance for extension use. */
3908
+ tableReady(event) {
3909
+ this.tableObj = event?.component;
3910
+ }
3874
3911
  refreshCells() {
3875
3912
  defer(() => {
3876
3913
  var params = {
@@ -6292,6 +6329,320 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
6292
6329
  args: [ZITI_DATA_SERVICE]
6293
6330
  }] }, { type: GrowlerService }] });
6294
6331
 
6332
+ /**
6333
+ * Name cell for {@link ListTableComponent}: a link to the entity's detail route,
6334
+ * with optional identity/router status circles and a prefix icon. Inputs come from
6335
+ * the column's `cellInputs(ctx)`: `{ row, cellValue, cellConfig }`, where
6336
+ * `cellConfig` carries `pathRoot`, `itemProp?`, `showIdentityIcons?`,
6337
+ * `showRouterIcons?`, `getNamePrefix?`, `cellNamePreCheck?`.
6338
+ */
6339
+ class NameCellComponent {
6340
+ set row(v) {
6341
+ this.item = v;
6342
+ }
6343
+ set cellValue(v) {
6344
+ this.value = v;
6345
+ }
6346
+ set cellConfig(cfg) {
6347
+ this.cellParams = cfg || {};
6348
+ }
6349
+ constructor(router) {
6350
+ this.router = router;
6351
+ this.cellParams = {};
6352
+ }
6353
+ get namePrefix() {
6354
+ const fn = this.cellParams?.getNamePrefix;
6355
+ if (typeof fn !== 'function') {
6356
+ return null;
6357
+ }
6358
+ const result = fn(this.item);
6359
+ if (!result?.show) {
6360
+ return null;
6361
+ }
6362
+ return result;
6363
+ }
6364
+ linkClicked(event) {
6365
+ event.stopPropagation();
6366
+ event.preventDefault();
6367
+ if (isFunction(this.cellParams?.cellNamePreCheck)) {
6368
+ this.cellParams?.cellNamePreCheck(this.item).then((result) => {
6369
+ if (!result?.confirmed) {
6370
+ return;
6371
+ }
6372
+ this.navigate();
6373
+ });
6374
+ return;
6375
+ }
6376
+ this.navigate();
6377
+ }
6378
+ navigate() {
6379
+ this.router.navigateByUrl(`${this.cellParams.pathRoot}/${get(this.item, this.cellParams.itemProp, this.item.id)}`);
6380
+ }
6381
+ get cellText() {
6382
+ let val = this.value;
6383
+ if (isEmpty(val)) {
6384
+ val = this.item?.name;
6385
+ }
6386
+ return val;
6387
+ }
6388
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: NameCellComponent, deps: [{ token: i4.Router }], target: i0.ɵɵFactoryTarget.Component }); }
6389
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: NameCellComponent, isStandalone: false, selector: "lib-name-cell", inputs: { row: "row", cellValue: "cellValue", cellConfig: "cellConfig" }, ngImport: i0, template: "<span class=\"cell-name-container\">\n <a href=\"./{{cellParams.pathRoot}}/{{item.id}}\" class=\"cell-name-link\" (click)=\"linkClicked($event)\">\n <div class=\"col cell-name-renderer\" data-id=\"{{item.id}}\">\n @if (cellParams.showIdentityIcons && item.edgeRouterConnectionStatus) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.edgeRouterConnectionStatus}}\" title=\"Edge Router Connection Status\"></span>\n </div>\n }\n @if (cellParams.showIdentityIcons && !item.edgeRouterConnectionStatus) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.hasApiSession}}\" title=\"Api Session\"></span>\n <span class=\"circle {{item.hasEdgeRouterConnection}}\" title=\"Edge Router Connected\"></span>\n </div>\n }\n @if (cellParams.showRouterIcons) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.isVerified}}\" title=\"{{item.isVerified ? 'Verified' : 'Unverified'}}\"></span>\n <span class=\"circle {{item.isOnline}}\" title=\"{{item.isOnline ? 'Online' : 'Offline'}}\"></span>\n </div>\n }\n @if (namePrefix) {\n <div class=\"cell-name-prefix-wrap\" [matTooltip]=\"namePrefix.tooltip\" [matTooltipShowDelay]=\"0\">\n <span class=\"cell-name-prefix-icon\" [ngClass]=\"namePrefix.iconClass\"></span>\n </div>\n }\n <strong>{{cellText}}</strong>\n </div>\n </a>\n</span>\n", styles: [".cell-name-renderer{display:flex;flex-direction:row;align-items:center;gap:.3125rem}.cell-name-container{display:flex;flex-direction:row;flex-wrap:nowrap}.cell-name-link{display:inline-flex;align-items:center}.cell-status-container{position:relative;display:flex;align-items:center;justify-content:center;gap:.25rem}.cell-name-prefix-wrap{display:flex;align-items:center;justify-content:center}.cell-name-prefix-icon{color:var(--highlighted);font-size:1rem}.circle.online{background-color:var(--green)}.circle.unknown{background-color:var(--highlighted)}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] }); }
6390
+ }
6391
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: NameCellComponent, decorators: [{
6392
+ type: Component,
6393
+ args: [{ selector: 'lib-name-cell', standalone: false, template: "<span class=\"cell-name-container\">\n <a href=\"./{{cellParams.pathRoot}}/{{item.id}}\" class=\"cell-name-link\" (click)=\"linkClicked($event)\">\n <div class=\"col cell-name-renderer\" data-id=\"{{item.id}}\">\n @if (cellParams.showIdentityIcons && item.edgeRouterConnectionStatus) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.edgeRouterConnectionStatus}}\" title=\"Edge Router Connection Status\"></span>\n </div>\n }\n @if (cellParams.showIdentityIcons && !item.edgeRouterConnectionStatus) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.hasApiSession}}\" title=\"Api Session\"></span>\n <span class=\"circle {{item.hasEdgeRouterConnection}}\" title=\"Edge Router Connected\"></span>\n </div>\n }\n @if (cellParams.showRouterIcons) {\n <div class=\"cell-status-container\">\n <span class=\"circle {{item.isVerified}}\" title=\"{{item.isVerified ? 'Verified' : 'Unverified'}}\"></span>\n <span class=\"circle {{item.isOnline}}\" title=\"{{item.isOnline ? 'Online' : 'Offline'}}\"></span>\n </div>\n }\n @if (namePrefix) {\n <div class=\"cell-name-prefix-wrap\" [matTooltip]=\"namePrefix.tooltip\" [matTooltipShowDelay]=\"0\">\n <span class=\"cell-name-prefix-icon\" [ngClass]=\"namePrefix.iconClass\"></span>\n </div>\n }\n <strong>{{cellText}}</strong>\n </div>\n </a>\n</span>\n", styles: [".cell-name-renderer{display:flex;flex-direction:row;align-items:center;gap:.3125rem}.cell-name-container{display:flex;flex-direction:row;flex-wrap:nowrap}.cell-name-link{display:inline-flex;align-items:center}.cell-status-container{position:relative;display:flex;align-items:center;justify-content:center;gap:.25rem}.cell-name-prefix-wrap{display:flex;align-items:center;justify-content:center}.cell-name-prefix-icon{color:var(--highlighted);font-size:1rem}.circle.online{background-color:var(--green)}.circle.unknown{background-color:var(--highlighted)}\n"] }]
6394
+ }], ctorParameters: () => [{ type: i4.Router }], propDecorators: { row: [{
6395
+ type: Input
6396
+ }], cellValue: [{
6397
+ type: Input
6398
+ }], cellConfig: [{
6399
+ type: Input
6400
+ }] } });
6401
+
6402
+ /*
6403
+ Copyright NetFoundry Inc.
6404
+
6405
+ Licensed under the Apache License, Version 2.0 (the "License");
6406
+ you may not use this file except in compliance with the License.
6407
+ You may obtain a copy of the License at
6408
+
6409
+ https://www.apache.org/licenses/LICENSE-2.0
6410
+
6411
+ Unless required by applicable law or agreed to in writing, software
6412
+ distributed under the License is distributed on an "AS IS" BASIS,
6413
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6414
+ See the License for the specific language governing permissions and
6415
+ limitations under the License.
6416
+ */
6417
+ /**
6418
+ * Modern hover card for the identities O/S cell — shows the environment info
6419
+ * (OS, version, release, arch) in a clean key/value layout. Rendered by
6420
+ * ListTableComponent's hover-tooltip mechanism (column `tooltip`).
6421
+ */
6422
+ class OsInfoTooltipComponent {
6423
+ get envInfo() {
6424
+ return this.row?.envInfo || this.row?.versionInfo || {};
6425
+ }
6426
+ get osClass() {
6427
+ const os = `${this.envInfo?.os || ''}`.toLowerCase();
6428
+ const ver = `${this.envInfo?.osVersion || ''}`.toLowerCase();
6429
+ if (ver.includes('windows') || os.includes('mingw') || os.includes('windows')) {
6430
+ return 'windows';
6431
+ }
6432
+ if (os.includes('darwin')) {
6433
+ return 'apple';
6434
+ }
6435
+ if (os.includes('linux')) {
6436
+ return 'linux';
6437
+ }
6438
+ if (os.includes('android')) {
6439
+ return 'android';
6440
+ }
6441
+ return 'other';
6442
+ }
6443
+ get osTitle() {
6444
+ switch (this.osClass) {
6445
+ case 'apple': return 'macOS';
6446
+ case 'windows': return 'Windows';
6447
+ case 'linux': return 'Linux';
6448
+ case 'android': return 'Android';
6449
+ default: return this.envInfo?.os || 'Unknown OS';
6450
+ }
6451
+ }
6452
+ /** Whether there is any environment info worth showing. */
6453
+ get hasInfo() {
6454
+ const e = this.envInfo;
6455
+ return !!(e && (e.os || e.osVersion || e.osRelease || e.arch));
6456
+ }
6457
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: OsInfoTooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6458
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: OsInfoTooltipComponent, isStandalone: false, selector: "lib-os-info-tooltip", inputs: { row: "row" }, ngImport: i0, template: "<div class=\"os-tip\">\n <div class=\"os-tip-head\">\n <span class=\"os-tip-icon {{ osClass }}\"></span>\n <span class=\"os-tip-title\">{{ osTitle }}</span>\n </div>\n @if (hasInfo) {\n <div class=\"os-tip-body\">\n @if (envInfo?.osVersion) {\n <div class=\"os-tip-row\"><span class=\"os-tip-key\">Version</span><span class=\"os-tip-val\">{{ envInfo.osVersion }}</span></div>\n }\n @if (envInfo?.osRelease) {\n <div class=\"os-tip-row\"><span class=\"os-tip-key\">Release</span><span class=\"os-tip-val\">{{ envInfo.osRelease }}</span></div>\n }\n @if (envInfo?.arch) {\n <div class=\"os-tip-row\"><span class=\"os-tip-key\">Arch</span><span class=\"os-tip-val\">{{ envInfo.arch }}</span></div>\n }\n </div>\n } @else {\n <div class=\"os-tip-empty\">No environment info reported</div>\n }\n</div>\n", styles: [":host{display:block}.os-tip{min-width:11rem;max-width:16rem;padding:.625rem .75rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);font-family:var(--lt-font)}.os-tip-head{display:flex;align-items:center;gap:.5rem;padding-bottom:.5rem;margin-bottom:.5rem;border-bottom:1px solid var(--lt-border)}.os-tip-icon{flex:0 0 auto;width:1.25rem;height:1.25rem;background-size:contain;background-repeat:no-repeat;background-position:center}.os-tip-icon.apple{background-image:url(/assets/images/apple.png)}.os-tip-icon.windows{background-image:url(/assets/images/windows.png)}.os-tip-icon.linux{background-image:url(/assets/images/linux.png)}.os-tip-icon.android{background-image:url(/assets/images/android.png)}.os-tip-icon.other{background-image:url(/assets/images/other.png)}.os-tip-title{font-size:.875rem;font-weight:700;color:var(--lt-cell-text)}.os-tip-body{display:flex;flex-direction:column;gap:.3125rem}.os-tip-row{display:flex;align-items:baseline;justify-content:space-between;gap:.75rem;font-size:.75rem}.os-tip-key{flex:0 0 auto;color:var(--groupText);font-size:.75rem}.os-tip-val{flex:1 1 auto;min-width:0;text-align:right;font-weight:600;color:var(--lt-cell-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.os-tip-empty{font-size:.75rem;font-style:italic;color:var(--groupText)}\n"] }); }
6459
+ }
6460
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: OsInfoTooltipComponent, decorators: [{
6461
+ type: Component,
6462
+ args: [{ selector: 'lib-os-info-tooltip', standalone: false, template: "<div class=\"os-tip\">\n <div class=\"os-tip-head\">\n <span class=\"os-tip-icon {{ osClass }}\"></span>\n <span class=\"os-tip-title\">{{ osTitle }}</span>\n </div>\n @if (hasInfo) {\n <div class=\"os-tip-body\">\n @if (envInfo?.osVersion) {\n <div class=\"os-tip-row\"><span class=\"os-tip-key\">Version</span><span class=\"os-tip-val\">{{ envInfo.osVersion }}</span></div>\n }\n @if (envInfo?.osRelease) {\n <div class=\"os-tip-row\"><span class=\"os-tip-key\">Release</span><span class=\"os-tip-val\">{{ envInfo.osRelease }}</span></div>\n }\n @if (envInfo?.arch) {\n <div class=\"os-tip-row\"><span class=\"os-tip-key\">Arch</span><span class=\"os-tip-val\">{{ envInfo.arch }}</span></div>\n }\n </div>\n } @else {\n <div class=\"os-tip-empty\">No environment info reported</div>\n }\n</div>\n", styles: [":host{display:block}.os-tip{min-width:11rem;max-width:16rem;padding:.625rem .75rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);font-family:var(--lt-font)}.os-tip-head{display:flex;align-items:center;gap:.5rem;padding-bottom:.5rem;margin-bottom:.5rem;border-bottom:1px solid var(--lt-border)}.os-tip-icon{flex:0 0 auto;width:1.25rem;height:1.25rem;background-size:contain;background-repeat:no-repeat;background-position:center}.os-tip-icon.apple{background-image:url(/assets/images/apple.png)}.os-tip-icon.windows{background-image:url(/assets/images/windows.png)}.os-tip-icon.linux{background-image:url(/assets/images/linux.png)}.os-tip-icon.android{background-image:url(/assets/images/android.png)}.os-tip-icon.other{background-image:url(/assets/images/other.png)}.os-tip-title{font-size:.875rem;font-weight:700;color:var(--lt-cell-text)}.os-tip-body{display:flex;flex-direction:column;gap:.3125rem}.os-tip-row{display:flex;align-items:baseline;justify-content:space-between;gap:.75rem;font-size:.75rem}.os-tip-key{flex:0 0 auto;color:var(--groupText);font-size:.75rem}.os-tip-val{flex:1 1 auto;min-width:0;text-align:right;font-weight:600;color:var(--lt-cell-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.os-tip-empty{font-size:.75rem;font-style:italic;color:var(--groupText)}\n"] }]
6463
+ }], propDecorators: { row: [{
6464
+ type: Input
6465
+ }] } });
6466
+
6467
+ /*
6468
+ Copyright NetFoundry Inc.
6469
+
6470
+ Licensed under the Apache License, Version 2.0 (the "License");
6471
+ you may not use this file except in compliance with the License.
6472
+ You may obtain a copy of the License at
6473
+
6474
+ https://www.apache.org/licenses/LICENSE-2.0
6475
+
6476
+ Unless required by applicable law or agreed to in writing, software
6477
+ distributed under the License is distributed on an "AS IS" BASIS,
6478
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6479
+ See the License for the specific language governing permissions and
6480
+ limitations under the License.
6481
+ */
6482
+ /**
6483
+ * Hover card for the identities SDK cell — shows the reported SDK details
6484
+ * (app id/version, version, type, branch, revision), the same fields the legacy
6485
+ * ag-grid SDK tooltip surfaced. Rendered by ListTableComponent's hover-tooltip
6486
+ * mechanism (column `tooltip`).
6487
+ */
6488
+ class SdkInfoTooltipComponent {
6489
+ get sdkInfo() {
6490
+ return this.row?.sdkInfo || {};
6491
+ }
6492
+ get title() {
6493
+ const s = this.sdkInfo;
6494
+ return s?.appId || s?.type || 'SDK';
6495
+ }
6496
+ get hasInfo() {
6497
+ const s = this.sdkInfo;
6498
+ return !!(s && (s.appId || s.appVersion || s.version || s.type || s.branch || s.revision));
6499
+ }
6500
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SdkInfoTooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6501
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: SdkInfoTooltipComponent, isStandalone: false, selector: "lib-sdk-info-tooltip", inputs: { row: "row" }, ngImport: i0, template: "<div class=\"sdk-tip\">\n <div class=\"sdk-tip-head\">\n <span class=\"sdk-tip-title\">{{ title }}</span>\n </div>\n @if (hasInfo) {\n <div class=\"sdk-tip-body\">\n @if (sdkInfo?.appId) {\n <div class=\"sdk-tip-row\"><span class=\"sdk-tip-key\">App ID</span><span class=\"sdk-tip-val\">{{ sdkInfo.appId }}</span></div>\n }\n @if (sdkInfo?.appVersion) {\n <div class=\"sdk-tip-row\"><span class=\"sdk-tip-key\">App Version</span><span class=\"sdk-tip-val\">{{ sdkInfo.appVersion }}</span></div>\n }\n @if (sdkInfo?.version) {\n <div class=\"sdk-tip-row\"><span class=\"sdk-tip-key\">Version</span><span class=\"sdk-tip-val\">{{ sdkInfo.version }}</span></div>\n }\n @if (sdkInfo?.type) {\n <div class=\"sdk-tip-row\"><span class=\"sdk-tip-key\">Type</span><span class=\"sdk-tip-val\">{{ sdkInfo.type }}</span></div>\n }\n @if (sdkInfo?.branch) {\n <div class=\"sdk-tip-row\"><span class=\"sdk-tip-key\">Branch</span><span class=\"sdk-tip-val\">{{ sdkInfo.branch }}</span></div>\n }\n @if (sdkInfo?.revision) {\n <div class=\"sdk-tip-row\"><span class=\"sdk-tip-key\">Revision</span><span class=\"sdk-tip-val\">{{ sdkInfo.revision }}</span></div>\n }\n </div>\n } @else {\n <div class=\"sdk-tip-empty\">No SDK info reported</div>\n }\n</div>\n", styles: [":host{display:block}.sdk-tip{min-width:11rem;max-width:18rem;padding:.625rem .75rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);font-family:var(--lt-font)}.sdk-tip-head{display:flex;align-items:center;gap:.5rem;padding-bottom:.5rem;margin-bottom:.5rem;border-bottom:1px solid var(--lt-border)}.sdk-tip-title{font-size:.875rem;font-weight:700;color:var(--lt-cell-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sdk-tip-body{display:flex;flex-direction:column;gap:.3125rem}.sdk-tip-row{display:flex;align-items:baseline;justify-content:space-between;gap:.75rem;font-size:.75rem}.sdk-tip-key{flex:0 0 auto;color:var(--groupText);font-size:.75rem}.sdk-tip-val{flex:1 1 auto;min-width:0;text-align:right;font-weight:600;color:var(--lt-cell-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sdk-tip-empty{font-size:.75rem;font-style:italic;color:var(--groupText)}\n"] }); }
6502
+ }
6503
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SdkInfoTooltipComponent, decorators: [{
6504
+ type: Component,
6505
+ args: [{ selector: 'lib-sdk-info-tooltip', standalone: false, template: "<div class=\"sdk-tip\">\n <div class=\"sdk-tip-head\">\n <span class=\"sdk-tip-title\">{{ title }}</span>\n </div>\n @if (hasInfo) {\n <div class=\"sdk-tip-body\">\n @if (sdkInfo?.appId) {\n <div class=\"sdk-tip-row\"><span class=\"sdk-tip-key\">App ID</span><span class=\"sdk-tip-val\">{{ sdkInfo.appId }}</span></div>\n }\n @if (sdkInfo?.appVersion) {\n <div class=\"sdk-tip-row\"><span class=\"sdk-tip-key\">App Version</span><span class=\"sdk-tip-val\">{{ sdkInfo.appVersion }}</span></div>\n }\n @if (sdkInfo?.version) {\n <div class=\"sdk-tip-row\"><span class=\"sdk-tip-key\">Version</span><span class=\"sdk-tip-val\">{{ sdkInfo.version }}</span></div>\n }\n @if (sdkInfo?.type) {\n <div class=\"sdk-tip-row\"><span class=\"sdk-tip-key\">Type</span><span class=\"sdk-tip-val\">{{ sdkInfo.type }}</span></div>\n }\n @if (sdkInfo?.branch) {\n <div class=\"sdk-tip-row\"><span class=\"sdk-tip-key\">Branch</span><span class=\"sdk-tip-val\">{{ sdkInfo.branch }}</span></div>\n }\n @if (sdkInfo?.revision) {\n <div class=\"sdk-tip-row\"><span class=\"sdk-tip-key\">Revision</span><span class=\"sdk-tip-val\">{{ sdkInfo.revision }}</span></div>\n }\n </div>\n } @else {\n <div class=\"sdk-tip-empty\">No SDK info reported</div>\n }\n</div>\n", styles: [":host{display:block}.sdk-tip{min-width:11rem;max-width:18rem;padding:.625rem .75rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);font-family:var(--lt-font)}.sdk-tip-head{display:flex;align-items:center;gap:.5rem;padding-bottom:.5rem;margin-bottom:.5rem;border-bottom:1px solid var(--lt-border)}.sdk-tip-title{font-size:.875rem;font-weight:700;color:var(--lt-cell-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sdk-tip-body{display:flex;flex-direction:column;gap:.3125rem}.sdk-tip-row{display:flex;align-items:baseline;justify-content:space-between;gap:.75rem;font-size:.75rem}.sdk-tip-key{flex:0 0 auto;color:var(--groupText);font-size:.75rem}.sdk-tip-val{flex:1 1 auto;min-width:0;text-align:right;font-weight:600;color:var(--lt-cell-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sdk-tip-empty{font-size:.75rem;font-style:italic;color:var(--groupText)}\n"] }]
6506
+ }], propDecorators: { row: [{
6507
+ type: Input
6508
+ }] } });
6509
+
6510
+ /*
6511
+ Copyright NetFoundry Inc.
6512
+
6513
+ Licensed under the Apache License, Version 2.0 (the "License");
6514
+ you may not use this file except in compliance with the License.
6515
+ You may obtain a copy of the License at
6516
+
6517
+ https://www.apache.org/licenses/LICENSE-2.0
6518
+
6519
+ Unless required by applicable law or agreed to in writing, software
6520
+ distributed under the License is distributed on an "AS IS" BASIS,
6521
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6522
+ See the License for the specific language governing permissions and
6523
+ limitations under the License.
6524
+ */
6525
+ /**
6526
+ * Renders a truthy boolean as a green check + domain label (via `trueLabel`);
6527
+ * a false value renders nothing, so the column reads as a clean presence marker.
6528
+ */
6529
+ class BooleanCellComponent {
6530
+ constructor() {
6531
+ this.trueLabel = 'Yes';
6532
+ }
6533
+ get isTrue() {
6534
+ return this.value === true || this.value === 'true';
6535
+ }
6536
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: BooleanCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6537
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: BooleanCellComponent, isStandalone: false, selector: "lib-boolean-cell", inputs: { value: "value", trueLabel: "trueLabel" }, ngImport: i0, template: "@if (isTrue) {\n <span class=\"bool bool-true\">\n <svg class=\"bool-check\" viewBox=\"0 0 14 14\" width=\"12\" height=\"12\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2.5 7.6 5.7 10.8 11.5 3.6\"/>\n </svg>\n <span class=\"bool-label\">{{ trueLabel }}</span>\n </span>\n}\n", styles: [":host{display:inline-flex;align-items:center}.bool-true{display:inline-flex;align-items:center;gap:.3125rem;padding:.1875rem .5rem .1875rem .4375rem;font-family:var(--lt-font);font-size:.75rem;font-weight:600;line-height:1.2;color:#15803d;background:color-mix(in srgb,#16a34a 12%,transparent);border-radius:999px}.bool-check{flex:0 0 auto;display:block;color:#16a34a}\n"] }); }
6538
+ }
6539
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: BooleanCellComponent, decorators: [{
6540
+ type: Component,
6541
+ args: [{ selector: 'lib-boolean-cell', standalone: false, template: "@if (isTrue) {\n <span class=\"bool bool-true\">\n <svg class=\"bool-check\" viewBox=\"0 0 14 14\" width=\"12\" height=\"12\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2.5 7.6 5.7 10.8 11.5 3.6\"/>\n </svg>\n <span class=\"bool-label\">{{ trueLabel }}</span>\n </span>\n}\n", styles: [":host{display:inline-flex;align-items:center}.bool-true{display:inline-flex;align-items:center;gap:.3125rem;padding:.1875rem .5rem .1875rem .4375rem;font-family:var(--lt-font);font-size:.75rem;font-weight:600;line-height:1.2;color:#15803d;background:color-mix(in srgb,#16a34a 12%,transparent);border-radius:999px}.bool-check{flex:0 0 auto;display:block;color:#16a34a}\n"] }]
6542
+ }], propDecorators: { value: [{
6543
+ type: Input
6544
+ }], trueLabel: [{
6545
+ type: Input
6546
+ }] } });
6547
+
6548
+ /*
6549
+ Copyright NetFoundry Inc.
6550
+
6551
+ Licensed under the Apache License, Version 2.0 (the "License");
6552
+ you may not use this file except in compliance with the License.
6553
+ You may obtain a copy of the License at
6554
+
6555
+ https://www.apache.org/licenses/LICENSE-2.0
6556
+
6557
+ Unless required by applicable law or agreed to in writing, software
6558
+ distributed under the License is distributed on an "AS IS" BASIS,
6559
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6560
+ See the License for the specific language governing permissions and
6561
+ limitations under the License.
6562
+ */
6563
+ /**
6564
+ * Enrollment-token cell for the identities list. Shows a status chip (Available /
6565
+ * Expired / —) that doubles as a dropdown of enrollment actions (Download JWT, Show
6566
+ * QR, Re-issue). Actions are emitted via the cell context's `emitAction`, so they run
6567
+ * through the same page handlers as the row ⋯ menu.
6568
+ */
6569
+ class TokenCellComponent {
6570
+ constructor() {
6571
+ this.open = false;
6572
+ this.menuLeft = 0;
6573
+ this.menuTop = 0;
6574
+ }
6575
+ get status() {
6576
+ if (!this.hasEnrolmentToken) {
6577
+ return 'none';
6578
+ }
6579
+ return this.tokenExpired ? 'expired' : 'available';
6580
+ }
6581
+ get statusLabel() {
6582
+ return this.status === 'expired' ? 'Expired' : 'Available';
6583
+ }
6584
+ get actions() {
6585
+ if (this.status === 'available') {
6586
+ return [
6587
+ { label: 'Download JWT', action: 'download-enrollment', icon: 'icon-download-jwt' },
6588
+ { label: 'Show QR code', action: 'qr-code', icon: 'icon-qr' },
6589
+ { label: 'Re-issue enrollment', action: 'reissue-enrollment', icon: 'icon-refresh' },
6590
+ ];
6591
+ }
6592
+ if (this.status === 'expired') {
6593
+ return [{ label: 'Re-issue enrollment', action: 'reissue-enrollment', icon: 'icon-refresh' }];
6594
+ }
6595
+ return [];
6596
+ }
6597
+ toggleMenu(event) {
6598
+ event.stopPropagation();
6599
+ if (this.open) {
6600
+ this.open = false;
6601
+ return;
6602
+ }
6603
+ const rect = this.trigger?.nativeElement.getBoundingClientRect();
6604
+ if (rect) {
6605
+ this.menuLeft = rect.left;
6606
+ this.menuTop = rect.bottom + 4;
6607
+ }
6608
+ this.open = true;
6609
+ }
6610
+ closeMenu() {
6611
+ this.open = false;
6612
+ }
6613
+ runAction(action, event) {
6614
+ event.stopPropagation();
6615
+ this.open = false;
6616
+ this.emitAction?.(action, this.row);
6617
+ }
6618
+ get hasEnrolmentToken() {
6619
+ const token = this.row?.enrollment?.ott?.jwt ||
6620
+ this.row?.enrollment?.ottca?.jwt ||
6621
+ this.row?.enrollment?.updb?.jwt;
6622
+ return !!(this.row?.enrollmentToken || token);
6623
+ }
6624
+ get tokenExpired() {
6625
+ const expiration = this.row?.enrollmentExpiresAt ||
6626
+ this.row?.enrollment?.ott?.expiresAt ||
6627
+ this.row?.enrollment?.ottca?.expiresAt ||
6628
+ this.row?.enrollment?.updb?.expiresAt;
6629
+ return expiration ? moment$1(expiration).isBefore() : false;
6630
+ }
6631
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TokenCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6632
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: TokenCellComponent, isStandalone: false, selector: "lib-token-cell", inputs: { row: "row", emitAction: "emitAction" }, viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true }], ngImport: i0, template: "@if (status !== 'none') {\n <button\n #trigger\n type=\"button\"\n class=\"token-chip {{ status }}\"\n [class.is-open]=\"open\"\n (click)=\"toggleMenu($event)\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"open\"\n >\n <span class=\"token-chip-label\">{{ statusLabel }}</span>\n <svg class=\"token-caret\" viewBox=\"0 0 10 6\" width=\"9\" height=\"6\" aria-hidden=\"true\">\n <path fill=\"currentColor\" d=\"M0 0h10L5 6z\"/>\n </svg>\n </button>\n\n @if (open) {\n <div class=\"token-menu\" [ngStyle]=\"{ left: menuLeft + 'px', top: menuTop + 'px' }\" (clickOutside)=\"closeMenu()\">\n @for (a of actions; track a.action) {\n <button type=\"button\" class=\"token-menu-item\" (click)=\"runAction(a.action, $event)\">\n <span class=\"token-menu-icon {{ a.icon }}\" aria-hidden=\"true\"></span>\n <span class=\"token-menu-label\">{{ a.label }}</span>\n </button>\n }\n </div>\n }\n} @else {\n <span class=\"token-empty\">&#8212;</span>\n}\n", styles: [":host{display:inline-flex;align-items:center}.token-chip{display:inline-flex;align-items:center;gap:.3125rem;height:1.5rem;padding:0 .5rem;font-family:var(--lt-font);font-size:.75rem;font-weight:600;border:none;border-radius:.75rem;cursor:pointer;white-space:nowrap;transition:filter .15s}.token-chip .token-caret{display:block;opacity:.75}.token-chip:hover,.token-chip.is-open{filter:brightness(.96)}.token-chip.available{color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 12%,var(--lt-control-bg))}.token-chip.expired{color:color-mix(in srgb,var(--highlighted) 62%,#000);background:color-mix(in srgb,var(--highlighted) 26%,var(--lt-control-bg))}.token-empty{color:var(--groupText)}.token-menu{position:fixed;z-index:1000;min-width:11rem;padding:.25rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:token-in .1s ease-out}@keyframes token-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.token-menu-item{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4375rem .5rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);text-align:left;background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer}.token-menu-item:hover{background:var(--lt-row-hover);color:var(--lt-link)}.token-menu-item:hover .token-menu-icon{color:var(--lt-link)}.token-menu-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:1rem;font-size:.9375rem;color:var(--lt-icon)}\n"], dependencies: [{ kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i3$1.ClickOutsideDirective, selector: "[clickOutside]", inputs: ["clickOutsideEnabled", "attachOutsideOnClick", "delayClickOutsideInit", "emitOnBlur", "exclude", "excludeBeforeClick", "clickOutsideEvents"], outputs: ["clickOutside"] }] }); }
6633
+ }
6634
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TokenCellComponent, decorators: [{
6635
+ type: Component,
6636
+ args: [{ selector: 'lib-token-cell', standalone: false, template: "@if (status !== 'none') {\n <button\n #trigger\n type=\"button\"\n class=\"token-chip {{ status }}\"\n [class.is-open]=\"open\"\n (click)=\"toggleMenu($event)\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"open\"\n >\n <span class=\"token-chip-label\">{{ statusLabel }}</span>\n <svg class=\"token-caret\" viewBox=\"0 0 10 6\" width=\"9\" height=\"6\" aria-hidden=\"true\">\n <path fill=\"currentColor\" d=\"M0 0h10L5 6z\"/>\n </svg>\n </button>\n\n @if (open) {\n <div class=\"token-menu\" [ngStyle]=\"{ left: menuLeft + 'px', top: menuTop + 'px' }\" (clickOutside)=\"closeMenu()\">\n @for (a of actions; track a.action) {\n <button type=\"button\" class=\"token-menu-item\" (click)=\"runAction(a.action, $event)\">\n <span class=\"token-menu-icon {{ a.icon }}\" aria-hidden=\"true\"></span>\n <span class=\"token-menu-label\">{{ a.label }}</span>\n </button>\n }\n </div>\n }\n} @else {\n <span class=\"token-empty\">&#8212;</span>\n}\n", styles: [":host{display:inline-flex;align-items:center}.token-chip{display:inline-flex;align-items:center;gap:.3125rem;height:1.5rem;padding:0 .5rem;font-family:var(--lt-font);font-size:.75rem;font-weight:600;border:none;border-radius:.75rem;cursor:pointer;white-space:nowrap;transition:filter .15s}.token-chip .token-caret{display:block;opacity:.75}.token-chip:hover,.token-chip.is-open{filter:brightness(.96)}.token-chip.available{color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 12%,var(--lt-control-bg))}.token-chip.expired{color:color-mix(in srgb,var(--highlighted) 62%,#000);background:color-mix(in srgb,var(--highlighted) 26%,var(--lt-control-bg))}.token-empty{color:var(--groupText)}.token-menu{position:fixed;z-index:1000;min-width:11rem;padding:.25rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:token-in .1s ease-out}@keyframes token-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.token-menu-item{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4375rem .5rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);text-align:left;background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer}.token-menu-item:hover{background:var(--lt-row-hover);color:var(--lt-link)}.token-menu-item:hover .token-menu-icon{color:var(--lt-link)}.token-menu-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:1rem;font-size:.9375rem;color:var(--lt-icon)}\n"] }]
6637
+ }], propDecorators: { row: [{
6638
+ type: Input
6639
+ }], emitAction: [{
6640
+ type: Input
6641
+ }], trigger: [{
6642
+ type: ViewChild,
6643
+ args: ['trigger']
6644
+ }] } });
6645
+
6295
6646
  /*
6296
6647
  Copyright NetFoundry Inc.
6297
6648
 
@@ -6343,19 +6694,21 @@ class IdentitiesPageService extends ListPageServiceClass {
6343
6694
  os: '',
6344
6695
  createdAt: '',
6345
6696
  };
6697
+ // `icon` (SVG markup) is rendered before the label in the new list-table's row kebab
6698
+ // (3-dot) menu. The legacy ag-grid menu ignores it.
6346
6699
  this.menuItems = [
6347
- { name: 'Edit', action: 'update' },
6348
- { name: 'Download JWT', action: 'download-enrollment' },
6349
- { name: 'View QR', action: 'qr-code' },
6350
- { name: 'Visualizer', action: 'identity-service-path' },
6351
- { name: 'Reset Enrollment', action: 'reset-enrollment' },
6352
- { name: 'Reissue Enrollment', action: 'reissue-enrollment' },
6353
- { name: 'Reset MFA', action: 'reset-mfa' },
6354
- { name: 'Override', action: 'override' },
6355
- { name: 'Delete', action: 'delete' },
6700
+ { name: 'Edit', action: 'update', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M11.2 2.6 13.4 4.8 6 12.2l-2.8.6.6-2.8z"/></svg>' },
6701
+ { name: 'Download JWT', action: 'download-enrollment', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M8 2.5v7M5 6.6 8 9.6l3-3M3.2 12.6h9.6"/></svg>' },
6702
+ { name: 'View QR', action: 'qr-code', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><g fill="none" stroke="currentColor" stroke-width="1.3"><rect x="2.5" y="2.5" width="4" height="4" rx="0.5"/><rect x="9.5" y="2.5" width="4" height="4" rx="0.5"/><rect x="2.5" y="9.5" width="4" height="4" rx="0.5"/></g><rect x="10" y="10" width="3.5" height="3.5" fill="currentColor"/></svg>' },
6703
+ { name: 'Visualizer', action: 'identity-service-path', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" d="M5.4 7.3 10.6 4.6M5.4 8.7l5.2 2.7"/><circle cx="4" cy="8" r="1.8" fill="currentColor"/><circle cx="12" cy="4" r="1.8" fill="currentColor"/><circle cx="12" cy="12" r="1.8" fill="currentColor"/></svg>' },
6704
+ { name: 'Reset Enrollment', action: 'reset-enrollment', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" d="M3.2 8a4.8 4.8 0 1 0 1.3-3.3"/><path fill="currentColor" d="M2.8 2.8l-.3 2.9 2.8-.4z"/></svg>' },
6705
+ { name: 'Reissue Enrollment', action: 'reissue-enrollment', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" d="M12.8 8a4.8 4.8 0 1 1-1.3-3.3"/><path fill="currentColor" d="M13.2 2.8l.3 2.9-2.8-.4z"/></svg>' },
6706
+ { name: 'Reset MFA', action: 'reset-mfa', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round" d="M8 2.3 12.8 4v3.8c0 3-2.1 4.9-4.8 5.9-2.7-1-4.8-2.9-4.8-5.9V4z"/></svg>' },
6707
+ { name: 'Override', action: 'override', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" d="M2.8 5h10.4M2.8 11h10.4"/><circle cx="6" cy="5" r="1.7" fill="currentColor"/><circle cx="10" cy="11" r="1.7" fill="currentColor"/></svg>' },
6708
+ { name: 'Delete', action: 'delete', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M3.4 4.6h9.2M6.4 4.6V3.2h3.2v1.4M4.6 4.6 5 13h6l.4-8.4"/></svg>' },
6356
6709
  ];
6357
6710
  this.tableHeaderActions = [
6358
- { name: 'Download All', action: 'download-all' },
6711
+ { name: 'Download All', action: 'download-all', icon: '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M8 2.5v7M5 6.6 8 9.6l3-3M3.2 12.6h9.6"/></svg>' },
6359
6712
  ];
6360
6713
  this.resourceType = 'identities';
6361
6714
  this.validate = (formData) => {
@@ -6611,15 +6964,280 @@ class IdentitiesPageService extends ListPageServiceClass {
6611
6964
  }
6612
6965
  return tableColumns;
6613
6966
  }
6967
+ /**
6968
+ * Column set for the new `lib-list-table` (the "beta" Identities page). Mirrors
6969
+ * {@link initTableColumns} but emits the declarative `ListColumn` shape instead of
6970
+ * ag-grid `ColDef`s: component cells via `cell`/`cellInputs`, legacy string
6971
+ * renderers via `cellHtml`, and header filters declared with `filterType`.
6972
+ * Loaded by {@link IdentitiesPageComponent} when the Beta Features toggle is on;
6973
+ * the legacy {@link initTableColumns} above is left untouched for the ag-grid table.
6974
+ */
6975
+ initListTableColumns() {
6976
+ this.initMenuActions();
6977
+ const identityRolesHeaderComponentParams = {
6978
+ filterType: 'ATTRIBUTE',
6979
+ enableSorting: true,
6980
+ appendAttributeHash: false,
6981
+ getRoleAttributes: () => this.identityRoleAttributes,
6982
+ getNamedAttributes: () => [],
6983
+ getSelectedRoleAttributes: () => this.selectedIdentityRoleAttributes,
6984
+ getSelectedNamedAttributes: () => [],
6985
+ setSelectedRoleAttributes: (attributes) => {
6986
+ this.selectedIdentityRoleAttributes = attributes;
6987
+ },
6988
+ setSelectedNamedAttributes: (attributes) => {
6989
+ // no-op
6990
+ },
6991
+ getNamedAttributesMap: () => ({}),
6992
+ };
6993
+ const osRenderer = (row) => {
6994
+ const os = (row?.data?.envInfo?.os || '').toLowerCase();
6995
+ const ver = (row?.data?.envInfo?.osVersion || '').toLowerCase();
6996
+ let cls = 'other';
6997
+ if (ver.includes('windows') || os.includes('windows') || os.includes('mingw'))
6998
+ cls = 'windows';
6999
+ else if (os.includes('darwin'))
7000
+ cls = 'apple';
7001
+ else if (os.includes('linux'))
7002
+ cls = 'linux';
7003
+ else if (os.includes('android'))
7004
+ cls = 'android';
7005
+ return `<span class="os ${cls}"></span>`;
7006
+ };
7007
+ const sdkRenderer = (row) => {
7008
+ // detail moves to the hover popover (SdkInfoTooltipComponent); the cell
7009
+ // just shows the version, so drop the legacy balloon attributes
7010
+ const version = row?.data?.sdkInfo?.version || '-';
7011
+ return `<span class="oneline">${version}</span>`;
7012
+ };
7013
+ const isAdminHeaderComponentParams = {
7014
+ filterType: 'SELECT',
7015
+ enableSorting: true,
7016
+ filterOptions: [
7017
+ { label: 'All', value: '' },
7018
+ { label: 'Admins', value: true },
7019
+ { label: 'Non-Admins', value: false },
7020
+ ]
7021
+ };
7022
+ // Online when any of the connection signals is live (the status icon is drawn
7023
+ // via CSS ::before, since [innerHTML] strips inline <svg>).
7024
+ const isOnline = (v) => v === true || /online|connected|true/i.test(String(v ?? ''));
7025
+ const statusRenderer = (row) => {
7026
+ const d = row?.data || {};
7027
+ const online = isOnline(d.edgeRouterConnectionStatus) || isOnline(d.hasEdgeRouterConnection) || isOnline(d.hasApiSession);
7028
+ return `<span class="lt-status lt-status-${online ? 'online' : 'offline'}">${online ? 'Online' : 'Offline'}</span>`;
7029
+ };
7030
+ // Color-coded Type pill with an icon: Default = blue, Router = green. Icon drawn
7031
+ // via CSS (::before) since [innerHTML] strips inline <svg>.
7032
+ const typeBadgeRenderer = (row) => {
7033
+ const t = row?.data?.type?.name || row?.data?.type || '';
7034
+ const key = String(t).toLowerCase();
7035
+ const cls = key === 'router' ? 'lt-type-router'
7036
+ : key === 'default' ? 'lt-type-default'
7037
+ : 'lt-type-other';
7038
+ return `<span class="lt-type-badge ${cls}">${t}</span>`;
7039
+ };
7040
+ // Light skinned role chips (the legacy rolesRenderer paints
7041
+ // heavy dark overlap chips; this stays scoped to the beta page).
7042
+ const rolesChipRenderer = (row) => {
7043
+ const colId = row?.column?.colId;
7044
+ const attrs = row?.data?.[colId + 'Display'] || row?.data?.[colId] || [];
7045
+ if (!attrs?.length) {
7046
+ return '';
7047
+ }
7048
+ return attrs.map((attr) => {
7049
+ const raw = String(attr?.name ?? attr ?? '').trim();
7050
+ if (!raw) {
7051
+ return '';
7052
+ }
7053
+ // named attributes (@) use the secondary color; role attributes (#)
7054
+ // the primary color, matching the tag selector's distinction
7055
+ const isNamed = raw.charAt(0) === '@';
7056
+ const label = isNamed ? raw : (raw.charAt(0) === '#' ? raw : '#' + raw);
7057
+ const cls = isNamed ? 'lt-chip lt-chip-at' : 'lt-chip';
7058
+ return `<span class="${cls}">${label}</span>`;
7059
+ }).join('');
7060
+ };
7061
+ let tableColumns = [
7062
+ {
7063
+ key: 'name',
7064
+ field: 'name',
7065
+ label: 'Name',
7066
+ filterType: 'TEXTINPUT',
7067
+ cell: NameCellComponent,
7068
+ cellInputs: (ctx) => ({
7069
+ row: ctx.row,
7070
+ cellValue: ctx.value,
7071
+ // status now lives in its own Status column (skinned), so the
7072
+ // name cell no longer carries the connection dots
7073
+ cellConfig: { pathRoot: this.basePath, showIdentityIcons: false },
7074
+ }),
7075
+ onCellClick: (ctx) => {
7076
+ if (this.hasSelectedText()) {
7077
+ return;
7078
+ }
7079
+ this.openEditForm(ctx.row?.id);
7080
+ },
7081
+ resizable: true,
7082
+ cellClass: 'nf-cell-vert-align',
7083
+ sortable: true,
7084
+ onSort: this.sort.bind(this),
7085
+ sortDir: 'asc',
7086
+ width: 300,
7087
+ },
7088
+ {
7089
+ key: 'status',
7090
+ field: 'hasEdgeRouterConnection',
7091
+ label: 'Status',
7092
+ cellHtml: statusRenderer,
7093
+ resizable: true,
7094
+ cellClass: 'nf-cell-vert-align',
7095
+ width: 120,
7096
+ },
7097
+ {
7098
+ key: 'roleAttributes',
7099
+ field: 'roleAttributes',
7100
+ label: 'Roles',
7101
+ filterType: 'ATTRIBUTE',
7102
+ headerParams: identityRolesHeaderComponentParams,
7103
+ cellHtml: rolesChipRenderer,
7104
+ onCellClick: (ctx) => {
7105
+ if (this.hasSelectedText()) {
7106
+ return;
7107
+ }
7108
+ this.openEditForm(ctx.row?.id);
7109
+ },
7110
+ resizable: true,
7111
+ cellClass: 'nf-cell-vert-align',
7112
+ sortable: false,
7113
+ },
7114
+ {
7115
+ key: 'os',
7116
+ field: 'os',
7117
+ label: 'O/S',
7118
+ width: 100,
7119
+ cellHtml: osRenderer,
7120
+ tooltip: OsInfoTooltipComponent,
7121
+ tooltipInputs: (ctx) => ({ row: ctx.row }),
7122
+ // envInfo.os is a JSON blob field the controller cannot filter on
7123
+ // ("unknown symbol") - no O/S filter control.
7124
+ resizable: true,
7125
+ cellClass: 'nf-cell-vert-align lt-cell-center',
7126
+ },
7127
+ {
7128
+ key: 'sdk',
7129
+ field: 'sdk',
7130
+ label: 'SDK',
7131
+ cellHtml: sdkRenderer,
7132
+ tooltip: SdkInfoTooltipComponent,
7133
+ tooltipInputs: (ctx) => ({ row: ctx.row }),
7134
+ resizable: true,
7135
+ cellClass: 'nf-cell-vert-align',
7136
+ width: 125,
7137
+ },
7138
+ {
7139
+ key: 'type',
7140
+ field: 'type.name',
7141
+ label: 'Type',
7142
+ filterType: 'SELECT',
7143
+ filterField: 'type',
7144
+ filterPlaceholder: 'Any type',
7145
+ // Only Default + Router exist as identity types in Ziti 2.x (confirmed
7146
+ // via /edge/management/v1/identity-types); Service/Device/User are legacy
7147
+ // and always return 0.
7148
+ filterOptions: [
7149
+ { label: 'Show All', value: '' },
7150
+ { label: 'Default', value: 'Default', chipClass: 'lt-type-badge lt-type-default' },
7151
+ { label: 'Router', value: 'Router', chipClass: 'lt-type-badge lt-type-router' },
7152
+ ],
7153
+ cellHtml: typeBadgeRenderer,
7154
+ resizable: true,
7155
+ sortable: true,
7156
+ cellClass: 'nf-cell-vert-align',
7157
+ onSort: this.sort.bind(this),
7158
+ },
7159
+ {
7160
+ key: 'isAdmin',
7161
+ field: 'isAdmin',
7162
+ label: 'Is Admin',
7163
+ filterType: 'SELECT',
7164
+ filterOptions: isAdminHeaderComponentParams.filterOptions,
7165
+ cell: BooleanCellComponent,
7166
+ cellInputs: (ctx) => ({ value: ctx.value, trueLabel: 'Admin' }),
7167
+ resizable: true,
7168
+ sortable: true,
7169
+ onSort: this.sort.bind(this),
7170
+ cellClass: 'nf-cell-vert-align',
7171
+ width: 100,
7172
+ },
7173
+ {
7174
+ key: 'createdAt',
7175
+ field: 'createdAt',
7176
+ label: 'Created At',
7177
+ valueFormatter: this.createdAtFormatter,
7178
+ filterType: 'DATETIME',
7179
+ filterPlaceholder: 'Any time',
7180
+ resizable: true,
7181
+ sortable: true,
7182
+ onSort: this.sort.bind(this),
7183
+ cellClass: 'nf-cell-vert-align',
7184
+ },
7185
+ {
7186
+ key: 'token',
7187
+ field: 'token',
7188
+ label: 'Token',
7189
+ cell: TokenCellComponent,
7190
+ cellInputs: (ctx) => ({ row: ctx.row, emitAction: ctx.emitAction }),
7191
+ resizable: true,
7192
+ cellClass: 'nf-cell-vert-align',
7193
+ },
7194
+ {
7195
+ key: 'isMfaEnabled',
7196
+ field: 'isMfaEnabled',
7197
+ label: 'MFA',
7198
+ // isMfaEnabled is a computed field the controller cannot filter on
7199
+ // ("unknown symbol") - no MFA filter control.
7200
+ cell: BooleanCellComponent,
7201
+ cellInputs: (ctx) => ({ value: ctx.value, trueLabel: 'Enabled' }),
7202
+ resizable: true,
7203
+ cellClass: 'nf-cell-vert-align',
7204
+ width: 100,
7205
+ },
7206
+ {
7207
+ key: 'id',
7208
+ field: 'id',
7209
+ label: 'ID',
7210
+ filterType: 'TEXTINPUT',
7211
+ cellClass: 'nf-cell-vert-align',
7212
+ resizable: true,
7213
+ sortable: true,
7214
+ onSort: this.sort.bind(this),
7215
+ onCellClick: (ctx) => {
7216
+ if (this.hasSelectedText()) {
7217
+ return;
7218
+ }
7219
+ this.openEditForm(ctx.row?.id);
7220
+ },
7221
+ hidden: true,
7222
+ },
7223
+ ];
7224
+ if (this.extService.processTableColumns) {
7225
+ tableColumns = this.extService.processTableColumns(tableColumns);
7226
+ }
7227
+ return tableColumns;
7228
+ }
6614
7229
  getIdentityRoleAttributes() {
6615
7230
  return this.zitiService.get('identity-role-attributes', {}, []).then((result) => {
6616
7231
  this.identityRoleAttributes = result.data;
6617
7232
  return result;
6618
7233
  });
6619
7234
  }
6620
- getData(filters, sort, page) {
7235
+ getData(filters, sort, page, pageSize) {
6621
7236
  // we can customize filters or sorting here before moving on...
6622
7237
  this.paging.page = page || this.paging.page;
7238
+ if (pageSize) {
7239
+ this.paging.total = pageSize;
7240
+ }
6623
7241
  return super.getTableData('identities', this.paging, filters, sort)
6624
7242
  .then((results) => {
6625
7243
  return this.processData(results);
@@ -9289,6 +9907,91 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
9289
9907
  args: [SHAREDZ_EXTENSION]
9290
9908
  }] }] });
9291
9909
 
9910
+ /*
9911
+ Copyright NetFoundry Inc.
9912
+
9913
+ Licensed under the Apache License, Version 2.0 (the "License");
9914
+ you may not use this file except in compliance with the License.
9915
+ You may obtain a copy of the License at
9916
+
9917
+ https://www.apache.org/licenses/LICENSE-2.0
9918
+
9919
+ Unless required by applicable law or agreed to in writing, software
9920
+ distributed under the License is distributed on an "AS IS" BASIS,
9921
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9922
+ See the License for the specific language governing permissions and
9923
+ limitations under the License.
9924
+ */
9925
+ /**
9926
+ * Header for the new list-table pages: an icon tile + title + one-line subtitle
9927
+ * (with an optional info affordance) on the left, and a primary "Create ..." action
9928
+ * on the right, over an optional tab strip. Emits the same `add` / `delete` actions
9929
+ * as {@link ListPageHeaderComponent} so pages can swap headers without changing their
9930
+ * handlers. Project the page's icon into the tile with a `header-icon` attribute.
9931
+ */
9932
+ class ListTableHeaderComponent {
9933
+ constructor() {
9934
+ this.title = '';
9935
+ this.subtitle = '';
9936
+ /** Label for the primary create button, e.g. "Create Identity". */
9937
+ this.createLabel = 'Create';
9938
+ /** Optional short helper text shown behind the (i) affordance next to the title. */
9939
+ this.infoText = '';
9940
+ this.tabs = [];
9941
+ this.showAdd = true;
9942
+ /** Hide the create control even when showAdd is true. */
9943
+ this.blockCreate = false;
9944
+ /** Hide the bulk-delete control shown when rows are selected. */
9945
+ this.blockDelete = false;
9946
+ this.actionClicked = new EventEmitter();
9947
+ }
9948
+ clickAction(value) {
9949
+ this.actionClicked.emit(value);
9950
+ }
9951
+ tabSelected(tab) {
9952
+ const parsedUrl = new URL(window.location.href);
9953
+ let path = parsedUrl.pathname;
9954
+ if (document.baseURI) {
9955
+ const base = new URL(document.baseURI).pathname;
9956
+ if (path.startsWith(base)) {
9957
+ path = path.slice(base.length);
9958
+ }
9959
+ }
9960
+ let tabUrl = tab.url;
9961
+ if (tab.url.startsWith('/')) {
9962
+ tabUrl = tab.url.slice(1);
9963
+ }
9964
+ if (path.startsWith('/')) {
9965
+ path = path.slice(1);
9966
+ }
9967
+ return tabUrl === path;
9968
+ }
9969
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListTableHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9970
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ListTableHeaderComponent, isStandalone: false, selector: "lib-list-table-header", inputs: { title: "title", subtitle: "subtitle", createLabel: "createLabel", infoText: "infoText", tabs: "tabs", showAdd: "showAdd", blockCreate: "blockCreate", blockDelete: "blockDelete" }, outputs: { actionClicked: "actionClicked" }, ngImport: i0, template: "<div class=\"lth\">\n <div class=\"lth-main\">\n <div class=\"lth-icon\" aria-hidden=\"true\">\n <ng-content select=\"[header-icon]\"></ng-content>\n </div>\n <div class=\"lth-titles\">\n <div class=\"lth-title-row\">\n <h1 class=\"lth-title\">{{ title }}</h1>\n @if (infoText) {\n <span class=\"lth-info\" [title]=\"infoText\" tabindex=\"0\" role=\"img\" [attr.aria-label]=\"infoText\">\n <span class=\"lth-info-badge\" aria-hidden=\"true\">i</span>\n </span>\n }\n </div>\n @if (subtitle) {\n <p class=\"lth-subtitle\">{{ subtitle }}</p>\n }\n </div>\n <div class=\"lth-actions\">\n @if (showAdd && !blockCreate) {\n <button type=\"button\" class=\"lth-create\" (click)=\"clickAction('add')\" data-action=\"add\" id=\"CreateButton\">\n <span class=\"lth-create-plus\" aria-hidden=\"true\">+</span>\n <span>{{ createLabel }}</span>\n </button>\n }\n @if (!showAdd && !blockDelete) {\n <button type=\"button\" class=\"lth-delete\" (click)=\"clickAction('delete')\" data-action=\"delete\" id=\"DeleteButton\">\n <span class=\"lth-delete-icon icon-minus\" aria-hidden=\"true\"></span>\n <span>Delete</span>\n </button>\n }\n </div>\n </div>\n @if (tabs?.length) {\n <div class=\"lth-tabs\">\n @for (tab of tabs; track tab) {\n <a\n class=\"lth-tab\"\n [ngClass]=\"{ selected: tabSelected(tab) }\"\n [routerLink]=\"tab.url\"\n [hidden]=\"tab.hidden\"\n >{{ tab.label }}</a>\n }\n </div>\n }\n</div>\n", styles: [":host{display:block;width:100%}.lth{display:flex;flex-direction:column;gap:.75rem;padding-bottom:.5rem}.lth-main{display:flex;align-items:center;gap:.875rem}.lth-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;border-radius:.75rem;color:var(--primary);background:color-mix(in srgb,var(--primary) 12%,var(--background));border:1px solid color-mix(in srgb,var(--primary) 22%,var(--background))}.lth-icon svg,.lth-icon img{display:block;width:1.5rem;height:1.5rem}:host ::ng-deep .lth-icon [class^=icon-],:host ::ng-deep .lth-icon [class*=\" icon-\"]{font-size:1.5rem;line-height:1}:host ::ng-deep .lth-icon [class^=icon-]:before,:host ::ng-deep .lth-icon [class*=\" icon-\"]:before{color:var(--primary)}.lth-titles{flex:1 1 auto;min-width:0}.lth-title-row{display:flex;align-items:center;gap:.5rem}.lth-title{margin:0;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:1.5rem;font-weight:700;line-height:1.15;color:var(--tableText, var(--text))}.lth-info{display:inline-flex;align-items:center;justify-content:center;padding:.25rem;background:var(--background);border:1px solid var(--stroke);border-radius:.375rem;box-shadow:0 1px 2px #0000000f;cursor:help}.lth-info:hover{border-color:var(--groupText)}.lth-info-badge{display:inline-flex;align-items:center;justify-content:center;width:.9375rem;height:.9375rem;font-size:.625rem;font-weight:700;font-style:italic;font-family:Georgia,Times New Roman,serif;line-height:1;color:#fff;background:var(--dark);border-radius:50%}.lth-subtitle{margin:.125rem 0 0;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;color:var(--groupText)}.lth-actions{flex:0 0 auto;display:flex;align-items:center;gap:.5rem}.lth-create{display:inline-flex;align-items:center;gap:.4375rem;height:2.5rem;padding:0 1rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:#fff;background:var(--primary);border:none;border-radius:var(--inputBorderRadius, .5rem);cursor:pointer;transition:filter .15s}.lth-create:hover{filter:brightness(1.06)}.lth-create-plus{font-size:1.125rem;line-height:1}.lth-delete{display:inline-flex;align-items:center;gap:.4375rem;height:2.5rem;padding:0 1rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:var(--redText, #9c0027);background:transparent;border:1px solid var(--redText, #9c0027);border-radius:var(--inputBorderRadius, .5rem);cursor:pointer;transition:background .15s}.lth-delete:hover{background:color-mix(in srgb,var(--redText, #9c0027) 8%,transparent)}.lth-tabs{display:flex;align-items:center;gap:1.25rem;border-bottom:1px solid var(--stroke)}.lth-tab{padding:.4375rem .125rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:var(--groupText);text-decoration:none;border-bottom:2px solid transparent;margin-bottom:-1px;cursor:pointer;transition:color .15s,border-color .15s}.lth-tab:hover{color:var(--text)}.lth-tab.selected{color:var(--primary);border-bottom-color:var(--primary)}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i4.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); }
9971
+ }
9972
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListTableHeaderComponent, decorators: [{
9973
+ type: Component,
9974
+ args: [{ selector: 'lib-list-table-header', standalone: false, template: "<div class=\"lth\">\n <div class=\"lth-main\">\n <div class=\"lth-icon\" aria-hidden=\"true\">\n <ng-content select=\"[header-icon]\"></ng-content>\n </div>\n <div class=\"lth-titles\">\n <div class=\"lth-title-row\">\n <h1 class=\"lth-title\">{{ title }}</h1>\n @if (infoText) {\n <span class=\"lth-info\" [title]=\"infoText\" tabindex=\"0\" role=\"img\" [attr.aria-label]=\"infoText\">\n <span class=\"lth-info-badge\" aria-hidden=\"true\">i</span>\n </span>\n }\n </div>\n @if (subtitle) {\n <p class=\"lth-subtitle\">{{ subtitle }}</p>\n }\n </div>\n <div class=\"lth-actions\">\n @if (showAdd && !blockCreate) {\n <button type=\"button\" class=\"lth-create\" (click)=\"clickAction('add')\" data-action=\"add\" id=\"CreateButton\">\n <span class=\"lth-create-plus\" aria-hidden=\"true\">+</span>\n <span>{{ createLabel }}</span>\n </button>\n }\n @if (!showAdd && !blockDelete) {\n <button type=\"button\" class=\"lth-delete\" (click)=\"clickAction('delete')\" data-action=\"delete\" id=\"DeleteButton\">\n <span class=\"lth-delete-icon icon-minus\" aria-hidden=\"true\"></span>\n <span>Delete</span>\n </button>\n }\n </div>\n </div>\n @if (tabs?.length) {\n <div class=\"lth-tabs\">\n @for (tab of tabs; track tab) {\n <a\n class=\"lth-tab\"\n [ngClass]=\"{ selected: tabSelected(tab) }\"\n [routerLink]=\"tab.url\"\n [hidden]=\"tab.hidden\"\n >{{ tab.label }}</a>\n }\n </div>\n }\n</div>\n", styles: [":host{display:block;width:100%}.lth{display:flex;flex-direction:column;gap:.75rem;padding-bottom:.5rem}.lth-main{display:flex;align-items:center;gap:.875rem}.lth-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;border-radius:.75rem;color:var(--primary);background:color-mix(in srgb,var(--primary) 12%,var(--background));border:1px solid color-mix(in srgb,var(--primary) 22%,var(--background))}.lth-icon svg,.lth-icon img{display:block;width:1.5rem;height:1.5rem}:host ::ng-deep .lth-icon [class^=icon-],:host ::ng-deep .lth-icon [class*=\" icon-\"]{font-size:1.5rem;line-height:1}:host ::ng-deep .lth-icon [class^=icon-]:before,:host ::ng-deep .lth-icon [class*=\" icon-\"]:before{color:var(--primary)}.lth-titles{flex:1 1 auto;min-width:0}.lth-title-row{display:flex;align-items:center;gap:.5rem}.lth-title{margin:0;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:1.5rem;font-weight:700;line-height:1.15;color:var(--tableText, var(--text))}.lth-info{display:inline-flex;align-items:center;justify-content:center;padding:.25rem;background:var(--background);border:1px solid var(--stroke);border-radius:.375rem;box-shadow:0 1px 2px #0000000f;cursor:help}.lth-info:hover{border-color:var(--groupText)}.lth-info-badge{display:inline-flex;align-items:center;justify-content:center;width:.9375rem;height:.9375rem;font-size:.625rem;font-weight:700;font-style:italic;font-family:Georgia,Times New Roman,serif;line-height:1;color:#fff;background:var(--dark);border-radius:50%}.lth-subtitle{margin:.125rem 0 0;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;color:var(--groupText)}.lth-actions{flex:0 0 auto;display:flex;align-items:center;gap:.5rem}.lth-create{display:inline-flex;align-items:center;gap:.4375rem;height:2.5rem;padding:0 1rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:#fff;background:var(--primary);border:none;border-radius:var(--inputBorderRadius, .5rem);cursor:pointer;transition:filter .15s}.lth-create:hover{filter:brightness(1.06)}.lth-create-plus{font-size:1.125rem;line-height:1}.lth-delete{display:inline-flex;align-items:center;gap:.4375rem;height:2.5rem;padding:0 1rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:var(--redText, #9c0027);background:transparent;border:1px solid var(--redText, #9c0027);border-radius:var(--inputBorderRadius, .5rem);cursor:pointer;transition:background .15s}.lth-delete:hover{background:color-mix(in srgb,var(--redText, #9c0027) 8%,transparent)}.lth-tabs{display:flex;align-items:center;gap:1.25rem;border-bottom:1px solid var(--stroke)}.lth-tab{padding:.4375rem .125rem;font-family:var(--lt-font, \"Open Sans\", sans-serif);font-size:.8125rem;font-weight:600;color:var(--groupText);text-decoration:none;border-bottom:2px solid transparent;margin-bottom:-1px;cursor:pointer;transition:color .15s,border-color .15s}.lth-tab:hover{color:var(--text)}.lth-tab.selected{color:var(--primary);border-bottom-color:var(--primary)}\n"] }]
9975
+ }], propDecorators: { title: [{
9976
+ type: Input
9977
+ }], subtitle: [{
9978
+ type: Input
9979
+ }], createLabel: [{
9980
+ type: Input
9981
+ }], infoText: [{
9982
+ type: Input
9983
+ }], tabs: [{
9984
+ type: Input
9985
+ }], showAdd: [{
9986
+ type: Input
9987
+ }], blockCreate: [{
9988
+ type: Input
9989
+ }], blockDelete: [{
9990
+ type: Input
9991
+ }], actionClicked: [{
9992
+ type: Output
9993
+ }] } });
9994
+
9292
9995
  /*
9293
9996
  Copyright NetFoundry Inc.
9294
9997
 
@@ -10769,6 +11472,1430 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
10769
11472
  args: ['document:keydown.escape', ['$event']]
10770
11473
  }] } });
10771
11474
 
11475
+ /*
11476
+ Copyright NetFoundry Inc.
11477
+
11478
+ Licensed under the Apache License, Version 2.0 (the "License");
11479
+ you may not use this file except in compliance with the License.
11480
+ You may obtain a copy of the License at
11481
+
11482
+ https://www.apache.org/licenses/LICENSE-2.0
11483
+
11484
+ Unless required by applicable law or agreed to in writing, software
11485
+ distributed under the License is distributed on an "AS IS" BASIS,
11486
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11487
+ See the License for the specific language governing permissions and
11488
+ limitations under the License.
11489
+ */
11490
+ /**
11491
+ * Per-instance state + localStorage persistence for {@link ListTableComponent}.
11492
+ * Reuses the legacy `ziti_<tableId>_table_state` / `_column_widths` keys so saved
11493
+ * layouts carry over; unknown (ag-only) fields are ignored.
11494
+ */
11495
+ class ListTableService {
11496
+ constructor() {
11497
+ this.tableId = '';
11498
+ /** Coordinates the toolbar dropdowns so only one (filter pill or column chooser)
11499
+ * is open at a time. Openers emit themselves; others close when they see an
11500
+ * owner that isn't them. */
11501
+ this.menuOpenedSubject = new Subject();
11502
+ this.menuOpened$ = this.menuOpenedSubject.asObservable();
11503
+ }
11504
+ notifyMenuOpened(owner) {
11505
+ this.menuOpenedSubject.next(owner);
11506
+ }
11507
+ stateKey() {
11508
+ return `ziti_${this.tableId}_table_state`;
11509
+ }
11510
+ widthsKey() {
11511
+ return `ziti_${this.tableId}_column_widths`;
11512
+ }
11513
+ /**
11514
+ * Returns a shallow-cloned copy of `columns` with any persisted width, visibility,
11515
+ * and order applied. Columns not present in the saved state keep their declared
11516
+ * position (appended after the saved ones).
11517
+ */
11518
+ applyPersistedState(columns) {
11519
+ let cols = columns.map((c) => ({ ...c }));
11520
+ // widths
11521
+ try {
11522
+ const raw = localStorage.getItem(this.widthsKey());
11523
+ if (raw) {
11524
+ const widths = JSON.parse(raw) || {};
11525
+ cols.forEach((c) => {
11526
+ if (widths[c.key] != null) {
11527
+ c.width = widths[c.key];
11528
+ }
11529
+ });
11530
+ }
11531
+ }
11532
+ catch {
11533
+ // ignore malformed / unavailable storage
11534
+ }
11535
+ // order + visibility
11536
+ try {
11537
+ const raw = localStorage.getItem(this.stateKey());
11538
+ if (raw) {
11539
+ const state = JSON.parse(raw) || [];
11540
+ const ordered = [];
11541
+ state.forEach((s) => {
11542
+ const found = cols.find((c) => c.key === s.colId);
11543
+ if (found && !ordered.includes(found)) {
11544
+ if (s.hide != null) {
11545
+ found.hidden = s.hide;
11546
+ }
11547
+ if (s.width != null) {
11548
+ found.width = s.width;
11549
+ }
11550
+ ordered.push(found);
11551
+ }
11552
+ });
11553
+ cols.forEach((c) => {
11554
+ if (!ordered.includes(c)) {
11555
+ ordered.push(c);
11556
+ }
11557
+ });
11558
+ if (ordered.length) {
11559
+ cols = ordered;
11560
+ }
11561
+ }
11562
+ }
11563
+ catch {
11564
+ // ignore malformed / unavailable storage
11565
+ }
11566
+ return cols;
11567
+ }
11568
+ /** Persists the current order + visibility (and widths embedded in each entry). */
11569
+ saveState(columns) {
11570
+ const state = columns.map((c) => ({ colId: c.key, hide: !!c.hidden, width: c.width }));
11571
+ try {
11572
+ localStorage.setItem(this.stateKey(), JSON.stringify(state));
11573
+ }
11574
+ catch {
11575
+ // ignore
11576
+ }
11577
+ }
11578
+ /** Persists a single column's width, then rewrites the ordered state. */
11579
+ saveWidth(colId, width, columns) {
11580
+ try {
11581
+ const raw = localStorage.getItem(this.widthsKey());
11582
+ const widths = raw ? JSON.parse(raw) : {};
11583
+ widths[colId] = width;
11584
+ localStorage.setItem(this.widthsKey(), JSON.stringify(widths));
11585
+ }
11586
+ catch {
11587
+ // ignore
11588
+ }
11589
+ this.saveState(columns);
11590
+ }
11591
+ /** Clears all persisted layout for this table ("Restore Default Table"). */
11592
+ reset() {
11593
+ try {
11594
+ localStorage.removeItem(this.stateKey());
11595
+ localStorage.removeItem(this.widthsKey());
11596
+ }
11597
+ catch {
11598
+ // ignore
11599
+ }
11600
+ }
11601
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListTableService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
11602
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListTableService }); }
11603
+ }
11604
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListTableService, decorators: [{
11605
+ type: Injectable
11606
+ }] });
11607
+
11608
+ /**
11609
+ * Generic, theme-driven action menu popover. Presentational only: the host owns
11610
+ * open state and position and handles `(itemClick)` / `(closed)`.
11611
+ */
11612
+ class ListMenuComponent {
11613
+ constructor() {
11614
+ /** Whether the menu is rendered. */
11615
+ this.open = false;
11616
+ /** Rows to render. */
11617
+ this.items = [];
11618
+ /** Fixed-position left offset (px). */
11619
+ this.left = 0;
11620
+ /** Fixed-position top offset (px). */
11621
+ this.top = 0;
11622
+ /** Emitted when a clickable row is selected. */
11623
+ this.itemClick = new EventEmitter();
11624
+ /** Emitted when the user clicks outside the open menu. */
11625
+ this.closed = new EventEmitter();
11626
+ this.trackItem = (_, item) => item.action ?? item.label;
11627
+ }
11628
+ onItem(item, event) {
11629
+ if (item.disabled) {
11630
+ return;
11631
+ }
11632
+ this.itemClick.emit({ item, event });
11633
+ }
11634
+ onClickOutside(event) {
11635
+ this.closed.emit(event);
11636
+ }
11637
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11638
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ListMenuComponent, isStandalone: false, selector: "lib-list-menu", inputs: { open: "open", items: "items", left: "left", top: "top", menuId: "menuId" }, outputs: { itemClick: "itemClick", closed: "closed" }, ngImport: i0, template: "@if (open) {\n <div\n class=\"lt-menu\"\n [attr.id]=\"menuId || null\"\n [ngStyle]=\"{ left: left + 'px', top: top + 'px' }\"\n (clickOutside)=\"onClickOutside($event)\"\n >\n @for (item of items; track trackItem($index, item)) {\n <button\n type=\"button\"\n class=\"lt-menu-item\"\n [class.has-icon]=\"!!item.icon || !!item.iconSvg\"\n [class.is-hidden]=\"item.hidden\"\n [disabled]=\"!!item.disabled\"\n [attr.id]=\"item.id || null\"\n (click)=\"onItem(item, $event)\"\n >\n @if (item.iconSvg) {\n <span class=\"lt-menu-icon\" aria-hidden=\"true\" [innerHTML]=\"item.iconSvg\"></span>\n } @else if (item.icon) {\n <span class=\"lt-menu-icon {{ item.icon }}\" aria-hidden=\"true\"></span>\n }\n <span class=\"lt-menu-label\">{{ item.label }}</span>\n </button>\n }\n </div>\n}\n", styles: [":host{display:contents}.lt-menu{position:fixed;z-index:1000;min-width:11rem;padding:.25rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);font-family:var(--lt-font);animation:lt-menu-in .1s ease-out}@keyframes lt-menu-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.lt-menu-item{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4375rem .5rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);text-align:left;background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer}.lt-menu-item:hover{background:var(--lt-row-hover);color:var(--lt-link)}.lt-menu-item:hover .lt-menu-icon{color:var(--lt-link)}.lt-menu-item.is-hidden{display:none}.lt-menu-item:disabled{opacity:.45;cursor:default}.lt-menu-item:disabled:hover{background:transparent;color:var(--lt-cell-text)}.lt-menu-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:1rem;font-size:.9375rem;color:var(--lt-icon)}.lt-menu-icon ::ng-deep svg{display:block;width:.9375rem;height:.9375rem}\n"], dependencies: [{ kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i3$1.ClickOutsideDirective, selector: "[clickOutside]", inputs: ["clickOutsideEnabled", "attachOutsideOnClick", "delayClickOutsideInit", "emitOnBlur", "exclude", "excludeBeforeClick", "clickOutsideEvents"], outputs: ["clickOutside"] }] }); }
11639
+ }
11640
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListMenuComponent, decorators: [{
11641
+ type: Component,
11642
+ args: [{ selector: 'lib-list-menu', standalone: false, template: "@if (open) {\n <div\n class=\"lt-menu\"\n [attr.id]=\"menuId || null\"\n [ngStyle]=\"{ left: left + 'px', top: top + 'px' }\"\n (clickOutside)=\"onClickOutside($event)\"\n >\n @for (item of items; track trackItem($index, item)) {\n <button\n type=\"button\"\n class=\"lt-menu-item\"\n [class.has-icon]=\"!!item.icon || !!item.iconSvg\"\n [class.is-hidden]=\"item.hidden\"\n [disabled]=\"!!item.disabled\"\n [attr.id]=\"item.id || null\"\n (click)=\"onItem(item, $event)\"\n >\n @if (item.iconSvg) {\n <span class=\"lt-menu-icon\" aria-hidden=\"true\" [innerHTML]=\"item.iconSvg\"></span>\n } @else if (item.icon) {\n <span class=\"lt-menu-icon {{ item.icon }}\" aria-hidden=\"true\"></span>\n }\n <span class=\"lt-menu-label\">{{ item.label }}</span>\n </button>\n }\n </div>\n}\n", styles: [":host{display:contents}.lt-menu{position:fixed;z-index:1000;min-width:11rem;padding:.25rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);font-family:var(--lt-font);animation:lt-menu-in .1s ease-out}@keyframes lt-menu-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.lt-menu-item{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4375rem .5rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);text-align:left;background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer}.lt-menu-item:hover{background:var(--lt-row-hover);color:var(--lt-link)}.lt-menu-item:hover .lt-menu-icon{color:var(--lt-link)}.lt-menu-item.is-hidden{display:none}.lt-menu-item:disabled{opacity:.45;cursor:default}.lt-menu-item:disabled:hover{background:transparent;color:var(--lt-cell-text)}.lt-menu-icon{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:1rem;font-size:.9375rem;color:var(--lt-icon)}.lt-menu-icon ::ng-deep svg{display:block;width:.9375rem;height:.9375rem}\n"] }]
11643
+ }], propDecorators: { open: [{
11644
+ type: Input
11645
+ }], items: [{
11646
+ type: Input
11647
+ }], left: [{
11648
+ type: Input
11649
+ }], top: [{
11650
+ type: Input
11651
+ }], menuId: [{
11652
+ type: Input
11653
+ }], itemClick: [{
11654
+ type: Output
11655
+ }], closed: [{
11656
+ type: Output
11657
+ }] } });
11658
+
11659
+ /**
11660
+ * Row selection toggle for {@link ListTableComponent}. Router / exclusive /
11661
+ * reserved / system rows cannot be toggled.
11662
+ */
11663
+ class SelectCellComponent {
11664
+ constructor() {
11665
+ this.item = {
11666
+ id: '',
11667
+ name: '',
11668
+ selected: false,
11669
+ hideSelect: false,
11670
+ };
11671
+ this.disableToggle = false;
11672
+ }
11673
+ set row(v) {
11674
+ this.item = v || this.item;
11675
+ this.disableToggle =
11676
+ _.get(this.item, 'typeId') === 'Router' ||
11677
+ _.get(this.item, 'exclusiveTo') ||
11678
+ _.get(this.item, 'reserved') === true ||
11679
+ _.get(this.item, 'isSystem') === true;
11680
+ }
11681
+ toggle(item) {
11682
+ if (this.disableToggle || !this.toggleItem) {
11683
+ return;
11684
+ }
11685
+ this.toggleItem(item);
11686
+ }
11687
+ get tooltip() {
11688
+ let tooltip = '';
11689
+ if (this.item?.isSystem) {
11690
+ tooltip = 'Deleting "system" entities is not allowed';
11691
+ }
11692
+ else if (this.item?.typeId === 'Router') {
11693
+ tooltip = 'Unable to delete "Router" Identities. Please delete the associated Router instead.';
11694
+ }
11695
+ return tooltip;
11696
+ }
11697
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SelectCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11698
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: SelectCellComponent, isStandalone: false, selector: "lib-select-cell", inputs: { row: "row", toggleItem: "toggleItem" }, ngImport: i0, template: "@if (!item.hideSelect) {\n <div\n #toggleButton\n (click)=\"toggle(item)\"\n [ngClass]=\"{ selected: item.selected, disabled: disableToggle }\"\n attr.name=\"RowSelect0_{{ item.name }}\"\n class=\"row-toggle tSelect\"\n id=\"RowSelect0_{{ item.id }}\"\n attr.data-id=\"{{item.id}}\"\n [matTooltip]=\"tooltip\"\n ></div>\n}\n", styles: [".row-toggle.tSelect{box-sizing:border-box;width:1rem;height:1rem;border:1px solid var(--lt-border);border-radius:.25rem;cursor:pointer;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background-color:var(--lt-control-bg);background-repeat:no-repeat;background-position:center center;transition:border-color .12s,background-color .12s}.row-toggle.tSelect:hover{border-color:var(--lt-link)}.row-toggle.tSelect.selected{border-color:var(--lt-link);background-color:var(--lt-link);background-size:.75rem;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M2.5 7.6 5.7 10.8 11.5 3.6'/%3E%3C/svg%3E\")}.row-toggle.tSelect.disabled{opacity:.3;cursor:not-allowed;transform:translate(-50%,-50%)!important}.row-toggle.tSelect.disabled:hover{border-color:var(--lt-border);background-color:var(--lt-control-bg)}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] }); }
11699
+ }
11700
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SelectCellComponent, decorators: [{
11701
+ type: Component,
11702
+ args: [{ selector: 'lib-select-cell', standalone: false, template: "@if (!item.hideSelect) {\n <div\n #toggleButton\n (click)=\"toggle(item)\"\n [ngClass]=\"{ selected: item.selected, disabled: disableToggle }\"\n attr.name=\"RowSelect0_{{ item.name }}\"\n class=\"row-toggle tSelect\"\n id=\"RowSelect0_{{ item.id }}\"\n attr.data-id=\"{{item.id}}\"\n [matTooltip]=\"tooltip\"\n ></div>\n}\n", styles: [".row-toggle.tSelect{box-sizing:border-box;width:1rem;height:1rem;border:1px solid var(--lt-border);border-radius:.25rem;cursor:pointer;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background-color:var(--lt-control-bg);background-repeat:no-repeat;background-position:center center;transition:border-color .12s,background-color .12s}.row-toggle.tSelect:hover{border-color:var(--lt-link)}.row-toggle.tSelect.selected{border-color:var(--lt-link);background-color:var(--lt-link);background-size:.75rem;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M2.5 7.6 5.7 10.8 11.5 3.6'/%3E%3C/svg%3E\")}.row-toggle.tSelect.disabled{opacity:.3;cursor:not-allowed;transform:translate(-50%,-50%)!important}.row-toggle.tSelect.disabled:hover{border-color:var(--lt-border);background-color:var(--lt-control-bg)}\n"] }]
11703
+ }], propDecorators: { row: [{
11704
+ type: Input
11705
+ }], toggleItem: [{
11706
+ type: Input
11707
+ }] } });
11708
+
11709
+ /*
11710
+ Copyright NetFoundry Inc.
11711
+
11712
+ Licensed under the Apache License, Version 2.0 (the "License");
11713
+ you may not use this file except in compliance with the License.
11714
+ You may obtain a copy of the License at
11715
+
11716
+ https://www.apache.org/licenses/LICENSE-2.0
11717
+
11718
+ Unless required by applicable law or agreed to in writing, software
11719
+ distributed under the License is distributed on an "AS IS" BASIS,
11720
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11721
+ See the License for the specific language governing permissions and
11722
+ limitations under the License.
11723
+ */
11724
+ /** Header cell for {@link ListTableComponent}: label, 3-state sort (asc/desc), and hide. */
11725
+ class ListHeaderCellComponent {
11726
+ constructor() {
11727
+ this.sortChanged = new EventEmitter();
11728
+ this.hideColumn = new EventEmitter();
11729
+ }
11730
+ get enableSorting() {
11731
+ return !!(this.column?.sortable || this.column?.enableSorting);
11732
+ }
11733
+ sort() {
11734
+ if (!this.enableSorting) {
11735
+ return;
11736
+ }
11737
+ const dir = !this.sortDir || this.sortDir === 'desc' ? 'asc' : 'desc';
11738
+ this.sortDir = dir;
11739
+ this.column.onSort?.(this.column.key, dir);
11740
+ this.sortChanged.emit({ colId: this.column.key, dir });
11741
+ }
11742
+ hide(event) {
11743
+ event.stopPropagation();
11744
+ this.hideColumn.emit(this.column);
11745
+ }
11746
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListHeaderCellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11747
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ListHeaderCellComponent, isStandalone: false, selector: "lib-list-header-cell", inputs: { column: "column", sortDir: "sortDir" }, outputs: { sortChanged: "sortChanged", hideColumn: "hideColumn" }, ngImport: i0, template: "<div\n (click)=\"sort()\"\n [ngClass]=\"{ 'nf-can-sort': enableSorting }\"\n class=\"lt-header-container\"\n id=\"TableHeaderContainer_{{ column.key }}\"\n>\n @if (column.hideable !== false) {\n <span\n (click)=\"hide($event)\"\n class=\"lt-header-icon lt-col-hide-button\"\n id=\"HeaderHide_{{ column.key }}\"\n title=\"Hide Column\"\n >\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"\n d=\"M1 8s2.7-4.5 7-4.5S15 8 15 8s-2.7 4.5-7 4.5S1 8 1 8Z\"/>\n <circle cx=\"8\" cy=\"8\" r=\"1.9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/>\n <line x1=\"2.5\" y1=\"13.5\" x2=\"13.5\" y2=\"2.5\" stroke=\"currentColor\" stroke-width=\"1.3\"/>\n </svg>\n </span>\n }\n <div class=\"lt-header-label\">\n <span\n [title]=\"column.label\"\n class=\"lt-header-text\"\n [class.is-sorted]=\"sortDir === 'asc' || sortDir === 'desc'\"\n id=\"HeaderName_{{ column.key }}\"\n unselectable=\"on\"\n >{{ column.label }}</span>\n @if (enableSorting) {\n <span class=\"lt-sort\" [class.asc]=\"sortDir === 'asc'\" [class.desc]=\"sortDir === 'desc'\">\n <svg viewBox=\"0 0 8 14\" width=\"8\" height=\"13\" aria-hidden=\"true\">\n <path class=\"lt-sort-up\" d=\"M4 0 7.5 4.5H0.5z\"/>\n <path class=\"lt-sort-down\" d=\"M4 14 0.5 9.5H7.5z\"/>\n </svg>\n </span>\n }\n </div>\n</div>\n", styles: [":host{display:flex;width:100%;align-items:center}.lt-header-container{display:flex;align-items:center;width:100%;height:100%;cursor:default;gap:.25rem}.lt-header-container.nf-can-sort{cursor:pointer}.lt-header-label{display:flex;align-items:center;flex:1 1 auto;min-width:0;gap:.25rem}.lt-header-text{color:var(--lt-header-text);font-family:var(--lt-font);font-size:.6875rem;font-weight:600;letter-spacing:.045em;text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .15s}.lt-header-text.is-sorted{color:var(--lt-icon-active);font-weight:700}.lt-header-text-container{display:flex;align-items:center;min-width:0;flex:1 1 auto}.lt-header-text-container.show-input-filter{width:100%}.lt-header-icon{display:inline-flex;align-items:center;justify-content:center;color:var(--lt-icon);cursor:pointer;transition:opacity .2s,color .2s}.lt-header-icon svg{display:block}.lt-header-icon:hover{color:var(--lt-icon-active)}.lt-col-hide-button,.lt-col-filter-button{opacity:0}.lt-header-container:hover .lt-col-hide-button,.lt-header-container:hover .lt-col-filter-button{opacity:1}.lt-col-filter-button.nf-filter-applied{opacity:1;color:var(--lt-icon-active)}.lt-sort{display:inline-flex;align-items:center;flex:0 0 auto;margin-left:auto;margin-right:.375rem}.lt-sort svg{display:block}.lt-sort .lt-sort-up,.lt-sort .lt-sort-down{fill:var(--lt-icon);opacity:.35;transition:opacity .15s,fill .15s}.lt-sort.asc .lt-sort-up,.lt-sort.desc .lt-sort-down{fill:var(--lt-icon-active);opacity:1}.nf-hidden{display:none}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
11748
+ }
11749
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListHeaderCellComponent, decorators: [{
11750
+ type: Component,
11751
+ args: [{ selector: 'lib-list-header-cell', standalone: false, template: "<div\n (click)=\"sort()\"\n [ngClass]=\"{ 'nf-can-sort': enableSorting }\"\n class=\"lt-header-container\"\n id=\"TableHeaderContainer_{{ column.key }}\"\n>\n @if (column.hideable !== false) {\n <span\n (click)=\"hide($event)\"\n class=\"lt-header-icon lt-col-hide-button\"\n id=\"HeaderHide_{{ column.key }}\"\n title=\"Hide Column\"\n >\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"\n d=\"M1 8s2.7-4.5 7-4.5S15 8 15 8s-2.7 4.5-7 4.5S1 8 1 8Z\"/>\n <circle cx=\"8\" cy=\"8\" r=\"1.9\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"/>\n <line x1=\"2.5\" y1=\"13.5\" x2=\"13.5\" y2=\"2.5\" stroke=\"currentColor\" stroke-width=\"1.3\"/>\n </svg>\n </span>\n }\n <div class=\"lt-header-label\">\n <span\n [title]=\"column.label\"\n class=\"lt-header-text\"\n [class.is-sorted]=\"sortDir === 'asc' || sortDir === 'desc'\"\n id=\"HeaderName_{{ column.key }}\"\n unselectable=\"on\"\n >{{ column.label }}</span>\n @if (enableSorting) {\n <span class=\"lt-sort\" [class.asc]=\"sortDir === 'asc'\" [class.desc]=\"sortDir === 'desc'\">\n <svg viewBox=\"0 0 8 14\" width=\"8\" height=\"13\" aria-hidden=\"true\">\n <path class=\"lt-sort-up\" d=\"M4 0 7.5 4.5H0.5z\"/>\n <path class=\"lt-sort-down\" d=\"M4 14 0.5 9.5H7.5z\"/>\n </svg>\n </span>\n }\n </div>\n</div>\n", styles: [":host{display:flex;width:100%;align-items:center}.lt-header-container{display:flex;align-items:center;width:100%;height:100%;cursor:default;gap:.25rem}.lt-header-container.nf-can-sort{cursor:pointer}.lt-header-label{display:flex;align-items:center;flex:1 1 auto;min-width:0;gap:.25rem}.lt-header-text{color:var(--lt-header-text);font-family:var(--lt-font);font-size:.6875rem;font-weight:600;letter-spacing:.045em;text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .15s}.lt-header-text.is-sorted{color:var(--lt-icon-active);font-weight:700}.lt-header-text-container{display:flex;align-items:center;min-width:0;flex:1 1 auto}.lt-header-text-container.show-input-filter{width:100%}.lt-header-icon{display:inline-flex;align-items:center;justify-content:center;color:var(--lt-icon);cursor:pointer;transition:opacity .2s,color .2s}.lt-header-icon svg{display:block}.lt-header-icon:hover{color:var(--lt-icon-active)}.lt-col-hide-button,.lt-col-filter-button{opacity:0}.lt-header-container:hover .lt-col-hide-button,.lt-header-container:hover .lt-col-filter-button{opacity:1}.lt-col-filter-button.nf-filter-applied{opacity:1;color:var(--lt-icon-active)}.lt-sort{display:inline-flex;align-items:center;flex:0 0 auto;margin-left:auto;margin-right:.375rem}.lt-sort svg{display:block}.lt-sort .lt-sort-up,.lt-sort .lt-sort-down{fill:var(--lt-icon);opacity:.35;transition:opacity .15s,fill .15s}.lt-sort.asc .lt-sort-up,.lt-sort.desc .lt-sort-down{fill:var(--lt-icon-active);opacity:1}.nf-hidden{display:none}\n"] }]
11752
+ }], propDecorators: { column: [{
11753
+ type: Input
11754
+ }], sortDir: [{
11755
+ type: Input
11756
+ }], sortChanged: [{
11757
+ type: Output
11758
+ }], hideColumn: [{
11759
+ type: Output
11760
+ }] } });
11761
+
11762
+ /*
11763
+ Copyright NetFoundry Inc.
11764
+
11765
+ Licensed under the Apache License, Version 2.0 (the "License");
11766
+ you may not use this file except in compliance with the License.
11767
+ You may obtain a copy of the License at
11768
+
11769
+ https://www.apache.org/licenses/LICENSE-2.0
11770
+
11771
+ Unless required by applicable law or agreed to in writing, software
11772
+ distributed under the License is distributed on an "AS IS" BASIS,
11773
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11774
+ See the License for the specific language governing permissions and
11775
+ limitations under the License.
11776
+ */
11777
+ const DATE_PRESETS = [
11778
+ { label: 'Any time', value: '' },
11779
+ { label: 'Last Hour', value: 'hour' },
11780
+ { label: 'Last Day', value: 'day' },
11781
+ { label: 'Last Week', value: 'week' },
11782
+ { label: 'Last Month', value: 'month' },
11783
+ ];
11784
+ /**
11785
+ * A single control in the table's filter row. Renders the right widget for the
11786
+ * column's `filterType` (text input / single-select / multi-select / date-range)
11787
+ * and pushes changes through {@link DataTableFilterService}, whose value contract the
11788
+ * controller query-builder already understands (icontains / = / anyOf() / datetime range).
11789
+ */
11790
+ class ListFilterCellComponent {
11791
+ constructor(filterService, listSvc) {
11792
+ this.filterService = filterService;
11793
+ this.listSvc = listSvc;
11794
+ this.mode = 'text';
11795
+ this.options = [];
11796
+ this.textValue = '';
11797
+ this.selectedValue = '';
11798
+ this.selectedValues = [];
11799
+ /** In-dropdown search text for the multi-select attribute combo. */
11800
+ this.attrSearch = '';
11801
+ this.open = false;
11802
+ this.menuLeft = 0;
11803
+ this.menuTop = 0;
11804
+ this.menuWidth = 0;
11805
+ // custom date range (daterange mode)
11806
+ this.dateRangeStart = null;
11807
+ this.dateRangeEnd = null;
11808
+ this.sub = new Subscription();
11809
+ this.textDebounced = _.debounce(() => this.emitText(), 400);
11810
+ }
11811
+ ngOnInit() {
11812
+ this.mode = this.resolveMode();
11813
+ this.options = this.resolveOptions();
11814
+ this.sub.add(this.filterService.filtersChanged.subscribe((filters) => this.syncFromFilters(filters)));
11815
+ // close this menu whenever another dropdown opens
11816
+ this.sub.add(this.listSvc.menuOpened$.subscribe((owner) => {
11817
+ if (owner !== this) {
11818
+ this.open = false;
11819
+ }
11820
+ }));
11821
+ }
11822
+ ngOnDestroy() {
11823
+ this.sub.unsubscribe();
11824
+ }
11825
+ // ------------------------------------------------------------------ config
11826
+ get field() {
11827
+ return this.column.filterField || this.column.field || this.column.key;
11828
+ }
11829
+ get placeholder() {
11830
+ return this.column.filterPlaceholder || 'All';
11831
+ }
11832
+ resolveMode() {
11833
+ switch (this.column.filterType) {
11834
+ case 'TEXTINPUT':
11835
+ return 'text';
11836
+ case 'ATTRIBUTE':
11837
+ return 'multi';
11838
+ case 'DATETIME':
11839
+ return 'daterange';
11840
+ default:
11841
+ return 'single';
11842
+ }
11843
+ }
11844
+ resolveOptions() {
11845
+ if (this.mode === 'daterange') {
11846
+ return DATE_PRESETS;
11847
+ }
11848
+ if (this.mode === 'multi') {
11849
+ const getAttrs = this.column.headerParams?.['getRoleAttributes'];
11850
+ const attrs = (typeof getAttrs === 'function' ? getAttrs() : null) || this.column.filterOptions || [];
11851
+ return attrs.map((a) => (typeof a === 'string' ? { label: a, value: a } : a));
11852
+ }
11853
+ const raw = this.column.getFilterOptions ? this.column.getFilterOptions() : this.column.filterOptions || [];
11854
+ return raw;
11855
+ }
11856
+ // ------------------------------------------------------------------ display
11857
+ /** Short display of the current selection, shown after the field label when set. */
11858
+ get valueLabel() {
11859
+ if (this.mode === 'multi') {
11860
+ if (this.selectedValues.length === 1) {
11861
+ return this.selectedValues[0];
11862
+ }
11863
+ return `${this.selectedValues.length}`;
11864
+ }
11865
+ if (this.mode === 'daterange') {
11866
+ if (this.selectedValue === 'custom' && this.dateRangeStart) {
11867
+ const start = moment$1(this.dateRangeStart).format('M/D/YY');
11868
+ const end = this.dateRangeEnd ? moment$1(this.dateRangeEnd).format('M/D/YY') : '';
11869
+ return end ? `${start} – ${end}` : start;
11870
+ }
11871
+ const found = this.options.find((o) => o.value === this.selectedValue);
11872
+ return found && found.value ? found.label : '';
11873
+ }
11874
+ const match = this.options.find((o) => `${o.value}` === `${this.selectedValue}`);
11875
+ return match && match.value !== '' && match.value != null ? match.label : '';
11876
+ }
11877
+ get hasValue() {
11878
+ if (this.mode === 'multi') {
11879
+ return this.selectedValues.length > 0;
11880
+ }
11881
+ return this.selectedValue !== '' && this.selectedValue != null;
11882
+ }
11883
+ isOptionSelected(opt) {
11884
+ if (this.mode === 'multi') {
11885
+ return this.selectedValues.includes(opt.value);
11886
+ }
11887
+ return `${opt.value}` === `${this.selectedValue}`;
11888
+ }
11889
+ /** Attribute options filtered by the in-dropdown search box (multi mode). */
11890
+ get filteredOptions() {
11891
+ if (this.mode !== 'multi' || !this.attrSearch) {
11892
+ return this.options;
11893
+ }
11894
+ const q = this.attrSearch.toLowerCase();
11895
+ return this.options.filter((o) => `${o.label ?? o.value}`.toLowerCase().includes(q));
11896
+ }
11897
+ /** A named attribute (rendered with @ + secondary color) vs a role attribute (# + primary). */
11898
+ isNamedOption(opt) {
11899
+ return String(opt?.value ?? opt?.label ?? '').charAt(0) === '@';
11900
+ }
11901
+ /** Option label with its #/@ prefix, matching the Roles column chips. */
11902
+ attrLabel(opt) {
11903
+ const raw = String(opt?.label ?? opt?.value ?? '');
11904
+ if (raw.charAt(0) === '@' || raw.charAt(0) === '#') {
11905
+ return raw;
11906
+ }
11907
+ return '#' + raw;
11908
+ }
11909
+ // ------------------------------------------------------------------ open/close
11910
+ toggleMenu(event) {
11911
+ event.stopPropagation();
11912
+ if (this.open) {
11913
+ this.open = false;
11914
+ return;
11915
+ }
11916
+ const rect = this.trigger?.nativeElement.getBoundingClientRect();
11917
+ if (rect) {
11918
+ this.menuLeft = rect.left;
11919
+ this.menuTop = rect.bottom + 4;
11920
+ this.menuWidth = Math.max(rect.width, this.mode === 'multi' ? 220 : 160);
11921
+ }
11922
+ // refresh dynamic option sources (e.g. role attributes) each open
11923
+ this.options = this.resolveOptions();
11924
+ this.attrSearch = '';
11925
+ this.open = true;
11926
+ this.listSvc.notifyMenuOpened(this);
11927
+ }
11928
+ closeMenu() {
11929
+ this.open = false;
11930
+ }
11931
+ // ------------------------------------------------------------------ text
11932
+ onTextInput() {
11933
+ this.textDebounced();
11934
+ }
11935
+ emitText() {
11936
+ this.filterService.updateFilter({
11937
+ filterName: this.column.label,
11938
+ columnId: this.field,
11939
+ value: this.textValue,
11940
+ label: this.textValue,
11941
+ type: 'TEXTINPUT',
11942
+ });
11943
+ }
11944
+ // ------------------------------------------------------------------ single
11945
+ selectOption(opt) {
11946
+ this.selectedValue = opt.value;
11947
+ this.open = false;
11948
+ if (opt.value === '' || opt.value == null) {
11949
+ this.removeThis();
11950
+ return;
11951
+ }
11952
+ this.filterService.updateFilter({
11953
+ filterName: this.column.label,
11954
+ columnId: this.field,
11955
+ value: opt.value,
11956
+ label: opt.label,
11957
+ type: this.column.filterType || 'SELECT',
11958
+ });
11959
+ }
11960
+ // ------------------------------------------------------------------ multi
11961
+ toggleOption(opt) {
11962
+ const idx = this.selectedValues.indexOf(opt.value);
11963
+ if (idx >= 0) {
11964
+ this.selectedValues.splice(idx, 1);
11965
+ }
11966
+ else {
11967
+ this.selectedValues.push(opt.value);
11968
+ }
11969
+ this.emitMulti();
11970
+ }
11971
+ emitMulti() {
11972
+ if (!this.selectedValues.length) {
11973
+ this.removeThis();
11974
+ return;
11975
+ }
11976
+ const filter = {
11977
+ filterName: this.column.label,
11978
+ columnId: this.field,
11979
+ value: [...this.selectedValues],
11980
+ label: this.selectedValues.join(', '),
11981
+ type: 'ATTRIBUTE',
11982
+ semantic: 'AnyOf',
11983
+ };
11984
+ this.filterService.updateFilter(filter);
11985
+ }
11986
+ // ------------------------------------------------------------------ date range
11987
+ selectDatePreset(opt) {
11988
+ this.selectedValue = opt.value;
11989
+ this.dateRangeStart = null;
11990
+ this.dateRangeEnd = null;
11991
+ this.open = false;
11992
+ if (!opt.value) {
11993
+ this.removeThis();
11994
+ return;
11995
+ }
11996
+ const end = moment$1();
11997
+ let start;
11998
+ switch (opt.value) {
11999
+ case 'hour':
12000
+ start = moment$1().subtract(1, 'hours');
12001
+ break;
12002
+ case 'week':
12003
+ start = moment$1().subtract(7, 'days');
12004
+ break;
12005
+ case 'month':
12006
+ start = moment$1().subtract(1, 'month');
12007
+ break;
12008
+ case 'day':
12009
+ default:
12010
+ start = moment$1().subtract(24, 'hours');
12011
+ break;
12012
+ }
12013
+ this.filterService.updateFilter({
12014
+ filterName: this.column.label,
12015
+ columnId: this.field,
12016
+ value: [encodeURIComponent(start.toISOString()), encodeURIComponent(end.toISOString())],
12017
+ label: opt.label,
12018
+ type: 'DATETIME',
12019
+ });
12020
+ }
12021
+ openCustomRange(event) {
12022
+ event.stopPropagation();
12023
+ this.open = false;
12024
+ // open the Material calendar on the next tick (after the menu closes)
12025
+ _.delay(() => this.rangePicker?.open(), 0);
12026
+ }
12027
+ onRangeStartChanged(date) {
12028
+ this.dateRangeStart = date;
12029
+ this.emitCustomRange();
12030
+ }
12031
+ onRangeEndChanged(date) {
12032
+ this.dateRangeEnd = date;
12033
+ this.emitCustomRange();
12034
+ }
12035
+ emitCustomRange() {
12036
+ if (!this.dateRangeStart || !this.dateRangeEnd) {
12037
+ return;
12038
+ }
12039
+ this.selectedValue = 'custom';
12040
+ const start = moment$1(this.dateRangeStart).startOf('day');
12041
+ const end = moment$1(this.dateRangeEnd).endOf('day');
12042
+ this.filterService.updateFilter({
12043
+ filterName: this.column.label,
12044
+ columnId: this.field,
12045
+ value: [encodeURIComponent(start.toISOString()), encodeURIComponent(end.toISOString())],
12046
+ label: `${start.format('M/D/YY')} – ${end.format('M/D/YY')}`,
12047
+ type: 'DATETIME',
12048
+ });
12049
+ }
12050
+ // ------------------------------------------------------------------ clear
12051
+ clear(event) {
12052
+ event.stopPropagation();
12053
+ this.selectedValue = '';
12054
+ this.selectedValues = [];
12055
+ this.textValue = '';
12056
+ this.dateRangeStart = null;
12057
+ this.dateRangeEnd = null;
12058
+ this.removeThis();
12059
+ }
12060
+ removeThis() {
12061
+ this.filterService.removeFilter({
12062
+ filterName: this.column.label,
12063
+ columnId: this.field,
12064
+ value: '',
12065
+ label: '',
12066
+ });
12067
+ }
12068
+ // ------------------------------------------------------------------ sync
12069
+ syncFromFilters(filters) {
12070
+ const match = filters.find((f) => f.columnId === this.field);
12071
+ if (!match) {
12072
+ this.textValue = '';
12073
+ this.selectedValue = '';
12074
+ this.selectedValues = [];
12075
+ this.dateRangeStart = null;
12076
+ this.dateRangeEnd = null;
12077
+ return;
12078
+ }
12079
+ if (this.mode === 'text') {
12080
+ this.textValue = match.value ?? '';
12081
+ }
12082
+ else if (this.mode === 'multi') {
12083
+ this.selectedValues = _.isArray(match.value) ? [...match.value] : [match.value];
12084
+ }
12085
+ else if (this.mode === 'daterange') {
12086
+ // keep the locally chosen preset label; match.value is the raw [start,end] range
12087
+ }
12088
+ else {
12089
+ this.selectedValue = match.value;
12090
+ }
12091
+ }
12092
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListFilterCellComponent, deps: [{ token: DataTableFilterService }, { token: ListTableService }], target: i0.ɵɵFactoryTarget.Component }); }
12093
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ListFilterCellComponent, isStandalone: false, selector: "lib-list-filter-cell", inputs: { column: "column" }, viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true }, { propertyName: "rangePicker", first: true, predicate: ["rangePicker"], descendants: true }], ngImport: i0, template: "@switch (mode) {\n @case ('text') {\n <div class=\"fc-text\">\n <input\n class=\"fc-input\"\n type=\"text\"\n [placeholder]=\"column.filterPlaceholder || 'Filter'\"\n [(ngModel)]=\"textValue\"\n (keyup)=\"onTextInput()\"\n />\n @if (textValue) {\n <span class=\"fc-clear\" (click)=\"clear($event)\" title=\"Clear\" aria-label=\"Clear filter\">&times;</span>\n }\n </div>\n }\n @default {\n <button\n #trigger\n type=\"button\"\n class=\"fc-pill\"\n [class.has-value]=\"hasValue\"\n [class.is-open]=\"open\"\n (click)=\"toggleMenu($event)\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"open\"\n >\n <span class=\"fc-pill-field\">{{ column.label }}</span>\n @if (hasValue) {\n <span class=\"fc-pill-value\" [title]=\"valueLabel\">{{ valueLabel }}</span>\n <span class=\"fc-clear\" (click)=\"clear($event)\" title=\"Clear\" aria-label=\"Clear filter\">&times;</span>\n } @else {\n <span class=\"fc-caret\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 10 6\" width=\"9\" height=\"6\"><path fill=\"currentColor\" d=\"M0 0h10L5 6z\"/></svg>\n </span>\n }\n </button>\n\n @if (open) {\n <div\n class=\"fc-menu\"\n (clickOutside)=\"closeMenu()\"\n [ngStyle]=\"{ left: menuLeft + 'px', top: menuTop + 'px', 'min-width.px': menuWidth }\"\n >\n @if (mode === 'multi') {\n <div class=\"fc-search\">\n <svg class=\"fc-search-icon\" viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" aria-hidden=\"true\">\n <circle cx=\"7\" cy=\"7\" r=\"4.3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>\n <line x1=\"10.4\" y1=\"10.4\" x2=\"14.6\" y2=\"14.6\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n </svg>\n <input\n class=\"fc-search-input\"\n type=\"text\"\n [(ngModel)]=\"attrSearch\"\n placeholder=\"Search attributes\"\n (click)=\"$event.stopPropagation()\"\n autocomplete=\"off\"\n />\n </div>\n @if (!filteredOptions.length) {\n <div class=\"fc-empty\">{{ options.length ? 'No matches' : 'No attributes available' }}</div>\n }\n <div class=\"fc-opt-list\">\n @for (opt of filteredOptions; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"toggleOption(opt)\">\n <span class=\"fc-check\" aria-hidden=\"true\">\n @if (isOptionSelected(opt)) {\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/></svg>\n }\n </span>\n <span class=\"fc-attr\" [class.at]=\"isNamedOption(opt)\">{{ attrLabel(opt) }}</span>\n </button>\n }\n </div>\n } @else if (mode === 'daterange') {\n @for (opt of options; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"selectDatePreset(opt)\">\n <span class=\"fc-opt-label\">{{ opt.label }}</span>\n </button>\n }\n <div class=\"fc-divider\"></div>\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"selectedValue === 'custom'\" (click)=\"openCustomRange($event)\">\n <span class=\"fc-opt-label\">Custom range\u2026</span>\n </button>\n } @else {\n @for (opt of options; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"selectOption(opt)\">\n @if (opt.chipClass) {\n <span [ngClass]=\"opt.chipClass\">{{ opt.label }}</span>\n } @else {\n <span class=\"fc-opt-label\">{{ opt.label }}</span>\n }\n </button>\n }\n }\n </div>\n }\n }\n}\n\n@if (mode === 'daterange') {\n <!-- hidden Material range input; the calendar is opened from the \"Custom range\u2026\" option -->\n <mat-date-range-input class=\"fc-hidden-range\" [rangePicker]=\"rangePicker\">\n <input matStartDate [(ngModel)]=\"dateRangeStart\" (dateChange)=\"onRangeStartChanged($event.value)\" tabindex=\"-1\" aria-hidden=\"true\" />\n <input matEndDate [(ngModel)]=\"dateRangeEnd\" (dateChange)=\"onRangeEndChanged($event.value)\" tabindex=\"-1\" aria-hidden=\"true\" />\n </mat-date-range-input>\n <mat-date-range-picker #rangePicker xPosition=\"start\" yPosition=\"below\"></mat-date-range-picker>\n}\n", styles: [":host{display:inline-flex;position:relative}.fc-hidden-range{position:absolute;left:0;top:100%;width:1px;height:1px;opacity:0;pointer-events:none;overflow:hidden}.fc-divider{height:1px;margin:.25rem;background:var(--lt-border)}.fc-text{position:relative;display:flex;align-items:center}.fc-input{width:100%;height:1.875rem;padding:0 1.5rem 0 .5rem;font-family:var(--lt-font);font-size:.75rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);outline:none}.fc-input::placeholder{color:var(--placeholder)}.fc-input:focus{border-color:var(--lt-link)}.fc-pill{display:inline-flex;align-items:center;gap:.375rem;height:var(--lt-toolbar-height);max-width:14rem;padding:0 .75rem;font-family:var(--lt-font);font-size:.8125rem;font-weight:600;color:var(--lt-cell-text);background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);cursor:pointer;white-space:nowrap;transition:border-color .15s,background .15s,color .15s}.fc-pill:hover,.fc-pill.is-open{border-color:var(--lt-link)}.fc-pill.has-value{border-color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 8%,var(--lt-control-bg))}.fc-pill-field{color:var(--lt-cell-text)}.fc-pill.has-value .fc-pill-field{color:var(--groupText)}.fc-pill.has-value .fc-pill-field:after{content:\":\"}.fc-pill-value{color:var(--lt-link);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-caret{flex:0 0 auto;display:inline-flex;color:var(--lt-icon)}.fc-clear{flex:0 0 auto;font-size:1rem;line-height:1;color:var(--lt-icon);cursor:pointer}.fc-clear:hover{color:var(--lt-icon-active)}.fc-text .fc-clear{position:absolute;right:.5rem}.fc-menu{position:fixed;z-index:1000;max-height:18rem;overflow-y:auto;padding:.25rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:fc-in .1s ease-out}@keyframes fc-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.fc-opt{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4375rem .5rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer;text-align:left}.fc-opt:hover{background:var(--lt-row-hover)}.fc-opt.selected{color:var(--lt-link);font-weight:600}.fc-opt-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-check{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:1rem;height:1rem;box-sizing:border-box;border:.09375rem solid var(--lt-border);border-radius:.25rem;color:var(--lt-control-bg);background:var(--lt-control-bg)}.fc-opt.selected .fc-check{background:var(--lt-link);border-color:var(--lt-link)}.fc-empty{padding:.5rem;font-size:.75rem;color:var(--groupText)}.fc-search{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:.375rem;padding:.125rem .25rem .375rem;background:var(--lt-control-bg)}.fc-search-icon{flex:0 0 auto;margin-left:.25rem;color:var(--lt-icon)}.fc-search-input{flex:1 1 auto;min-width:0;height:1.75rem;padding:0 .5rem;font-family:var(--lt-font);font-size:.75rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);outline:none}.fc-search-input::placeholder{color:var(--placeholder)}.fc-search-input:focus{border-color:var(--lt-link)}.fc-opt-list{display:flex;flex-direction:column}.fc-attr{display:inline-flex;align-items:center;padding:.0625rem .375rem;font-size:.75rem;font-weight:600;line-height:1.4;border-radius:.25rem;color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 12%,transparent);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-attr.at{color:var(--secondary);background:color-mix(in srgb,var(--secondary) 14%,transparent)}.fc-opt.selected .fc-attr{color:var(--lt-link)}.fc-opt.selected .fc-attr.at{color:var(--secondary)}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2.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: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i6.MatDateRangeInput, selector: "mat-date-range-input", inputs: ["rangePicker", "required", "dateFilter", "min", "max", "disabled", "separator", "comparisonStart", "comparisonEnd"], exportAs: ["matDateRangeInput"] }, { kind: "directive", type: i6.MatStartDate, selector: "input[matStartDate]", outputs: ["dateChange", "dateInput"] }, { kind: "directive", type: i6.MatEndDate, selector: "input[matEndDate]", outputs: ["dateChange", "dateInput"] }, { kind: "component", type: i6.MatDateRangePicker, selector: "mat-date-range-picker", exportAs: ["matDateRangePicker"] }, { kind: "directive", type: i3$1.ClickOutsideDirective, selector: "[clickOutside]", inputs: ["clickOutsideEnabled", "attachOutsideOnClick", "delayClickOutsideInit", "emitOnBlur", "exclude", "excludeBeforeClick", "clickOutsideEvents"], outputs: ["clickOutside"] }] }); }
12094
+ }
12095
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListFilterCellComponent, decorators: [{
12096
+ type: Component,
12097
+ args: [{ selector: 'lib-list-filter-cell', standalone: false, template: "@switch (mode) {\n @case ('text') {\n <div class=\"fc-text\">\n <input\n class=\"fc-input\"\n type=\"text\"\n [placeholder]=\"column.filterPlaceholder || 'Filter'\"\n [(ngModel)]=\"textValue\"\n (keyup)=\"onTextInput()\"\n />\n @if (textValue) {\n <span class=\"fc-clear\" (click)=\"clear($event)\" title=\"Clear\" aria-label=\"Clear filter\">&times;</span>\n }\n </div>\n }\n @default {\n <button\n #trigger\n type=\"button\"\n class=\"fc-pill\"\n [class.has-value]=\"hasValue\"\n [class.is-open]=\"open\"\n (click)=\"toggleMenu($event)\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"open\"\n >\n <span class=\"fc-pill-field\">{{ column.label }}</span>\n @if (hasValue) {\n <span class=\"fc-pill-value\" [title]=\"valueLabel\">{{ valueLabel }}</span>\n <span class=\"fc-clear\" (click)=\"clear($event)\" title=\"Clear\" aria-label=\"Clear filter\">&times;</span>\n } @else {\n <span class=\"fc-caret\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 10 6\" width=\"9\" height=\"6\"><path fill=\"currentColor\" d=\"M0 0h10L5 6z\"/></svg>\n </span>\n }\n </button>\n\n @if (open) {\n <div\n class=\"fc-menu\"\n (clickOutside)=\"closeMenu()\"\n [ngStyle]=\"{ left: menuLeft + 'px', top: menuTop + 'px', 'min-width.px': menuWidth }\"\n >\n @if (mode === 'multi') {\n <div class=\"fc-search\">\n <svg class=\"fc-search-icon\" viewBox=\"0 0 16 16\" width=\"13\" height=\"13\" aria-hidden=\"true\">\n <circle cx=\"7\" cy=\"7\" r=\"4.3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>\n <line x1=\"10.4\" y1=\"10.4\" x2=\"14.6\" y2=\"14.6\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n </svg>\n <input\n class=\"fc-search-input\"\n type=\"text\"\n [(ngModel)]=\"attrSearch\"\n placeholder=\"Search attributes\"\n (click)=\"$event.stopPropagation()\"\n autocomplete=\"off\"\n />\n </div>\n @if (!filteredOptions.length) {\n <div class=\"fc-empty\">{{ options.length ? 'No matches' : 'No attributes available' }}</div>\n }\n <div class=\"fc-opt-list\">\n @for (opt of filteredOptions; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"toggleOption(opt)\">\n <span class=\"fc-check\" aria-hidden=\"true\">\n @if (isOptionSelected(opt)) {\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\"><path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/></svg>\n }\n </span>\n <span class=\"fc-attr\" [class.at]=\"isNamedOption(opt)\">{{ attrLabel(opt) }}</span>\n </button>\n }\n </div>\n } @else if (mode === 'daterange') {\n @for (opt of options; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"selectDatePreset(opt)\">\n <span class=\"fc-opt-label\">{{ opt.label }}</span>\n </button>\n }\n <div class=\"fc-divider\"></div>\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"selectedValue === 'custom'\" (click)=\"openCustomRange($event)\">\n <span class=\"fc-opt-label\">Custom range\u2026</span>\n </button>\n } @else {\n @for (opt of options; track opt.value) {\n <button type=\"button\" class=\"fc-opt\" [class.selected]=\"isOptionSelected(opt)\" (click)=\"selectOption(opt)\">\n @if (opt.chipClass) {\n <span [ngClass]=\"opt.chipClass\">{{ opt.label }}</span>\n } @else {\n <span class=\"fc-opt-label\">{{ opt.label }}</span>\n }\n </button>\n }\n }\n </div>\n }\n }\n}\n\n@if (mode === 'daterange') {\n <!-- hidden Material range input; the calendar is opened from the \"Custom range\u2026\" option -->\n <mat-date-range-input class=\"fc-hidden-range\" [rangePicker]=\"rangePicker\">\n <input matStartDate [(ngModel)]=\"dateRangeStart\" (dateChange)=\"onRangeStartChanged($event.value)\" tabindex=\"-1\" aria-hidden=\"true\" />\n <input matEndDate [(ngModel)]=\"dateRangeEnd\" (dateChange)=\"onRangeEndChanged($event.value)\" tabindex=\"-1\" aria-hidden=\"true\" />\n </mat-date-range-input>\n <mat-date-range-picker #rangePicker xPosition=\"start\" yPosition=\"below\"></mat-date-range-picker>\n}\n", styles: [":host{display:inline-flex;position:relative}.fc-hidden-range{position:absolute;left:0;top:100%;width:1px;height:1px;opacity:0;pointer-events:none;overflow:hidden}.fc-divider{height:1px;margin:.25rem;background:var(--lt-border)}.fc-text{position:relative;display:flex;align-items:center}.fc-input{width:100%;height:1.875rem;padding:0 1.5rem 0 .5rem;font-family:var(--lt-font);font-size:.75rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);outline:none}.fc-input::placeholder{color:var(--placeholder)}.fc-input:focus{border-color:var(--lt-link)}.fc-pill{display:inline-flex;align-items:center;gap:.375rem;height:var(--lt-toolbar-height);max-width:14rem;padding:0 .75rem;font-family:var(--lt-font);font-size:.8125rem;font-weight:600;color:var(--lt-cell-text);background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);cursor:pointer;white-space:nowrap;transition:border-color .15s,background .15s,color .15s}.fc-pill:hover,.fc-pill.is-open{border-color:var(--lt-link)}.fc-pill.has-value{border-color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 8%,var(--lt-control-bg))}.fc-pill-field{color:var(--lt-cell-text)}.fc-pill.has-value .fc-pill-field{color:var(--groupText)}.fc-pill.has-value .fc-pill-field:after{content:\":\"}.fc-pill-value{color:var(--lt-link);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-caret{flex:0 0 auto;display:inline-flex;color:var(--lt-icon)}.fc-clear{flex:0 0 auto;font-size:1rem;line-height:1;color:var(--lt-icon);cursor:pointer}.fc-clear:hover{color:var(--lt-icon-active)}.fc-text .fc-clear{position:absolute;right:.5rem}.fc-menu{position:fixed;z-index:1000;max-height:18rem;overflow-y:auto;padding:.25rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:fc-in .1s ease-out}@keyframes fc-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.fc-opt{display:flex;align-items:center;gap:.5rem;width:100%;padding:.4375rem .5rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer;text-align:left}.fc-opt:hover{background:var(--lt-row-hover)}.fc-opt.selected{color:var(--lt-link);font-weight:600}.fc-opt-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-check{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:1rem;height:1rem;box-sizing:border-box;border:.09375rem solid var(--lt-border);border-radius:.25rem;color:var(--lt-control-bg);background:var(--lt-control-bg)}.fc-opt.selected .fc-check{background:var(--lt-link);border-color:var(--lt-link)}.fc-empty{padding:.5rem;font-size:.75rem;color:var(--groupText)}.fc-search{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:.375rem;padding:.125rem .25rem .375rem;background:var(--lt-control-bg)}.fc-search-icon{flex:0 0 auto;margin-left:.25rem;color:var(--lt-icon)}.fc-search-input{flex:1 1 auto;min-width:0;height:1.75rem;padding:0 .5rem;font-family:var(--lt-font);font-size:.75rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);outline:none}.fc-search-input::placeholder{color:var(--placeholder)}.fc-search-input:focus{border-color:var(--lt-link)}.fc-opt-list{display:flex;flex-direction:column}.fc-attr{display:inline-flex;align-items:center;padding:.0625rem .375rem;font-size:.75rem;font-weight:600;line-height:1.4;border-radius:.25rem;color:var(--lt-link);background:color-mix(in srgb,var(--lt-link) 12%,transparent);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fc-attr.at{color:var(--secondary);background:color-mix(in srgb,var(--secondary) 14%,transparent)}.fc-opt.selected .fc-attr{color:var(--lt-link)}.fc-opt.selected .fc-attr.at{color:var(--secondary)}\n"] }]
12098
+ }], ctorParameters: () => [{ type: DataTableFilterService }, { type: ListTableService }], propDecorators: { column: [{
12099
+ type: Input
12100
+ }], trigger: [{
12101
+ type: ViewChild,
12102
+ args: ['trigger']
12103
+ }], rangePicker: [{
12104
+ type: ViewChild,
12105
+ args: ['rangePicker']
12106
+ }] } });
12107
+
12108
+ /*
12109
+ Copyright NetFoundry Inc.
12110
+
12111
+ Licensed under the Apache License, Version 2.0 (the "License");
12112
+ you may not use this file except in compliance with the License.
12113
+ You may obtain a copy of the License at
12114
+
12115
+ https://www.apache.org/licenses/LICENSE-2.0
12116
+
12117
+ Unless required by applicable law or agreed to in writing, software
12118
+ distributed under the License is distributed on an "AS IS" BASIS,
12119
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12120
+ See the License for the specific language governing permissions and
12121
+ limitations under the License.
12122
+ */
12123
+ /**
12124
+ * Custom, token-skinnable list table. Columns are declared with {@link ListColumn};
12125
+ * sort, filter, and pagination flow through {@link DataTableFilterService}.
12126
+ */
12127
+ class ListTableComponent {
12128
+ set tableId(id) {
12129
+ this._tableId = id;
12130
+ this.svc.tableId = id;
12131
+ }
12132
+ get tableId() {
12133
+ return this._tableId;
12134
+ }
12135
+ set columns(value) {
12136
+ this._rawColumns = value || [];
12137
+ if (this._initialized) {
12138
+ this.buildColumns();
12139
+ }
12140
+ }
12141
+ constructor(svc, tableFilterService, el, ngZone, sanitizer) {
12142
+ this.svc = svc;
12143
+ this.tableFilterService = tableFilterService;
12144
+ this.el = el;
12145
+ this.ngZone = ngZone;
12146
+ this.sanitizer = sanitizer;
12147
+ this._tableId = '';
12148
+ this.rowData = [];
12149
+ this.isLoading = false;
12150
+ this.options = { noSelect: false, noMenu: false };
12151
+ this.startCount = '-';
12152
+ this.endCount = '-';
12153
+ this.totalCount = '-';
12154
+ this.filterApplied = false;
12155
+ this.menuItems = [];
12156
+ this.headerActions = [];
12157
+ this.noItemsImage = 'nodata';
12158
+ this.showNoItemsAdd = true;
12159
+ this.noItemsReadonly = false;
12160
+ this.accessDenied = false;
12161
+ this.entityTypeName = '';
12162
+ this.filterName = 'name';
12163
+ this.filterColumn = 'name';
12164
+ this.actionRequested = new EventEmitter();
12165
+ this.tableReady = new EventEmitter();
12166
+ /** Full column list (includes hidden), after persisted state. */
12167
+ this.allColumns = [];
12168
+ /** Currently rendered columns (visible + ordered). */
12169
+ this.visibleColumns = [];
12170
+ /** Hidden columns, shaped for the shared hidden-columns bar (`headerName`). */
12171
+ this.hiddenColumns = [];
12172
+ this.entityTypeLabel = '';
12173
+ // ---- action menus ----
12174
+ this.openMenu = false;
12175
+ this.openHeaderMenu = false;
12176
+ this.menuLeft = 0;
12177
+ this.menuTop = 0;
12178
+ this.selectedItem = { actionList: [] };
12179
+ // ---- selection ----
12180
+ this.allToggled = false;
12181
+ // ---- column chooser (skinned show/hide + reorder panel) ----
12182
+ this.showColumnChooser = false;
12183
+ // ---- integrated toolbar: search + filter chips + pagination ----
12184
+ this.searchString = '';
12185
+ this.activeFilters = [];
12186
+ this.filtering = false;
12187
+ this.inputChangedDebounced = _.debounce(() => this.applySearch(), 400);
12188
+ // ---- responsive filter overflow: controls that don't fit collapse into "More" ----
12189
+ this.visibleFilterColumns = [];
12190
+ this.overflowFilterColumns = [];
12191
+ this.showFilterOverflow = false;
12192
+ this.filterWidths = new Map();
12193
+ this.filtersDirty = true;
12194
+ this.overflowRaf = 0;
12195
+ this.FILTER_GAP = 8; // px, matches the .lt-filter-zone/.lt-filters gap
12196
+ this.MORE_BTN_W = 92; // px reserved for the "More" trigger when overflowing
12197
+ this.onPointerMove = (e) => this.handleResizeMove(e);
12198
+ this.onPointerUp = () => this.handleResizeEnd();
12199
+ this._rawColumns = [];
12200
+ this._initialColumns = [];
12201
+ this._initialized = false;
12202
+ this.subscription = new Subscription();
12203
+ this.trackRow = (index, row) => row?.id ?? row?.name ?? index;
12204
+ this.trackCol = (index, col) => col.key;
12205
+ // ---------------------------------------------------------------- hover tooltip
12206
+ this.hoverTooltip = null;
12207
+ // ---------------------------------------------------------------- selection
12208
+ this.toggleItem = (item) => {
12209
+ item.selected = !item.selected;
12210
+ this.allToggled = !_.isEmpty(this.rowData) && _.every(this.rowData, { selected: true });
12211
+ this.actionRequested.emit({ action: 'toggleItem', item });
12212
+ };
12213
+ // ---------------------------------------------------------------- action menus
12214
+ this.openActionMenu = (event, item) => {
12215
+ this.selectedItem = item;
12216
+ this.openMenu = true;
12217
+ _.delay(() => {
12218
+ const height = this.contextMenu?.nativeElement?.offsetHeight || 120;
12219
+ const windowOffset = window.innerHeight - event.clientY;
12220
+ const menuOffset = windowOffset <= height ? height - windowOffset : 0;
12221
+ this.menuLeft = event.clientX - 100;
12222
+ this.menuTop = event.clientY + 5 - menuOffset;
12223
+ }, 10);
12224
+ };
12225
+ this.closeActionMenu = () => {
12226
+ this.selectedItem = { actionList: [] };
12227
+ this.openMenu = false;
12228
+ this.openHeaderMenu = false;
12229
+ };
12230
+ this.closeHeaderActionMenu = () => {
12231
+ this.openHeaderMenu = false;
12232
+ };
12233
+ /** Restore-default (rotate) icon for the built-in header action. */
12234
+ this.RESET_TABLE_ICON = '<svg viewBox="0 0 16 16" width="15" height="15"><path fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" d="M3.2 8a4.8 4.8 0 1 0 1.3-3.3"/><path fill="currentColor" d="M2.8 2.8l-.3 2.9 2.8-.4z"/></svg>';
12235
+ // ---- rows-per-page + numbered pagination (the skin footer) ----
12236
+ this.pageSizeOptions = [25, 50, 100];
12237
+ // ---- auto-refresh (the skin toolbar toggle) ----
12238
+ this.autoRefresh = false;
12239
+ this.autoRefreshIntervalMs = 30000;
12240
+ // ---- row action icons ----
12241
+ // A menu item's host-supplied `icon` (SVG markup) is rendered before its label in
12242
+ // the row's kebab menu. Sanitized once per unique SVG and cached.
12243
+ this.iconCache = new Map();
12244
+ }
12245
+ ngOnInit() {
12246
+ this._initialized = true;
12247
+ this.buildColumns();
12248
+ this.updateEntityTypeLabel();
12249
+ this.subscription.add(this.tableFilterService.filtersChanged.subscribe((filters) => {
12250
+ // chips: every non-hidden filter except the free-text search (shown in the box)
12251
+ this.activeFilters = filters.filter((f) => f.hidden !== true && f.columnId !== this.filterColumn);
12252
+ const match = filters.find((f) => f.columnId === this.filterColumn);
12253
+ this.searchString = match ? match.value : '';
12254
+ // an activated filter changes which controls are pinned and how wide they render
12255
+ this.filtersDirty = true;
12256
+ this.scheduleFilterOverflow();
12257
+ }));
12258
+ this.subscription.add(this.tableFilterService.filtering.subscribe((f) => (this.filtering = f)));
12259
+ // close the column chooser whenever a filter dropdown (or anything else) opens
12260
+ this.subscription.add(this.svc.menuOpened$.subscribe((owner) => {
12261
+ if (owner !== this) {
12262
+ this.showColumnChooser = false;
12263
+ }
12264
+ }));
12265
+ this.tableReady.emit({ component: this });
12266
+ }
12267
+ ngAfterViewInit() {
12268
+ this.ensureFilterResizeObserver();
12269
+ }
12270
+ ngAfterViewChecked() {
12271
+ // the whole table sits behind @if(showTable), so the toolbar may not exist
12272
+ // until row data arrives; (re)attach the observer once/whenever it appears
12273
+ this.ensureFilterResizeObserver();
12274
+ }
12275
+ ngOnDestroy() {
12276
+ this.subscription.unsubscribe();
12277
+ this.detachResizeListeners();
12278
+ this.filterRO?.disconnect();
12279
+ if (this.overflowRaf) {
12280
+ cancelAnimationFrame(this.overflowRaf);
12281
+ }
12282
+ if (this.autoRefreshTimer) {
12283
+ clearInterval(this.autoRefreshTimer);
12284
+ }
12285
+ }
12286
+ get showTable() {
12287
+ return this.isLoading || this.filterApplied || this.rowData?.length > 0;
12288
+ }
12289
+ /** Columns surfaced as inline filter controls in the toolbar. Text filters are
12290
+ * excluded because the free-text search box already covers the primary text field. */
12291
+ get filterableColumns() {
12292
+ return this.allColumns.filter((c) => c.filterType && c.filterType !== 'TEXTINPUT');
12293
+ }
12294
+ // -------------------------------------------------- responsive filter overflow
12295
+ /** A filter column is "active" when it has an applied filter. */
12296
+ isFilterActive(col) {
12297
+ return this.activeFilters.some((f) => f.columnId === col.key);
12298
+ }
12299
+ /** Applied filters that are currently hidden in the overflow dropdown. */
12300
+ get hiddenActiveCount() {
12301
+ return this.overflowFilterColumns.filter((c) => this.isFilterActive(c)).length;
12302
+ }
12303
+ toggleFilterOverflow(event) {
12304
+ event.stopPropagation();
12305
+ this.showFilterOverflow = !this.showFilterOverflow;
12306
+ if (this.showFilterOverflow) {
12307
+ this.svc.notifyMenuOpened(this);
12308
+ }
12309
+ }
12310
+ closeFilterOverflow(event) {
12311
+ // keep open when the click lands in a filter's own Material overlay (e.g. the
12312
+ // date-range calendar), which renders outside this popover's DOM subtree
12313
+ const target = event?.target;
12314
+ if (target?.closest?.('.cdk-overlay-container')) {
12315
+ return;
12316
+ }
12317
+ this.showFilterOverflow = false;
12318
+ }
12319
+ ensureFilterResizeObserver() {
12320
+ // observe the toolbar row (not the content-sized zone) so a window resize,
12321
+ // which changes the row's available width, actually fires the observer
12322
+ const target = this.filterZone?.nativeElement?.parentElement ?? undefined;
12323
+ if (!target || typeof ResizeObserver === 'undefined' || target === this.observedToolbar) {
12324
+ return;
12325
+ }
12326
+ // ResizeObserver fires outside Angular's zone; re-enter so the recomputed
12327
+ // visible/overflow split actually triggers change detection.
12328
+ if (!this.filterRO) {
12329
+ this.filterRO = new ResizeObserver(() => this.ngZone.run(() => this.scheduleFilterOverflow()));
12330
+ }
12331
+ else {
12332
+ this.filterRO.disconnect();
12333
+ }
12334
+ this.filterRO.observe(target);
12335
+ this.observedToolbar = target;
12336
+ // compute the split for the newly-observed row (deferred, so it never mutates
12337
+ // bound state inside the change-detection pass that called us)
12338
+ this.scheduleFilterOverflow();
12339
+ }
12340
+ scheduleFilterOverflow() {
12341
+ if (this.overflowRaf) {
12342
+ cancelAnimationFrame(this.overflowRaf);
12343
+ }
12344
+ this.overflowRaf = requestAnimationFrame(() => {
12345
+ this.overflowRaf = 0;
12346
+ this.ensureFilterResizeObserver();
12347
+ this.recomputeFilterOverflow();
12348
+ });
12349
+ }
12350
+ recomputeFilterOverflow() {
12351
+ const cols = this.filterableColumns;
12352
+ if (!cols.length) {
12353
+ this.visibleFilterColumns = [];
12354
+ this.overflowFilterColumns = [];
12355
+ return;
12356
+ }
12357
+ const needMeasure = this.filtersDirty || cols.some((c) => !this.filterWidths.has(c.key));
12358
+ if (!needMeasure) {
12359
+ this.applyFilterSplit();
12360
+ return;
12361
+ }
12362
+ // render every control inline for one frame so each can be measured, then collapse
12363
+ this.visibleFilterColumns = cols;
12364
+ this.overflowFilterColumns = [];
12365
+ requestAnimationFrame(() => {
12366
+ const row = this.filtersRow?.nativeElement;
12367
+ if (row) {
12368
+ row.querySelectorAll('[data-filter-key]').forEach((el) => {
12369
+ const key = el.getAttribute('data-filter-key');
12370
+ if (key) {
12371
+ this.filterWidths.set(key, Math.ceil(el.getBoundingClientRect().width));
12372
+ }
12373
+ });
12374
+ }
12375
+ this.filtersDirty = false;
12376
+ this.applyFilterSplit();
12377
+ });
12378
+ }
12379
+ /** Split filter controls into inline vs. overflow, pinning active filters and
12380
+ * dropping inactive (then, only if forced, active) from the right until they fit. */
12381
+ applyFilterSplit() {
12382
+ const zone = this.filterZone?.nativeElement;
12383
+ const cols = this.filterableColumns;
12384
+ if (!zone || !cols.length) {
12385
+ return;
12386
+ }
12387
+ // budget = the toolbar row's width minus its fixed siblings (search / divider /
12388
+ // Columns) and the gaps between them, i.e. the space filters may take before
12389
+ // they'd push the Columns control off the row
12390
+ const toolbarLeft = zone.parentElement;
12391
+ const siblingWidth = (sel) => {
12392
+ const node = toolbarLeft?.querySelector(sel);
12393
+ return node ? Math.ceil(node.getBoundingClientRect().width) : 0;
12394
+ };
12395
+ const available = (toolbarLeft?.clientWidth ?? zone.clientWidth) -
12396
+ siblingWidth('.lt-search') -
12397
+ siblingWidth('.lt-divider') -
12398
+ siblingWidth('.lt-columns') -
12399
+ 3 * this.FILTER_GAP;
12400
+ const widthOf = (c) => this.filterWidths.get(c.key) ?? 0;
12401
+ const totalOf = (list, withMore) => {
12402
+ let w = 0;
12403
+ list.forEach((c, i) => (w += widthOf(c) + (i > 0 ? this.FILTER_GAP : 0)));
12404
+ if (withMore) {
12405
+ w += (list.length ? this.FILTER_GAP : 0) + this.MORE_BTN_W;
12406
+ }
12407
+ return w;
12408
+ };
12409
+ if (totalOf(cols, false) <= available) {
12410
+ this.visibleFilterColumns = cols;
12411
+ this.overflowFilterColumns = [];
12412
+ return;
12413
+ }
12414
+ // removal priority: inactive right-to-left first, then active right-to-left
12415
+ const inactive = cols.filter((c) => !this.isFilterActive(c)).reverse();
12416
+ const active = cols.filter((c) => this.isFilterActive(c)).reverse();
12417
+ const removalOrder = [...inactive, ...active];
12418
+ const overflow = new Set();
12419
+ for (const c of removalOrder) {
12420
+ overflow.add(c.key);
12421
+ const visible = cols.filter((x) => !overflow.has(x.key));
12422
+ if (totalOf(visible, true) <= available) {
12423
+ break;
12424
+ }
12425
+ }
12426
+ this.visibleFilterColumns = cols.filter((c) => !overflow.has(c.key));
12427
+ this.overflowFilterColumns = cols.filter((c) => overflow.has(c.key));
12428
+ if (!this.overflowFilterColumns.length) {
12429
+ this.showFilterOverflow = false;
12430
+ }
12431
+ }
12432
+ // ---------------------------------------------------------------- columns
12433
+ buildColumns() {
12434
+ this._initialColumns = this._rawColumns.map((c) => ({ ...c }));
12435
+ this.allColumns = this.svc.applyPersistedState(this._rawColumns);
12436
+ this.updateVisible();
12437
+ // start with every filter control visible; the overflow pass collapses to fit
12438
+ this.visibleFilterColumns = this.filterableColumns;
12439
+ this.overflowFilterColumns = [];
12440
+ this.filtersDirty = true;
12441
+ this.scheduleFilterOverflow();
12442
+ }
12443
+ updateVisible() {
12444
+ this.visibleColumns = this.allColumns.filter((c) => !c.hidden);
12445
+ this.hiddenColumns = this.allColumns
12446
+ .filter((c) => c.hidden)
12447
+ .map((c) => ({ ...c, headerName: c.label }));
12448
+ }
12449
+ colWidth(col) {
12450
+ return col.width != null ? `${col.width}px` : null;
12451
+ }
12452
+ // ---------------------------------------------------------------- cells
12453
+ rawValue(col, row) {
12454
+ return _.get(row, col.field || col.key);
12455
+ }
12456
+ cellContext(col, row, i) {
12457
+ return {
12458
+ row,
12459
+ value: this.rawValue(col, row),
12460
+ colId: col.key,
12461
+ rowIndex: i,
12462
+ emitAction: (action, item = row) => this.actionRequested.emit({ action, item }),
12463
+ };
12464
+ }
12465
+ legacyShim(col, row, i) {
12466
+ return { data: row, value: this.rawValue(col, row), column: { colId: col.key }, rowIndex: i };
12467
+ }
12468
+ renderMode(col) {
12469
+ if (col.cell) {
12470
+ return 'component';
12471
+ }
12472
+ if (col.cellHtml) {
12473
+ return 'html';
12474
+ }
12475
+ return 'text';
12476
+ }
12477
+ cellInputs(col, row, i) {
12478
+ if (col.cellInputs) {
12479
+ return col.cellInputs(this.cellContext(col, row, i));
12480
+ }
12481
+ return { row, value: this.rawValue(col, row) };
12482
+ }
12483
+ cellHtml(col, row, i) {
12484
+ return col.cellHtml ? col.cellHtml(this.legacyShim(col, row, i)) : '';
12485
+ }
12486
+ cellText(col, row, i) {
12487
+ if (col.valueFormatter) {
12488
+ return col.valueFormatter(this.legacyShim(col, row, i));
12489
+ }
12490
+ return this.rawValue(col, row);
12491
+ }
12492
+ cellClass(col) {
12493
+ return col.cellClass || '';
12494
+ }
12495
+ onCellClick(col, row, i) {
12496
+ col.onCellClick?.(this.cellContext(col, row, i));
12497
+ }
12498
+ tooltipInputs(col, row, i) {
12499
+ return col.tooltipInputs ? col.tooltipInputs(this.cellContext(col, row, i)) : { row };
12500
+ }
12501
+ onCellHover(col, row, i, event) {
12502
+ if (!col.tooltip) {
12503
+ return;
12504
+ }
12505
+ const rect = event.currentTarget.getBoundingClientRect();
12506
+ this.hoverTooltip = {
12507
+ component: col.tooltip,
12508
+ inputs: this.tooltipInputs(col, row, i),
12509
+ left: rect.left,
12510
+ top: rect.bottom + 6,
12511
+ };
12512
+ }
12513
+ onCellLeave() {
12514
+ this.hoverTooltip = null;
12515
+ }
12516
+ /** Some rows selected but the header select-all isn't fully on (indeterminate). */
12517
+ get someSelected() {
12518
+ return !this.allToggled && _.some(this.rowData, { selected: true });
12519
+ }
12520
+ toggleAll() {
12521
+ const newState = !this.allToggled;
12522
+ this.allToggled = newState;
12523
+ _.forEach(this.rowData, (row) => {
12524
+ row.selected = newState;
12525
+ });
12526
+ this.actionRequested.emit({ action: 'toggleAll' });
12527
+ }
12528
+ // ---------------------------------------------------------------- sort
12529
+ onHeaderSort(event) {
12530
+ this.allColumns.forEach((c) => {
12531
+ c.sortDir = c.key === event.colId ? event.dir : undefined;
12532
+ });
12533
+ }
12534
+ // ---------------------------------------------------------------- hide / show / reorder
12535
+ hideColumn(col) {
12536
+ const target = this.allColumns.find((c) => c.key === col.key);
12537
+ if (!target) {
12538
+ return;
12539
+ }
12540
+ target.hidden = true;
12541
+ this.updateVisible();
12542
+ this.svc.saveState(this.allColumns);
12543
+ }
12544
+ showColumn(event) {
12545
+ const target = this.allColumns.find((c) => c.key === event.column?.key);
12546
+ if (!target) {
12547
+ return;
12548
+ }
12549
+ target.hidden = false;
12550
+ this.updateVisible();
12551
+ this.svc.saveState(this.allColumns);
12552
+ }
12553
+ onReorder(event) {
12554
+ if (event.previousIndex === event.currentIndex) {
12555
+ return;
12556
+ }
12557
+ moveItemInArray(this.visibleColumns, event.previousIndex, event.currentIndex);
12558
+ const nextVisible = [...this.visibleColumns];
12559
+ let vi = 0;
12560
+ this.allColumns = this.allColumns.map((c) => (c.hidden ? c : nextVisible[vi++]));
12561
+ this.updateVisible();
12562
+ this.svc.saveState(this.allColumns);
12563
+ }
12564
+ resetTableColumns() {
12565
+ this.svc.reset();
12566
+ this.allColumns = this._initialColumns.map((c) => ({ ...c }));
12567
+ this.updateVisible();
12568
+ this.closeActionMenu();
12569
+ }
12570
+ // ---------------------------------------------------------------- column chooser
12571
+ /** Count of columns the user has hidden beyond the defaults (drives the badge).
12572
+ * Columns hidden by default don't count, so a fresh/default load shows no badge. */
12573
+ get hiddenColumnCount() {
12574
+ return this.allColumns.filter((c) => c.hidden && !this.isDefaultHidden(c)).length;
12575
+ }
12576
+ /** True when visibility + order match the originally declared columns. */
12577
+ get columnsAtDefault() {
12578
+ if (this.allColumns.length !== this._initialColumns.length) {
12579
+ return false;
12580
+ }
12581
+ return this.allColumns.every((c, i) => {
12582
+ const init = this._initialColumns[i];
12583
+ return init && init.key === c.key && !!init.hidden === !!c.hidden;
12584
+ });
12585
+ }
12586
+ /** True when the column is hidden in the originally declared defaults. */
12587
+ isDefaultHidden(col) {
12588
+ const init = this._initialColumns.find((c) => c.key === col.key);
12589
+ return !!init?.hidden;
12590
+ }
12591
+ toggleColumnChooser(event) {
12592
+ event.stopPropagation();
12593
+ this.showColumnChooser = !this.showColumnChooser;
12594
+ if (this.showColumnChooser) {
12595
+ this.svc.notifyMenuOpened(this);
12596
+ }
12597
+ }
12598
+ closeColumnChooser() {
12599
+ this.showColumnChooser = false;
12600
+ }
12601
+ toggleColumnVisible(col) {
12602
+ if (col.hideable === false) {
12603
+ return;
12604
+ }
12605
+ const target = this.allColumns.find((c) => c.key === col.key);
12606
+ if (!target) {
12607
+ return;
12608
+ }
12609
+ target.hidden = !target.hidden;
12610
+ this.updateVisible();
12611
+ this.svc.saveState(this.allColumns);
12612
+ }
12613
+ chooserReorder(event) {
12614
+ if (event.previousIndex === event.currentIndex) {
12615
+ return;
12616
+ }
12617
+ moveItemInArray(this.allColumns, event.previousIndex, event.currentIndex);
12618
+ this.updateVisible();
12619
+ this.svc.saveState(this.allColumns);
12620
+ }
12621
+ // ---------------------------------------------------------------- resize
12622
+ startResize(col, event) {
12623
+ if (col.resizable === false) {
12624
+ return;
12625
+ }
12626
+ event.preventDefault();
12627
+ event.stopPropagation();
12628
+ const th = event.target?.closest('th');
12629
+ const startWidth = col.width ?? th?.offsetWidth ?? 150;
12630
+ this.resizing = { col, startX: event.clientX, startWidth };
12631
+ document.addEventListener('pointermove', this.onPointerMove);
12632
+ document.addEventListener('pointerup', this.onPointerUp);
12633
+ }
12634
+ handleResizeMove(event) {
12635
+ if (!this.resizing) {
12636
+ return;
12637
+ }
12638
+ const delta = event.clientX - this.resizing.startX;
12639
+ const min = this.resizing.col.minWidth ?? 40;
12640
+ this.resizing.col.width = Math.max(min, Math.round(this.resizing.startWidth + delta));
12641
+ }
12642
+ handleResizeEnd() {
12643
+ if (this.resizing) {
12644
+ this.svc.saveWidth(this.resizing.col.key, this.resizing.col.width, this.allColumns);
12645
+ }
12646
+ this.resizing = undefined;
12647
+ this.detachResizeListeners();
12648
+ }
12649
+ detachResizeListeners() {
12650
+ document.removeEventListener('pointermove', this.onPointerMove);
12651
+ document.removeEventListener('pointerup', this.onPointerUp);
12652
+ }
12653
+ openHeaderActionMenu(event) {
12654
+ this.menuLeft = event.clientX - 150;
12655
+ this.menuTop = event.clientY + 5;
12656
+ _.delay(() => {
12657
+ this.openHeaderMenu = true;
12658
+ }, 100);
12659
+ }
12660
+ hideMenuItem(menuItem, selectedItem) {
12661
+ if (_.isFunction(menuItem.hidden)) {
12662
+ return menuItem.hidden(selectedItem);
12663
+ }
12664
+ else if (_.isBoolean(menuItem.hidden)) {
12665
+ return menuItem.hidden;
12666
+ }
12667
+ return !selectedItem.actionList?.includes(menuItem.action);
12668
+ }
12669
+ /** Header action menu rows: the reset action followed by host-provided header actions.
12670
+ * A host action's `icon` (SVG markup) is rendered before its label, like the row menu. */
12671
+ get headerMenuItems() {
12672
+ const items = [
12673
+ { label: 'Restore Default Table', action: '__reset_table__', id: 'ResetTableButton', iconSvg: this.safeIcon(this.RESET_TABLE_ICON) },
12674
+ ];
12675
+ _.forEach(this.headerActions, (ha) => {
12676
+ items.push({
12677
+ label: ha.name || ha.label,
12678
+ action: ha.action,
12679
+ id: 'HeaderAction_' + ha.action,
12680
+ iconSvg: ha.icon ? this.safeIcon(ha.icon) : undefined,
12681
+ data: ha,
12682
+ });
12683
+ });
12684
+ return items;
12685
+ }
12686
+ onHeaderMenuClick(evt) {
12687
+ if (evt.item.action === '__reset_table__') {
12688
+ this.resetTableColumns();
12689
+ }
12690
+ else {
12691
+ this.actionRequested.emit({ action: evt.item.action });
12692
+ }
12693
+ this.closeHeaderActionMenu();
12694
+ }
12695
+ /** Row action menu rows, built from `menuItems` against the currently selected row.
12696
+ * A menu item's `icon` (SVG markup) is rendered before its label in the menu. */
12697
+ get rowMenuItems() {
12698
+ return _.map(this.menuItems, (mi) => ({
12699
+ label: mi.name || mi.label,
12700
+ action: mi.action,
12701
+ id: 'TableActionButton_' + mi.action,
12702
+ hidden: this.hideMenuItem(mi, this.selectedItem),
12703
+ iconSvg: mi.icon ? this.safeIcon(mi.icon) : undefined,
12704
+ data: mi,
12705
+ }));
12706
+ }
12707
+ onRowMenuClick(evt) {
12708
+ this.actionRequested.emit({ action: evt.item.action, item: this.selectedItem });
12709
+ this.closeActionMenu();
12710
+ }
12711
+ openCreate() {
12712
+ this.actionRequested.emit({ action: 'create' });
12713
+ }
12714
+ // ---------------------------------------------------------------- toolbar search + pagination
12715
+ onSearchInput() {
12716
+ this.inputChangedDebounced();
12717
+ }
12718
+ applySearch() {
12719
+ this.tableFilterService.currentPage = 1;
12720
+ this.tableFilterService.updateFilter({
12721
+ filterName: this.filterName,
12722
+ columnId: this.filterColumn,
12723
+ value: this.searchString,
12724
+ label: this.searchString,
12725
+ });
12726
+ }
12727
+ nextPage() {
12728
+ if (this.nextDisabled) {
12729
+ return;
12730
+ }
12731
+ this.tableFilterService.changePage((this.tableFilterService.currentPage || 1) + 1);
12732
+ }
12733
+ prevPage() {
12734
+ if (this.prevDisabled) {
12735
+ return;
12736
+ }
12737
+ this.tableFilterService.changePage((this.tableFilterService.currentPage || 1) - 1);
12738
+ }
12739
+ get nextDisabled() {
12740
+ if (!_.isNumber(this.totalCount) || !_.isNumber(this.endCount) || this.filtering) {
12741
+ return true;
12742
+ }
12743
+ return Number(this.endCount) >= Number(this.totalCount);
12744
+ }
12745
+ get prevDisabled() {
12746
+ if (!_.isNumber(this.startCount) || !_.isNumber(this.totalCount) || this.filtering) {
12747
+ return true;
12748
+ }
12749
+ return Number(this.startCount) <= 1;
12750
+ }
12751
+ get pageSize() {
12752
+ return this.tableFilterService.pageSize;
12753
+ }
12754
+ get currentPageNum() {
12755
+ return this.tableFilterService.currentPage || 1;
12756
+ }
12757
+ get totalPages() {
12758
+ if (!_.isNumber(this.totalCount) || Number(this.totalCount) <= 0) {
12759
+ return 1;
12760
+ }
12761
+ return Math.max(1, Math.ceil(Number(this.totalCount) / this.pageSize));
12762
+ }
12763
+ /** Page numbers to render, with `0` marking a gap ("..."). Shows the first and
12764
+ * last page plus a window around the current page so long lists stay compact. */
12765
+ get pageWindow() {
12766
+ const total = this.totalPages;
12767
+ const cur = this.currentPageNum;
12768
+ if (total <= 7) {
12769
+ return Array.from({ length: total }, (_v, i) => i + 1);
12770
+ }
12771
+ const pages = new Set([1, total, cur, cur - 1, cur + 1]);
12772
+ const sorted = [...pages].filter((p) => p >= 1 && p <= total).sort((a, b) => a - b);
12773
+ const out = [];
12774
+ let prev = 0;
12775
+ for (const p of sorted) {
12776
+ if (prev && p - prev > 1) {
12777
+ out.push(0); // gap sentinel
12778
+ }
12779
+ out.push(p);
12780
+ prev = p;
12781
+ }
12782
+ return out;
12783
+ }
12784
+ goToPage(page) {
12785
+ if (page < 1 || page > this.totalPages || page === this.currentPageNum || this.filtering) {
12786
+ return;
12787
+ }
12788
+ this.tableFilterService.changePage(page);
12789
+ }
12790
+ onPageSizeChange(size) {
12791
+ if (!size || size === this.pageSize) {
12792
+ return;
12793
+ }
12794
+ this.tableFilterService.changePageSize(size);
12795
+ }
12796
+ /** Re-fetch the current page (re-emits the page so the host reloads its data). */
12797
+ refresh() {
12798
+ if (this.filtering) {
12799
+ return;
12800
+ }
12801
+ this.tableFilterService.changePage(this.currentPageNum);
12802
+ }
12803
+ toggleAutoRefresh() {
12804
+ this.autoRefresh = !this.autoRefresh;
12805
+ if (this.autoRefresh) {
12806
+ this.autoRefreshTimer = setInterval(() => this.ngZone.run(() => this.refresh()), this.autoRefreshIntervalMs);
12807
+ }
12808
+ else if (this.autoRefreshTimer) {
12809
+ clearInterval(this.autoRefreshTimer);
12810
+ this.autoRefreshTimer = undefined;
12811
+ }
12812
+ }
12813
+ safeIcon(svg) {
12814
+ let safe = this.iconCache.get(svg);
12815
+ if (!safe) {
12816
+ safe = this.sanitizer.bypassSecurityTrustHtml(svg);
12817
+ this.iconCache.set(svg, safe);
12818
+ }
12819
+ return safe;
12820
+ }
12821
+ updateEntityTypeLabel() {
12822
+ const map = {
12823
+ 'api-sessions': 'API Sessions',
12824
+ 'auth-policies': 'Auth Policies',
12825
+ 'certificate-authorities': 'Certificate Authorities',
12826
+ 'ext-jwt-signers': 'External JWT Signers',
12827
+ identities: 'Identities',
12828
+ 'posture-checks': 'Posture Checks',
12829
+ 'edge-routers': 'Edge Routers',
12830
+ services: 'Services',
12831
+ configurations: 'Configs',
12832
+ 'config-types': 'Config Types',
12833
+ 'service-policies': 'Service Policies',
12834
+ 'edge-router-policies': 'Edge Router Policies',
12835
+ 'service-edge-router-policies': 'Service Edge Router Policies',
12836
+ sessions: 'Sessions',
12837
+ terminators: 'Terminators',
12838
+ 'transit-routers': 'Transit Routers',
12839
+ };
12840
+ this.entityTypeLabel = map[this._tableId] || this.entityTypeName || '';
12841
+ }
12842
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListTableComponent, deps: [{ token: ListTableService }, { token: DataTableFilterService }, { token: i0.ElementRef }, { token: i0.NgZone }, { token: i1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component }); }
12843
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ListTableComponent, isStandalone: false, selector: "lib-list-table", inputs: { tableId: "tableId", columns: "columns", rowData: "rowData", isLoading: "isLoading", options: "options", startCount: "startCount", endCount: "endCount", totalCount: "totalCount", filterApplied: "filterApplied", menuItems: "menuItems", headerActions: "headerActions", noItemsImage: "noItemsImage", showNoItemsAdd: "showNoItemsAdd", noItemsReadonly: "noItemsReadonly", accessDenied: "accessDenied", entityTypeName: "entityTypeName", filterName: "filterName", filterColumn: "filterColumn" }, outputs: { actionRequested: "actionRequested", tableReady: "tableReady" }, providers: [ListTableService], viewQueries: [{ propertyName: "contextMenu", first: true, predicate: ["contextMenu"], descendants: true }, { propertyName: "filterZone", first: true, predicate: ["filterZone"], descendants: true }, { propertyName: "filtersRow", first: true, predicate: ["filtersRow"], descendants: true }], ngImport: i0, template: "@if (showTable && !accessDenied) {\n <div class=\"lt-container\">\n <div class=\"lt-toolbar\">\n <div class=\"lt-toolbar-left\">\n <div class=\"lt-search\">\n <svg class=\"lt-search-icon\" viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <circle cx=\"7\" cy=\"7\" r=\"4.3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>\n <line x1=\"10.4\" y1=\"10.4\" x2=\"14.6\" y2=\"14.6\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n </svg>\n <input\n class=\"lt-search-input\"\n id=\"SearchFilter\"\n type=\"text\"\n [placeholder]=\"'Search ' + (entityTypeLabel || 'items')\"\n [(ngModel)]=\"searchString\"\n (keyup)=\"onSearchInput()\"\n />\n </div>\n @if (filterableColumns.length) {\n <div class=\"lt-filter-zone\" #filterZone>\n <div class=\"lt-filters\" #filtersRow>\n @for (col of visibleFilterColumns; track col.key) {\n <span class=\"lt-filter-item\" [attr.data-filter-key]=\"col.key\">\n <lib-list-filter-cell [column]=\"col\"></lib-list-filter-cell>\n </span>\n }\n </div>\n @if (overflowFilterColumns.length) {\n <div class=\"lt-filter-more\">\n <button\n type=\"button\"\n class=\"lt-filter-more-btn\"\n [class.is-on]=\"hiddenActiveCount > 0\"\n [class.is-open]=\"showFilterOverflow\"\n (click)=\"toggleFilterOverflow($event)\"\n aria-haspopup=\"true\"\n [attr.aria-expanded]=\"showFilterOverflow\"\n [attr.aria-label]=\"overflowFilterColumns.length + ' more filters'\"\n id=\"FilterOverflowButton\"\n >\n <span class=\"lt-filter-more-icon\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\"><path fill=\"currentColor\" d=\"M2 3h12l-4.6 5.6v4l-2.8 1.4v-5.4z\"/></svg>\n </span>\n <span class=\"lt-filter-more-label\">More</span>\n <span class=\"lt-filter-more-count\">{{ overflowFilterColumns.length }}</span>\n </button>\n @if (showFilterOverflow) {\n <div class=\"lt-filter-overflow\" (clickOutside)=\"closeFilterOverflow($event)\">\n <div class=\"lt-filter-overflow-title\">Filters</div>\n @for (col of overflowFilterColumns; track col.key) {\n <div class=\"lt-filter-overflow-row\" [class.is-active]=\"isFilterActive(col)\">\n <lib-list-filter-cell [column]=\"col\"></lib-list-filter-cell>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n <div class=\"lt-toolbar-right\">\n <ng-template #refreshIcon>\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" d=\"M13.4 8a5.4 5.4 0 1 1-1.5-3.8\"/>\n <path fill=\"currentColor\" d=\"M13.9 2.1l.5 3.2-3.2-.5z\"/>\n </svg>\n </ng-template>\n <button\n type=\"button\"\n class=\"lt-refresh-btn\"\n (click)=\"refresh()\"\n [disabled]=\"filtering\"\n aria-label=\"Refresh\"\n title=\"Refresh\"\n id=\"RefreshTableButton\"\n >\n <ng-container [ngTemplateOutlet]=\"refreshIcon\"></ng-container>\n </button>\n <button\n type=\"button\"\n class=\"lt-autorefresh-btn\"\n [class.is-on]=\"autoRefresh\"\n (click)=\"toggleAutoRefresh()\"\n [attr.aria-pressed]=\"autoRefresh\"\n id=\"AutoRefreshButton\"\n >\n <ng-container [ngTemplateOutlet]=\"refreshIcon\"></ng-container>\n <span>Auto-refresh: {{ autoRefresh ? 'On' : 'Off' }}</span>\n </button>\n <div class=\"lt-columns\">\n <button\n type=\"button\"\n class=\"lt-columns-btn\"\n [class.is-on]=\"hiddenColumnCount > 0\"\n [class.is-open]=\"showColumnChooser\"\n (click)=\"toggleColumnChooser($event)\"\n aria-haspopup=\"true\"\n [attr.aria-expanded]=\"showColumnChooser\"\n id=\"ColumnChooserButton\"\n >\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"currentColor\" d=\"M2 3.5h9v1.4H2zM2 7.3h6v1.4H2zM2 11.1h11v1.4H2z\"/>\n <circle cx=\"13\" cy=\"4.2\" r=\"1.7\" fill=\"currentColor\"/>\n <circle cx=\"10\" cy=\"8\" r=\"1.7\" fill=\"currentColor\"/>\n <circle cx=\"14.4\" cy=\"11.8\" r=\"1.7\" fill=\"currentColor\"/>\n </svg>\n <span>Columns</span>\n @if (hiddenColumnCount > 0) {\n <span class=\"lt-columns-count\">{{ hiddenColumnCount }}</span>\n }\n </button>\n @if (showColumnChooser) {\n <div class=\"lt-column-chooser\" (clickOutside)=\"closeColumnChooser()\">\n <div class=\"lt-chooser-header\">\n <span class=\"lt-chooser-title\">Columns</span>\n <span class=\"lt-chooser-subtitle\">Customize order and visibility</span>\n </div>\n <ul class=\"lt-chooser-list\" cdkDropList cdkDropListLockAxis=\"y\" (cdkDropListDropped)=\"chooserReorder($event)\">\n @for (col of allColumns; track col.key) {\n <li class=\"lt-chooser-row\" cdkDrag cdkDragLockAxis=\"y\" [cdkDragDisabled]=\"col.reorderable === false\">\n <button\n type=\"button\"\n class=\"lt-chooser-toggle\"\n [class.checked]=\"!col.hidden\"\n [disabled]=\"col.hideable === false\"\n (click)=\"toggleColumnVisible(col)\"\n [attr.aria-pressed]=\"!col.hidden\"\n >\n <span class=\"lt-chooser-box\" aria-hidden=\"true\">\n @if (!col.hidden) {\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/>\n </svg>\n }\n </span>\n <span class=\"lt-chooser-text\">{{ col.label }}</span>\n @if (isDefaultHidden(col)) {\n <span class=\"lt-chooser-default-tag\">hidden by default</span>\n }\n </button>\n @if (col.reorderable !== false) {\n <span class=\"lt-chooser-handle\" cdkDragHandle aria-label=\"Drag to reorder\" title=\"Drag to reorder\">\n <svg viewBox=\"0 0 10 16\" width=\"10\" height=\"15\" fill=\"currentColor\" aria-hidden=\"true\">\n <circle cx=\"3\" cy=\"3\" r=\"1.25\"/><circle cx=\"7\" cy=\"3\" r=\"1.25\"/>\n <circle cx=\"3\" cy=\"8\" r=\"1.25\"/><circle cx=\"7\" cy=\"8\" r=\"1.25\"/>\n <circle cx=\"3\" cy=\"13\" r=\"1.25\"/><circle cx=\"7\" cy=\"13\" r=\"1.25\"/>\n </svg>\n </span>\n }\n </li>\n }\n </ul>\n <div class=\"lt-chooser-footer\">\n <button\n type=\"button\"\n class=\"lt-chooser-reset\"\n [disabled]=\"columnsAtDefault\"\n (click)=\"resetTableColumns()\"\n id=\"ResetColumnsButton\"\n >\n Reset to default\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n\n <div class=\"lt-scroll\" id=\"ItemTable\">\n <table class=\"lt-table\">\n <colgroup>\n @if (!options.noSelect) { <col class=\"lt-col-select\" /> }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <col [style.width]=\"colWidth(col)\" />\n }\n @if (!options.noMenu) { <col class=\"lt-col-menu\" /> }\n </colgroup>\n <thead class=\"lt-head\">\n <tr cdkDropList cdkDropListOrientation=\"horizontal\" (cdkDropListDropped)=\"onReorder($event)\">\n @if (!options.noSelect) {\n <th class=\"lt-select-cell\">\n <div\n class=\"lt-select-all\"\n [class.checked]=\"allToggled\"\n [class.indeterminate]=\"someSelected\"\n (click)=\"toggleAll()\"\n id=\"SelectAllRows\"\n ></div>\n </th>\n }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <th\n cdkDrag\n [cdkDragDisabled]=\"col.reorderable === false\"\n class=\"lt-th\"\n [ngClass]=\"col.cellClass\"\n id=\"Header_{{ col.key }}\"\n >\n <lib-list-header-cell\n [column]=\"col\"\n [sortDir]=\"col.sortDir\"\n (sortChanged)=\"onHeaderSort($event)\"\n (hideColumn)=\"hideColumn($event)\"\n ></lib-list-header-cell>\n @if (col.resizable !== false) {\n <span class=\"lt-resize\" (pointerdown)=\"startResize(col, $event)\"></span>\n }\n </th>\n }\n @if (!options.noMenu) {\n <th class=\"lt-menu-cell\">\n <div class=\"lt-header-menu-trigger\" (click)=\"openHeaderActionMenu($event)\" id=\"ColumnMenuHeader\">\n &#8942;\n </div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of rowData; track trackRow($index, row); let i = $index) {\n <tr\n class=\"lt-row\"\n [class.lt-row-odd]=\"i % 2 === 1\"\n [class.lt-row-selected]=\"row.selected\"\n [class.row-invalid]=\"row.invalid\"\n [class.row-copied]=\"row.copied\"\n >\n @if (!options.noSelect) {\n <td class=\"lt-select-cell\">\n <lib-select-cell [row]=\"row\" [toggleItem]=\"toggleItem\"></lib-select-cell>\n </td>\n }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <td\n class=\"lt-cell\"\n [ngClass]=\"cellClass(col)\"\n (click)=\"onCellClick(col, row, i)\"\n (mouseenter)=\"onCellHover(col, row, i, $event)\"\n (mouseleave)=\"onCellLeave()\"\n >\n @switch (renderMode(col)) {\n @case ('component') {\n <ng-container *ngComponentOutlet=\"col.cell; inputs: cellInputs(col, row, i)\"></ng-container>\n }\n @case ('html') {\n <span class=\"lt-cell-html\" [innerHTML]=\"cellHtml(col, row, i)\"></span>\n }\n @default {\n <span class=\"lt-cell-text\">{{ cellText(col, row, i) }}</span>\n }\n }\n </td>\n }\n @if (!options.noMenu) {\n <td class=\"lt-menu-cell\">\n @if (row.actionList?.length) {\n <button\n type=\"button\"\n class=\"lt-row-menu-trigger\"\n (click)=\"$event.stopPropagation(); openActionMenu($event, row)\"\n attr.id=\"RowMenu_{{ row.id }}\"\n aria-label=\"Row actions\"\n >&#8942;</button>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n @if (!isLoading && filterApplied && (!rowData || rowData.length === 0)) {\n <div class=\"no-rows-message\">No Rows To Show</div>\n }\n </div>\n\n <div class=\"lt-footer\">\n <div class=\"lt-footer-count\">\n Showing <span id=\"Start\">{{ startCount }}</span>&#8211;<span id=\"End\">{{ endCount }}</span>\n of <span id=\"Total\">{{ totalCount }}</span>\n </div>\n <div class=\"lt-footer-controls\">\n <div class=\"lt-rpp\">\n <span class=\"lt-rpp-label\">Rows per page</span>\n <select\n class=\"lt-rpp-select\"\n [disabled]=\"filtering\"\n [value]=\"pageSize\"\n (change)=\"onPageSizeChange(+$any($event.target).value)\"\n aria-label=\"Rows per page\"\n >\n @for (opt of pageSizeOptions; track opt) {\n <option [value]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n <div class=\"lt-pager\">\n <button type=\"button\" class=\"lt-page-btn icon-prev\" [disabled]=\"prevDisabled\" (click)=\"prevPage()\" aria-label=\"Previous page\"></button>\n @for (p of pageWindow; track $index) {\n @if (p === 0) {\n <span class=\"lt-page-gap\">&#8230;</span>\n } @else {\n <button\n type=\"button\"\n class=\"lt-page-num\"\n [class.is-current]=\"p === currentPageNum\"\n (click)=\"goToPage(p)\"\n >{{ p }}</button>\n }\n }\n <button type=\"button\" class=\"lt-page-btn icon-next\" [disabled]=\"nextDisabled\" (click)=\"nextPage()\" aria-label=\"Next page\"></button>\n </div>\n </div>\n </div>\n\n <!-- header action menu -->\n <lib-list-menu\n menuId=\"HeaderActionMenu\"\n [open]=\"openHeaderMenu\"\n [items]=\"headerMenuItems\"\n [left]=\"menuLeft\"\n [top]=\"menuTop\"\n (itemClick)=\"onHeaderMenuClick($event)\"\n (closed)=\"closeHeaderActionMenu()\"\n ></lib-list-menu>\n\n <!-- row action menu -->\n <lib-list-menu\n menuId=\"RowActionMenu\"\n [open]=\"openMenu\"\n [items]=\"rowMenuItems\"\n [left]=\"menuLeft\"\n [top]=\"menuTop\"\n (itemClick)=\"onRowMenuClick($event)\"\n (closed)=\"closeActionMenu()\"\n ></lib-list-menu>\n </div>\n} @else {\n <lib-no-items\n (clickEmit)=\"openCreate()\"\n [image]=\"noItemsImage\"\n [isEmpty]=\"!rowData || rowData.length === 0\"\n [isLoading]=\"isLoading\"\n [typeName]=\"entityTypeLabel\"\n [hasAdd]=\"showNoItemsAdd\"\n [readonly]=\"noItemsReadonly\"\n [accessDenied]=\"accessDenied\"\n ></lib-no-items>\n}\n\n@if (hoverTooltip) {\n <div class=\"lt-tooltip\" [ngStyle]=\"{ left: hoverTooltip.left + 'px', top: hoverTooltip.top + 'px' }\">\n <ng-container *ngComponentOutlet=\"hoverTooltip.component; inputs: hoverTooltip.inputs\"></ng-container>\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;width:100%;height:100%}.lt-columns-btn,.lt-autorefresh-btn,.lt-refresh-btn,.lt-filter-more-btn{display:inline-flex;align-items:center;gap:.4375rem;height:var(--lt-toolbar-height);padding:0 .75rem;font-family:var(--lt-font);font-size:.8125rem;font-weight:600;color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-border);border-radius:var(--lt-radius);cursor:pointer;white-space:nowrap;transition:border-color .15s,background .15s,color .15s}.lt-columns-btn svg,.lt-autorefresh-btn svg,.lt-refresh-btn svg,.lt-filter-more-btn svg{display:block}.lt-columns-btn:hover,.lt-autorefresh-btn:hover,.lt-refresh-btn:hover,.lt-filter-more-btn:hover,.is-open.lt-columns-btn,.is-open.lt-autorefresh-btn,.is-open.lt-refresh-btn,.is-open.lt-filter-more-btn,.is-on.lt-columns-btn,.is-on.lt-autorefresh-btn,.is-on.lt-refresh-btn,.is-on.lt-filter-more-btn{border-color:var(--lt-link);color:var(--lt-link)}.lt-columns-count,.lt-filter-more-count{display:inline-flex;align-items:center;justify-content:center;min-width:1.125rem;height:1.125rem;padding:0 .3125rem;font-size:.6875rem;font-weight:700;color:var(--lt-control-bg);background:var(--lt-link);border-radius:.5625rem}.lt-container{display:flex;position:relative;height:calc(100% - 5.625rem);width:100%;flex-direction:column}.lt-scroll{position:relative;z-index:1;width:100%;flex:0 1 auto;min-height:0;overflow:auto;background-color:var(--lt-bg)}.lt-scroll .no-rows-message{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--lt-cell-text);font-size:.875rem;pointer-events:none}.lt-table{width:100%;border-collapse:collapse;table-layout:fixed;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text)}.lt-head{position:sticky;top:0;z-index:10}.lt-head th{position:relative;box-sizing:border-box;height:2.75rem;padding:0 .3125rem 0 .625rem;text-align:left;vertical-align:middle;background-color:var(--lt-header-bg);border-bottom:1px solid var(--lt-header-underline);white-space:nowrap;-webkit-user-select:none;user-select:none}.lt-head th.lt-th{border-right:1px solid var(--lt-border)}.lt-head .lt-select-cell,.lt-head .lt-menu-cell{width:3.75rem;padding:0}.lt-resize{position:absolute;right:0;top:50%;transform:translateY(-50%);width:.625rem;height:.875rem;cursor:col-resize}.lt-resize:before,.lt-resize:after{content:\"\";position:absolute;top:0;height:100%;width:1px;background:transparent;transition:background .15s}.lt-resize:before{right:.25rem}.lt-resize:after{right:.125rem}.lt-resize:hover:before,.lt-resize:hover:after{background:var(--lt-icon)}.lt-head th:hover .lt-resize:before,.lt-head th:hover .lt-resize:after{background:var(--lt-border)}.lt-select-all{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);box-sizing:border-box;width:1rem;height:1rem;border:1px solid var(--lt-border);border-radius:.25rem;background:var(--lt-control-bg);background-repeat:no-repeat;background-position:center center;cursor:pointer;transition:border-color .12s,background-color .12s}.lt-select-all:hover{border-color:var(--lt-link)}.lt-select-all.checked{background-color:var(--lt-link);border-color:var(--lt-link);background-size:.75rem;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M2.5 7.6 5.7 10.8 11.5 3.6'/%3E%3C/svg%3E\")}.lt-select-all.indeterminate{background-color:var(--lt-link);border-color:var(--lt-link);background-image:linear-gradient(#fff,#fff);background-size:.5rem .125rem}.lt-header-menu-trigger{display:flex;align-items:center;justify-content:center;height:100%;font-size:1.25rem;color:var(--lt-icon);cursor:pointer}.lt-header-menu-trigger:hover{color:var(--lt-icon-active)}.lt-row-menu-trigger{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;padding:0;font-size:1.25rem;line-height:1;color:var(--lt-icon);background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer;transition:color .15s,background .15s}.lt-row-menu-trigger:hover{color:var(--lt-icon-active);background:var(--lt-row-hover)}.cdk-drag-preview{box-sizing:border-box;background-color:var(--lt-header-bg);color:var(--lt-header-text);box-shadow:var(--shadowLarge);padding:0 .625rem;display:flex;align-items:center;border-radius:var(--lt-radius)}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.lt-row{height:var(--lt-row-height);background-color:var(--lt-row-bg);color:var(--lt-cell-text)}.lt-row td{border-bottom:1px solid var(--lt-border)}.lt-row.lt-row-odd{background-color:var(--lt-row-alt-bg)}.lt-row:hover{background-color:var(--lt-row-hover)}.lt-row.lt-row-selected{background-color:var(--lt-row-selected)}.lt-row.lt-row-selected:hover{background-color:var(--lt-row-hover)}.lt-row.row-invalid{background-color:var(--invalid)!important}.lt-row.row-copied{background-color:var(--copiedRow)!important}.lt-cell{height:var(--lt-row-height);padding:0 .625rem 0 1.25rem;text-align:left;vertical-align:middle;border:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;-webkit-user-select:text;user-select:text}.lt-cell-text{display:inline-block;overflow:hidden;text-overflow:ellipsis;max-width:100%;vertical-align:middle}.lt-cell.lt-cell-center{text-align:center}.lt-badge-cell .lt-cell-text{padding:.1875rem .5rem;font-size:.75rem;font-weight:600;line-height:1.2;color:var(--lt-cell-text);background:color-mix(in srgb,var(--lt-border) 45%,transparent);border:1px solid var(--lt-border);border-radius:999px}.lt-badge-cell .lt-cell-text:empty{display:none}.lt-select-cell,.lt-menu-cell{width:3.75rem;padding:0;text-align:center;vertical-align:middle;overflow:visible;position:relative}.lt-toolbar{display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:0 0 .625rem;min-height:var(--lt-toolbar-height);position:relative;z-index:30}.lt-toolbar-left{display:flex;align-items:center;gap:.5rem;flex:1 1 auto;min-width:0;flex-wrap:nowrap}.lt-filter-zone{display:flex;align-items:center;flex-wrap:nowrap;gap:.5rem;flex:0 1 auto;min-width:0}.lt-filters{display:flex;align-items:center;flex-wrap:nowrap;gap:.5rem;min-width:0;flex:0 1 auto;overflow:hidden}.lt-filter-item{display:inline-flex;flex:0 0 auto}.lt-filter-more{position:relative;flex:0 0 auto}.lt-filter-more-btn{gap:.375rem;padding:0 .625rem}.lt-filter-more-btn .lt-filter-more-icon{display:inline-flex}.lt-filter-overflow{position:absolute;top:calc(100% + .25rem);right:0;z-index:1000;min-width:15rem;max-width:min(22rem,90vw);display:flex;flex-direction:column;gap:.5rem;padding:.625rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:lt-chooser-in .12s ease-out}.lt-filter-overflow-title{font-family:var(--lt-font);font-size:.6875rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--groupText)}.lt-filter-overflow-row{display:flex}.lt-filter-overflow-row lib-list-filter-cell,.lt-filter-overflow-row .lt-filter-item{flex:1 1 auto;min-width:0}.lt-toolbar-right{display:flex;align-items:center;gap:.75rem;flex:0 0 auto}.lt-divider{flex:0 0 auto;width:1px;height:1.5rem;background:var(--lt-border)}.lt-search{position:relative;display:flex;align-items:center;width:18rem;max-width:40vw;flex:0 1 18rem}.lt-search-icon{position:absolute;left:.625rem;display:block;color:var(--lt-icon);pointer-events:none}.lt-search-input{width:100%;height:var(--lt-toolbar-height);padding:0 .75rem 0 1.75rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-border);border-radius:var(--lt-radius);outline:none;transition:border-color .15s,box-shadow .15s}.lt-search-input::placeholder{color:var(--placeholder)}.lt-search-input:focus{border-color:var(--lt-link);box-shadow:0 0 0 .1875rem color-mix(in srgb,var(--lt-link) 18%,transparent)}.lt-count{font-size:.8125rem;color:var(--groupText);white-space:nowrap}.lt-count .lt-count-of{color:var(--groupText)}.lt-pager{display:flex;align-items:center;gap:.25rem}.lt-page-btn{display:inline-flex;align-items:center;justify-content:center;width:var(--lt-toolbar-height);height:var(--lt-toolbar-height);color:var(--lt-icon);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-border);border-radius:var(--lt-radius);cursor:pointer;font-size:.9375rem;transition:border-color .15s,color .15s}.lt-page-btn:not(:disabled):hover{border-color:var(--lt-link);color:var(--lt-link)}.lt-page-btn:disabled{opacity:.4;cursor:default}.lt-refresh-btn{padding:0;width:var(--lt-toolbar-height);justify-content:center}.lt-refresh-btn:disabled{opacity:.5;cursor:default;border-color:var(--lt-border);color:var(--lt-cell-text)}.lt-columns{position:relative;flex:0 0 auto}.lt-column-chooser{position:absolute;top:calc(100% + .25rem);right:0;z-index:1000;width:15rem;max-height:calc(100vh - 9rem);display:flex;flex-direction:column;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);overflow:hidden;animation:lt-chooser-in .12s ease-out}@keyframes lt-chooser-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.lt-chooser-header{flex:0 0 auto;display:flex;flex-direction:column;padding:.625rem .75rem .5rem;border-bottom:1px solid var(--lt-border)}.lt-chooser-title{font-size:.8125rem;font-weight:700;color:var(--lt-cell-text)}.lt-chooser-subtitle{font-size:.6875rem;color:var(--groupText)}.lt-chooser-list{flex:1 1 auto;list-style:none;margin:0;padding:.375rem .5rem;min-height:0;overflow-y:auto}.lt-chooser-row{display:flex;align-items:center;justify-content:space-between;min-height:2.5rem;padding:.5rem;border-radius:var(--lt-radius);background:var(--lt-control-bg)}.lt-chooser-row:hover{background:var(--lt-row-hover)}.lt-chooser-row:hover .lt-chooser-handle{opacity:1}.lt-chooser-toggle{display:flex;align-items:center;gap:.625rem;flex:1 1 auto;min-width:0;padding:0;background:transparent;border:none;font:inherit;font-size:.875rem;color:var(--lt-cell-text);text-align:left;cursor:pointer}.lt-chooser-toggle:disabled{cursor:default;opacity:.5}.lt-chooser-box{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;width:1rem;height:1rem;border:.09375rem solid var(--lt-border);border-radius:.25rem;color:var(--lt-control-bg);background:var(--lt-control-bg);transition:background .12s,border-color .12s}.lt-chooser-toggle.checked .lt-chooser-box{background:var(--lt-link);border-color:var(--lt-link)}.lt-chooser-toggle:not(:disabled):hover .lt-chooser-box{border-color:var(--lt-link)}.lt-chooser-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lt-chooser-default-tag{flex:0 0 auto;margin-left:.375rem;font-size:.6875rem;font-weight:400;font-style:italic;color:var(--groupText);white-space:nowrap}.lt-chooser-handle{flex:0 0 auto;display:inline-flex;align-items:center;color:var(--lt-icon);cursor:grab;padding-left:.5rem;-webkit-user-select:none;user-select:none}.lt-chooser-handle:active{cursor:grabbing}.lt-chooser-footer{flex:0 0 auto;padding:.5rem .75rem;border-top:1px solid var(--lt-border)}.lt-chooser-reset{width:100%;height:1.875rem;font-size:.75rem;font-weight:600;color:var(--lt-link);background:transparent;border:1px solid var(--lt-border);border-radius:var(--lt-radius);cursor:pointer}.lt-chooser-reset:disabled{color:var(--groupText);cursor:default;opacity:.6}.lt-chooser-reset:not(:disabled):hover{border-color:var(--lt-link)}.lt-footer{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;padding:.625rem .125rem 0;font-family:var(--lt-font);font-size:.8125rem;color:var(--groupText)}.lt-footer-count{white-space:nowrap}.lt-footer-count span{color:var(--lt-cell-text);font-weight:600}.lt-footer-controls{display:flex;align-items:center;gap:1.25rem;flex-wrap:nowrap}.lt-rpp{display:inline-flex;align-items:center;gap:.5rem;white-space:nowrap}.lt-rpp-label{text-transform:none;font-weight:400;color:var(--groupText)}.lt-rpp-select{width:auto;min-width:4rem;height:var(--lt-toolbar-height);padding:0 1.75rem 0 .625rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background-color:var(--lt-control-bg);background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%236b7280' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M3 4.5 6 7.5 9 4.5'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right .5rem center;border:var(--inputBorderWidth) solid var(--lt-border);border-radius:var(--lt-radius);appearance:none;-webkit-appearance:none;cursor:pointer}.lt-rpp-select:focus{outline:none;border-color:var(--lt-link)}.lt-page-num{display:inline-flex;align-items:center;justify-content:center;min-width:var(--lt-toolbar-height);height:var(--lt-toolbar-height);padding:0 .5rem;font-family:var(--lt-font);font-size:.8125rem;font-weight:600;color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-border);border-radius:var(--lt-radius);cursor:pointer;transition:border-color .15s,color .15s,background .15s}.lt-page-num:hover{border-color:var(--lt-link);color:var(--lt-link)}.lt-page-num.is-current{color:#fff;background:var(--lt-link);border-color:var(--lt-link);cursor:default}.lt-page-gap{display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;color:var(--groupText)}.lt-tooltip{position:fixed;z-index:1100;pointer-events:none;animation:lt-tooltip-in .12s ease-out}@keyframes lt-tooltip-in{0%{opacity:0;transform:translateY(-.1875rem)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.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: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i6$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i6$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i6$1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i3$1.ClickOutsideDirective, selector: "[clickOutside]", inputs: ["clickOutsideEnabled", "attachOutsideOnClick", "delayClickOutsideInit", "emitOnBlur", "exclude", "excludeBeforeClick", "clickOutsideEvents"], outputs: ["clickOutside"] }, { kind: "component", type: ListMenuComponent, selector: "lib-list-menu", inputs: ["open", "items", "left", "top", "menuId"], outputs: ["itemClick", "closed"] }, { kind: "component", type: SelectCellComponent, selector: "lib-select-cell", inputs: ["row", "toggleItem"] }, { kind: "component", type: ListHeaderCellComponent, selector: "lib-list-header-cell", inputs: ["column", "sortDir"], outputs: ["sortChanged", "hideColumn"] }, { kind: "component", type: ListFilterCellComponent, selector: "lib-list-filter-cell", inputs: ["column"] }, { kind: "component", type: NoItemsComponent, selector: "lib-no-items", inputs: ["image", "typeName", "isEmpty", "hasAdd", "hiddenResults", "isLoading", "readonly", "accessDenied"], outputs: ["clickEmit", "refresh"] }] }); }
12844
+ }
12845
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ListTableComponent, decorators: [{
12846
+ type: Component,
12847
+ args: [{ selector: 'lib-list-table', standalone: false, providers: [ListTableService], template: "@if (showTable && !accessDenied) {\n <div class=\"lt-container\">\n <div class=\"lt-toolbar\">\n <div class=\"lt-toolbar-left\">\n <div class=\"lt-search\">\n <svg class=\"lt-search-icon\" viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <circle cx=\"7\" cy=\"7\" r=\"4.3\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"/>\n <line x1=\"10.4\" y1=\"10.4\" x2=\"14.6\" y2=\"14.6\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"/>\n </svg>\n <input\n class=\"lt-search-input\"\n id=\"SearchFilter\"\n type=\"text\"\n [placeholder]=\"'Search ' + (entityTypeLabel || 'items')\"\n [(ngModel)]=\"searchString\"\n (keyup)=\"onSearchInput()\"\n />\n </div>\n @if (filterableColumns.length) {\n <div class=\"lt-filter-zone\" #filterZone>\n <div class=\"lt-filters\" #filtersRow>\n @for (col of visibleFilterColumns; track col.key) {\n <span class=\"lt-filter-item\" [attr.data-filter-key]=\"col.key\">\n <lib-list-filter-cell [column]=\"col\"></lib-list-filter-cell>\n </span>\n }\n </div>\n @if (overflowFilterColumns.length) {\n <div class=\"lt-filter-more\">\n <button\n type=\"button\"\n class=\"lt-filter-more-btn\"\n [class.is-on]=\"hiddenActiveCount > 0\"\n [class.is-open]=\"showFilterOverflow\"\n (click)=\"toggleFilterOverflow($event)\"\n aria-haspopup=\"true\"\n [attr.aria-expanded]=\"showFilterOverflow\"\n [attr.aria-label]=\"overflowFilterColumns.length + ' more filters'\"\n id=\"FilterOverflowButton\"\n >\n <span class=\"lt-filter-more-icon\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\"><path fill=\"currentColor\" d=\"M2 3h12l-4.6 5.6v4l-2.8 1.4v-5.4z\"/></svg>\n </span>\n <span class=\"lt-filter-more-label\">More</span>\n <span class=\"lt-filter-more-count\">{{ overflowFilterColumns.length }}</span>\n </button>\n @if (showFilterOverflow) {\n <div class=\"lt-filter-overflow\" (clickOutside)=\"closeFilterOverflow($event)\">\n <div class=\"lt-filter-overflow-title\">Filters</div>\n @for (col of overflowFilterColumns; track col.key) {\n <div class=\"lt-filter-overflow-row\" [class.is-active]=\"isFilterActive(col)\">\n <lib-list-filter-cell [column]=\"col\"></lib-list-filter-cell>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n <div class=\"lt-toolbar-right\">\n <ng-template #refreshIcon>\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" d=\"M13.4 8a5.4 5.4 0 1 1-1.5-3.8\"/>\n <path fill=\"currentColor\" d=\"M13.9 2.1l.5 3.2-3.2-.5z\"/>\n </svg>\n </ng-template>\n <button\n type=\"button\"\n class=\"lt-refresh-btn\"\n (click)=\"refresh()\"\n [disabled]=\"filtering\"\n aria-label=\"Refresh\"\n title=\"Refresh\"\n id=\"RefreshTableButton\"\n >\n <ng-container [ngTemplateOutlet]=\"refreshIcon\"></ng-container>\n </button>\n <button\n type=\"button\"\n class=\"lt-autorefresh-btn\"\n [class.is-on]=\"autoRefresh\"\n (click)=\"toggleAutoRefresh()\"\n [attr.aria-pressed]=\"autoRefresh\"\n id=\"AutoRefreshButton\"\n >\n <ng-container [ngTemplateOutlet]=\"refreshIcon\"></ng-container>\n <span>Auto-refresh: {{ autoRefresh ? 'On' : 'Off' }}</span>\n </button>\n <div class=\"lt-columns\">\n <button\n type=\"button\"\n class=\"lt-columns-btn\"\n [class.is-on]=\"hiddenColumnCount > 0\"\n [class.is-open]=\"showColumnChooser\"\n (click)=\"toggleColumnChooser($event)\"\n aria-haspopup=\"true\"\n [attr.aria-expanded]=\"showColumnChooser\"\n id=\"ColumnChooserButton\"\n >\n <svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" aria-hidden=\"true\">\n <path fill=\"currentColor\" d=\"M2 3.5h9v1.4H2zM2 7.3h6v1.4H2zM2 11.1h11v1.4H2z\"/>\n <circle cx=\"13\" cy=\"4.2\" r=\"1.7\" fill=\"currentColor\"/>\n <circle cx=\"10\" cy=\"8\" r=\"1.7\" fill=\"currentColor\"/>\n <circle cx=\"14.4\" cy=\"11.8\" r=\"1.7\" fill=\"currentColor\"/>\n </svg>\n <span>Columns</span>\n @if (hiddenColumnCount > 0) {\n <span class=\"lt-columns-count\">{{ hiddenColumnCount }}</span>\n }\n </button>\n @if (showColumnChooser) {\n <div class=\"lt-column-chooser\" (clickOutside)=\"closeColumnChooser()\">\n <div class=\"lt-chooser-header\">\n <span class=\"lt-chooser-title\">Columns</span>\n <span class=\"lt-chooser-subtitle\">Customize order and visibility</span>\n </div>\n <ul class=\"lt-chooser-list\" cdkDropList cdkDropListLockAxis=\"y\" (cdkDropListDropped)=\"chooserReorder($event)\">\n @for (col of allColumns; track col.key) {\n <li class=\"lt-chooser-row\" cdkDrag cdkDragLockAxis=\"y\" [cdkDragDisabled]=\"col.reorderable === false\">\n <button\n type=\"button\"\n class=\"lt-chooser-toggle\"\n [class.checked]=\"!col.hidden\"\n [disabled]=\"col.hideable === false\"\n (click)=\"toggleColumnVisible(col)\"\n [attr.aria-pressed]=\"!col.hidden\"\n >\n <span class=\"lt-chooser-box\" aria-hidden=\"true\">\n @if (!col.hidden) {\n <svg viewBox=\"0 0 12 12\" width=\"10\" height=\"10\">\n <path fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M2 6.3 4.6 9 10 3\"/>\n </svg>\n }\n </span>\n <span class=\"lt-chooser-text\">{{ col.label }}</span>\n @if (isDefaultHidden(col)) {\n <span class=\"lt-chooser-default-tag\">hidden by default</span>\n }\n </button>\n @if (col.reorderable !== false) {\n <span class=\"lt-chooser-handle\" cdkDragHandle aria-label=\"Drag to reorder\" title=\"Drag to reorder\">\n <svg viewBox=\"0 0 10 16\" width=\"10\" height=\"15\" fill=\"currentColor\" aria-hidden=\"true\">\n <circle cx=\"3\" cy=\"3\" r=\"1.25\"/><circle cx=\"7\" cy=\"3\" r=\"1.25\"/>\n <circle cx=\"3\" cy=\"8\" r=\"1.25\"/><circle cx=\"7\" cy=\"8\" r=\"1.25\"/>\n <circle cx=\"3\" cy=\"13\" r=\"1.25\"/><circle cx=\"7\" cy=\"13\" r=\"1.25\"/>\n </svg>\n </span>\n }\n </li>\n }\n </ul>\n <div class=\"lt-chooser-footer\">\n <button\n type=\"button\"\n class=\"lt-chooser-reset\"\n [disabled]=\"columnsAtDefault\"\n (click)=\"resetTableColumns()\"\n id=\"ResetColumnsButton\"\n >\n Reset to default\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n\n <div class=\"lt-scroll\" id=\"ItemTable\">\n <table class=\"lt-table\">\n <colgroup>\n @if (!options.noSelect) { <col class=\"lt-col-select\" /> }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <col [style.width]=\"colWidth(col)\" />\n }\n @if (!options.noMenu) { <col class=\"lt-col-menu\" /> }\n </colgroup>\n <thead class=\"lt-head\">\n <tr cdkDropList cdkDropListOrientation=\"horizontal\" (cdkDropListDropped)=\"onReorder($event)\">\n @if (!options.noSelect) {\n <th class=\"lt-select-cell\">\n <div\n class=\"lt-select-all\"\n [class.checked]=\"allToggled\"\n [class.indeterminate]=\"someSelected\"\n (click)=\"toggleAll()\"\n id=\"SelectAllRows\"\n ></div>\n </th>\n }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <th\n cdkDrag\n [cdkDragDisabled]=\"col.reorderable === false\"\n class=\"lt-th\"\n [ngClass]=\"col.cellClass\"\n id=\"Header_{{ col.key }}\"\n >\n <lib-list-header-cell\n [column]=\"col\"\n [sortDir]=\"col.sortDir\"\n (sortChanged)=\"onHeaderSort($event)\"\n (hideColumn)=\"hideColumn($event)\"\n ></lib-list-header-cell>\n @if (col.resizable !== false) {\n <span class=\"lt-resize\" (pointerdown)=\"startResize(col, $event)\"></span>\n }\n </th>\n }\n @if (!options.noMenu) {\n <th class=\"lt-menu-cell\">\n <div class=\"lt-header-menu-trigger\" (click)=\"openHeaderActionMenu($event)\" id=\"ColumnMenuHeader\">\n &#8942;\n </div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of rowData; track trackRow($index, row); let i = $index) {\n <tr\n class=\"lt-row\"\n [class.lt-row-odd]=\"i % 2 === 1\"\n [class.lt-row-selected]=\"row.selected\"\n [class.row-invalid]=\"row.invalid\"\n [class.row-copied]=\"row.copied\"\n >\n @if (!options.noSelect) {\n <td class=\"lt-select-cell\">\n <lib-select-cell [row]=\"row\" [toggleItem]=\"toggleItem\"></lib-select-cell>\n </td>\n }\n @for (col of visibleColumns; track trackCol($index, col)) {\n <td\n class=\"lt-cell\"\n [ngClass]=\"cellClass(col)\"\n (click)=\"onCellClick(col, row, i)\"\n (mouseenter)=\"onCellHover(col, row, i, $event)\"\n (mouseleave)=\"onCellLeave()\"\n >\n @switch (renderMode(col)) {\n @case ('component') {\n <ng-container *ngComponentOutlet=\"col.cell; inputs: cellInputs(col, row, i)\"></ng-container>\n }\n @case ('html') {\n <span class=\"lt-cell-html\" [innerHTML]=\"cellHtml(col, row, i)\"></span>\n }\n @default {\n <span class=\"lt-cell-text\">{{ cellText(col, row, i) }}</span>\n }\n }\n </td>\n }\n @if (!options.noMenu) {\n <td class=\"lt-menu-cell\">\n @if (row.actionList?.length) {\n <button\n type=\"button\"\n class=\"lt-row-menu-trigger\"\n (click)=\"$event.stopPropagation(); openActionMenu($event, row)\"\n attr.id=\"RowMenu_{{ row.id }}\"\n aria-label=\"Row actions\"\n >&#8942;</button>\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n @if (!isLoading && filterApplied && (!rowData || rowData.length === 0)) {\n <div class=\"no-rows-message\">No Rows To Show</div>\n }\n </div>\n\n <div class=\"lt-footer\">\n <div class=\"lt-footer-count\">\n Showing <span id=\"Start\">{{ startCount }}</span>&#8211;<span id=\"End\">{{ endCount }}</span>\n of <span id=\"Total\">{{ totalCount }}</span>\n </div>\n <div class=\"lt-footer-controls\">\n <div class=\"lt-rpp\">\n <span class=\"lt-rpp-label\">Rows per page</span>\n <select\n class=\"lt-rpp-select\"\n [disabled]=\"filtering\"\n [value]=\"pageSize\"\n (change)=\"onPageSizeChange(+$any($event.target).value)\"\n aria-label=\"Rows per page\"\n >\n @for (opt of pageSizeOptions; track opt) {\n <option [value]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n <div class=\"lt-pager\">\n <button type=\"button\" class=\"lt-page-btn icon-prev\" [disabled]=\"prevDisabled\" (click)=\"prevPage()\" aria-label=\"Previous page\"></button>\n @for (p of pageWindow; track $index) {\n @if (p === 0) {\n <span class=\"lt-page-gap\">&#8230;</span>\n } @else {\n <button\n type=\"button\"\n class=\"lt-page-num\"\n [class.is-current]=\"p === currentPageNum\"\n (click)=\"goToPage(p)\"\n >{{ p }}</button>\n }\n }\n <button type=\"button\" class=\"lt-page-btn icon-next\" [disabled]=\"nextDisabled\" (click)=\"nextPage()\" aria-label=\"Next page\"></button>\n </div>\n </div>\n </div>\n\n <!-- header action menu -->\n <lib-list-menu\n menuId=\"HeaderActionMenu\"\n [open]=\"openHeaderMenu\"\n [items]=\"headerMenuItems\"\n [left]=\"menuLeft\"\n [top]=\"menuTop\"\n (itemClick)=\"onHeaderMenuClick($event)\"\n (closed)=\"closeHeaderActionMenu()\"\n ></lib-list-menu>\n\n <!-- row action menu -->\n <lib-list-menu\n menuId=\"RowActionMenu\"\n [open]=\"openMenu\"\n [items]=\"rowMenuItems\"\n [left]=\"menuLeft\"\n [top]=\"menuTop\"\n (itemClick)=\"onRowMenuClick($event)\"\n (closed)=\"closeActionMenu()\"\n ></lib-list-menu>\n </div>\n} @else {\n <lib-no-items\n (clickEmit)=\"openCreate()\"\n [image]=\"noItemsImage\"\n [isEmpty]=\"!rowData || rowData.length === 0\"\n [isLoading]=\"isLoading\"\n [typeName]=\"entityTypeLabel\"\n [hasAdd]=\"showNoItemsAdd\"\n [readonly]=\"noItemsReadonly\"\n [accessDenied]=\"accessDenied\"\n ></lib-no-items>\n}\n\n@if (hoverTooltip) {\n <div class=\"lt-tooltip\" [ngStyle]=\"{ left: hoverTooltip.left + 'px', top: hoverTooltip.top + 'px' }\">\n <ng-container *ngComponentOutlet=\"hoverTooltip.component; inputs: hoverTooltip.inputs\"></ng-container>\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;width:100%;height:100%}.lt-columns-btn,.lt-autorefresh-btn,.lt-refresh-btn,.lt-filter-more-btn{display:inline-flex;align-items:center;gap:.4375rem;height:var(--lt-toolbar-height);padding:0 .75rem;font-family:var(--lt-font);font-size:.8125rem;font-weight:600;color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-border);border-radius:var(--lt-radius);cursor:pointer;white-space:nowrap;transition:border-color .15s,background .15s,color .15s}.lt-columns-btn svg,.lt-autorefresh-btn svg,.lt-refresh-btn svg,.lt-filter-more-btn svg{display:block}.lt-columns-btn:hover,.lt-autorefresh-btn:hover,.lt-refresh-btn:hover,.lt-filter-more-btn:hover,.is-open.lt-columns-btn,.is-open.lt-autorefresh-btn,.is-open.lt-refresh-btn,.is-open.lt-filter-more-btn,.is-on.lt-columns-btn,.is-on.lt-autorefresh-btn,.is-on.lt-refresh-btn,.is-on.lt-filter-more-btn{border-color:var(--lt-link);color:var(--lt-link)}.lt-columns-count,.lt-filter-more-count{display:inline-flex;align-items:center;justify-content:center;min-width:1.125rem;height:1.125rem;padding:0 .3125rem;font-size:.6875rem;font-weight:700;color:var(--lt-control-bg);background:var(--lt-link);border-radius:.5625rem}.lt-container{display:flex;position:relative;height:calc(100% - 5.625rem);width:100%;flex-direction:column}.lt-scroll{position:relative;z-index:1;width:100%;flex:0 1 auto;min-height:0;overflow:auto;background-color:var(--lt-bg)}.lt-scroll .no-rows-message{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--lt-cell-text);font-size:.875rem;pointer-events:none}.lt-table{width:100%;border-collapse:collapse;table-layout:fixed;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text)}.lt-head{position:sticky;top:0;z-index:10}.lt-head th{position:relative;box-sizing:border-box;height:2.75rem;padding:0 .3125rem 0 .625rem;text-align:left;vertical-align:middle;background-color:var(--lt-header-bg);border-bottom:1px solid var(--lt-header-underline);white-space:nowrap;-webkit-user-select:none;user-select:none}.lt-head th.lt-th{border-right:1px solid var(--lt-border)}.lt-head .lt-select-cell,.lt-head .lt-menu-cell{width:3.75rem;padding:0}.lt-resize{position:absolute;right:0;top:50%;transform:translateY(-50%);width:.625rem;height:.875rem;cursor:col-resize}.lt-resize:before,.lt-resize:after{content:\"\";position:absolute;top:0;height:100%;width:1px;background:transparent;transition:background .15s}.lt-resize:before{right:.25rem}.lt-resize:after{right:.125rem}.lt-resize:hover:before,.lt-resize:hover:after{background:var(--lt-icon)}.lt-head th:hover .lt-resize:before,.lt-head th:hover .lt-resize:after{background:var(--lt-border)}.lt-select-all{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);box-sizing:border-box;width:1rem;height:1rem;border:1px solid var(--lt-border);border-radius:.25rem;background:var(--lt-control-bg);background-repeat:no-repeat;background-position:center center;cursor:pointer;transition:border-color .12s,background-color .12s}.lt-select-all:hover{border-color:var(--lt-link)}.lt-select-all.checked{background-color:var(--lt-link);border-color:var(--lt-link);background-size:.75rem;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M2.5 7.6 5.7 10.8 11.5 3.6'/%3E%3C/svg%3E\")}.lt-select-all.indeterminate{background-color:var(--lt-link);border-color:var(--lt-link);background-image:linear-gradient(#fff,#fff);background-size:.5rem .125rem}.lt-header-menu-trigger{display:flex;align-items:center;justify-content:center;height:100%;font-size:1.25rem;color:var(--lt-icon);cursor:pointer}.lt-header-menu-trigger:hover{color:var(--lt-icon-active)}.lt-row-menu-trigger{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;padding:0;font-size:1.25rem;line-height:1;color:var(--lt-icon);background:transparent;border:none;border-radius:var(--lt-radius);cursor:pointer;transition:color .15s,background .15s}.lt-row-menu-trigger:hover{color:var(--lt-icon-active);background:var(--lt-row-hover)}.cdk-drag-preview{box-sizing:border-box;background-color:var(--lt-header-bg);color:var(--lt-header-text);box-shadow:var(--shadowLarge);padding:0 .625rem;display:flex;align-items:center;border-radius:var(--lt-radius)}.cdk-drag-placeholder{opacity:.3}.cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.lt-row{height:var(--lt-row-height);background-color:var(--lt-row-bg);color:var(--lt-cell-text)}.lt-row td{border-bottom:1px solid var(--lt-border)}.lt-row.lt-row-odd{background-color:var(--lt-row-alt-bg)}.lt-row:hover{background-color:var(--lt-row-hover)}.lt-row.lt-row-selected{background-color:var(--lt-row-selected)}.lt-row.lt-row-selected:hover{background-color:var(--lt-row-hover)}.lt-row.row-invalid{background-color:var(--invalid)!important}.lt-row.row-copied{background-color:var(--copiedRow)!important}.lt-cell{height:var(--lt-row-height);padding:0 .625rem 0 1.25rem;text-align:left;vertical-align:middle;border:none;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;-webkit-user-select:text;user-select:text}.lt-cell-text{display:inline-block;overflow:hidden;text-overflow:ellipsis;max-width:100%;vertical-align:middle}.lt-cell.lt-cell-center{text-align:center}.lt-badge-cell .lt-cell-text{padding:.1875rem .5rem;font-size:.75rem;font-weight:600;line-height:1.2;color:var(--lt-cell-text);background:color-mix(in srgb,var(--lt-border) 45%,transparent);border:1px solid var(--lt-border);border-radius:999px}.lt-badge-cell .lt-cell-text:empty{display:none}.lt-select-cell,.lt-menu-cell{width:3.75rem;padding:0;text-align:center;vertical-align:middle;overflow:visible;position:relative}.lt-toolbar{display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:0 0 .625rem;min-height:var(--lt-toolbar-height);position:relative;z-index:30}.lt-toolbar-left{display:flex;align-items:center;gap:.5rem;flex:1 1 auto;min-width:0;flex-wrap:nowrap}.lt-filter-zone{display:flex;align-items:center;flex-wrap:nowrap;gap:.5rem;flex:0 1 auto;min-width:0}.lt-filters{display:flex;align-items:center;flex-wrap:nowrap;gap:.5rem;min-width:0;flex:0 1 auto;overflow:hidden}.lt-filter-item{display:inline-flex;flex:0 0 auto}.lt-filter-more{position:relative;flex:0 0 auto}.lt-filter-more-btn{gap:.375rem;padding:0 .625rem}.lt-filter-more-btn .lt-filter-more-icon{display:inline-flex}.lt-filter-overflow{position:absolute;top:calc(100% + .25rem);right:0;z-index:1000;min-width:15rem;max-width:min(22rem,90vw);display:flex;flex-direction:column;gap:.5rem;padding:.625rem;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);animation:lt-chooser-in .12s ease-out}.lt-filter-overflow-title{font-family:var(--lt-font);font-size:.6875rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--groupText)}.lt-filter-overflow-row{display:flex}.lt-filter-overflow-row lib-list-filter-cell,.lt-filter-overflow-row .lt-filter-item{flex:1 1 auto;min-width:0}.lt-toolbar-right{display:flex;align-items:center;gap:.75rem;flex:0 0 auto}.lt-divider{flex:0 0 auto;width:1px;height:1.5rem;background:var(--lt-border)}.lt-search{position:relative;display:flex;align-items:center;width:18rem;max-width:40vw;flex:0 1 18rem}.lt-search-icon{position:absolute;left:.625rem;display:block;color:var(--lt-icon);pointer-events:none}.lt-search-input{width:100%;height:var(--lt-toolbar-height);padding:0 .75rem 0 1.75rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-border);border-radius:var(--lt-radius);outline:none;transition:border-color .15s,box-shadow .15s}.lt-search-input::placeholder{color:var(--placeholder)}.lt-search-input:focus{border-color:var(--lt-link);box-shadow:0 0 0 .1875rem color-mix(in srgb,var(--lt-link) 18%,transparent)}.lt-count{font-size:.8125rem;color:var(--groupText);white-space:nowrap}.lt-count .lt-count-of{color:var(--groupText)}.lt-pager{display:flex;align-items:center;gap:.25rem}.lt-page-btn{display:inline-flex;align-items:center;justify-content:center;width:var(--lt-toolbar-height);height:var(--lt-toolbar-height);color:var(--lt-icon);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-border);border-radius:var(--lt-radius);cursor:pointer;font-size:.9375rem;transition:border-color .15s,color .15s}.lt-page-btn:not(:disabled):hover{border-color:var(--lt-link);color:var(--lt-link)}.lt-page-btn:disabled{opacity:.4;cursor:default}.lt-refresh-btn{padding:0;width:var(--lt-toolbar-height);justify-content:center}.lt-refresh-btn:disabled{opacity:.5;cursor:default;border-color:var(--lt-border);color:var(--lt-cell-text)}.lt-columns{position:relative;flex:0 0 auto}.lt-column-chooser{position:absolute;top:calc(100% + .25rem);right:0;z-index:1000;width:15rem;max-height:calc(100vh - 9rem);display:flex;flex-direction:column;background:var(--lt-control-bg);border:1px solid var(--lt-border);border-radius:var(--lt-radius);box-shadow:var(--shadowLarge);overflow:hidden;animation:lt-chooser-in .12s ease-out}@keyframes lt-chooser-in{0%{opacity:0;transform:translateY(-.25rem)}to{opacity:1;transform:translateY(0)}}.lt-chooser-header{flex:0 0 auto;display:flex;flex-direction:column;padding:.625rem .75rem .5rem;border-bottom:1px solid var(--lt-border)}.lt-chooser-title{font-size:.8125rem;font-weight:700;color:var(--lt-cell-text)}.lt-chooser-subtitle{font-size:.6875rem;color:var(--groupText)}.lt-chooser-list{flex:1 1 auto;list-style:none;margin:0;padding:.375rem .5rem;min-height:0;overflow-y:auto}.lt-chooser-row{display:flex;align-items:center;justify-content:space-between;min-height:2.5rem;padding:.5rem;border-radius:var(--lt-radius);background:var(--lt-control-bg)}.lt-chooser-row:hover{background:var(--lt-row-hover)}.lt-chooser-row:hover .lt-chooser-handle{opacity:1}.lt-chooser-toggle{display:flex;align-items:center;gap:.625rem;flex:1 1 auto;min-width:0;padding:0;background:transparent;border:none;font:inherit;font-size:.875rem;color:var(--lt-cell-text);text-align:left;cursor:pointer}.lt-chooser-toggle:disabled{cursor:default;opacity:.5}.lt-chooser-box{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;width:1rem;height:1rem;border:.09375rem solid var(--lt-border);border-radius:.25rem;color:var(--lt-control-bg);background:var(--lt-control-bg);transition:background .12s,border-color .12s}.lt-chooser-toggle.checked .lt-chooser-box{background:var(--lt-link);border-color:var(--lt-link)}.lt-chooser-toggle:not(:disabled):hover .lt-chooser-box{border-color:var(--lt-link)}.lt-chooser-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lt-chooser-default-tag{flex:0 0 auto;margin-left:.375rem;font-size:.6875rem;font-weight:400;font-style:italic;color:var(--groupText);white-space:nowrap}.lt-chooser-handle{flex:0 0 auto;display:inline-flex;align-items:center;color:var(--lt-icon);cursor:grab;padding-left:.5rem;-webkit-user-select:none;user-select:none}.lt-chooser-handle:active{cursor:grabbing}.lt-chooser-footer{flex:0 0 auto;padding:.5rem .75rem;border-top:1px solid var(--lt-border)}.lt-chooser-reset{width:100%;height:1.875rem;font-size:.75rem;font-weight:600;color:var(--lt-link);background:transparent;border:1px solid var(--lt-border);border-radius:var(--lt-radius);cursor:pointer}.lt-chooser-reset:disabled{color:var(--groupText);cursor:default;opacity:.6}.lt-chooser-reset:not(:disabled):hover{border-color:var(--lt-link)}.lt-footer{flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;padding:.625rem .125rem 0;font-family:var(--lt-font);font-size:.8125rem;color:var(--groupText)}.lt-footer-count{white-space:nowrap}.lt-footer-count span{color:var(--lt-cell-text);font-weight:600}.lt-footer-controls{display:flex;align-items:center;gap:1.25rem;flex-wrap:nowrap}.lt-rpp{display:inline-flex;align-items:center;gap:.5rem;white-space:nowrap}.lt-rpp-label{text-transform:none;font-weight:400;color:var(--groupText)}.lt-rpp-select{width:auto;min-width:4rem;height:var(--lt-toolbar-height);padding:0 1.75rem 0 .625rem;font-family:var(--lt-font);font-size:.8125rem;color:var(--lt-cell-text);background-color:var(--lt-control-bg);background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%236b7280' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M3 4.5 6 7.5 9 4.5'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right .5rem center;border:var(--inputBorderWidth) solid var(--lt-border);border-radius:var(--lt-radius);appearance:none;-webkit-appearance:none;cursor:pointer}.lt-rpp-select:focus{outline:none;border-color:var(--lt-link)}.lt-page-num{display:inline-flex;align-items:center;justify-content:center;min-width:var(--lt-toolbar-height);height:var(--lt-toolbar-height);padding:0 .5rem;font-family:var(--lt-font);font-size:.8125rem;font-weight:600;color:var(--lt-cell-text);background:var(--lt-control-bg);border:var(--inputBorderWidth) solid var(--lt-border);border-radius:var(--lt-radius);cursor:pointer;transition:border-color .15s,color .15s,background .15s}.lt-page-num:hover{border-color:var(--lt-link);color:var(--lt-link)}.lt-page-num.is-current{color:#fff;background:var(--lt-link);border-color:var(--lt-link);cursor:default}.lt-page-gap{display:inline-flex;align-items:center;justify-content:center;min-width:1.25rem;color:var(--groupText)}.lt-tooltip{position:fixed;z-index:1100;pointer-events:none;animation:lt-tooltip-in .12s ease-out}@keyframes lt-tooltip-in{0%{opacity:0;transform:translateY(-.1875rem)}to{opacity:1;transform:translateY(0)}}\n"] }]
12848
+ }], ctorParameters: () => [{ type: ListTableService }, { type: DataTableFilterService }, { type: i0.ElementRef }, { type: i0.NgZone }, { type: i1.DomSanitizer }], propDecorators: { tableId: [{
12849
+ type: Input
12850
+ }], columns: [{
12851
+ type: Input
12852
+ }], rowData: [{
12853
+ type: Input
12854
+ }], isLoading: [{
12855
+ type: Input
12856
+ }], options: [{
12857
+ type: Input
12858
+ }], startCount: [{
12859
+ type: Input
12860
+ }], endCount: [{
12861
+ type: Input
12862
+ }], totalCount: [{
12863
+ type: Input
12864
+ }], filterApplied: [{
12865
+ type: Input
12866
+ }], menuItems: [{
12867
+ type: Input
12868
+ }], headerActions: [{
12869
+ type: Input
12870
+ }], noItemsImage: [{
12871
+ type: Input
12872
+ }], showNoItemsAdd: [{
12873
+ type: Input
12874
+ }], noItemsReadonly: [{
12875
+ type: Input
12876
+ }], accessDenied: [{
12877
+ type: Input
12878
+ }], entityTypeName: [{
12879
+ type: Input
12880
+ }], filterName: [{
12881
+ type: Input
12882
+ }], filterColumn: [{
12883
+ type: Input
12884
+ }], actionRequested: [{
12885
+ type: Output
12886
+ }], tableReady: [{
12887
+ type: Output
12888
+ }], contextMenu: [{
12889
+ type: ViewChild,
12890
+ args: ['contextMenu']
12891
+ }], filterZone: [{
12892
+ type: ViewChild,
12893
+ args: ['filterZone']
12894
+ }], filtersRow: [{
12895
+ type: ViewChild,
12896
+ args: ['filtersRow']
12897
+ }] } });
12898
+
10772
12899
  class Link {
10773
12900
  constructor() {
10774
12901
  this.statusReason = null;
@@ -12828,11 +14955,11 @@ class SideModalComponent {
12828
14955
  this.openChange.emit(this.open);
12829
14956
  }
12830
14957
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SideModalComponent, deps: [{ token: ConsoleEventsService }], target: i0.ɵɵFactoryTarget.Component }); }
12831
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: SideModalComponent, isStandalone: false, selector: "lib-side-modal", inputs: { showClose: "showClose", open: "open" }, outputs: { openChange: "openChange" }, ngImport: i0, template: "<div class=\"side-modal-container\" [ngClass]=\"{'side-modal-open': open}\">\n @if (showClose) {\n <div class=\"close icon-close\" (click)=\"close()\"></div>\n }\n <ng-content></ng-content>\n</div>\n\n", styles: [".side-modal-container{position:fixed;right:-30%;top:0;padding:1.875rem;height:100%;width:30%;min-width:28.125rem;max-width:37.5rem;overflow-y:auto;transition:right .3s ease-in-out;z-index:999999;background-color:var(--formBackground);box-shadow:-.25rem 0 1.25rem #0003;border-left:.0625rem solid var(--tableBorder)}.side-modal-container.side-modal-open{right:0}.side-modal-container .close{position:absolute;top:1rem;right:1rem;font-size:1.5rem;cursor:pointer;color:var(--tableText);transition:all .2s ease;z-index:10}.side-modal-container .close:hover{color:var(--primary);transform:scale(1.1)}::ng-deep .sideNavClosed .side-modal-container.side-modal-open{right:0}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
14958
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: SideModalComponent, isStandalone: false, selector: "lib-side-modal", inputs: { showClose: "showClose", open: "open" }, outputs: { openChange: "openChange" }, ngImport: i0, template: "<div class=\"side-modal-container\" [ngClass]=\"{'side-modal-open': open}\">\n @if (showClose) {\n <div class=\"close icon-close\" (click)=\"close()\"></div>\n }\n <ng-content></ng-content>\n</div>\n\n", styles: [".side-modal-container{position:fixed;right:-42rem;top:0;padding:1.875rem;height:100%;width:30%;min-width:28.125rem;max-width:37.5rem;overflow-y:auto;transition:right .3s ease-in-out;z-index:999999;background-color:var(--formBackground);box-shadow:-.25rem 0 1.25rem #0003;border-left:.0625rem solid var(--tableBorder)}.side-modal-container.side-modal-open{right:0}.side-modal-container .close{position:absolute;top:1rem;right:1rem;font-size:1.5rem;cursor:pointer;color:var(--tableText);transition:all .2s ease;z-index:10}.side-modal-container .close:hover{color:var(--primary);transform:scale(1.1)}::ng-deep .sideNavClosed .side-modal-container.side-modal-open{right:0}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
12832
14959
  }
12833
14960
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SideModalComponent, decorators: [{
12834
14961
  type: Component,
12835
- args: [{ selector: 'lib-side-modal', standalone: false, template: "<div class=\"side-modal-container\" [ngClass]=\"{'side-modal-open': open}\">\n @if (showClose) {\n <div class=\"close icon-close\" (click)=\"close()\"></div>\n }\n <ng-content></ng-content>\n</div>\n\n", styles: [".side-modal-container{position:fixed;right:-30%;top:0;padding:1.875rem;height:100%;width:30%;min-width:28.125rem;max-width:37.5rem;overflow-y:auto;transition:right .3s ease-in-out;z-index:999999;background-color:var(--formBackground);box-shadow:-.25rem 0 1.25rem #0003;border-left:.0625rem solid var(--tableBorder)}.side-modal-container.side-modal-open{right:0}.side-modal-container .close{position:absolute;top:1rem;right:1rem;font-size:1.5rem;cursor:pointer;color:var(--tableText);transition:all .2s ease;z-index:10}.side-modal-container .close:hover{color:var(--primary);transform:scale(1.1)}::ng-deep .sideNavClosed .side-modal-container.side-modal-open{right:0}\n"] }]
14962
+ args: [{ selector: 'lib-side-modal', standalone: false, template: "<div class=\"side-modal-container\" [ngClass]=\"{'side-modal-open': open}\">\n @if (showClose) {\n <div class=\"close icon-close\" (click)=\"close()\"></div>\n }\n <ng-content></ng-content>\n</div>\n\n", styles: [".side-modal-container{position:fixed;right:-42rem;top:0;padding:1.875rem;height:100%;width:30%;min-width:28.125rem;max-width:37.5rem;overflow-y:auto;transition:right .3s ease-in-out;z-index:999999;background-color:var(--formBackground);box-shadow:-.25rem 0 1.25rem #0003;border-left:.0625rem solid var(--tableBorder)}.side-modal-container.side-modal-open{right:0}.side-modal-container .close{position:absolute;top:1rem;right:1rem;font-size:1.5rem;cursor:pointer;color:var(--tableText);transition:all .2s ease;z-index:10}.side-modal-container .close:hover{color:var(--primary);transform:scale(1.1)}::ng-deep .sideNavClosed .side-modal-container.side-modal-open{right:0}\n"] }]
12836
14963
  }], ctorParameters: () => [{ type: ConsoleEventsService }], propDecorators: { showClose: [{
12837
14964
  type: Input
12838
14965
  }], open: [{
@@ -13317,6 +15444,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
13317
15444
  See the License for the specific language governing permissions and
13318
15445
  limitations under the License.
13319
15446
  */
15447
+ /**
15448
+ * Legacy Identities page (ag-grid `lib-data-table`). Selected by the route when the
15449
+ * "Beta Features" flag is off. Slated for removal once the new list-table page (see
15450
+ * {@link IdentitiesListPageComponent}) graduates — at which point this file and its
15451
+ * template are deleted outright.
15452
+ */
13320
15453
  class IdentitiesPageComponent extends ListPageComponent {
13321
15454
  constructor(svc, filterService, dialogForm, tabNames, router, consoleEvents, extService) {
13322
15455
  super(filterService, svc, consoleEvents, dialogForm, extService);
@@ -13513,6 +15646,229 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
13513
15646
  args: [IDENTITY_EXTENSION_SERVICE]
13514
15647
  }] }] });
13515
15648
 
15649
+ /*
15650
+ Copyright NetFoundry Inc.
15651
+
15652
+ Licensed under the Apache License, Version 2.0 (the "License");
15653
+ you may not use this file except in compliance with the License.
15654
+ You may obtain a copy of the License at
15655
+
15656
+ https://www.apache.org/licenses/LICENSE-2.0
15657
+
15658
+ Unless required by applicable law or agreed to in writing, software
15659
+ distributed under the License is distributed on an "AS IS" BASIS,
15660
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15661
+ See the License for the specific language governing permissions and
15662
+ limitations under the License.
15663
+ */
15664
+ /**
15665
+ * New Identities page, rendered on the shared `lib-list-table`. Extends the common
15666
+ * {@link ListPageComponent} (which future list-table pages extend the same way).
15667
+ * Selected by the route when the "Beta Features" flag is on; the legacy ag-grid page
15668
+ * ({@link IdentitiesPageComponent}) handles the flag-off case. The two pages carry
15669
+ * their own copies of the identity actions during the migration so the legacy one can
15670
+ * be deleted outright once this graduates.
15671
+ */
15672
+ class IdentitiesListPageComponent extends ListPageComponent {
15673
+ constructor(svc, filterService, dialogForm, tabNames, router, consoleEvents, extService) {
15674
+ super(filterService, svc, consoleEvents, dialogForm, extService);
15675
+ this.svc = svc;
15676
+ this.tabNames = tabNames;
15677
+ this.router = router;
15678
+ this.extService = extService;
15679
+ this.title = 'Identities';
15680
+ this.isLoading = false;
15681
+ this.formDataChanged = false;
15682
+ }
15683
+ /** The new table consumes the declarative ListColumn set. */
15684
+ loadTableColumns() {
15685
+ return this.svc.initListTableColumns();
15686
+ }
15687
+ ngOnInit() {
15688
+ this.tabs = this.tabNames.getTabs('identities');
15689
+ this.svc.refreshData = this.refreshData;
15690
+ this.subscription.add(this.router.events.subscribe((event) => {
15691
+ if (event instanceof NavigationEnd) {
15692
+ this.refreshData();
15693
+ }
15694
+ }));
15695
+ this.svc.getIdentityRoleAttributes();
15696
+ super.ngOnInit();
15697
+ }
15698
+ headerActionClicked(action) {
15699
+ switch (action) {
15700
+ case 'add':
15701
+ this.svc.openEditForm();
15702
+ break;
15703
+ case 'edit':
15704
+ this.svc.openUpdate();
15705
+ break;
15706
+ case 'delete':
15707
+ const selectedItems = this.rowData.filter((row) => {
15708
+ return row.selected;
15709
+ });
15710
+ const label = selectedItems.length > 1 ? 'identities' : 'identity';
15711
+ this.openBulkDelete(selectedItems, label);
15712
+ break;
15713
+ default:
15714
+ }
15715
+ }
15716
+ closeModal(event) {
15717
+ this.svc.sideModalOpen = false;
15718
+ if (event?.refresh) {
15719
+ this.refreshData();
15720
+ this.svc.getIdentityRoleAttributes();
15721
+ }
15722
+ }
15723
+ dataChanged(event) {
15724
+ this.formDataChanged = event;
15725
+ }
15726
+ tableAction(event) {
15727
+ const extensionActionFound = this.handleExtensionActions(event);
15728
+ this.trackMenuAction(event?.action, event?.item);
15729
+ if (extensionActionFound) {
15730
+ return;
15731
+ }
15732
+ switch (event?.action) {
15733
+ case 'toggleAll':
15734
+ this.itemToggled(event.item);
15735
+ this.rowData.forEach((item) => {
15736
+ if (item.typeId === 'Router') {
15737
+ item.selected = false;
15738
+ }
15739
+ });
15740
+ break;
15741
+ case 'toggleItem':
15742
+ this.itemToggled(event.item);
15743
+ break;
15744
+ case 'update':
15745
+ this.svc.openEditForm(event?.item?.id);
15746
+ break;
15747
+ case 'create':
15748
+ this.svc.openEditForm();
15749
+ break;
15750
+ case 'override':
15751
+ this.svc.openOverridesModal(event.item);
15752
+ break;
15753
+ case 'delete':
15754
+ this.deleteItem(event.item);
15755
+ break;
15756
+ case 'download-enrollment':
15757
+ this.downloadJWT(event.item);
15758
+ break;
15759
+ case 'reset-enrollment':
15760
+ this.resetEnrollment(event.item);
15761
+ break;
15762
+ case 'reissue-enrollment':
15763
+ this.reissueEnrollment(event.item);
15764
+ break;
15765
+ case "identity-service-path":
15766
+ this.openIdentityVisualizer(event.item);
15767
+ break;
15768
+ case "qr-code":
15769
+ this.showQRCode(event.item);
15770
+ break;
15771
+ case 'download-all':
15772
+ this.downloadAllItems();
15773
+ break;
15774
+ case 'download-selected':
15775
+ this.svc.downloadItems(this.selectedItems);
15776
+ break;
15777
+ case 'reset-mfa':
15778
+ this.isLoading = true;
15779
+ this.svc.resetMFA(event.item).finally(() => {
15780
+ this.isLoading = false;
15781
+ this.refreshData();
15782
+ });
15783
+ break;
15784
+ default:
15785
+ break;
15786
+ }
15787
+ }
15788
+ downloadJWT(item) {
15789
+ const jwt = this.svc.getJWT(item);
15790
+ this.svc.downloadJWT(jwt, item.name);
15791
+ }
15792
+ showQRCode(item) {
15793
+ const data = {
15794
+ jwt: this.svc.getJWT(item),
15795
+ expiration: this.svc.getEnrollmentExpiration(item),
15796
+ qrCodeSize: 500,
15797
+ identity: item,
15798
+ qrOnly: true,
15799
+ };
15800
+ this.dialogRef = this.dialogForm.open(QrCodeComponent, {
15801
+ data: data,
15802
+ autoFocus: false,
15803
+ });
15804
+ }
15805
+ trackMenuAction(action, item) {
15806
+ this.extService?.consoleActionTriggered?.emit({
15807
+ action,
15808
+ page: 'identity',
15809
+ item,
15810
+ type: 'menu-action'
15811
+ });
15812
+ }
15813
+ deleteItem(item) {
15814
+ this.openBulkDelete([item], 'identity');
15815
+ }
15816
+ openIdentityVisualizer(identity) {
15817
+ this.dialogRef = this.dialogForm.open(IdentityServicePathComponent, {
15818
+ data: {
15819
+ identity: identity,
15820
+ },
15821
+ autoFocus: false,
15822
+ });
15823
+ }
15824
+ resetEnrollment(identity) {
15825
+ this.dialogRef = this.dialogForm.open(ResetEnrollmentComponent, {
15826
+ data: {
15827
+ identity: identity,
15828
+ type: 'reset',
15829
+ },
15830
+ autoFocus: false,
15831
+ });
15832
+ this.dialogRef.afterClosed().subscribe((result) => {
15833
+ if (result) {
15834
+ this.refreshData(undefined, true);
15835
+ }
15836
+ });
15837
+ }
15838
+ reissueEnrollment(identity) {
15839
+ this.dialogRef = this.dialogForm.open(ResetEnrollmentComponent, {
15840
+ data: {
15841
+ identity: identity,
15842
+ type: 'reissue',
15843
+ },
15844
+ autoFocus: false,
15845
+ });
15846
+ this.dialogRef.afterClosed().subscribe((result) => {
15847
+ if (result) {
15848
+ this.refreshData(undefined, true);
15849
+ }
15850
+ });
15851
+ }
15852
+ ngOnDestroy() {
15853
+ super.ngOnDestroy();
15854
+ }
15855
+ canDeactivate() {
15856
+ if (this.formDataChanged && this.svc.sideModalOpen) {
15857
+ return confirm('You have unsaved changes. Do you want to leave this page and discard your changes or stay on this page?');
15858
+ }
15859
+ return true;
15860
+ }
15861
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: IdentitiesListPageComponent, deps: [{ token: IdentitiesPageService }, { token: DataTableFilterService }, { token: i5.MatDialog }, { token: TabNameService }, { token: i4.Router }, { token: ConsoleEventsService }, { token: IDENTITY_EXTENSION_SERVICE }], target: i0.ɵɵFactoryTarget.Component }); }
15862
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: IdentitiesListPageComponent, isStandalone: false, selector: "lib-identities-list", usesInheritance: true, ngImport: i0, template: "<div class=\"ziti-page-container ziti-identities-container lt-skin-page\">\n <lib-list-table-header\n [title]=\"title\"\n subtitle=\"Manage identities across the network\"\n createLabel=\"Create Identity\"\n infoText=\"Identities are the clients, services, and routers enrolled in this network.\"\n [tabs]=\"tabs\"\n [showAdd]=\"!itemsSelected\"\n [blockCreate]=\"headerBlockCreate\"\n [blockDelete]=\"headerBlockDelete\"\n (actionClicked)=\"headerActionClicked($event)\"\n >\n <span header-icon class=\"icon-identity\" aria-hidden=\"true\"></span>\n </lib-list-table-header>\n <ng-content select=\"[slot=list-page-subheader]\"></ng-content>\n <lib-list-table class=\"lt-skin\" [tableId]=\"'identities'\"\n [noItemsReadonly]=\"headerBlockCreate\"\n [accessDenied]=\"headerBlockRead\"\n [rowData]=\"rowData\"\n [isLoading]=\"isLoading\"\n [columns]=\"columnDefs\"\n (actionRequested)=\"tableAction($event)\"\n [startCount]=\"startCount\"\n [endCount]=\"endCount\"\n [totalCount]=\"totalCount\"\n [filterApplied]=\"filterApplied\"\n [menuItems]=\"svc.menuItems\"\n [headerActions]=\"svc.tableHeaderActions\"\n [noItemsImage]=\"'No_Clients'\"\n (tableReady)=\"tableReady($event)\"\n >\n </lib-list-table>\n</div>\n<lib-side-modal [(open)]=\"svc.sideModalOpen\" [showClose]=\"false\">\n @if (svc.modalType === 'overrides' && svc.sideModalOpen) {\n <lib-overrides\n [identity]=\"svc.selectedIdentity\"\n (close)=\"closeModal($event)\"\n ></lib-overrides>\n }\n</lib-side-modal>\n@if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n}\n", styles: [".ziti-page-container .action{line-height:3.75rem;top:1.5625rem;right:1.5625rem;z-index:999}.ziti-identities-container ::ng-deep .zac-wrapper-container{height:0rem;left:-62499.9375rem;position:fixed}.ziti-identities-container ::ng-deep .zac-wrapper-container .action{display:none}.ziti-ag-grid{height:100%;position:relative;width:100%}.ziti-ag-grid .ziti-ag-grid-table{height:100%;width:100%}.ziti-ag-grid .ziti-ag-grid-table .ag-root-wrapper{border-bottom-style:none}.ziti-ag-grid .ziti-ag-grid-table .ag-cell{align-items:center;display:flex}.system-policies-toggle,.hidden-results-container{position:relative;right:0;display:flex;flex-direction:row;justify-content:center;align-items:flex-start;margin-top:.625rem;padding-left:1.25rem;border-radius:var(--inputBorderRadius);padding-bottom:0;padding-right:0;padding-top:0;flex-wrap:nowrap;flex:1 0 auto}.system-policies-toggle .toggle-text,.hidden-results-container .toggle-text{font-weight:600;font-size:.75rem}.system-policies-toggle .toggle-text a,.hidden-results-container .toggle-text a{cursor:pointer}.system-policies-toggle .toggle,.hidden-results-container .toggle{width:4.6875rem;margin-left:.625rem}.system-policies-toggle .toggle.on .switch,.hidden-results-container .toggle.on .switch{left:3.25rem}.system-policies-toggle .spinner,.hidden-results-container .spinner{display:inline-block;width:.625rem;height:.625rem;border-radius:50%;background:transparent;border-top:.125rem solid var(--tableText);border-right:.125rem solid var(--tableText);border-bottom:.125rem solid transparent;border-left:.125rem solid transparent;animation:loading .5s infinite linear;margin-left:.3125rem}.hidden-results-container.loading-results{opacity:.7}.nf-cell-vert-align,.ag-row .ag-cell{align-items:center;display:flex}.hidden-columns-list{align-items:center;display:flex;flex-direction:row;margin-left:.4375rem;min-height:1.875rem;flex-wrap:wrap;flex:0 1 auto}.hidden-columns-list.hidden-results{flex-wrap:nowrap;overflow:auto}.hidden-columns-list.hidden-results .hidden-column-item{flex:1 0 auto}.hidden-columns-list::-webkit-scrollbar{height:.875rem}.hidden-columns-list .hidden-column-item{align-items:center;background-color:var(--background);border:.125rem solid var(--background);border-radius:.75rem;cursor:pointer;color:var(----stroke);font-weight:600;font-family:Open Sans,sans-serif;display:flex;flex:0 1 auto;height:1.5625rem!important;margin-left:.625rem;margin-top:.3125rem;padding:.125rem .625rem .125rem .3125rem;position:relative;text-align:center;box-shadow:unset;transform:translateY(0)}.hidden-columns-list .ag-dnd-ghost:hover{border:.125rem solid var(--primaryColor)!important}.hidden-columns-list .ag-dnd-ghost:hover .ag-dnd-ghost-label,.hidden-columns-list .ag-dnd-ghost:hover .ag-dnd-ghost-icon .ag-icon-eye-slash{color:var(--ag-alpine-active-color, var(--primaryColor))}.hidden-columns-list .ag-dnd-ghost:hover .ag-dnd-ghost-icon .ag-icon-eye-slash:before{content:\"\\f113\"}.hidden-columns-list .ag-dnd-ghost-label{line-height:initial}::ng-deep .darkmode .ag-dnd-ghost-icon .ag-icon-eye-slash{color:#fff!important}.tMenu{transition-property:opacity;z-index:99999999999;height:fit-content}.tMenu.action-menu{position:fixed;width:10.9375rem}.tMenu .tActionRow{transition-property:opacity}.tMenu .tActionRow.has-icon{display:flex;flex-direction:row;align-items:center}.tMenu .tActionRow .v7Offline{background-color:var(--placeholder)}.tMenu .tActionRow .v7OnlineOffline{width:.875rem;height:.875rem;border-color:var(--placeholder)}.tMenu .tActionRow .v7OnlineOffline:hover{border-color:var(--placeholder)}.hidden-items-container{display:flex;flex-direction:row;width:100%;flex-wrap:nowrap;justify-content:space-between}.os-icon{width:1.4375rem;height:1.4375rem;background-size:1.4375rem 1.4375rem;display:inline-flex;margin-right:.5rem;margin-top:.3125rem;margin-bottom:.3125rem}::ng-deep .date-time-container{position:fixed}::ng-deep .date-time-container .p-calendar .p-inputtext{height:.0625rem;width:.0625rem;padding:0}::ng-deep .date-time-container .filter-button-container{height:3.125rem;display:flex;flex-direction:row;justify-content:center;align-items:center}::ng-deep .date-time-container .filter-button-container .toggleButton{padding-left:.3125rem;padding-right:.3125rem;width:unset}::ng-deep .date-time-container .p-calendar{height:1.875rem}::ng-deep .date-time-container .p-calendar .p-inputtext{height:1.875rem;border-top-left-radius:1.25rem;border-bottom-left-radius:1.25rem;text-indent:.3125rem}::ng-deep .date-time-container .p-calendar .p-button-icon-only{border-top-right-radius:1.25rem;border-bottom-right-radius:1.25rem}#TimeSearch{position:relative;float:left;display:inline-block;margin-left:.625rem;width:3.125rem;height:2.5rem;background-color:var(--primary)}#TimeSearch #TimeSearchList{width:2.75rem;height:0;position:absolute;top:0;left:-.125rem;overflow:hidden;transition:var(--transition);background-color:var(--menu);border-radius:.5rem;z-index:30}#TimeSearch #TimeSearchList .time{width:2.5rem;background-color:var(--background);padding-left:.5rem;padding-right:.5rem;border-radius:.5rem;line-height:1.625rem;font-size:.75rem;font-weight:700;text-align:center;margin-top:.125rem;margin-left:.125rem;left:-.125rem;top:-3.1875rem;cursor:pointer;transition:var(--transition)}#TimeSearch #TimeSearchList.open{top:-3.1875rem;width:2.75rem;height:14.125rem}#TimeSearch #TimeSelectButton{width:2.5rem;background-color:var(--background);padding-left:.5rem;padding-right:.5rem;border-radius:.5rem;line-height:1.625rem;text-align:center;font-size:.75rem;font-weight:700;margin-top:.4375rem;cursor:pointer;transition:var(--transition)}#TimeSearch #TimeSelectButton:hover,#TimeSearch #TimeSearchList .time:hover{background-color:var(--primaryColor);color:var(--white)}::ng-deep p-togglebutton .p-togglebutton.p-button{border-radius:var(--inputBorderRadius)!important;box-shadow:0 .3125rem 1.25rem #0000000d!important;margin-top:-.125rem!important}::ng-deep p-togglebutton .p-togglebutton.p-button:hover{filter:brightness(99%)!important;box-shadow:0 .3125rem 1.25rem #0000000d!important;transition:all .3s ease}::ng-deep p-togglebutton .p-togglebutton.p-button:active{filter:brightness(95%)!important;box-shadow:none!important;transform:translateY(.0625rem)!important;transition:all .3s ease}#TimeSearch #TimeSelectButton{margin-top:.25rem!important}::ng-deep .p-calendar .p-inputtext{height:2.3125rem}::ng-deep .p-inputtext{border-radius:var(--inputBorderRadius)}.calendar{display:inline-block;padding-left:.3125rem;float:left;padding-right:.625rem;margin-top:-.125rem}::ng-deep .p-datepicker:not(.p-datepicker-inline){background:var(--table);border:0 none;box-shadow:#091e4226 0 1.25rem 2rem -.5rem;border-radius:.75rem}::ng-deep .p-button.p-button-text{background-color:transparent;color:var(--primaryColor);border-color:transparent}::ng-deep .p-button.p-button-text:enabled:hover{background:var(--primaryColorShadowLight);color:var(--primaryColor);border-color:transparent}::ng-deep .p-datepicker table td>span.p-highlight{color:var(--tableText);background:var(--primaryColorShadowLight)}::ng-deep .p-inputtext:enabled:hover{border-color:var(--primaryColor)}::ng-deep .p-datepicker{padding:.5rem;padding-bottom:0;background:var(--table);color:var(--tableText);border:.0625rem solid var(--inputBorder);border-radius:.1875rem}::ng-deep .p-inputtext:enabled:focus{outline:0 none;outline-offset:0;box-shadow:0 0 0 1 var(--primaryColorShadowLight);border-color:var(--primaryColor)}::ng-deep .p-inputtext{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol;font-size:1rem;color:var(--tableText);background:var(--table);padding:.5rem;border:.0625rem solid var(--inputBorder);transition:background-color .2s,color .2s,border-color .2s,box-shadow .2s;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:var(--inputBorderRadius)}::ng-deep .p-datepicker:not(.p-datepicker-inline) .p-datepicker-header{background:var(--table)}::ng-deep .p-datepicker .p-datepicker-header{padding:.5rem;color:var(--tableText);background:var(--table);font-weight:600;margin:0;border-bottom:.0625rem solid var(--inputBorder);border-top-right-radius:.75rem;border-top-left-radius:.75rem}::ng-deep .p-datepicker:not(.p-disabled) table td span:not(.p-highlight):not(.p-disabled):hover{background:#e9ecef;color:var(--table)}::ng-deep .p-datepicker .p-timepicker{border-top:.0625rem solid var(--background);padding:.5rem}::ng-deep .p-togglebutton.p-button{background:var(--background);border:.0625rem solid var(--inputBorder);color:var(--tableText);transition:background-color .2s,color .2s,border-color .2s,box-shadow .2s}::ng-deep .p-datepicker .p-datepicker-buttonbar{padding:1rem 0;border-top:.0625rem solid var(--inputBorder)}::ng-deep .p-button{color:#fff;background:var(--primaryColor);border:.0625rem solid var(--primaryColor);padding:.495rem 1rem;font-size:1rem;transition:background-color .2s,color .2s,border-color .2s,box-shadow .2s;border-radius:var(--inputBorderRadius)}::ng-deep .p-togglebutton.p-button.p-highlight{background:var(--primaryColor);border:.0625rem solid var(--primaryColor);color:var(--background)}::ng-deep .p-togglebutton.p-button.p-highlight:hover{background:var(--primaryColor);border:.0625rem solid var(--primaryColor);color:var(--background)}app-zac-wrapper{z-index:999999}\n"], dependencies: [{ kind: "component", type: ListTableHeaderComponent, selector: "lib-list-table-header", inputs: ["title", "subtitle", "createLabel", "infoText", "tabs", "showAdd", "blockCreate", "blockDelete"], outputs: ["actionClicked"] }, { kind: "component", type: ListTableComponent, selector: "lib-list-table", inputs: ["tableId", "columns", "rowData", "isLoading", "options", "startCount", "endCount", "totalCount", "filterApplied", "menuItems", "headerActions", "noItemsImage", "showNoItemsAdd", "noItemsReadonly", "accessDenied", "entityTypeName", "filterName", "filterColumn"], outputs: ["actionRequested", "tableReady"] }, { kind: "component", type: SideModalComponent, selector: "lib-side-modal", inputs: ["showClose", "open"], outputs: ["openChange"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "lib-loading-indicator", inputs: ["isLoading"] }, { kind: "component", type: OverridesComponent, selector: "lib-overrides", inputs: ["identity"], outputs: ["close"] }] }); }
15863
+ }
15864
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: IdentitiesListPageComponent, decorators: [{
15865
+ type: Component,
15866
+ args: [{ selector: 'lib-identities-list', standalone: false, template: "<div class=\"ziti-page-container ziti-identities-container lt-skin-page\">\n <lib-list-table-header\n [title]=\"title\"\n subtitle=\"Manage identities across the network\"\n createLabel=\"Create Identity\"\n infoText=\"Identities are the clients, services, and routers enrolled in this network.\"\n [tabs]=\"tabs\"\n [showAdd]=\"!itemsSelected\"\n [blockCreate]=\"headerBlockCreate\"\n [blockDelete]=\"headerBlockDelete\"\n (actionClicked)=\"headerActionClicked($event)\"\n >\n <span header-icon class=\"icon-identity\" aria-hidden=\"true\"></span>\n </lib-list-table-header>\n <ng-content select=\"[slot=list-page-subheader]\"></ng-content>\n <lib-list-table class=\"lt-skin\" [tableId]=\"'identities'\"\n [noItemsReadonly]=\"headerBlockCreate\"\n [accessDenied]=\"headerBlockRead\"\n [rowData]=\"rowData\"\n [isLoading]=\"isLoading\"\n [columns]=\"columnDefs\"\n (actionRequested)=\"tableAction($event)\"\n [startCount]=\"startCount\"\n [endCount]=\"endCount\"\n [totalCount]=\"totalCount\"\n [filterApplied]=\"filterApplied\"\n [menuItems]=\"svc.menuItems\"\n [headerActions]=\"svc.tableHeaderActions\"\n [noItemsImage]=\"'No_Clients'\"\n (tableReady)=\"tableReady($event)\"\n >\n </lib-list-table>\n</div>\n<lib-side-modal [(open)]=\"svc.sideModalOpen\" [showClose]=\"false\">\n @if (svc.modalType === 'overrides' && svc.sideModalOpen) {\n <lib-overrides\n [identity]=\"svc.selectedIdentity\"\n (close)=\"closeModal($event)\"\n ></lib-overrides>\n }\n</lib-side-modal>\n@if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n}\n", styles: [".ziti-page-container .action{line-height:3.75rem;top:1.5625rem;right:1.5625rem;z-index:999}.ziti-identities-container ::ng-deep .zac-wrapper-container{height:0rem;left:-62499.9375rem;position:fixed}.ziti-identities-container ::ng-deep .zac-wrapper-container .action{display:none}.ziti-ag-grid{height:100%;position:relative;width:100%}.ziti-ag-grid .ziti-ag-grid-table{height:100%;width:100%}.ziti-ag-grid .ziti-ag-grid-table .ag-root-wrapper{border-bottom-style:none}.ziti-ag-grid .ziti-ag-grid-table .ag-cell{align-items:center;display:flex}.system-policies-toggle,.hidden-results-container{position:relative;right:0;display:flex;flex-direction:row;justify-content:center;align-items:flex-start;margin-top:.625rem;padding-left:1.25rem;border-radius:var(--inputBorderRadius);padding-bottom:0;padding-right:0;padding-top:0;flex-wrap:nowrap;flex:1 0 auto}.system-policies-toggle .toggle-text,.hidden-results-container .toggle-text{font-weight:600;font-size:.75rem}.system-policies-toggle .toggle-text a,.hidden-results-container .toggle-text a{cursor:pointer}.system-policies-toggle .toggle,.hidden-results-container .toggle{width:4.6875rem;margin-left:.625rem}.system-policies-toggle .toggle.on .switch,.hidden-results-container .toggle.on .switch{left:3.25rem}.system-policies-toggle .spinner,.hidden-results-container .spinner{display:inline-block;width:.625rem;height:.625rem;border-radius:50%;background:transparent;border-top:.125rem solid var(--tableText);border-right:.125rem solid var(--tableText);border-bottom:.125rem solid transparent;border-left:.125rem solid transparent;animation:loading .5s infinite linear;margin-left:.3125rem}.hidden-results-container.loading-results{opacity:.7}.nf-cell-vert-align,.ag-row .ag-cell{align-items:center;display:flex}.hidden-columns-list{align-items:center;display:flex;flex-direction:row;margin-left:.4375rem;min-height:1.875rem;flex-wrap:wrap;flex:0 1 auto}.hidden-columns-list.hidden-results{flex-wrap:nowrap;overflow:auto}.hidden-columns-list.hidden-results .hidden-column-item{flex:1 0 auto}.hidden-columns-list::-webkit-scrollbar{height:.875rem}.hidden-columns-list .hidden-column-item{align-items:center;background-color:var(--background);border:.125rem solid var(--background);border-radius:.75rem;cursor:pointer;color:var(----stroke);font-weight:600;font-family:Open Sans,sans-serif;display:flex;flex:0 1 auto;height:1.5625rem!important;margin-left:.625rem;margin-top:.3125rem;padding:.125rem .625rem .125rem .3125rem;position:relative;text-align:center;box-shadow:unset;transform:translateY(0)}.hidden-columns-list .ag-dnd-ghost:hover{border:.125rem solid var(--primaryColor)!important}.hidden-columns-list .ag-dnd-ghost:hover .ag-dnd-ghost-label,.hidden-columns-list .ag-dnd-ghost:hover .ag-dnd-ghost-icon .ag-icon-eye-slash{color:var(--ag-alpine-active-color, var(--primaryColor))}.hidden-columns-list .ag-dnd-ghost:hover .ag-dnd-ghost-icon .ag-icon-eye-slash:before{content:\"\\f113\"}.hidden-columns-list .ag-dnd-ghost-label{line-height:initial}::ng-deep .darkmode .ag-dnd-ghost-icon .ag-icon-eye-slash{color:#fff!important}.tMenu{transition-property:opacity;z-index:99999999999;height:fit-content}.tMenu.action-menu{position:fixed;width:10.9375rem}.tMenu .tActionRow{transition-property:opacity}.tMenu .tActionRow.has-icon{display:flex;flex-direction:row;align-items:center}.tMenu .tActionRow .v7Offline{background-color:var(--placeholder)}.tMenu .tActionRow .v7OnlineOffline{width:.875rem;height:.875rem;border-color:var(--placeholder)}.tMenu .tActionRow .v7OnlineOffline:hover{border-color:var(--placeholder)}.hidden-items-container{display:flex;flex-direction:row;width:100%;flex-wrap:nowrap;justify-content:space-between}.os-icon{width:1.4375rem;height:1.4375rem;background-size:1.4375rem 1.4375rem;display:inline-flex;margin-right:.5rem;margin-top:.3125rem;margin-bottom:.3125rem}::ng-deep .date-time-container{position:fixed}::ng-deep .date-time-container .p-calendar .p-inputtext{height:.0625rem;width:.0625rem;padding:0}::ng-deep .date-time-container .filter-button-container{height:3.125rem;display:flex;flex-direction:row;justify-content:center;align-items:center}::ng-deep .date-time-container .filter-button-container .toggleButton{padding-left:.3125rem;padding-right:.3125rem;width:unset}::ng-deep .date-time-container .p-calendar{height:1.875rem}::ng-deep .date-time-container .p-calendar .p-inputtext{height:1.875rem;border-top-left-radius:1.25rem;border-bottom-left-radius:1.25rem;text-indent:.3125rem}::ng-deep .date-time-container .p-calendar .p-button-icon-only{border-top-right-radius:1.25rem;border-bottom-right-radius:1.25rem}#TimeSearch{position:relative;float:left;display:inline-block;margin-left:.625rem;width:3.125rem;height:2.5rem;background-color:var(--primary)}#TimeSearch #TimeSearchList{width:2.75rem;height:0;position:absolute;top:0;left:-.125rem;overflow:hidden;transition:var(--transition);background-color:var(--menu);border-radius:.5rem;z-index:30}#TimeSearch #TimeSearchList .time{width:2.5rem;background-color:var(--background);padding-left:.5rem;padding-right:.5rem;border-radius:.5rem;line-height:1.625rem;font-size:.75rem;font-weight:700;text-align:center;margin-top:.125rem;margin-left:.125rem;left:-.125rem;top:-3.1875rem;cursor:pointer;transition:var(--transition)}#TimeSearch #TimeSearchList.open{top:-3.1875rem;width:2.75rem;height:14.125rem}#TimeSearch #TimeSelectButton{width:2.5rem;background-color:var(--background);padding-left:.5rem;padding-right:.5rem;border-radius:.5rem;line-height:1.625rem;text-align:center;font-size:.75rem;font-weight:700;margin-top:.4375rem;cursor:pointer;transition:var(--transition)}#TimeSearch #TimeSelectButton:hover,#TimeSearch #TimeSearchList .time:hover{background-color:var(--primaryColor);color:var(--white)}::ng-deep p-togglebutton .p-togglebutton.p-button{border-radius:var(--inputBorderRadius)!important;box-shadow:0 .3125rem 1.25rem #0000000d!important;margin-top:-.125rem!important}::ng-deep p-togglebutton .p-togglebutton.p-button:hover{filter:brightness(99%)!important;box-shadow:0 .3125rem 1.25rem #0000000d!important;transition:all .3s ease}::ng-deep p-togglebutton .p-togglebutton.p-button:active{filter:brightness(95%)!important;box-shadow:none!important;transform:translateY(.0625rem)!important;transition:all .3s ease}#TimeSearch #TimeSelectButton{margin-top:.25rem!important}::ng-deep .p-calendar .p-inputtext{height:2.3125rem}::ng-deep .p-inputtext{border-radius:var(--inputBorderRadius)}.calendar{display:inline-block;padding-left:.3125rem;float:left;padding-right:.625rem;margin-top:-.125rem}::ng-deep .p-datepicker:not(.p-datepicker-inline){background:var(--table);border:0 none;box-shadow:#091e4226 0 1.25rem 2rem -.5rem;border-radius:.75rem}::ng-deep .p-button.p-button-text{background-color:transparent;color:var(--primaryColor);border-color:transparent}::ng-deep .p-button.p-button-text:enabled:hover{background:var(--primaryColorShadowLight);color:var(--primaryColor);border-color:transparent}::ng-deep .p-datepicker table td>span.p-highlight{color:var(--tableText);background:var(--primaryColorShadowLight)}::ng-deep .p-inputtext:enabled:hover{border-color:var(--primaryColor)}::ng-deep .p-datepicker{padding:.5rem;padding-bottom:0;background:var(--table);color:var(--tableText);border:.0625rem solid var(--inputBorder);border-radius:.1875rem}::ng-deep .p-inputtext:enabled:focus{outline:0 none;outline-offset:0;box-shadow:0 0 0 1 var(--primaryColorShadowLight);border-color:var(--primaryColor)}::ng-deep .p-inputtext{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\",Segoe UI Symbol;font-size:1rem;color:var(--tableText);background:var(--table);padding:.5rem;border:.0625rem solid var(--inputBorder);transition:background-color .2s,color .2s,border-color .2s,box-shadow .2s;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:var(--inputBorderRadius)}::ng-deep .p-datepicker:not(.p-datepicker-inline) .p-datepicker-header{background:var(--table)}::ng-deep .p-datepicker .p-datepicker-header{padding:.5rem;color:var(--tableText);background:var(--table);font-weight:600;margin:0;border-bottom:.0625rem solid var(--inputBorder);border-top-right-radius:.75rem;border-top-left-radius:.75rem}::ng-deep .p-datepicker:not(.p-disabled) table td span:not(.p-highlight):not(.p-disabled):hover{background:#e9ecef;color:var(--table)}::ng-deep .p-datepicker .p-timepicker{border-top:.0625rem solid var(--background);padding:.5rem}::ng-deep .p-togglebutton.p-button{background:var(--background);border:.0625rem solid var(--inputBorder);color:var(--tableText);transition:background-color .2s,color .2s,border-color .2s,box-shadow .2s}::ng-deep .p-datepicker .p-datepicker-buttonbar{padding:1rem 0;border-top:.0625rem solid var(--inputBorder)}::ng-deep .p-button{color:#fff;background:var(--primaryColor);border:.0625rem solid var(--primaryColor);padding:.495rem 1rem;font-size:1rem;transition:background-color .2s,color .2s,border-color .2s,box-shadow .2s;border-radius:var(--inputBorderRadius)}::ng-deep .p-togglebutton.p-button.p-highlight{background:var(--primaryColor);border:.0625rem solid var(--primaryColor);color:var(--background)}::ng-deep .p-togglebutton.p-button.p-highlight:hover{background:var(--primaryColor);border:.0625rem solid var(--primaryColor);color:var(--background)}app-zac-wrapper{z-index:999999}\n"] }]
15867
+ }], ctorParameters: () => [{ type: IdentitiesPageService }, { type: DataTableFilterService }, { type: i5.MatDialog }, { type: TabNameService }, { type: i4.Router }, { type: ConsoleEventsService }, { type: undefined, decorators: [{
15868
+ type: Inject,
15869
+ args: [IDENTITY_EXTENSION_SERVICE]
15870
+ }] }] });
15871
+
13516
15872
  class EdgeRoutersPageComponent extends ListPageComponent {
13517
15873
  constructor(svc, filterService, dialogForm, tabNames, consoleEvents, extService) {
13518
15874
  super(filterService, svc, consoleEvents, dialogForm);
@@ -19888,13 +22244,16 @@ class EdgeRouterFormComponent extends ProjectableForm {
19888
22244
  get tunnelerEnabledReadOnly() {
19889
22245
  return this.config.isOpenZiti ? true : !this.isTunnelerDisabled();
19890
22246
  }
22247
+ get isCustomerHosted() {
22248
+ return this.extService.disabledComponents.some(item => item.key === 'customer-hosted');
22249
+ }
19891
22250
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: EdgeRouterFormComponent, deps: [{ token: SETTINGS_SERVICE }, { token: EdgeRouterFormService }, { token: ZITI_DATA_SERVICE }, { token: GrowlerService }, { token: EDGE_ROUTER_EXTENSION_SERVICE }, { token: DEFAULT_APP_CONFIG }, { token: i4.Router }, { token: i4.ActivatedRoute }, { token: i1$1.Location }, { token: i5.MatDialog }], target: i0.ɵɵFactoryTarget.Component }); }
19892
22251
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: EdgeRouterFormComponent, isStandalone: false, selector: "lib-edge-router-form", inputs: { edgeRouterRoleAttributes: "edgeRouterRoleAttributes" }, outputs: { close: "close" }, providers: [
19893
22252
  {
19894
22253
  provide: MatDialogRef,
19895
22254
  useValue: {}
19896
22255
  }
19897
- ], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"projectable-form-wrapper\"\n (keyup.enter)=\"save($event)\"\n (keyup.escape)=\"closeModal(false)\"\n [class.projectable-form-hidden]=\"!dataInit\"\n >\n <lib-form-header\n [data]=\"formData\"\n [title]=\"formData.id ? 'Edit Edge Router: ' : 'Create New Edge Router'\"\n [moreActions]=\"moreActions\"\n [multiActions]=\"multiActions\"\n (actionRequested)=\"headerActionRequested($event)\"\n (multiActionRequested)=\"multiActionRequested($event)\"\n [(formView)]=\"formView\"\n [class.is-scrolled]=\"isScrolled\"\n [saveDisabled]=\"formPermissionSaveDisabled\"\n [saveTooltip]=\"formPermissionSaveTooltip\"\n [strictReadonlyUi]=\"useStrictReadonlyForm\"\n ></lib-form-header>\n <ng-content select=\"[slot=tabs-slot]\"></ng-content>\n <div class=\"edge-router-form-container projectable-form-container\" #formContainer>\n @if (formView === 'simple') {\n <div class=\"projectable-form-main-column form-group-row\">\n <div class=\"form-group-column three-fifths\">\n <lib-form-field-container [title]=\"'Edge Router Name'\" [label]=\"useStrictReadonlyForm ? '' : 'REQUIRED'\">\n <input\n class=\"form-field-input\"\n placeholder=\"Name this edge router\"\n [ngClass]=\"{error: errors['name']}\"\n [(ngModel)]=\"formData.name\"\n autofocus\n #nameFieldInput\n [disabled]=\"useStrictReadonlyForm\"\n />\n </lib-form-field-container>\n <lib-form-field-container\n [title]=\"'Select or create Edge Router attributes'\"\n [label]=\"'OPTIONAL'\"\n [contentStyle]=\"'z-index: 99999999'\"\n [helpText]=\"'Attributes are tags applied to an resource. Apply the same tag to other Edge Routers to form a group of Edge Routers.'\"\n >\n <lib-tag-selector\n [disableField]=\"useStrictReadonlyForm\"\n [disableCreate]=\"useStrictReadonlyForm\"\n [(selectedRoleAttributes)]=\"formData.roleAttributes\"\n [availableRoleAttributes]=\"edgeRouterRoleAttributes\"\n [placeholder]=\"'Add attributes to group Edge Routers'\"\n ></lib-tag-selector>\n </lib-form-field-container>\n <ng-content select=\"[slot=column-1-slot-1]\"></ng-content>\n <lib-form-field-toggle [(toggleOn)]=\"showMore\" style=\"margin: 0px 10px\"></lib-form-field-toggle>\n @if (showMore) {\n <div class=\"form-group-column\">\n <lib-form-field-container\n [title]=\"'TUNNELER'\"\n [title2]=\"'TRAVERSAL'\"\n [layout]=\"'row'\"\n class=\"form-field-advanced\"\n >\n <div class=\"config-item\" [class.disabled]=\"!tunnelerEnabledReadOnly\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">TUNNELER ENABLED</div>\n <div (click)=\"tunnelerEnabledReadOnly && !useStrictReadonlyForm && toggleTunnelerEnabled()\"\n [ngClass]=\"{ on: formData.isTunnelerEnabled, disabled: !tunnelerEnabledReadOnly, 'toggle-readonly': useStrictReadonlyForm }\" class=\"toggle\"\n [matTooltip]=\"useStrictReadonlyForm ? '' : (!tunnelerEnabledReadOnly ? 'Disabled because already hosted by netfoundry ' : '')\"\n matTooltipPosition=\"above\" style=\"pointer-events: auto;\">\n <span [hidden]=\"!formData.isTunnelerEnabled\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.isTunnelerEnabled\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">ALLOW TRAVERSAL</div>\n <div\n (click)=\"!useStrictReadonlyForm && toggleNoTraversal()\"\n [ngClass]=\"{ on: !formData.noTraversal, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"formData.noTraversal\" class=\"on-label\">YES</span>\n <span [hidden]=\"!formData.noTraversal\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n @if (formData?.hostname || formData?.supportedProtocols?.tls || formData?.supportedProtocols?.wss) {\n <lib-form-field-container [showHeader]=\"false\">\n @if (formData?.hostname) {\n <div class=\"config-item\">\n <div class=\"config-item-label-container\">\n <span class=\"config-title\">Domain Name</span>\n </div>\n <div class=\"config-container domain-container\">\n <div class=\"config-container-label domain-label\">\n {{ formData?.hostname }}\n </div>\n </div>\n </div>\n }\n @if (formData?.supportedProtocols?.tls || formData?.supportedProtocols?.wss) {\n <div class=\"config-item\">\n <div class=\"config-item-label-container\">\n <span class=\"config-title\">ADVERTISE ADDRESS</span>\n </div>\n <div class=\"config-container advertise-address-container\">\n <div class=\"config-container-label advertise-address-label\">\n {{ formData?.supportedProtocols?.tls }}\n <br />\n {{ formData?.supportedProtocols?.wss }}\n </div>\n </div>\n </div>\n }\n </lib-form-field-container>\n }\n <lib-form-field-container\n [title]=\"'Custom Tags'\"\n [label]=\"'OPTIONAL'\"\n class=\"form-field-advanced\"\n >\n <lib-custom-tags [(tags)]=\"formData.tags\" [readOnly]=\"useStrictReadonlyForm\"></lib-custom-tags>\n </lib-form-field-container>\n <lib-form-field-container\n [title]=\"'App Data'\"\n [label]=\"'OPTIONAL'\"\n class=\"form-field-advanced\"\n >\n <lib-json-view [(data)]=\"formData.appData\" [readOnly]=\"useStrictReadonlyForm\"></lib-json-view>\n </lib-form-field-container>\n </div>\n }\n </div>\n <div class=\"form-group-column two-fifths\">\n <ng-content select=\"[slot=column-2-slot-1]\"></ng-content>\n @if (hasEnrolmentToken && !extService['hideZitiRegistration']) {\n <lib-form-field-container\n [showHeader]=\"false\"\n >\n <lib-qr-code\n [identity]=\"formData\"\n [jwt]=\"formData.enrollmentJwt\"\n [token]=\"formData.enrollmentToken\"\n [expiration]=\"formData.enrollmentExpiresAt\"\n (doRefresh)=\"refreshEdgeRouter()\"\n [type]=\"'router'\"\n ></lib-qr-code>\n </lib-form-field-container>\n }\n @if (formData.id) {\n <lib-form-field-container\n [title]=\"'Associated Services'\"\n [count]=\"svc.associatedServices.length\"\n [class]=\"'hide-overflow'\"\n [helpText]=\"'Preview list of all services that this Edge Router is associated with.'\"\n >\n <lib-preview-list\n [clickable]=\"false\"\n [isLoading]=\"servicesLoading\"\n [allNames]=\"svc.associatedServiceNames\"\n ></lib-preview-list>\n </lib-form-field-container>\n }\n @if (formData.id) {\n <lib-form-field-container\n [title]=\"'Associated Identities'\"\n [count]=\"svc.associatedIdentities.length\"\n [helpText]=\"'Preview list of all Identities that this Edge Router is associated with.'\"\n >\n <lib-preview-list\n [clickable]=\"false\"\n [isLoading]=\"identitiesLoading\"\n [allNames]=\"svc.associatedIdentityNames\"\n ></lib-preview-list>\n </lib-form-field-container>\n }\n @if (showApiCalls) {\n <lib-form-field-container\n [title]=\"'API Calls'\"\n [label]=\"'OPTIONAL'\"\n >\n <div class=\"form-row\">\n <input class=\"form-field-input\" [value]=\"apiCallURL\"/>\n <div class=\"icon-copy copy-text\" (click)=\"copyToClipboard(apiCallURL)\"></div>\n </div>\n @if (formData) {\n <lib-json-view [(data)]=\"apiData\" [readOnly]=\"true\" [showCopy]=\"true\"></lib-json-view>\n }\n </lib-form-field-container>\n }\n </div>\n </div>\n }\n @if (formView === 'raw') {\n <div class=\"form-group-column\">\n @if (formData) {\n <lib-json-view [(data)]=\"formData\" [readOnly]=\"useStrictReadonlyForm\"></lib-json-view>\n }\n </div>\n }\n </div>\n</div>\n@if (isLoading || !dataInit) {\n <lib-loading-indicator [isLoading]=\"isLoading || !dataInit\"></lib-loading-indicator>\n}\n", styles: [".edge-router-form-container{display:flex;flex-direction:column;height:100%;width:100%;align-items:center;gap:.9375rem}.form-field-dropdown{background-image:url(/assets/svgs/ArrowDown.svg);background-position:center right .625rem;background-repeat:no-repeat;background-size:1.125rem;padding-right:1.75rem}.disabled{opacity:.5;pointer-events:none}.toggle.disabled{cursor:not-allowed}::ng-deep .admin-toggle{width:100%;width:35%;display:flex}::ng-deep .admin-toggle .form-field-line{display:none}::ng-deep .admin-toggle .toggle{width:100%}::ng-deep .is-admin-container .form-field-title-container.title-container-2{width:35%!important}.svc-test-result{position:absolute;transition:var(--transition);height:0;width:100%;background-color:var(--background);bottom:0;font-weight:600;padding-left:.625rem;padding-right:.625rem;border-color:var(--inputBorder);border-style:solid;border-width:var(--inputBorderWidth);border-bottom-right-radius:var(--inputBorderRadius);border-bottom-left-radius:var(--inputBorderRadius);border-top-style:none}.svc-test-result.test-result-open{height:fit-content;box-shadow:0 -.125rem .6875rem 0 var(--boxShadow);padding-bottom:.9375rem;padding-top:.9375rem}.svc-test-result .close.icon-close{top:.625rem;right:.625rem;font-size:1.125rem;height:1.125rem;width:1.125rem;line-height:1.125rem}::ng-deep .admin-toggle .form-field-toggle-container:focus .form-field-label{color:var(--primaryColor)}::ng-deep .admin-toggle .form-field-toggle-container:focus .form-field-line{border-color:var(--primaryColor)}::ng-deep .admin-toggle .form-field-toggle-container:focus .toggle{border-color:var(--primaryColor);border-style:solid;border-width:var(--inputBorderWidth)}::ng-deep .admin-toggle .form-field-toggle-container:focus .toggle .switch{top:.0625rem}.config-container-label.domain-label,.config-container-label.advertise-address-label{text-transform:lowercase}.config-container.domain-container,.config-container.advertise-address-container{background-color:var(--stroke)}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.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: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: FormHeaderComponent, selector: "lib-form-header", inputs: ["data", "badges", "title", "moreActions", "formView", "saveDisabled", "saveTooltip", "actionButtonText", "moreActionsText", "showHeaderToggle", "showHeaderButton", "strictReadonlyUi", "multiActions"], outputs: ["formViewChange", "actionRequested", "multiActionRequested"] }, { kind: "component", type: FormFieldContainerComponent, selector: "lib-form-field-container", inputs: ["title", "title2", "titleIcon", "layout", "helpText", "helpText2", "label", "count", "action", "actionLabel", "class", "contentStyle", "contentClass", "showHeader", "showToggle", "headerActions", "headerToggle"], outputs: ["actionRequested", "headerToggleChange", "helpClicked"] }, { kind: "component", type: PreviewListComponent, selector: "lib-preview-list", inputs: ["label", "clickable", "isLoading", "allNames", "items", "allowRemove", "tooltip", "showfilter", "localFilter"], outputs: ["itemSelected", "itemRemoved", "filterChanged"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "lib-loading-indicator", inputs: ["isLoading"] }, { kind: "component", type: FormFieldToggleComponent, selector: "lib-form-field-toggle", inputs: ["label", "orientation", "labelOn", "labelOff", "toggleOn"], outputs: ["toggleOnChange"] }, { kind: "component", type: JsonViewComponent, selector: "lib-json-view", inputs: ["data", "schema", "readOnly", "jsonInvalid", "showCopy", "showLineNumbers"], outputs: ["dataChange", "jsonChange", "jsonInvalidChange"] }, { kind: "component", type: TagSelectorComponent, selector: "lib-tag-selector", inputs: ["placeholder", "disableField", "disableCreate", "hideOption", "isLoading", "errorMessage", "focusOnInit", "namedAttributesServerFiltered", "selectedRoleAttributes", "selectedNamedAttributes", "hasError", "availableRoleAttributes", "availableNamedAttributes"], outputs: ["addRoleAttribute", "addNamedAttribute", "roleAttributeRemoved", "namedAttributeRemoved", "selectedRoleAttributesChange", "selectedNamedAttributesChange", "filterChange"] }, { kind: "component", type: QrCodeComponent, selector: "lib-qr-code", inputs: ["jwt", "token", "expiration", "identity", "type", "authenticators", "qrOnly", "canExpand"], outputs: ["doRefresh"] }, { kind: "component", type: CustomTagsComponent, selector: "lib-custom-tags", inputs: ["tags", "readOnly"], outputs: ["tagsChange"] }] }); }
22256
+ ], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<div class=\"projectable-form-wrapper\"\n (keyup.enter)=\"save($event)\"\n (keyup.escape)=\"closeModal(false)\"\n [class.projectable-form-hidden]=\"!dataInit\"\n >\n <lib-form-header\n [data]=\"formData\"\n [title]=\"formData.id ? 'Edit Edge Router: ' : 'Create New Edge Router'\"\n [moreActions]=\"moreActions\"\n [multiActions]=\"multiActions\"\n (actionRequested)=\"headerActionRequested($event)\"\n (multiActionRequested)=\"multiActionRequested($event)\"\n [(formView)]=\"formView\"\n [class.is-scrolled]=\"isScrolled\"\n [saveDisabled]=\"formPermissionSaveDisabled\"\n [saveTooltip]=\"formPermissionSaveTooltip\"\n [strictReadonlyUi]=\"useStrictReadonlyForm\"\n ></lib-form-header>\n <ng-content select=\"[slot=tabs-slot]\"></ng-content>\n <div class=\"edge-router-form-container projectable-form-container\" #formContainer>\n @if (formView === 'simple') {\n <div class=\"projectable-form-main-column form-group-row\">\n <div class=\"form-group-column three-fifths\">\n <lib-form-field-container [title]=\"'Edge Router Name'\" [label]=\"useStrictReadonlyForm ? '' : 'REQUIRED'\">\n <input\n class=\"form-field-input\"\n placeholder=\"Name this edge router\"\n [ngClass]=\"{error: errors['name']}\"\n [(ngModel)]=\"formData.name\"\n autofocus\n #nameFieldInput\n [disabled]=\"useStrictReadonlyForm\"\n />\n </lib-form-field-container>\n <lib-form-field-container\n [title]=\"'Select or create Edge Router attributes'\"\n [label]=\"'OPTIONAL'\"\n [contentStyle]=\"'z-index: 99999999'\"\n [helpText]=\"'Attributes are tags applied to an resource. Apply the same tag to other Edge Routers to form a group of Edge Routers.'\"\n >\n <lib-tag-selector\n [disableField]=\"useStrictReadonlyForm\"\n [disableCreate]=\"useStrictReadonlyForm\"\n [(selectedRoleAttributes)]=\"formData.roleAttributes\"\n [availableRoleAttributes]=\"edgeRouterRoleAttributes\"\n [placeholder]=\"'Add attributes to group Edge Routers'\"\n ></lib-tag-selector>\n </lib-form-field-container>\n <ng-content select=\"[slot=column-1-slot-1]\"></ng-content>\n <lib-form-field-toggle [(toggleOn)]=\"showMore\" style=\"margin: 0px 10px\"></lib-form-field-toggle>\n @if (showMore) {\n <div class=\"form-group-column\">\n <lib-form-field-container\n [title]=\"'TUNNELER'\"\n [title2]=\"'TRAVERSAL'\"\n [layout]=\"'row'\"\n class=\"form-field-advanced\"\n >\n <div class=\"config-item\" [class.disabled]=\"!tunnelerEnabledReadOnly\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">TUNNELER ENABLED</div>\n <div (click)=\"tunnelerEnabledReadOnly && !useStrictReadonlyForm && toggleTunnelerEnabled()\"\n [ngClass]=\"{ on: formData.isTunnelerEnabled, disabled: !tunnelerEnabledReadOnly, 'toggle-readonly': useStrictReadonlyForm }\" class=\"toggle\"\n [matTooltip]=\"useStrictReadonlyForm ? '' : (!tunnelerEnabledReadOnly ? 'Disabled because already hosted by netfoundry ' : '')\"\n matTooltipPosition=\"above\" style=\"pointer-events: auto;\">\n <span [hidden]=\"!formData.isTunnelerEnabled\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.isTunnelerEnabled\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">ALLOW TRAVERSAL</div>\n <div\n (click)=\"!useStrictReadonlyForm && toggleNoTraversal()\"\n [ngClass]=\"{ on: !formData.noTraversal, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"formData.noTraversal\" class=\"on-label\">YES</span>\n <span [hidden]=\"!formData.noTraversal\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n @if (isCustomerHosted) {\n <lib-form-field-container\n [title]=\"'LINK LISTENER'\"\n class=\"form-field-advanced\"\n >\n <div class=\"config-item disabled\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">ENABLE LINK LISTENER</div>\n <div\n [ngClass]=\"{ on: formData.linkListener, disabled: true }\"\n class=\"toggle\"\n role=\"switch\"\n aria-disabled=\"true\"\n [attr.aria-checked]=\"!!formData.linkListener\"\n matTooltip=\"This value is reported by the router and can't be changed here.\"\n matTooltipPosition=\"above\"\n >\n <span [hidden]=\"!formData.linkListener\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.linkListener\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n }\n @if (formData?.hostname || formData?.supportedProtocols?.tls || formData?.supportedProtocols?.wss) {\n <lib-form-field-container [showHeader]=\"false\">\n @if (formData?.hostname) {\n <div class=\"config-item\">\n <div class=\"config-item-label-container\">\n <span class=\"config-title\">Domain Name</span>\n </div>\n <div class=\"config-container domain-container\">\n <div class=\"config-container-label domain-label\">\n {{ formData?.hostname }}\n </div>\n </div>\n </div>\n }\n @if (formData?.supportedProtocols?.tls || formData?.supportedProtocols?.wss) {\n <div class=\"config-item\">\n <div class=\"config-item-label-container\">\n <span class=\"config-title\">ADVERTISE ADDRESS</span>\n </div>\n <div class=\"config-container advertise-address-container\">\n <div class=\"config-container-label advertise-address-label\">\n {{ formData?.supportedProtocols?.tls }}\n <br />\n {{ formData?.supportedProtocols?.wss }}\n </div>\n </div>\n </div>\n }\n </lib-form-field-container>\n }\n <lib-form-field-container\n [title]=\"'Custom Tags'\"\n [label]=\"'OPTIONAL'\"\n class=\"form-field-advanced\"\n >\n <lib-custom-tags [(tags)]=\"formData.tags\" [readOnly]=\"useStrictReadonlyForm\"></lib-custom-tags>\n </lib-form-field-container>\n <lib-form-field-container\n [title]=\"'App Data'\"\n [label]=\"'OPTIONAL'\"\n class=\"form-field-advanced\"\n >\n <lib-json-view [(data)]=\"formData.appData\" [readOnly]=\"useStrictReadonlyForm\"></lib-json-view>\n </lib-form-field-container>\n </div>\n }\n </div>\n <div class=\"form-group-column two-fifths\">\n <ng-content select=\"[slot=column-2-slot-1]\"></ng-content>\n @if (hasEnrolmentToken && !extService['hideZitiRegistration']) {\n <lib-form-field-container\n [showHeader]=\"false\"\n >\n <lib-qr-code\n [identity]=\"formData\"\n [jwt]=\"formData.enrollmentJwt\"\n [token]=\"formData.enrollmentToken\"\n [expiration]=\"formData.enrollmentExpiresAt\"\n (doRefresh)=\"refreshEdgeRouter()\"\n [type]=\"'router'\"\n ></lib-qr-code>\n </lib-form-field-container>\n }\n @if (formData.id) {\n <lib-form-field-container\n [title]=\"'Associated Services'\"\n [count]=\"svc.associatedServices.length\"\n [class]=\"'hide-overflow'\"\n [helpText]=\"'Preview list of all services that this Edge Router is associated with.'\"\n >\n <lib-preview-list\n [clickable]=\"false\"\n [isLoading]=\"servicesLoading\"\n [allNames]=\"svc.associatedServiceNames\"\n ></lib-preview-list>\n </lib-form-field-container>\n }\n @if (formData.id) {\n <lib-form-field-container\n [title]=\"'Associated Identities'\"\n [count]=\"svc.associatedIdentities.length\"\n [helpText]=\"'Preview list of all Identities that this Edge Router is associated with.'\"\n >\n <lib-preview-list\n [clickable]=\"false\"\n [isLoading]=\"identitiesLoading\"\n [allNames]=\"svc.associatedIdentityNames\"\n ></lib-preview-list>\n </lib-form-field-container>\n }\n @if (showApiCalls) {\n <lib-form-field-container\n [title]=\"'API Calls'\"\n [label]=\"'OPTIONAL'\"\n >\n <div class=\"form-row\">\n <input class=\"form-field-input\" [value]=\"apiCallURL\"/>\n <div class=\"icon-copy copy-text\" (click)=\"copyToClipboard(apiCallURL)\"></div>\n </div>\n @if (formData) {\n <lib-json-view [(data)]=\"apiData\" [readOnly]=\"true\" [showCopy]=\"true\"></lib-json-view>\n }\n </lib-form-field-container>\n }\n </div>\n </div>\n }\n @if (formView === 'raw') {\n <div class=\"form-group-column\">\n @if (formData) {\n <lib-json-view [(data)]=\"formData\" [readOnly]=\"useStrictReadonlyForm\"></lib-json-view>\n }\n </div>\n }\n </div>\n</div>\n@if (isLoading || !dataInit) {\n <lib-loading-indicator [isLoading]=\"isLoading || !dataInit\"></lib-loading-indicator>\n}\n", styles: [".edge-router-form-container{display:flex;flex-direction:column;height:100%;width:100%;align-items:center;gap:.9375rem}.form-field-dropdown{background-image:url(/assets/svgs/ArrowDown.svg);background-position:center right .625rem;background-repeat:no-repeat;background-size:1.125rem;padding-right:1.75rem}.disabled{opacity:.5;pointer-events:none}.toggle.disabled{cursor:not-allowed}::ng-deep .admin-toggle{width:100%;width:35%;display:flex}::ng-deep .admin-toggle .form-field-line{display:none}::ng-deep .admin-toggle .toggle{width:100%}::ng-deep .is-admin-container .form-field-title-container.title-container-2{width:35%!important}.svc-test-result{position:absolute;transition:var(--transition);height:0;width:100%;background-color:var(--background);bottom:0;font-weight:600;padding-left:.625rem;padding-right:.625rem;border-color:var(--inputBorder);border-style:solid;border-width:var(--inputBorderWidth);border-bottom-right-radius:var(--inputBorderRadius);border-bottom-left-radius:var(--inputBorderRadius);border-top-style:none}.svc-test-result.test-result-open{height:fit-content;box-shadow:0 -.125rem .6875rem 0 var(--boxShadow);padding-bottom:.9375rem;padding-top:.9375rem}.svc-test-result .close.icon-close{top:.625rem;right:.625rem;font-size:1.125rem;height:1.125rem;width:1.125rem;line-height:1.125rem}::ng-deep .admin-toggle .form-field-toggle-container:focus .form-field-label{color:var(--primaryColor)}::ng-deep .admin-toggle .form-field-toggle-container:focus .form-field-line{border-color:var(--primaryColor)}::ng-deep .admin-toggle .form-field-toggle-container:focus .toggle{border-color:var(--primaryColor);border-style:solid;border-width:var(--inputBorderWidth)}::ng-deep .admin-toggle .form-field-toggle-container:focus .toggle .switch{top:.0625rem}.config-container-label.domain-label,.config-container-label.advertise-address-label{text-transform:lowercase}.config-container-label.advertise-address-label{height:auto;padding:.125rem 0}.config-container.domain-container,.config-container.advertise-address-container{background-color:var(--stroke)}.config-container.advertise-address-container{height:auto;min-height:var(--defaultInputHeight);padding:0rem 1.25rem}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.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: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: FormHeaderComponent, selector: "lib-form-header", inputs: ["data", "badges", "title", "moreActions", "formView", "saveDisabled", "saveTooltip", "actionButtonText", "moreActionsText", "showHeaderToggle", "showHeaderButton", "strictReadonlyUi", "multiActions"], outputs: ["formViewChange", "actionRequested", "multiActionRequested"] }, { kind: "component", type: FormFieldContainerComponent, selector: "lib-form-field-container", inputs: ["title", "title2", "titleIcon", "layout", "helpText", "helpText2", "label", "count", "action", "actionLabel", "class", "contentStyle", "contentClass", "showHeader", "showToggle", "headerActions", "headerToggle"], outputs: ["actionRequested", "headerToggleChange", "helpClicked"] }, { kind: "component", type: PreviewListComponent, selector: "lib-preview-list", inputs: ["label", "clickable", "isLoading", "allNames", "items", "allowRemove", "tooltip", "showfilter", "localFilter"], outputs: ["itemSelected", "itemRemoved", "filterChanged"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "lib-loading-indicator", inputs: ["isLoading"] }, { kind: "component", type: FormFieldToggleComponent, selector: "lib-form-field-toggle", inputs: ["label", "orientation", "labelOn", "labelOff", "toggleOn"], outputs: ["toggleOnChange"] }, { kind: "component", type: JsonViewComponent, selector: "lib-json-view", inputs: ["data", "schema", "readOnly", "jsonInvalid", "showCopy", "showLineNumbers"], outputs: ["dataChange", "jsonChange", "jsonInvalidChange"] }, { kind: "component", type: TagSelectorComponent, selector: "lib-tag-selector", inputs: ["placeholder", "disableField", "disableCreate", "hideOption", "isLoading", "errorMessage", "focusOnInit", "namedAttributesServerFiltered", "selectedRoleAttributes", "selectedNamedAttributes", "hasError", "availableRoleAttributes", "availableNamedAttributes"], outputs: ["addRoleAttribute", "addNamedAttribute", "roleAttributeRemoved", "namedAttributeRemoved", "selectedRoleAttributesChange", "selectedNamedAttributesChange", "filterChange"] }, { kind: "component", type: QrCodeComponent, selector: "lib-qr-code", inputs: ["jwt", "token", "expiration", "identity", "type", "authenticators", "qrOnly", "canExpand"], outputs: ["doRefresh"] }, { kind: "component", type: CustomTagsComponent, selector: "lib-custom-tags", inputs: ["tags", "readOnly"], outputs: ["tagsChange"] }] }); }
19898
22257
  }
19899
22258
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: EdgeRouterFormComponent, decorators: [{
19900
22259
  type: Component,
@@ -19903,7 +22262,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
19903
22262
  provide: MatDialogRef,
19904
22263
  useValue: {}
19905
22264
  }
19906
- ], standalone: false, template: "<div class=\"projectable-form-wrapper\"\n (keyup.enter)=\"save($event)\"\n (keyup.escape)=\"closeModal(false)\"\n [class.projectable-form-hidden]=\"!dataInit\"\n >\n <lib-form-header\n [data]=\"formData\"\n [title]=\"formData.id ? 'Edit Edge Router: ' : 'Create New Edge Router'\"\n [moreActions]=\"moreActions\"\n [multiActions]=\"multiActions\"\n (actionRequested)=\"headerActionRequested($event)\"\n (multiActionRequested)=\"multiActionRequested($event)\"\n [(formView)]=\"formView\"\n [class.is-scrolled]=\"isScrolled\"\n [saveDisabled]=\"formPermissionSaveDisabled\"\n [saveTooltip]=\"formPermissionSaveTooltip\"\n [strictReadonlyUi]=\"useStrictReadonlyForm\"\n ></lib-form-header>\n <ng-content select=\"[slot=tabs-slot]\"></ng-content>\n <div class=\"edge-router-form-container projectable-form-container\" #formContainer>\n @if (formView === 'simple') {\n <div class=\"projectable-form-main-column form-group-row\">\n <div class=\"form-group-column three-fifths\">\n <lib-form-field-container [title]=\"'Edge Router Name'\" [label]=\"useStrictReadonlyForm ? '' : 'REQUIRED'\">\n <input\n class=\"form-field-input\"\n placeholder=\"Name this edge router\"\n [ngClass]=\"{error: errors['name']}\"\n [(ngModel)]=\"formData.name\"\n autofocus\n #nameFieldInput\n [disabled]=\"useStrictReadonlyForm\"\n />\n </lib-form-field-container>\n <lib-form-field-container\n [title]=\"'Select or create Edge Router attributes'\"\n [label]=\"'OPTIONAL'\"\n [contentStyle]=\"'z-index: 99999999'\"\n [helpText]=\"'Attributes are tags applied to an resource. Apply the same tag to other Edge Routers to form a group of Edge Routers.'\"\n >\n <lib-tag-selector\n [disableField]=\"useStrictReadonlyForm\"\n [disableCreate]=\"useStrictReadonlyForm\"\n [(selectedRoleAttributes)]=\"formData.roleAttributes\"\n [availableRoleAttributes]=\"edgeRouterRoleAttributes\"\n [placeholder]=\"'Add attributes to group Edge Routers'\"\n ></lib-tag-selector>\n </lib-form-field-container>\n <ng-content select=\"[slot=column-1-slot-1]\"></ng-content>\n <lib-form-field-toggle [(toggleOn)]=\"showMore\" style=\"margin: 0px 10px\"></lib-form-field-toggle>\n @if (showMore) {\n <div class=\"form-group-column\">\n <lib-form-field-container\n [title]=\"'TUNNELER'\"\n [title2]=\"'TRAVERSAL'\"\n [layout]=\"'row'\"\n class=\"form-field-advanced\"\n >\n <div class=\"config-item\" [class.disabled]=\"!tunnelerEnabledReadOnly\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">TUNNELER ENABLED</div>\n <div (click)=\"tunnelerEnabledReadOnly && !useStrictReadonlyForm && toggleTunnelerEnabled()\"\n [ngClass]=\"{ on: formData.isTunnelerEnabled, disabled: !tunnelerEnabledReadOnly, 'toggle-readonly': useStrictReadonlyForm }\" class=\"toggle\"\n [matTooltip]=\"useStrictReadonlyForm ? '' : (!tunnelerEnabledReadOnly ? 'Disabled because already hosted by netfoundry ' : '')\"\n matTooltipPosition=\"above\" style=\"pointer-events: auto;\">\n <span [hidden]=\"!formData.isTunnelerEnabled\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.isTunnelerEnabled\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">ALLOW TRAVERSAL</div>\n <div\n (click)=\"!useStrictReadonlyForm && toggleNoTraversal()\"\n [ngClass]=\"{ on: !formData.noTraversal, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"formData.noTraversal\" class=\"on-label\">YES</span>\n <span [hidden]=\"!formData.noTraversal\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n @if (formData?.hostname || formData?.supportedProtocols?.tls || formData?.supportedProtocols?.wss) {\n <lib-form-field-container [showHeader]=\"false\">\n @if (formData?.hostname) {\n <div class=\"config-item\">\n <div class=\"config-item-label-container\">\n <span class=\"config-title\">Domain Name</span>\n </div>\n <div class=\"config-container domain-container\">\n <div class=\"config-container-label domain-label\">\n {{ formData?.hostname }}\n </div>\n </div>\n </div>\n }\n @if (formData?.supportedProtocols?.tls || formData?.supportedProtocols?.wss) {\n <div class=\"config-item\">\n <div class=\"config-item-label-container\">\n <span class=\"config-title\">ADVERTISE ADDRESS</span>\n </div>\n <div class=\"config-container advertise-address-container\">\n <div class=\"config-container-label advertise-address-label\">\n {{ formData?.supportedProtocols?.tls }}\n <br />\n {{ formData?.supportedProtocols?.wss }}\n </div>\n </div>\n </div>\n }\n </lib-form-field-container>\n }\n <lib-form-field-container\n [title]=\"'Custom Tags'\"\n [label]=\"'OPTIONAL'\"\n class=\"form-field-advanced\"\n >\n <lib-custom-tags [(tags)]=\"formData.tags\" [readOnly]=\"useStrictReadonlyForm\"></lib-custom-tags>\n </lib-form-field-container>\n <lib-form-field-container\n [title]=\"'App Data'\"\n [label]=\"'OPTIONAL'\"\n class=\"form-field-advanced\"\n >\n <lib-json-view [(data)]=\"formData.appData\" [readOnly]=\"useStrictReadonlyForm\"></lib-json-view>\n </lib-form-field-container>\n </div>\n }\n </div>\n <div class=\"form-group-column two-fifths\">\n <ng-content select=\"[slot=column-2-slot-1]\"></ng-content>\n @if (hasEnrolmentToken && !extService['hideZitiRegistration']) {\n <lib-form-field-container\n [showHeader]=\"false\"\n >\n <lib-qr-code\n [identity]=\"formData\"\n [jwt]=\"formData.enrollmentJwt\"\n [token]=\"formData.enrollmentToken\"\n [expiration]=\"formData.enrollmentExpiresAt\"\n (doRefresh)=\"refreshEdgeRouter()\"\n [type]=\"'router'\"\n ></lib-qr-code>\n </lib-form-field-container>\n }\n @if (formData.id) {\n <lib-form-field-container\n [title]=\"'Associated Services'\"\n [count]=\"svc.associatedServices.length\"\n [class]=\"'hide-overflow'\"\n [helpText]=\"'Preview list of all services that this Edge Router is associated with.'\"\n >\n <lib-preview-list\n [clickable]=\"false\"\n [isLoading]=\"servicesLoading\"\n [allNames]=\"svc.associatedServiceNames\"\n ></lib-preview-list>\n </lib-form-field-container>\n }\n @if (formData.id) {\n <lib-form-field-container\n [title]=\"'Associated Identities'\"\n [count]=\"svc.associatedIdentities.length\"\n [helpText]=\"'Preview list of all Identities that this Edge Router is associated with.'\"\n >\n <lib-preview-list\n [clickable]=\"false\"\n [isLoading]=\"identitiesLoading\"\n [allNames]=\"svc.associatedIdentityNames\"\n ></lib-preview-list>\n </lib-form-field-container>\n }\n @if (showApiCalls) {\n <lib-form-field-container\n [title]=\"'API Calls'\"\n [label]=\"'OPTIONAL'\"\n >\n <div class=\"form-row\">\n <input class=\"form-field-input\" [value]=\"apiCallURL\"/>\n <div class=\"icon-copy copy-text\" (click)=\"copyToClipboard(apiCallURL)\"></div>\n </div>\n @if (formData) {\n <lib-json-view [(data)]=\"apiData\" [readOnly]=\"true\" [showCopy]=\"true\"></lib-json-view>\n }\n </lib-form-field-container>\n }\n </div>\n </div>\n }\n @if (formView === 'raw') {\n <div class=\"form-group-column\">\n @if (formData) {\n <lib-json-view [(data)]=\"formData\" [readOnly]=\"useStrictReadonlyForm\"></lib-json-view>\n }\n </div>\n }\n </div>\n</div>\n@if (isLoading || !dataInit) {\n <lib-loading-indicator [isLoading]=\"isLoading || !dataInit\"></lib-loading-indicator>\n}\n", styles: [".edge-router-form-container{display:flex;flex-direction:column;height:100%;width:100%;align-items:center;gap:.9375rem}.form-field-dropdown{background-image:url(/assets/svgs/ArrowDown.svg);background-position:center right .625rem;background-repeat:no-repeat;background-size:1.125rem;padding-right:1.75rem}.disabled{opacity:.5;pointer-events:none}.toggle.disabled{cursor:not-allowed}::ng-deep .admin-toggle{width:100%;width:35%;display:flex}::ng-deep .admin-toggle .form-field-line{display:none}::ng-deep .admin-toggle .toggle{width:100%}::ng-deep .is-admin-container .form-field-title-container.title-container-2{width:35%!important}.svc-test-result{position:absolute;transition:var(--transition);height:0;width:100%;background-color:var(--background);bottom:0;font-weight:600;padding-left:.625rem;padding-right:.625rem;border-color:var(--inputBorder);border-style:solid;border-width:var(--inputBorderWidth);border-bottom-right-radius:var(--inputBorderRadius);border-bottom-left-radius:var(--inputBorderRadius);border-top-style:none}.svc-test-result.test-result-open{height:fit-content;box-shadow:0 -.125rem .6875rem 0 var(--boxShadow);padding-bottom:.9375rem;padding-top:.9375rem}.svc-test-result .close.icon-close{top:.625rem;right:.625rem;font-size:1.125rem;height:1.125rem;width:1.125rem;line-height:1.125rem}::ng-deep .admin-toggle .form-field-toggle-container:focus .form-field-label{color:var(--primaryColor)}::ng-deep .admin-toggle .form-field-toggle-container:focus .form-field-line{border-color:var(--primaryColor)}::ng-deep .admin-toggle .form-field-toggle-container:focus .toggle{border-color:var(--primaryColor);border-style:solid;border-width:var(--inputBorderWidth)}::ng-deep .admin-toggle .form-field-toggle-container:focus .toggle .switch{top:.0625rem}.config-container-label.domain-label,.config-container-label.advertise-address-label{text-transform:lowercase}.config-container.domain-container,.config-container.advertise-address-container{background-color:var(--stroke)}\n"] }]
22265
+ ], standalone: false, template: "<div class=\"projectable-form-wrapper\"\n (keyup.enter)=\"save($event)\"\n (keyup.escape)=\"closeModal(false)\"\n [class.projectable-form-hidden]=\"!dataInit\"\n >\n <lib-form-header\n [data]=\"formData\"\n [title]=\"formData.id ? 'Edit Edge Router: ' : 'Create New Edge Router'\"\n [moreActions]=\"moreActions\"\n [multiActions]=\"multiActions\"\n (actionRequested)=\"headerActionRequested($event)\"\n (multiActionRequested)=\"multiActionRequested($event)\"\n [(formView)]=\"formView\"\n [class.is-scrolled]=\"isScrolled\"\n [saveDisabled]=\"formPermissionSaveDisabled\"\n [saveTooltip]=\"formPermissionSaveTooltip\"\n [strictReadonlyUi]=\"useStrictReadonlyForm\"\n ></lib-form-header>\n <ng-content select=\"[slot=tabs-slot]\"></ng-content>\n <div class=\"edge-router-form-container projectable-form-container\" #formContainer>\n @if (formView === 'simple') {\n <div class=\"projectable-form-main-column form-group-row\">\n <div class=\"form-group-column three-fifths\">\n <lib-form-field-container [title]=\"'Edge Router Name'\" [label]=\"useStrictReadonlyForm ? '' : 'REQUIRED'\">\n <input\n class=\"form-field-input\"\n placeholder=\"Name this edge router\"\n [ngClass]=\"{error: errors['name']}\"\n [(ngModel)]=\"formData.name\"\n autofocus\n #nameFieldInput\n [disabled]=\"useStrictReadonlyForm\"\n />\n </lib-form-field-container>\n <lib-form-field-container\n [title]=\"'Select or create Edge Router attributes'\"\n [label]=\"'OPTIONAL'\"\n [contentStyle]=\"'z-index: 99999999'\"\n [helpText]=\"'Attributes are tags applied to an resource. Apply the same tag to other Edge Routers to form a group of Edge Routers.'\"\n >\n <lib-tag-selector\n [disableField]=\"useStrictReadonlyForm\"\n [disableCreate]=\"useStrictReadonlyForm\"\n [(selectedRoleAttributes)]=\"formData.roleAttributes\"\n [availableRoleAttributes]=\"edgeRouterRoleAttributes\"\n [placeholder]=\"'Add attributes to group Edge Routers'\"\n ></lib-tag-selector>\n </lib-form-field-container>\n <ng-content select=\"[slot=column-1-slot-1]\"></ng-content>\n <lib-form-field-toggle [(toggleOn)]=\"showMore\" style=\"margin: 0px 10px\"></lib-form-field-toggle>\n @if (showMore) {\n <div class=\"form-group-column\">\n <lib-form-field-container\n [title]=\"'TUNNELER'\"\n [title2]=\"'TRAVERSAL'\"\n [layout]=\"'row'\"\n class=\"form-field-advanced\"\n >\n <div class=\"config-item\" [class.disabled]=\"!tunnelerEnabledReadOnly\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">TUNNELER ENABLED</div>\n <div (click)=\"tunnelerEnabledReadOnly && !useStrictReadonlyForm && toggleTunnelerEnabled()\"\n [ngClass]=\"{ on: formData.isTunnelerEnabled, disabled: !tunnelerEnabledReadOnly, 'toggle-readonly': useStrictReadonlyForm }\" class=\"toggle\"\n [matTooltip]=\"useStrictReadonlyForm ? '' : (!tunnelerEnabledReadOnly ? 'Disabled because already hosted by netfoundry ' : '')\"\n matTooltipPosition=\"above\" style=\"pointer-events: auto;\">\n <span [hidden]=\"!formData.isTunnelerEnabled\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.isTunnelerEnabled\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">ALLOW TRAVERSAL</div>\n <div\n (click)=\"!useStrictReadonlyForm && toggleNoTraversal()\"\n [ngClass]=\"{ on: !formData.noTraversal, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"formData.noTraversal\" class=\"on-label\">YES</span>\n <span [hidden]=\"!formData.noTraversal\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n @if (isCustomerHosted) {\n <lib-form-field-container\n [title]=\"'LINK LISTENER'\"\n class=\"form-field-advanced\"\n >\n <div class=\"config-item disabled\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">ENABLE LINK LISTENER</div>\n <div\n [ngClass]=\"{ on: formData.linkListener, disabled: true }\"\n class=\"toggle\"\n role=\"switch\"\n aria-disabled=\"true\"\n [attr.aria-checked]=\"!!formData.linkListener\"\n matTooltip=\"This value is reported by the router and can't be changed here.\"\n matTooltipPosition=\"above\"\n >\n <span [hidden]=\"!formData.linkListener\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.linkListener\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n }\n @if (formData?.hostname || formData?.supportedProtocols?.tls || formData?.supportedProtocols?.wss) {\n <lib-form-field-container [showHeader]=\"false\">\n @if (formData?.hostname) {\n <div class=\"config-item\">\n <div class=\"config-item-label-container\">\n <span class=\"config-title\">Domain Name</span>\n </div>\n <div class=\"config-container domain-container\">\n <div class=\"config-container-label domain-label\">\n {{ formData?.hostname }}\n </div>\n </div>\n </div>\n }\n @if (formData?.supportedProtocols?.tls || formData?.supportedProtocols?.wss) {\n <div class=\"config-item\">\n <div class=\"config-item-label-container\">\n <span class=\"config-title\">ADVERTISE ADDRESS</span>\n </div>\n <div class=\"config-container advertise-address-container\">\n <div class=\"config-container-label advertise-address-label\">\n {{ formData?.supportedProtocols?.tls }}\n <br />\n {{ formData?.supportedProtocols?.wss }}\n </div>\n </div>\n </div>\n }\n </lib-form-field-container>\n }\n <lib-form-field-container\n [title]=\"'Custom Tags'\"\n [label]=\"'OPTIONAL'\"\n class=\"form-field-advanced\"\n >\n <lib-custom-tags [(tags)]=\"formData.tags\" [readOnly]=\"useStrictReadonlyForm\"></lib-custom-tags>\n </lib-form-field-container>\n <lib-form-field-container\n [title]=\"'App Data'\"\n [label]=\"'OPTIONAL'\"\n class=\"form-field-advanced\"\n >\n <lib-json-view [(data)]=\"formData.appData\" [readOnly]=\"useStrictReadonlyForm\"></lib-json-view>\n </lib-form-field-container>\n </div>\n }\n </div>\n <div class=\"form-group-column two-fifths\">\n <ng-content select=\"[slot=column-2-slot-1]\"></ng-content>\n @if (hasEnrolmentToken && !extService['hideZitiRegistration']) {\n <lib-form-field-container\n [showHeader]=\"false\"\n >\n <lib-qr-code\n [identity]=\"formData\"\n [jwt]=\"formData.enrollmentJwt\"\n [token]=\"formData.enrollmentToken\"\n [expiration]=\"formData.enrollmentExpiresAt\"\n (doRefresh)=\"refreshEdgeRouter()\"\n [type]=\"'router'\"\n ></lib-qr-code>\n </lib-form-field-container>\n }\n @if (formData.id) {\n <lib-form-field-container\n [title]=\"'Associated Services'\"\n [count]=\"svc.associatedServices.length\"\n [class]=\"'hide-overflow'\"\n [helpText]=\"'Preview list of all services that this Edge Router is associated with.'\"\n >\n <lib-preview-list\n [clickable]=\"false\"\n [isLoading]=\"servicesLoading\"\n [allNames]=\"svc.associatedServiceNames\"\n ></lib-preview-list>\n </lib-form-field-container>\n }\n @if (formData.id) {\n <lib-form-field-container\n [title]=\"'Associated Identities'\"\n [count]=\"svc.associatedIdentities.length\"\n [helpText]=\"'Preview list of all Identities that this Edge Router is associated with.'\"\n >\n <lib-preview-list\n [clickable]=\"false\"\n [isLoading]=\"identitiesLoading\"\n [allNames]=\"svc.associatedIdentityNames\"\n ></lib-preview-list>\n </lib-form-field-container>\n }\n @if (showApiCalls) {\n <lib-form-field-container\n [title]=\"'API Calls'\"\n [label]=\"'OPTIONAL'\"\n >\n <div class=\"form-row\">\n <input class=\"form-field-input\" [value]=\"apiCallURL\"/>\n <div class=\"icon-copy copy-text\" (click)=\"copyToClipboard(apiCallURL)\"></div>\n </div>\n @if (formData) {\n <lib-json-view [(data)]=\"apiData\" [readOnly]=\"true\" [showCopy]=\"true\"></lib-json-view>\n }\n </lib-form-field-container>\n }\n </div>\n </div>\n }\n @if (formView === 'raw') {\n <div class=\"form-group-column\">\n @if (formData) {\n <lib-json-view [(data)]=\"formData\" [readOnly]=\"useStrictReadonlyForm\"></lib-json-view>\n }\n </div>\n }\n </div>\n</div>\n@if (isLoading || !dataInit) {\n <lib-loading-indicator [isLoading]=\"isLoading || !dataInit\"></lib-loading-indicator>\n}\n", styles: [".edge-router-form-container{display:flex;flex-direction:column;height:100%;width:100%;align-items:center;gap:.9375rem}.form-field-dropdown{background-image:url(/assets/svgs/ArrowDown.svg);background-position:center right .625rem;background-repeat:no-repeat;background-size:1.125rem;padding-right:1.75rem}.disabled{opacity:.5;pointer-events:none}.toggle.disabled{cursor:not-allowed}::ng-deep .admin-toggle{width:100%;width:35%;display:flex}::ng-deep .admin-toggle .form-field-line{display:none}::ng-deep .admin-toggle .toggle{width:100%}::ng-deep .is-admin-container .form-field-title-container.title-container-2{width:35%!important}.svc-test-result{position:absolute;transition:var(--transition);height:0;width:100%;background-color:var(--background);bottom:0;font-weight:600;padding-left:.625rem;padding-right:.625rem;border-color:var(--inputBorder);border-style:solid;border-width:var(--inputBorderWidth);border-bottom-right-radius:var(--inputBorderRadius);border-bottom-left-radius:var(--inputBorderRadius);border-top-style:none}.svc-test-result.test-result-open{height:fit-content;box-shadow:0 -.125rem .6875rem 0 var(--boxShadow);padding-bottom:.9375rem;padding-top:.9375rem}.svc-test-result .close.icon-close{top:.625rem;right:.625rem;font-size:1.125rem;height:1.125rem;width:1.125rem;line-height:1.125rem}::ng-deep .admin-toggle .form-field-toggle-container:focus .form-field-label{color:var(--primaryColor)}::ng-deep .admin-toggle .form-field-toggle-container:focus .form-field-line{border-color:var(--primaryColor)}::ng-deep .admin-toggle .form-field-toggle-container:focus .toggle{border-color:var(--primaryColor);border-style:solid;border-width:var(--inputBorderWidth)}::ng-deep .admin-toggle .form-field-toggle-container:focus .toggle .switch{top:.0625rem}.config-container-label.domain-label,.config-container-label.advertise-address-label{text-transform:lowercase}.config-container-label.advertise-address-label{height:auto;padding:.125rem 0}.config-container.domain-container,.config-container.advertise-address-container{background-color:var(--stroke)}.config-container.advertise-address-container{height:auto;min-height:var(--defaultInputHeight);padding:0rem 1.25rem}\n"] }]
19907
22266
  }], ctorParameters: () => [{ type: SettingsService, decorators: [{
19908
22267
  type: Inject,
19909
22268
  args: [SETTINGS_SERVICE]
@@ -26403,6 +28762,9 @@ class JwtSigner {
26403
28762
  this.targetToken = 'ACCESS';
26404
28763
  this.enrollToCertEnabled = false;
26405
28764
  this.enrollToTokenEnabled = false;
28765
+ this.enrollAuthPolicyId = '';
28766
+ this.enrollNameClaimsSelector = '';
28767
+ this.enrollAttributeClaimsSelector = '';
26406
28768
  }
26407
28769
  }
26408
28770
  ;
@@ -26616,6 +28978,7 @@ class JwtSignerFormComponent extends ProjectableForm {
26616
28978
  this.configKey = 'oauth_test_callback_config';
26617
28979
  this.tokenTypeKey = 'oauth_test_callback_token_type';
26618
28980
  this.oidcVerified = false;
28981
+ this.authPolicies = [];
26619
28982
  this.usePreviousLocation = false;
26620
28983
  this.entityType = 'external-jwt-signers';
26621
28984
  this.entityClass = JwtSigner;
@@ -26627,10 +28990,25 @@ class JwtSignerFormComponent extends ProjectableForm {
26627
28990
  this.settingsService.settingsChange.subscribe((results) => {
26628
28991
  this.settings = results;
26629
28992
  });
28993
+ this.getAuthPolicies();
26630
28994
  if (window.location.href.indexOf('test-auth') > 0) {
26631
28995
  this.handleOAuthCallback();
26632
28996
  }
26633
28997
  }
28998
+ getAuthPolicies() {
28999
+ const paging = {
29000
+ filter: "",
29001
+ noSearch: true,
29002
+ order: "asc",
29003
+ page: 1,
29004
+ searchOn: "name",
29005
+ sort: "name",
29006
+ total: 100
29007
+ };
29008
+ this.zitiService.get('auth-policies', paging, []).then((result) => {
29009
+ this.authPolicies = [...result.data];
29010
+ });
29011
+ }
26634
29012
  ngAfterViewInit() {
26635
29013
  super.ngAfterViewInit(true);
26636
29014
  }
@@ -26796,6 +29174,18 @@ class JwtSignerFormComponent extends ProjectableForm {
26796
29174
  }
26797
29175
  this.formData.targetToken = val;
26798
29176
  }
29177
+ get enrollAuthPolicyId() {
29178
+ if (this.formData && isNil(this.formData.enrollAuthPolicyId)) {
29179
+ this.formData.enrollAuthPolicyId = '';
29180
+ }
29181
+ return this.formData?.enrollAuthPolicyId;
29182
+ }
29183
+ set enrollAuthPolicyId(val) {
29184
+ if (!this.formData) {
29185
+ return;
29186
+ }
29187
+ this.formData.enrollAuthPolicyId = val;
29188
+ }
26799
29189
  get apiCallURL() {
26800
29190
  return this.settings.selectedEdgeController + '/edge/management/v1/external-jwt-signers' + (this.formData.id ? `/${this.formData.id}` : '');
26801
29191
  }
@@ -26822,6 +29212,11 @@ class JwtSignerFormComponent extends ProjectableForm {
26822
29212
  if (!isEmpty(this.formData.targetToken) && this.showTokenType) {
26823
29213
  data.targetToken = this.formData.targetToken;
26824
29214
  }
29215
+ if (this.showAutoEnrollment) {
29216
+ data.enrollAuthPolicyId = this.formData.enrollAuthPolicyId || '';
29217
+ data.enrollNameClaimsSelector = this.formData.enrollNameClaimsSelector || '';
29218
+ data.enrollAttributeClaimsSelector = this.formData.enrollAttributeClaimsSelector || '';
29219
+ }
26825
29220
  if (!isEmpty(this.formData.id)) {
26826
29221
  data.id = this.formData.id;
26827
29222
  }
@@ -27091,11 +29486,11 @@ class JwtSignerFormComponent extends ProjectableForm {
27091
29486
  return !this.errors.jwksEndpoint;
27092
29487
  }
27093
29488
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: JwtSignerFormComponent, deps: [{ token: JwtSignerFormService }, { token: SchemaService }, { token: GrowlerService }, { token: SHAREDZ_EXTENSION }, { token: SETTINGS_SERVICE }, { token: ZITI_DATA_SERVICE }, { token: i4.Router }, { token: i4.ActivatedRoute }, { token: i1$1.Location }, { token: ValidationService }, { token: AuthService }, { token: i1$6.OAuthService }, { token: i1$3.HttpClient }, { token: ZAC_LOGIN_SERVICE }, { token: i5.MatDialog }], target: i0.ɵɵFactoryTarget.Component }); }
27094
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: JwtSignerFormComponent, isStandalone: false, selector: "lib-configuration", inputs: { errors: "errors" }, outputs: { close: "close" }, viewQueries: [{ propertyName: "filterInput", first: true, predicate: ["fileSelect"], descendants: true }, { propertyName: "oidcVerification", first: true, predicate: ["oidcVerification"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"projectable-form-wrapper\"\n (keyup.enter)=\"save($event)\"\n (keyup.escape)=\"closeModal(false)\"\n tabindex=\"0\"\n >\n <lib-form-header\n [data]=\"formData\"\n [title]=\"(formData.id ? 'JWT Signer Details' : 'Create JWT Signer')\"\n [moreActions]=\"moreActions\"\n [multiActions]=\"multiActions\"\n (actionRequested)=\"headerActionRequested($event)\"\n (multiActionRequested)=\"multiActionRequested($event)\"\n [(formView)]=\"formView\"\n [saveDisabled]=\"svc.saveDisabled || formDataInvalid || formPermissionSaveDisabled\"\n [saveTooltip]=\"formPermissionSaveTooltip\"\n [strictReadonlyUi]=\"useStrictReadonlyForm\"\n ></lib-form-header>\n <div class=\"jwt-signer-form-container projectable-form-container\" #scrollContainer>\n <div class=\"projectable-form-main-column form-group-row\" [hidden]=\"formView !== 'simple'\">\n <div class=\"form-group-column three-fifths\">\n <lib-form-field-container\n [title]=\"'Name'\"\n [label]=\"useStrictReadonlyForm ? '' : 'REQUIRED'\"\n >\n @if (!useStrictReadonlyForm) {\n <input class=\"form-field-input\" [(ngModel)]=\"formData.name\" [ngClass]=\"{error: errors['name']}\" placeholder=\"Name this JWT Signer\" #nameFieldInput/>\n } @else {\n <div class=\"form-field-readonly-value\">{{ formData.name }}</div>\n }\n </lib-form-field-container>\n <lib-form-field-container\n [layout]=\"'column'\"\n [showHeader]=\"false\"\n >\n <div class=\"form-field-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Issuer</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The &quot;issuer&quot; field on the JWT Signer must match the &quot;iss&quot; field on the JWT\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <span class=\"form-field-label\" >{{ useStrictReadonlyForm ? '' : 'REQUIRED' }}</span>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter issuer\" [(ngModel)]=\"formData.issuer\" [ngClass]=\"{error: errors['issuer']}\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Audience</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The expected &quot;aud&quot; (audience) field on the JWT\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter audience\" [(ngModel)]=\"formData.audience\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-row\">\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Claims Property</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The field on the JWT for mapping to an identity. Matches the identity's id or externalId if &quot;Use External Id&quot; is set to true. Defaults to &quot;sub&quot;\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter claims property\" [(ngModel)]=\"formData.claimsProperty\" (keyup)=\"checkExternalIdToggle()\" [ngClass]=\"{error: errors['claimsProperty']}\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <div class=\"config-container-label\" style=\"font-weight: 600; font-size: 14px;\">External ID</div>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"If enabled, will match the &quot;claimsProperty&quot; to an identity's &quot;externalId&quot; field. If false, it will match to the identity's id field.\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n </div>\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"form-field-title-container\">\n <div class=\"config-container-label\">Use External ID</div>\n </div>\n <div\n (click)=\"!useStrictReadonlyForm && toggleUseExternalId()\"\n [ngClass]=\"{ on: formData.useExternalId, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"!formData.useExternalId\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.useExternalId\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n <lib-form-field-container\n [showHeader]=\"false\"\n >\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Client ID</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The client id to use when authenticating to the IDP\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter client ID\" [(ngModel)]=\"formData.clientId\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">External Auth Url</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The URL that users are directed to, to obtain a JWT\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter external auth URL\" [(ngModel)]=\"formData.externalAuthUrl\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Scopes</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"Enter as a comma or space seperated list of claims/permissions\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <lib-chips-input\n (keyup)=\"scopesOnKeyup($event)\"\n [(ngModel)]=\"formData.scopes\"\n [allowDuplicate]=\"false\"\n placeholder=\"Enter scopes\"\n [addOnBlur]=\"true\"\n separator=\",\"\n [disabled]=\"useStrictReadonlyForm\"\n ></lib-chips-input>\n </div>\n </lib-form-field-container>\n @if (showAutoEnrollment) {\n <lib-form-field-container\n [title]=\"'Auto Enrollment'\"\n [helpText]=\"'Click for more information'\"\n (helpClicked)=\"showAutoEnrollHelp()\"\n >\n <div class=\"form-field-row\">\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">Auto-Enroll to Cert</div>\n <div\n (click)=\"toggleEnrollToCertEnabled()\"\n [ngClass]=\"{ on: formData.enrollToCertEnabled, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"!formData.enrollToCertEnabled\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.enrollToCertEnabled\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">Auto-Enroll to Token</div>\n <div\n (click)=\"toggleEnrollToTokenEnabled()\"\n [ngClass]=\"{ on: formData.enrollToTokenEnabled, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"!formData.enrollToTokenEnabled\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.enrollToTokenEnabled\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n }\n <lib-form-field-toggle [(toggleOn)]=\"showMore\" (toggleOnChange)=\"showMoreChanged($event)\" style=\"margin: 0px 10px\"></lib-form-field-toggle>\n @if (showMore && showTokenType) {\n <lib-form-field-container\n [title]=\"'Target Token Type'\"\n [label]=\"'Required'\"\n [helpText]=\"'The target token type. Can be either Access or ID'\"\n class=\"form-field-advanced\"\n >\n <select\n [(ngModel)]=\"targetToken\"\n id=\"authPolicyId\"\n class=\"form-field-dropdown\"\n [disabled]=\"useStrictReadonlyForm\"\n >\n <option value=\"ACCESS\">Access</option>\n <option value=\"ID\">ID</option>\n </select>\n </lib-form-field-container>\n }\n <div [hidden]=\"!showMore\" class=\"form-group-column\">\n <lib-form-field-container\n [title]=\"'Verify OIDC Authentication'\"\n class=\"form-field-advanced\"\n [ngClass]=\"{'formFieldError': !oidcVerified && oidcErrorMessageDetail, 'formFieldSuccess': oidcVerified}\"\n >\n <div class=\"form-field-row\" #oidcVerification>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <div class=\"form-field-title\">Callback URL</div>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"This URL must be added to the list of allowed callback URLs in your OAuth provider configuration\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <div class=\"copy-link-container\">\n <pre class=\"read-only-2\" (click)=\"copyCallbackURL()\">{{ callbackURL }}</pre>\n <div class=\"copy-button\" (click)=\"copyCallbackURL()\"></div>\n </div>\n </div>\n @if (!useStrictReadonlyForm) {\n <div class=\"test-oauth-button\"\n [ngClass]=\"{'oidc-verified': oidcVerified}\"\n (click)=\"testOIDCAuthentication()\"\n >\n {{oidcVerified ? 'Verified' : 'Authenticate' }}\n @if (oauthLoading) {\n <div class=\"spinner\"></div>\n }\n @if (oidcVerified) {\n <input type=\"checkbox\" [disabled]=\"true\" [checked]=\"true\" class=\"success-check\">\n }\n </div>\n }\n </div>\n @if (oidcErrorMessageDetail) {\n <div class=\"oidc-error-message-container\">\n <code class=\"oidc-error-message-source\">{{oidcErrorMessageSource}}: </code>\n <code class=\"oidc-error-message-detail\">{{oidcErrorMessageDetail}}</code>\n @if (oidcErrorMessageDetail2) {\n <code class=\"oidc-error-message-detail2\">{{oidcErrorMessageDetail2}}</code>\n }\n @if (oidcErrorMessageDetail3) {\n <code class=\"oidc-error-message-detail2\">{{oidcErrorMessageDetail3}}</code>\n }\n </div>\n }\n @if (oidcAuthTokenClaims) {\n <div class=\"oidc-token-claims-container\">\n <code class=\"oidc-token-claims-title\">OAuth Token Claims: </code>\n <lib-json-view [showLineNumbers]=\"false\" [(data)]=\"oidcAuthTokenClaims\" [readOnly]=\"useStrictReadonlyForm\"></lib-json-view>\n </div>\n }\n </lib-form-field-container>\n <lib-form-field-container\n [title]=\"'Custom Tags'\"\n [label]=\"'OPTIONAL'\"\n class=\"form-field-advanced\"\n >\n <lib-custom-tags [(tags)]=\"formData.tags\" [readOnly]=\"useStrictReadonlyForm\"></lib-custom-tags>\n </lib-form-field-container>\n </div>\n </div>\n <div class=\"form-group-column two-fifths\">\n <lib-form-field-container\n [title]=\"'Verification'\"\n [label]=\"useStrictReadonlyForm ? '' : 'REQUIRED'\"\n >\n <div\n class=\"radio-group-container\"\n [class.radio-group-readonly]=\"useStrictReadonlyForm\"\n (keydown)=\"radioKeyDownHandler($event)\"\n [attr.tabindex]=\"useStrictReadonlyForm ? -1 : 0\"\n >\n <div\n class=\"radio-button-container\"\n [ngClass]=\"{'selected': signatureMethod === 'JWKS_ENDPOINT', 'toggle-readonly': useStrictReadonlyForm}\"\n (click)=\"!useStrictReadonlyForm && selectSignatureMethod('JWKS_ENDPOINT')\"\n >\n <div class=\"radio-button-circle\">\n <div class=\"radio-button-inner-circle\"></div>\n </div>\n <span class=\"radio-button-label\">JWKS Endpoint</span>\n </div>\n <div\n class=\"radio-button-container\"\n [ngClass]=\"{'selected': signatureMethod === 'CERT_PEM', 'toggle-readonly': useStrictReadonlyForm}\"\n (click)=\"!useStrictReadonlyForm && selectSignatureMethod('CERT_PEM')\"\n >\n <div class=\"radio-button-circle\">\n <div class=\"radio-button-inner-circle\"></div>\n </div>\n <span class=\"radio-button-label\">Cert PEM</span>\n </div>\n </div>\n @if (signatureMethod === 'JWKS_ENDPOINT') {\n <div class=\"form-field-input-group\">\n <div class=\"form-field-label-container select-file-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">JWKS Endpoint</span>\n </div>\n </div>\n <input\n class=\"form-field-input\"\n placeholder=\"Enter endpoint URL\"\n [(ngModel)]=\"formData.jwksEndpoint\"\n [ngClass]=\"{error: errors['jwksEndpoint']}\"\n [disabled]=\"useStrictReadonlyForm\"\n />\n </div>\n </div>\n }\n @if (signatureMethod === 'CERT_PEM') {\n <div class=\"form-field-input-group\">\n <div class=\"form-field-label-container select-file-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">PEM</span>\n </div>\n <span\n class=\"select-file-button\"\n [class.toggle-readonly]=\"useStrictReadonlyForm\"\n (click)=\"!useStrictReadonlyForm && openFileSelect($event)\"\n >\n Select File\n @if (fileSelectOpening) {\n <div class=\"spinner\"></div>\n }\n </span>\n <input\n #fileSelect\n type=\"file\"\n style=\"display:none\"\n [disabled]=\"useStrictReadonlyForm\"\n (change)=\"selectPemFile($event)\"\n >\n </div>\n <textarea\n placeholder=\"Paste pem contents or select file\"\n [(ngModel)]=\"formData.certPem\"\n [ngClass]=\"{error: errors['certPem']}\"\n [disabled]=\"useStrictReadonlyForm\"\n ></textarea>\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">KID</span>\n </div>\n <span class=\"form-field-label\" >Optional</span>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter KID\" [(ngModel)]=\"formData.kid\" [disabled]=\"useStrictReadonlyForm\"/>\n </div>\n </div>\n }\n </lib-form-field-container>\n <lib-form-field-container\n [showHeader]=\"false\"\n >\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">ENABLED</div>\n <div\n (click)=\"!useStrictReadonlyForm && toggleEnabled()\"\n [ngClass]=\"{ on: formData.enabled, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"!formData.enabled\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.enabled\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n @if (showApiCalls) {\n <lib-form-field-container\n [title]=\"'API Calls'\"\n [headerActions]=\"apiOptions\"\n (actionRequested)=\"apiActionRequested($event)\"\n [class]=\"'api-data-no-wrap'\"\n >\n <div class=\"form-row\">\n <input class=\"form-field-input\" [value]=\"apiCallURL\"/>\n <div class=\"icon-copy copy-text\" (click)=\"copyToClipboard(apiCallURL)\"></div>\n </div>\n @if (formData) {\n <lib-json-view [showLineNumbers]=\"false\" [(data)]=\"apiData\" [readOnly]=\"true\" [showCopy]=\"true\"></lib-json-view>\n }\n </lib-form-field-container>\n }\n </div>\n </div>\n @if (formView === 'raw') {\n <div class=\"form-group-column\">\n @if (formData) {\n <lib-json-view [(data)]=\"formData\" [readOnly]=\"useStrictReadonlyForm\"></lib-json-view>\n }\n </div>\n }\n </div>\n</div>\n@if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n}\n<lib-side-modal [(open)]=\"showAutoEnrollHelpModal\" [showClose]=\"true\">\n <div class=\"help-panel-content\">\n <h2 class=\"help-panel-title\">What is Auto Enrollment?</h2>\n <p class=\"help-panel-intro\">\n Auto Enrollment enables automatic identity creation when users authenticate via JWT tokens.\n </p>\n\n <div class=\"help-panel-section\">\n <h3 class=\"help-panel-subtitle\">\n <span class=\"icon-certificate\"></span>\n Auto-Enroll to Cert\n </h3>\n <p class=\"help-panel-text\">\n When enabled, the controller will automatically create an identity and enroll it with a certificate when a user authenticates using a JWT token. This is useful for certificate-based authentication workflows.\n </p>\n </div>\n\n <div class=\"help-panel-section\">\n <h3 class=\"help-panel-subtitle\">\n <span class=\"icon-key\"></span>\n Auto-Enroll to Token\n </h3>\n <p class=\"help-panel-text\">\n When enabled, the controller will automatically create an identity and enroll it with an authentication token when a user authenticates using a JWT token. This is useful for token-based authentication workflows.\n </p>\n </div>\n\n <div class=\"help-panel-note\">\n <strong>Note:</strong> These options are only available in Ziti 2.0 and later versions.\n </div>\n </div>\n</lib-side-modal>\n", styles: ["@charset \"UTF-8\";.jwt-signer-form-container{display:flex;flex-direction:column;height:100%;width:100%;align-items:center;gap:var(--gapXL)}.jwt-signer-form-container .select-file-button{padding-left:var(--paddingMedium);padding-right:var(--paddingMedium);height:1.5625rem;width:fit-content;white-space:nowrap;display:flex;align-items:center;justify-content:center;font-weight:600;font-size:.875rem;background:var(--primary);border-style:solid;border-width:.0625rem;border-color:var(--stroke);color:var(--white);cursor:pointer;border-radius:var(--inputBorderRadius);gap:var(--marginMedium);margin-top:-.3125rem}.jwt-signer-form-container .select-file-button:hover{filter:brightness(.8)}.jwt-signer-form-container .select-file-button:active{filter:grayscale(1);transform:translateY(.0625rem)}.jwt-signer-form-container .select-file-button .spinner{display:inline-block;width:.5rem;height:.5rem;border-radius:50%;background:transparent;border-top:.125rem solid white;border-right:.125rem solid white;border-bottom:.125rem solid transparent;border-left:.125rem solid transparent;-webkit-animation:loading .5s infinite linear;animation:loading .5s infinite linear}.jwt-signer-form-container .select-file-label-container{gap:var(--marginMedium)}::ng-deep .jwt-signer-form-container select:disabled,::ng-deep .jwt-signer-form-container input:disabled{opacity:.75!important;cursor:text;background-color:var(--stroke)!important}::ng-deep .api-data-no-wrap .jse-text-mode{width:100%}::ng-deep .api-data-no-wrap .jse-text-mode .cm-editor{width:100%}::ng-deep .api-data-no-wrap .jse-text-mode .cm-editor .cm-scroller{width:100%}::ng-deep .api-data-no-wrap .jse-text-mode .cm-editor .cm-scroller .cm-content{width:100%;overflow:auto}::ng-deep .api-data-no-wrap .jse-text-mode .cm-editor .cm-scroller .cm-content .\\37cr{white-space:nowrap}.radio-group-container{display:flex;gap:var(--marginLarge)}.radio-group-container:focus .radio-button-container .radio-button-circle{border-color:var(--primaryColor)}.radio-group-container .radio-button-container{display:flex;flex-direction:row;align-items:center;background-color:var(--formGroup);border-radius:var(--inputBorderRadius);width:100%;padding-left:var(--paddingMedium);padding-right:var(--paddingMedium);padding-top:var(--paddingLarge);padding-bottom:var(--paddingLarge);cursor:pointer;gap:var(--marginMedium)}.radio-group-container .radio-button-container .radio-button-circle{display:flex;width:1.3125rem;height:1.3125rem;border-radius:1.3125rem;border-width:.125rem;border-style:solid;border-color:var(--offWhite_ALWAYS);align-items:center;justify-content:center;background-color:var(--offWhite)}.radio-group-container .radio-button-container .radio-button-circle .radio-button-inner-circle{display:none;width:.9375rem;height:.9375rem;border-radius:.9375rem;align-items:center;justify-content:center;background-color:var(--primaryColor)}.radio-group-container .radio-button-container.selected .radio-button-circle .radio-button-inner-circle{display:flex}.radio-group-container .radio-button-container:active .radio-button-circle .radio-button-inner-circle{display:flex;background-color:var(--menu)}.radio-group-container .radio-button-container .radio-button-label{color:var(--offWhite_ALWAYS);font-size:.875rem;font-weight:600}.radio-button-container.toggle-readonly{cursor:default;pointer-events:none;opacity:.92}.select-file-button.toggle-readonly{cursor:default;pointer-events:none;opacity:.85}.test-oauth-button{color:var(--offWhite_ALWAYS);height:var(--defaultInputHeight);border-style:solid;border-color:var(--inputBorder);border-radius:var(--inputBorderRadius);vertical-align:middle;text-align:center;position:relative;display:flex;align-items:center;justify-content:center;overflow:hidden;transition:color .2s;font-size:.9375rem;font-weight:600;font-family:Open Sans,sans-serif;text-transform:uppercase;cursor:pointer;width:100%;background-color:var(--primaryColor);margin-top:1.5rem;max-width:15rem}.test-oauth-button.oidc-verified{filter:brightness(.95);color:var(--offWhite_ALWAYS);background-color:var(--green)}.test-oauth-button .spinner{display:inline-block;width:var(--spinnerSizeMedium);height:var(--spinnerSizeMedium);border-radius:50%;background:transparent;border-top:var(--spinnerBorderTopMedium);border-right:var(--spinnerBorderRightMedium);border-bottom:var(--spinnerBorderBottomMedium);border-left:var(--spinnerBorderLeftMedium);animation:loading .5s infinite linear;margin-left:var(--marginSmall)}.test-oauth-button:hover{filter:brightness(.95)}.test-oauth-button:active{transform:translateY(1px)}.success-check{width:1.0625rem;height:1.0625rem;cursor:pointer;background-color:var(--navigation);margin:0;margin-top:.25rem;border-radius:2rem;margin-left:var(--marginSmall)}.success-check:checked{background-color:var(--green)!important;background-image:url(/assets/images/check.png);background-size:.675rem;background-position:center center;background-repeat:no-repeat;border:none}.success-check:checked:disabled{opacity:1!important}.oidc-token-claims-container{background-color:var(--backgroundMuted);height:fit-content;display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;padding:var(--paddingMedium);border-radius:var(--inputBorderRadius);color:var(--tableText);font-weight:600;overflow:auto}.oidc-token-claims-container .oidc-token-claims-title{font-weight:600;font-size:1rem;white-space:nowrap;margin-bottom:var(--marginMedium)}.oidc-error-message-container{background-color:var(--lightRed);height:fit-content;display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;padding:var(--paddingMedium);border-radius:var(--inputBorderRadius);color:var(--red);font-weight:600;overflow:auto}.oidc-error-message-container .oidc-error-message-source{font-weight:600;font-size:1rem;white-space:nowrap}.oidc-error-message-container .oidc-error-message-detail2,.oidc-error-message-container .oidc-error-message-detail{margin-top:var(--marginSmall);font-weight:400;white-space:nowrap}.oidc-error-message-container::-webkit-scrollbar{height:6px;width:6px}.oidc-error-message-container::-webkit-scrollbar-thumb{background-color:var(--menu);border-radius:10px}.oidc-error-message-container:hover::-webkit-scrollbar-thumb{background-clip:unset!important}.read-only-2{display:flex;overflow:auto;white-space:nowrap;opacity:1!important;cursor:pointer;padding:var(--paddingMedium);padding-bottom:var(--paddingSmall)}.read-only-2::-webkit-scrollbar{height:6px;width:6px}.read-only-2::-webkit-scrollbar-thumb{background-color:var(--menu);border-radius:10px}.read-only-2:hover{text-decoration:underline}.read-only-2:hover::-webkit-scrollbar-thumb{background-clip:unset}.copy-link-container{display:flex;flex-direction:row;justify-content:space-between;align-items:center;opacity:1!important;cursor:text;border-radius:var(--inputBorderRadius);height:var(--defaultInputHeight);background-color:var(--backgroundMuted)!important}.copy-button{display:flex;align-items:center;justify-content:center;height:100%;width:2.575rem;margin-left:.125rem;background:url(/assets/svgs/copy.svg) no-repeat;background-size:1.125rem 1.125rem;background-position:center;cursor:pointer;border-top-right-radius:var(--inputBorderRadius);border-bottom-right-radius:var(--inputBorderRadius);background-color:#0000000f}.copy-button:hover{filter:brightness(.95);background-color:#0000001f}.help-panel-content{display:flex;flex-direction:column;padding-top:3rem}.help-panel-content .help-panel-title{margin:0 0 .25rem;color:var(--primary);font-size:1.5rem;font-weight:600;line-height:1.3;padding-right:2rem}.help-panel-content .help-panel-intro{line-height:1.6;margin-bottom:1.5rem;color:var(--tableText);font-size:.9375rem}.help-panel-content .help-panel-section{margin-bottom:1.5rem;padding:1rem;background-color:var(--navigation);border-radius:var(--inputBorderRadius);border-left:.25rem solid var(--primary)}.help-panel-content .help-panel-section .help-panel-subtitle{margin:0 0 .75rem;color:var(--primary);font-size:1.125rem;font-weight:600;display:flex;align-items:center;gap:.5rem}.help-panel-content .help-panel-section .help-panel-subtitle span[class^=icon-]{font-size:1rem;opacity:.8}.help-panel-content .help-panel-section .help-panel-text{margin:0;line-height:1.6;color:var(--tableText);font-size:.875rem}.help-panel-content .help-panel-note{padding:1rem;background-color:#ffc1071a;border-radius:var(--inputBorderRadius);border-left:.25rem solid #ffc107;color:var(--tableText);font-size:.875rem;line-height:1.6}.help-panel-content .help-panel-note strong{color:#ffc107}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.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: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: ChipsInputComponent, selector: "lib-chips-input", inputs: ["placeholder", "inputId", "addOnBlur", "allowDuplicate", "separator", "invalid"], outputs: ["onAdd", "onRemove", "onModelChange", "onBlur", "keyup"] }, { kind: "component", type: SideModalComponent, selector: "lib-side-modal", inputs: ["showClose", "open"], outputs: ["openChange"] }, { kind: "component", type: FormHeaderComponent, selector: "lib-form-header", inputs: ["data", "badges", "title", "moreActions", "formView", "saveDisabled", "saveTooltip", "actionButtonText", "moreActionsText", "showHeaderToggle", "showHeaderButton", "strictReadonlyUi", "multiActions"], outputs: ["formViewChange", "actionRequested", "multiActionRequested"] }, { kind: "component", type: FormFieldContainerComponent, selector: "lib-form-field-container", inputs: ["title", "title2", "titleIcon", "layout", "helpText", "helpText2", "label", "count", "action", "actionLabel", "class", "contentStyle", "contentClass", "showHeader", "showToggle", "headerActions", "headerToggle"], outputs: ["actionRequested", "headerToggleChange", "helpClicked"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "lib-loading-indicator", inputs: ["isLoading"] }, { kind: "component", type: FormFieldToggleComponent, selector: "lib-form-field-toggle", inputs: ["label", "orientation", "labelOn", "labelOff", "toggleOn"], outputs: ["toggleOnChange"] }, { kind: "component", type: JsonViewComponent, selector: "lib-json-view", inputs: ["data", "schema", "readOnly", "jsonInvalid", "showCopy", "showLineNumbers"], outputs: ["dataChange", "jsonChange", "jsonInvalidChange"] }, { kind: "component", type: CustomTagsComponent, selector: "lib-custom-tags", inputs: ["tags", "readOnly"], outputs: ["tagsChange"] }] }); }
29489
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: JwtSignerFormComponent, isStandalone: false, selector: "lib-configuration", inputs: { errors: "errors" }, outputs: { close: "close" }, viewQueries: [{ propertyName: "filterInput", first: true, predicate: ["fileSelect"], descendants: true }, { propertyName: "oidcVerification", first: true, predicate: ["oidcVerification"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"projectable-form-wrapper\"\n (keyup.enter)=\"save($event)\"\n (keyup.escape)=\"closeModal(false)\"\n tabindex=\"0\"\n >\n <lib-form-header\n [data]=\"formData\"\n [title]=\"(formData.id ? 'JWT Signer Details' : 'Create JWT Signer')\"\n [moreActions]=\"moreActions\"\n [multiActions]=\"multiActions\"\n (actionRequested)=\"headerActionRequested($event)\"\n (multiActionRequested)=\"multiActionRequested($event)\"\n [(formView)]=\"formView\"\n [saveDisabled]=\"svc.saveDisabled || formDataInvalid || formPermissionSaveDisabled\"\n [saveTooltip]=\"formPermissionSaveTooltip\"\n [strictReadonlyUi]=\"useStrictReadonlyForm\"\n ></lib-form-header>\n <div class=\"jwt-signer-form-container projectable-form-container\" #scrollContainer>\n <div class=\"projectable-form-main-column form-group-row\" [hidden]=\"formView !== 'simple'\">\n <div class=\"form-group-column three-fifths\">\n <lib-form-field-container\n [title]=\"'Name'\"\n [label]=\"useStrictReadonlyForm ? '' : 'REQUIRED'\"\n >\n @if (!useStrictReadonlyForm) {\n <input class=\"form-field-input\" [(ngModel)]=\"formData.name\" [ngClass]=\"{error: errors['name']}\" placeholder=\"Name this JWT Signer\" #nameFieldInput/>\n } @else {\n <div class=\"form-field-readonly-value\">{{ formData.name }}</div>\n }\n </lib-form-field-container>\n <lib-form-field-container\n [layout]=\"'column'\"\n [showHeader]=\"false\"\n >\n <div class=\"form-field-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Issuer</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The &quot;issuer&quot; field on the JWT Signer must match the &quot;iss&quot; field on the JWT\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <span class=\"form-field-label\" >{{ useStrictReadonlyForm ? '' : 'REQUIRED' }}</span>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter issuer\" [(ngModel)]=\"formData.issuer\" [ngClass]=\"{error: errors['issuer']}\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Audience</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The expected &quot;aud&quot; (audience) field on the JWT\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter audience\" [(ngModel)]=\"formData.audience\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-row\">\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Claims Property</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The field on the JWT for mapping to an identity. Matches the identity's id or externalId if &quot;Use External Id&quot; is set to true. Defaults to &quot;sub&quot;\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter claims property\" [(ngModel)]=\"formData.claimsProperty\" (keyup)=\"checkExternalIdToggle()\" [ngClass]=\"{error: errors['claimsProperty']}\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <div class=\"config-container-label\" style=\"font-weight: 600; font-size: 14px;\">External ID</div>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"If enabled, will match the &quot;claimsProperty&quot; to an identity's &quot;externalId&quot; field. If false, it will match to the identity's id field.\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n </div>\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"form-field-title-container\">\n <div class=\"config-container-label\">Use External ID</div>\n </div>\n <div\n (click)=\"!useStrictReadonlyForm && toggleUseExternalId()\"\n [ngClass]=\"{ on: formData.useExternalId, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"!formData.useExternalId\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.useExternalId\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n <lib-form-field-container\n [showHeader]=\"false\"\n >\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Client ID</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The client id to use when authenticating to the IDP\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter client ID\" [(ngModel)]=\"formData.clientId\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">External Auth Url</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The URL that users are directed to, to obtain a JWT\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter external auth URL\" [(ngModel)]=\"formData.externalAuthUrl\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Scopes</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"Enter as a comma or space seperated list of claims/permissions\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <lib-chips-input\n (keyup)=\"scopesOnKeyup($event)\"\n [(ngModel)]=\"formData.scopes\"\n [allowDuplicate]=\"false\"\n placeholder=\"Enter scopes\"\n [addOnBlur]=\"true\"\n separator=\",\"\n [disabled]=\"useStrictReadonlyForm\"\n ></lib-chips-input>\n </div>\n </lib-form-field-container>\n @if (showAutoEnrollment) {\n <lib-form-field-container\n [title]=\"'Auto Enrollment'\"\n [helpText]=\"'Click for more information'\"\n (helpClicked)=\"showAutoEnrollHelp()\"\n >\n <div class=\"form-field-row\">\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">Auto-Enroll to Cert</div>\n <div\n (click)=\"toggleEnrollToCertEnabled()\"\n [ngClass]=\"{ on: formData.enrollToCertEnabled, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"!formData.enrollToCertEnabled\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.enrollToCertEnabled\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">Auto-Enroll to Token</div>\n <div\n (click)=\"toggleEnrollToTokenEnabled()\"\n [ngClass]=\"{ on: formData.enrollToTokenEnabled, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"!formData.enrollToTokenEnabled\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.enrollToTokenEnabled\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n }\n <lib-form-field-toggle [(toggleOn)]=\"showMore\" (toggleOnChange)=\"showMoreChanged($event)\" style=\"margin: 0px 10px\"></lib-form-field-toggle>\n @if (showMore && showTokenType) {\n <lib-form-field-container\n [title]=\"'Target Token Type'\"\n [label]=\"'Required'\"\n [helpText]=\"'The target token type. Can be either Access or ID'\"\n class=\"form-field-advanced\"\n >\n <select\n [(ngModel)]=\"targetToken\"\n id=\"authPolicyId\"\n class=\"form-field-dropdown\"\n [disabled]=\"useStrictReadonlyForm\"\n >\n <option value=\"ACCESS\">Access</option>\n <option value=\"ID\">ID</option>\n </select>\n </lib-form-field-container>\n }\n <div [hidden]=\"!showMore\" class=\"form-group-column\">\n @if (showAutoEnrollment) {\n <lib-form-field-container\n [title]=\"'Auto Enrollment Options'\"\n class=\"form-field-advanced\"\n >\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Enrollment Auth Policy</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The authentication policy assigned to identities created through auto-enrollment. Defaults to the &quot;default&quot; policy.\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <select\n [(ngModel)]=\"enrollAuthPolicyId\"\n class=\"form-field-dropdown\"\n [disabled]=\"useStrictReadonlyForm\"\n >\n <option value=\"\">Select an Authentication Policy</option>\n @for (policy of authPolicies; track policy.id) {\n <option [value]=\"policy.id\">{{ policy.name }}</option>\n }\n </select>\n </div>\n <div class=\"form-field-row\">\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Name Claims Selector</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The claims JSON pointer selector or top level property used for the name of enrolling identities. Defaults to /sub\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter name claims selector\" [(ngModel)]=\"formData.enrollNameClaimsSelector\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Attribute Claims Selector</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The claims JSON pointer selector or top level property used for the attributes of enrolling identities.\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter attribute claims selector\" [(ngModel)]=\"formData.enrollAttributeClaimsSelector\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n </div>\n </lib-form-field-container>\n }\n <lib-form-field-container\n [title]=\"'Verify OIDC Authentication'\"\n class=\"form-field-advanced\"\n [ngClass]=\"{'formFieldError': !oidcVerified && oidcErrorMessageDetail, 'formFieldSuccess': oidcVerified}\"\n >\n <div class=\"form-field-row\" #oidcVerification>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <div class=\"form-field-title\">Callback URL</div>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"This URL must be added to the list of allowed callback URLs in your OAuth provider configuration\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <div class=\"copy-link-container\">\n <pre class=\"read-only-2\" (click)=\"copyCallbackURL()\">{{ callbackURL }}</pre>\n <div class=\"copy-button\" (click)=\"copyCallbackURL()\"></div>\n </div>\n </div>\n @if (!useStrictReadonlyForm) {\n <div class=\"test-oauth-button\"\n [ngClass]=\"{'oidc-verified': oidcVerified}\"\n (click)=\"testOIDCAuthentication()\"\n >\n {{oidcVerified ? 'Verified' : 'Authenticate' }}\n @if (oauthLoading) {\n <div class=\"spinner\"></div>\n }\n @if (oidcVerified) {\n <input type=\"checkbox\" [disabled]=\"true\" [checked]=\"true\" class=\"success-check\">\n }\n </div>\n }\n </div>\n @if (oidcErrorMessageDetail) {\n <div class=\"oidc-error-message-container\">\n <code class=\"oidc-error-message-source\">{{oidcErrorMessageSource}}: </code>\n <code class=\"oidc-error-message-detail\">{{oidcErrorMessageDetail}}</code>\n @if (oidcErrorMessageDetail2) {\n <code class=\"oidc-error-message-detail2\">{{oidcErrorMessageDetail2}}</code>\n }\n @if (oidcErrorMessageDetail3) {\n <code class=\"oidc-error-message-detail2\">{{oidcErrorMessageDetail3}}</code>\n }\n </div>\n }\n @if (oidcAuthTokenClaims) {\n <div class=\"oidc-token-claims-container\">\n <code class=\"oidc-token-claims-title\">OAuth Token Claims: </code>\n <lib-json-view [showLineNumbers]=\"false\" [(data)]=\"oidcAuthTokenClaims\" [readOnly]=\"useStrictReadonlyForm\"></lib-json-view>\n </div>\n }\n </lib-form-field-container>\n <lib-form-field-container\n [title]=\"'Custom Tags'\"\n [label]=\"'OPTIONAL'\"\n class=\"form-field-advanced\"\n >\n <lib-custom-tags [(tags)]=\"formData.tags\" [readOnly]=\"useStrictReadonlyForm\"></lib-custom-tags>\n </lib-form-field-container>\n </div>\n </div>\n <div class=\"form-group-column two-fifths\">\n <lib-form-field-container\n [title]=\"'Verification'\"\n [label]=\"useStrictReadonlyForm ? '' : 'REQUIRED'\"\n >\n <div\n class=\"radio-group-container\"\n [class.radio-group-readonly]=\"useStrictReadonlyForm\"\n (keydown)=\"radioKeyDownHandler($event)\"\n [attr.tabindex]=\"useStrictReadonlyForm ? -1 : 0\"\n >\n <div\n class=\"radio-button-container\"\n [ngClass]=\"{'selected': signatureMethod === 'JWKS_ENDPOINT', 'toggle-readonly': useStrictReadonlyForm}\"\n (click)=\"!useStrictReadonlyForm && selectSignatureMethod('JWKS_ENDPOINT')\"\n >\n <div class=\"radio-button-circle\">\n <div class=\"radio-button-inner-circle\"></div>\n </div>\n <span class=\"radio-button-label\">JWKS Endpoint</span>\n </div>\n <div\n class=\"radio-button-container\"\n [ngClass]=\"{'selected': signatureMethod === 'CERT_PEM', 'toggle-readonly': useStrictReadonlyForm}\"\n (click)=\"!useStrictReadonlyForm && selectSignatureMethod('CERT_PEM')\"\n >\n <div class=\"radio-button-circle\">\n <div class=\"radio-button-inner-circle\"></div>\n </div>\n <span class=\"radio-button-label\">Cert PEM</span>\n </div>\n </div>\n @if (signatureMethod === 'JWKS_ENDPOINT') {\n <div class=\"form-field-input-group\">\n <div class=\"form-field-label-container select-file-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">JWKS Endpoint</span>\n </div>\n </div>\n <input\n class=\"form-field-input\"\n placeholder=\"Enter endpoint URL\"\n [(ngModel)]=\"formData.jwksEndpoint\"\n [ngClass]=\"{error: errors['jwksEndpoint']}\"\n [disabled]=\"useStrictReadonlyForm\"\n />\n </div>\n </div>\n }\n @if (signatureMethod === 'CERT_PEM') {\n <div class=\"form-field-input-group\">\n <div class=\"form-field-label-container select-file-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">PEM</span>\n </div>\n <span\n class=\"select-file-button\"\n [class.toggle-readonly]=\"useStrictReadonlyForm\"\n (click)=\"!useStrictReadonlyForm && openFileSelect($event)\"\n >\n Select File\n @if (fileSelectOpening) {\n <div class=\"spinner\"></div>\n }\n </span>\n <input\n #fileSelect\n type=\"file\"\n style=\"display:none\"\n [disabled]=\"useStrictReadonlyForm\"\n (change)=\"selectPemFile($event)\"\n >\n </div>\n <textarea\n placeholder=\"Paste pem contents or select file\"\n [(ngModel)]=\"formData.certPem\"\n [ngClass]=\"{error: errors['certPem']}\"\n [disabled]=\"useStrictReadonlyForm\"\n ></textarea>\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">KID</span>\n </div>\n <span class=\"form-field-label\" >Optional</span>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter KID\" [(ngModel)]=\"formData.kid\" [disabled]=\"useStrictReadonlyForm\"/>\n </div>\n </div>\n }\n </lib-form-field-container>\n <lib-form-field-container\n [showHeader]=\"false\"\n >\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">ENABLED</div>\n <div\n (click)=\"!useStrictReadonlyForm && toggleEnabled()\"\n [ngClass]=\"{ on: formData.enabled, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"!formData.enabled\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.enabled\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n @if (showApiCalls) {\n <lib-form-field-container\n [title]=\"'API Calls'\"\n [headerActions]=\"apiOptions\"\n (actionRequested)=\"apiActionRequested($event)\"\n [class]=\"'api-data-no-wrap'\"\n >\n <div class=\"form-row\">\n <input class=\"form-field-input\" [value]=\"apiCallURL\"/>\n <div class=\"icon-copy copy-text\" (click)=\"copyToClipboard(apiCallURL)\"></div>\n </div>\n @if (formData) {\n <lib-json-view [showLineNumbers]=\"false\" [(data)]=\"apiData\" [readOnly]=\"true\" [showCopy]=\"true\"></lib-json-view>\n }\n </lib-form-field-container>\n }\n </div>\n </div>\n @if (formView === 'raw') {\n <div class=\"form-group-column\">\n @if (formData) {\n <lib-json-view [(data)]=\"formData\" [readOnly]=\"useStrictReadonlyForm\"></lib-json-view>\n }\n </div>\n }\n </div>\n</div>\n@if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n}\n<lib-side-modal [(open)]=\"showAutoEnrollHelpModal\" [showClose]=\"true\">\n <div class=\"help-panel-content\">\n <h2 class=\"help-panel-title\">What is Auto Enrollment?</h2>\n <p class=\"help-panel-intro\">\n Auto Enrollment enables automatic identity creation when users authenticate via JWT tokens.\n </p>\n\n <div class=\"help-panel-section\">\n <h3 class=\"help-panel-subtitle\">\n <span class=\"icon-certificate\"></span>\n Auto-Enroll to Cert\n </h3>\n <p class=\"help-panel-text\">\n When enabled, the controller will automatically create an identity and enroll it with a certificate when a user authenticates using a JWT token. This is useful for certificate-based authentication workflows.\n </p>\n </div>\n\n <div class=\"help-panel-section\">\n <h3 class=\"help-panel-subtitle\">\n <span class=\"icon-key\"></span>\n Auto-Enroll to Token\n </h3>\n <p class=\"help-panel-text\">\n When enabled, the controller will automatically create an identity and enroll it with an authentication token when a user authenticates using a JWT token. This is useful for token-based authentication workflows.\n </p>\n </div>\n\n <div class=\"help-panel-section\">\n <h3 class=\"help-panel-subtitle\">\n <span class=\"icon-lock\"></span>\n Enrollment Auth Policy\n </h3>\n <p class=\"help-panel-text\">\n The authentication policy assigned to identities that are created through auto-enrollment. When left unset, the controller's \"default\" authentication policy is used.\n </p>\n </div>\n\n <div class=\"help-panel-section\">\n <h3 class=\"help-panel-subtitle\">\n <span class=\"icon-identity\"></span>\n Name Claims Selector\n </h3>\n <p class=\"help-panel-text\">\n A JSON pointer selector (or top-level claim property) identifying which JWT claim supplies the name for enrolling identities. Defaults to <code>/sub</code> when unset.\n </p>\n </div>\n\n <div class=\"help-panel-section\">\n <h3 class=\"help-panel-subtitle\">\n <span class=\"icon-identity\"></span>\n Attribute Claims Selector\n </h3>\n <p class=\"help-panel-text\">\n A JSON pointer selector (or top-level claim property) identifying which JWT claim supplies the role attributes for enrolling identities.\n </p>\n </div>\n\n <div class=\"help-panel-note\">\n <strong>Note:</strong> These options are only available in Ziti 2.0 and later versions.\n </div>\n </div>\n</lib-side-modal>\n", styles: ["@charset \"UTF-8\";.jwt-signer-form-container{display:flex;flex-direction:column;height:100%;width:100%;align-items:center;gap:var(--gapXL)}.jwt-signer-form-container .select-file-button{padding-left:var(--paddingMedium);padding-right:var(--paddingMedium);height:1.5625rem;width:fit-content;white-space:nowrap;display:flex;align-items:center;justify-content:center;font-weight:600;font-size:.875rem;background:var(--primary);border-style:solid;border-width:.0625rem;border-color:var(--stroke);color:var(--white);cursor:pointer;border-radius:var(--inputBorderRadius);gap:var(--marginMedium);margin-top:-.3125rem}.jwt-signer-form-container .select-file-button:hover{filter:brightness(.8)}.jwt-signer-form-container .select-file-button:active{filter:grayscale(1);transform:translateY(.0625rem)}.jwt-signer-form-container .select-file-button .spinner{display:inline-block;width:.5rem;height:.5rem;border-radius:50%;background:transparent;border-top:.125rem solid white;border-right:.125rem solid white;border-bottom:.125rem solid transparent;border-left:.125rem solid transparent;-webkit-animation:loading .5s infinite linear;animation:loading .5s infinite linear}.jwt-signer-form-container .select-file-label-container{gap:var(--marginMedium)}::ng-deep .jwt-signer-form-container select:disabled,::ng-deep .jwt-signer-form-container input:disabled{opacity:.75!important;cursor:text;background-color:var(--stroke)!important}::ng-deep .api-data-no-wrap .jse-text-mode{width:100%}::ng-deep .api-data-no-wrap .jse-text-mode .cm-editor{width:100%}::ng-deep .api-data-no-wrap .jse-text-mode .cm-editor .cm-scroller{width:100%}::ng-deep .api-data-no-wrap .jse-text-mode .cm-editor .cm-scroller .cm-content{width:100%;overflow:auto}::ng-deep .api-data-no-wrap .jse-text-mode .cm-editor .cm-scroller .cm-content .\\37cr{white-space:nowrap}.radio-group-container{display:flex;gap:var(--marginLarge)}.radio-group-container:focus .radio-button-container .radio-button-circle{border-color:var(--primaryColor)}.radio-group-container .radio-button-container{display:flex;flex-direction:row;align-items:center;background-color:var(--formGroup);border-radius:var(--inputBorderRadius);width:100%;padding-left:var(--paddingMedium);padding-right:var(--paddingMedium);padding-top:var(--paddingLarge);padding-bottom:var(--paddingLarge);cursor:pointer;gap:var(--marginMedium)}.radio-group-container .radio-button-container .radio-button-circle{display:flex;width:1.3125rem;height:1.3125rem;border-radius:1.3125rem;border-width:.125rem;border-style:solid;border-color:var(--offWhite_ALWAYS);align-items:center;justify-content:center;background-color:var(--offWhite)}.radio-group-container .radio-button-container .radio-button-circle .radio-button-inner-circle{display:none;width:.9375rem;height:.9375rem;border-radius:.9375rem;align-items:center;justify-content:center;background-color:var(--primaryColor)}.radio-group-container .radio-button-container.selected .radio-button-circle .radio-button-inner-circle{display:flex}.radio-group-container .radio-button-container:active .radio-button-circle .radio-button-inner-circle{display:flex;background-color:var(--menu)}.radio-group-container .radio-button-container .radio-button-label{color:var(--offWhite_ALWAYS);font-size:.875rem;font-weight:600}.radio-button-container.toggle-readonly{cursor:default;pointer-events:none;opacity:.92}.select-file-button.toggle-readonly{cursor:default;pointer-events:none;opacity:.85}.test-oauth-button{color:var(--offWhite_ALWAYS);height:var(--defaultInputHeight);border-style:solid;border-color:var(--inputBorder);border-radius:var(--inputBorderRadius);vertical-align:middle;text-align:center;position:relative;display:flex;align-items:center;justify-content:center;overflow:hidden;transition:color .2s;font-size:.9375rem;font-weight:600;font-family:Open Sans,sans-serif;text-transform:uppercase;cursor:pointer;width:100%;background-color:var(--primaryColor);margin-top:1.5rem;max-width:15rem}.test-oauth-button.oidc-verified{filter:brightness(.95);color:var(--offWhite_ALWAYS);background-color:var(--green)}.test-oauth-button .spinner{display:inline-block;width:var(--spinnerSizeMedium);height:var(--spinnerSizeMedium);border-radius:50%;background:transparent;border-top:var(--spinnerBorderTopMedium);border-right:var(--spinnerBorderRightMedium);border-bottom:var(--spinnerBorderBottomMedium);border-left:var(--spinnerBorderLeftMedium);animation:loading .5s infinite linear;margin-left:var(--marginSmall)}.test-oauth-button:hover{filter:brightness(.95)}.test-oauth-button:active{transform:translateY(1px)}.success-check{width:1.0625rem;height:1.0625rem;cursor:pointer;background-color:var(--navigation);margin:0;margin-top:.25rem;border-radius:2rem;margin-left:var(--marginSmall)}.success-check:checked{background-color:var(--green)!important;background-image:url(/assets/images/check.png);background-size:.675rem;background-position:center center;background-repeat:no-repeat;border:none}.success-check:checked:disabled{opacity:1!important}.oidc-token-claims-container{background-color:var(--backgroundMuted);height:fit-content;display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;padding:var(--paddingMedium);border-radius:var(--inputBorderRadius);color:var(--tableText);font-weight:600;overflow:auto}.oidc-token-claims-container .oidc-token-claims-title{font-weight:600;font-size:1rem;white-space:nowrap;margin-bottom:var(--marginMedium)}.oidc-error-message-container{background-color:var(--lightRed);height:fit-content;display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;padding:var(--paddingMedium);border-radius:var(--inputBorderRadius);color:var(--red);font-weight:600;overflow:auto}.oidc-error-message-container .oidc-error-message-source{font-weight:600;font-size:1rem;white-space:nowrap}.oidc-error-message-container .oidc-error-message-detail2,.oidc-error-message-container .oidc-error-message-detail{margin-top:var(--marginSmall);font-weight:400;white-space:nowrap}.oidc-error-message-container::-webkit-scrollbar{height:6px;width:6px}.oidc-error-message-container::-webkit-scrollbar-thumb{background-color:var(--menu);border-radius:10px}.oidc-error-message-container:hover::-webkit-scrollbar-thumb{background-clip:unset!important}.read-only-2{display:flex;overflow:auto;white-space:nowrap;opacity:1!important;cursor:pointer;padding:var(--paddingMedium);padding-bottom:var(--paddingSmall)}.read-only-2::-webkit-scrollbar{height:6px;width:6px}.read-only-2::-webkit-scrollbar-thumb{background-color:var(--menu);border-radius:10px}.read-only-2:hover{text-decoration:underline}.read-only-2:hover::-webkit-scrollbar-thumb{background-clip:unset}.copy-link-container{display:flex;flex-direction:row;justify-content:space-between;align-items:center;opacity:1!important;cursor:text;border-radius:var(--inputBorderRadius);height:var(--defaultInputHeight);background-color:var(--backgroundMuted)!important}.copy-button{display:flex;align-items:center;justify-content:center;height:100%;width:2.575rem;margin-left:.125rem;background:url(/assets/svgs/copy.svg) no-repeat;background-size:1.125rem 1.125rem;background-position:center;cursor:pointer;border-top-right-radius:var(--inputBorderRadius);border-bottom-right-radius:var(--inputBorderRadius);background-color:#0000000f}.copy-button:hover{filter:brightness(.95);background-color:#0000001f}.help-panel-content{display:flex;flex-direction:column;padding-top:3rem}.help-panel-content .help-panel-title{margin:0 0 .25rem;color:var(--primary);font-size:1.5rem;font-weight:600;line-height:1.3;padding-right:2rem}.help-panel-content .help-panel-intro{line-height:1.6;margin-bottom:1.5rem;color:var(--tableText);font-size:.9375rem}.help-panel-content .help-panel-section{margin-bottom:1.5rem;padding:1rem;background-color:var(--navigation);border-radius:var(--inputBorderRadius);border-left:.25rem solid var(--primary)}.help-panel-content .help-panel-section .help-panel-subtitle{margin:0 0 .75rem;color:var(--primary);font-size:1.125rem;font-weight:600;display:flex;align-items:center;gap:.5rem}.help-panel-content .help-panel-section .help-panel-subtitle span[class^=icon-]{font-size:1rem;opacity:.8}.help-panel-content .help-panel-section .help-panel-text{margin:0;line-height:1.6;color:var(--tableText);font-size:.875rem}.help-panel-content .help-panel-note{padding:1rem;background-color:#ffc1071a;border-radius:var(--inputBorderRadius);border-left:.25rem solid #ffc107;color:var(--tableText);font-size:.875rem;line-height:1.6}.help-panel-content .help-panel-note strong{color:#ffc107}\n"], dependencies: [{ kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.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: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i3.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: ChipsInputComponent, selector: "lib-chips-input", inputs: ["placeholder", "inputId", "addOnBlur", "allowDuplicate", "separator", "invalid"], outputs: ["onAdd", "onRemove", "onModelChange", "onBlur", "keyup"] }, { kind: "component", type: SideModalComponent, selector: "lib-side-modal", inputs: ["showClose", "open"], outputs: ["openChange"] }, { kind: "component", type: FormHeaderComponent, selector: "lib-form-header", inputs: ["data", "badges", "title", "moreActions", "formView", "saveDisabled", "saveTooltip", "actionButtonText", "moreActionsText", "showHeaderToggle", "showHeaderButton", "strictReadonlyUi", "multiActions"], outputs: ["formViewChange", "actionRequested", "multiActionRequested"] }, { kind: "component", type: FormFieldContainerComponent, selector: "lib-form-field-container", inputs: ["title", "title2", "titleIcon", "layout", "helpText", "helpText2", "label", "count", "action", "actionLabel", "class", "contentStyle", "contentClass", "showHeader", "showToggle", "headerActions", "headerToggle"], outputs: ["actionRequested", "headerToggleChange", "helpClicked"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "lib-loading-indicator", inputs: ["isLoading"] }, { kind: "component", type: FormFieldToggleComponent, selector: "lib-form-field-toggle", inputs: ["label", "orientation", "labelOn", "labelOff", "toggleOn"], outputs: ["toggleOnChange"] }, { kind: "component", type: JsonViewComponent, selector: "lib-json-view", inputs: ["data", "schema", "readOnly", "jsonInvalid", "showCopy", "showLineNumbers"], outputs: ["dataChange", "jsonChange", "jsonInvalidChange"] }, { kind: "component", type: CustomTagsComponent, selector: "lib-custom-tags", inputs: ["tags", "readOnly"], outputs: ["tagsChange"] }] }); }
27095
29490
  }
27096
29491
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: JwtSignerFormComponent, decorators: [{
27097
29492
  type: Component,
27098
- args: [{ selector: 'lib-configuration', standalone: false, template: "<div class=\"projectable-form-wrapper\"\n (keyup.enter)=\"save($event)\"\n (keyup.escape)=\"closeModal(false)\"\n tabindex=\"0\"\n >\n <lib-form-header\n [data]=\"formData\"\n [title]=\"(formData.id ? 'JWT Signer Details' : 'Create JWT Signer')\"\n [moreActions]=\"moreActions\"\n [multiActions]=\"multiActions\"\n (actionRequested)=\"headerActionRequested($event)\"\n (multiActionRequested)=\"multiActionRequested($event)\"\n [(formView)]=\"formView\"\n [saveDisabled]=\"svc.saveDisabled || formDataInvalid || formPermissionSaveDisabled\"\n [saveTooltip]=\"formPermissionSaveTooltip\"\n [strictReadonlyUi]=\"useStrictReadonlyForm\"\n ></lib-form-header>\n <div class=\"jwt-signer-form-container projectable-form-container\" #scrollContainer>\n <div class=\"projectable-form-main-column form-group-row\" [hidden]=\"formView !== 'simple'\">\n <div class=\"form-group-column three-fifths\">\n <lib-form-field-container\n [title]=\"'Name'\"\n [label]=\"useStrictReadonlyForm ? '' : 'REQUIRED'\"\n >\n @if (!useStrictReadonlyForm) {\n <input class=\"form-field-input\" [(ngModel)]=\"formData.name\" [ngClass]=\"{error: errors['name']}\" placeholder=\"Name this JWT Signer\" #nameFieldInput/>\n } @else {\n <div class=\"form-field-readonly-value\">{{ formData.name }}</div>\n }\n </lib-form-field-container>\n <lib-form-field-container\n [layout]=\"'column'\"\n [showHeader]=\"false\"\n >\n <div class=\"form-field-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Issuer</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The &quot;issuer&quot; field on the JWT Signer must match the &quot;iss&quot; field on the JWT\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <span class=\"form-field-label\" >{{ useStrictReadonlyForm ? '' : 'REQUIRED' }}</span>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter issuer\" [(ngModel)]=\"formData.issuer\" [ngClass]=\"{error: errors['issuer']}\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Audience</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The expected &quot;aud&quot; (audience) field on the JWT\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter audience\" [(ngModel)]=\"formData.audience\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-row\">\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Claims Property</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The field on the JWT for mapping to an identity. Matches the identity's id or externalId if &quot;Use External Id&quot; is set to true. Defaults to &quot;sub&quot;\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter claims property\" [(ngModel)]=\"formData.claimsProperty\" (keyup)=\"checkExternalIdToggle()\" [ngClass]=\"{error: errors['claimsProperty']}\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <div class=\"config-container-label\" style=\"font-weight: 600; font-size: 14px;\">External ID</div>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"If enabled, will match the &quot;claimsProperty&quot; to an identity's &quot;externalId&quot; field. If false, it will match to the identity's id field.\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n </div>\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"form-field-title-container\">\n <div class=\"config-container-label\">Use External ID</div>\n </div>\n <div\n (click)=\"!useStrictReadonlyForm && toggleUseExternalId()\"\n [ngClass]=\"{ on: formData.useExternalId, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"!formData.useExternalId\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.useExternalId\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n <lib-form-field-container\n [showHeader]=\"false\"\n >\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Client ID</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The client id to use when authenticating to the IDP\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter client ID\" [(ngModel)]=\"formData.clientId\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">External Auth Url</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The URL that users are directed to, to obtain a JWT\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter external auth URL\" [(ngModel)]=\"formData.externalAuthUrl\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Scopes</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"Enter as a comma or space seperated list of claims/permissions\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <lib-chips-input\n (keyup)=\"scopesOnKeyup($event)\"\n [(ngModel)]=\"formData.scopes\"\n [allowDuplicate]=\"false\"\n placeholder=\"Enter scopes\"\n [addOnBlur]=\"true\"\n separator=\",\"\n [disabled]=\"useStrictReadonlyForm\"\n ></lib-chips-input>\n </div>\n </lib-form-field-container>\n @if (showAutoEnrollment) {\n <lib-form-field-container\n [title]=\"'Auto Enrollment'\"\n [helpText]=\"'Click for more information'\"\n (helpClicked)=\"showAutoEnrollHelp()\"\n >\n <div class=\"form-field-row\">\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">Auto-Enroll to Cert</div>\n <div\n (click)=\"toggleEnrollToCertEnabled()\"\n [ngClass]=\"{ on: formData.enrollToCertEnabled, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"!formData.enrollToCertEnabled\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.enrollToCertEnabled\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">Auto-Enroll to Token</div>\n <div\n (click)=\"toggleEnrollToTokenEnabled()\"\n [ngClass]=\"{ on: formData.enrollToTokenEnabled, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"!formData.enrollToTokenEnabled\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.enrollToTokenEnabled\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n }\n <lib-form-field-toggle [(toggleOn)]=\"showMore\" (toggleOnChange)=\"showMoreChanged($event)\" style=\"margin: 0px 10px\"></lib-form-field-toggle>\n @if (showMore && showTokenType) {\n <lib-form-field-container\n [title]=\"'Target Token Type'\"\n [label]=\"'Required'\"\n [helpText]=\"'The target token type. Can be either Access or ID'\"\n class=\"form-field-advanced\"\n >\n <select\n [(ngModel)]=\"targetToken\"\n id=\"authPolicyId\"\n class=\"form-field-dropdown\"\n [disabled]=\"useStrictReadonlyForm\"\n >\n <option value=\"ACCESS\">Access</option>\n <option value=\"ID\">ID</option>\n </select>\n </lib-form-field-container>\n }\n <div [hidden]=\"!showMore\" class=\"form-group-column\">\n <lib-form-field-container\n [title]=\"'Verify OIDC Authentication'\"\n class=\"form-field-advanced\"\n [ngClass]=\"{'formFieldError': !oidcVerified && oidcErrorMessageDetail, 'formFieldSuccess': oidcVerified}\"\n >\n <div class=\"form-field-row\" #oidcVerification>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <div class=\"form-field-title\">Callback URL</div>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"This URL must be added to the list of allowed callback URLs in your OAuth provider configuration\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <div class=\"copy-link-container\">\n <pre class=\"read-only-2\" (click)=\"copyCallbackURL()\">{{ callbackURL }}</pre>\n <div class=\"copy-button\" (click)=\"copyCallbackURL()\"></div>\n </div>\n </div>\n @if (!useStrictReadonlyForm) {\n <div class=\"test-oauth-button\"\n [ngClass]=\"{'oidc-verified': oidcVerified}\"\n (click)=\"testOIDCAuthentication()\"\n >\n {{oidcVerified ? 'Verified' : 'Authenticate' }}\n @if (oauthLoading) {\n <div class=\"spinner\"></div>\n }\n @if (oidcVerified) {\n <input type=\"checkbox\" [disabled]=\"true\" [checked]=\"true\" class=\"success-check\">\n }\n </div>\n }\n </div>\n @if (oidcErrorMessageDetail) {\n <div class=\"oidc-error-message-container\">\n <code class=\"oidc-error-message-source\">{{oidcErrorMessageSource}}: </code>\n <code class=\"oidc-error-message-detail\">{{oidcErrorMessageDetail}}</code>\n @if (oidcErrorMessageDetail2) {\n <code class=\"oidc-error-message-detail2\">{{oidcErrorMessageDetail2}}</code>\n }\n @if (oidcErrorMessageDetail3) {\n <code class=\"oidc-error-message-detail2\">{{oidcErrorMessageDetail3}}</code>\n }\n </div>\n }\n @if (oidcAuthTokenClaims) {\n <div class=\"oidc-token-claims-container\">\n <code class=\"oidc-token-claims-title\">OAuth Token Claims: </code>\n <lib-json-view [showLineNumbers]=\"false\" [(data)]=\"oidcAuthTokenClaims\" [readOnly]=\"useStrictReadonlyForm\"></lib-json-view>\n </div>\n }\n </lib-form-field-container>\n <lib-form-field-container\n [title]=\"'Custom Tags'\"\n [label]=\"'OPTIONAL'\"\n class=\"form-field-advanced\"\n >\n <lib-custom-tags [(tags)]=\"formData.tags\" [readOnly]=\"useStrictReadonlyForm\"></lib-custom-tags>\n </lib-form-field-container>\n </div>\n </div>\n <div class=\"form-group-column two-fifths\">\n <lib-form-field-container\n [title]=\"'Verification'\"\n [label]=\"useStrictReadonlyForm ? '' : 'REQUIRED'\"\n >\n <div\n class=\"radio-group-container\"\n [class.radio-group-readonly]=\"useStrictReadonlyForm\"\n (keydown)=\"radioKeyDownHandler($event)\"\n [attr.tabindex]=\"useStrictReadonlyForm ? -1 : 0\"\n >\n <div\n class=\"radio-button-container\"\n [ngClass]=\"{'selected': signatureMethod === 'JWKS_ENDPOINT', 'toggle-readonly': useStrictReadonlyForm}\"\n (click)=\"!useStrictReadonlyForm && selectSignatureMethod('JWKS_ENDPOINT')\"\n >\n <div class=\"radio-button-circle\">\n <div class=\"radio-button-inner-circle\"></div>\n </div>\n <span class=\"radio-button-label\">JWKS Endpoint</span>\n </div>\n <div\n class=\"radio-button-container\"\n [ngClass]=\"{'selected': signatureMethod === 'CERT_PEM', 'toggle-readonly': useStrictReadonlyForm}\"\n (click)=\"!useStrictReadonlyForm && selectSignatureMethod('CERT_PEM')\"\n >\n <div class=\"radio-button-circle\">\n <div class=\"radio-button-inner-circle\"></div>\n </div>\n <span class=\"radio-button-label\">Cert PEM</span>\n </div>\n </div>\n @if (signatureMethod === 'JWKS_ENDPOINT') {\n <div class=\"form-field-input-group\">\n <div class=\"form-field-label-container select-file-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">JWKS Endpoint</span>\n </div>\n </div>\n <input\n class=\"form-field-input\"\n placeholder=\"Enter endpoint URL\"\n [(ngModel)]=\"formData.jwksEndpoint\"\n [ngClass]=\"{error: errors['jwksEndpoint']}\"\n [disabled]=\"useStrictReadonlyForm\"\n />\n </div>\n </div>\n }\n @if (signatureMethod === 'CERT_PEM') {\n <div class=\"form-field-input-group\">\n <div class=\"form-field-label-container select-file-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">PEM</span>\n </div>\n <span\n class=\"select-file-button\"\n [class.toggle-readonly]=\"useStrictReadonlyForm\"\n (click)=\"!useStrictReadonlyForm && openFileSelect($event)\"\n >\n Select File\n @if (fileSelectOpening) {\n <div class=\"spinner\"></div>\n }\n </span>\n <input\n #fileSelect\n type=\"file\"\n style=\"display:none\"\n [disabled]=\"useStrictReadonlyForm\"\n (change)=\"selectPemFile($event)\"\n >\n </div>\n <textarea\n placeholder=\"Paste pem contents or select file\"\n [(ngModel)]=\"formData.certPem\"\n [ngClass]=\"{error: errors['certPem']}\"\n [disabled]=\"useStrictReadonlyForm\"\n ></textarea>\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">KID</span>\n </div>\n <span class=\"form-field-label\" >Optional</span>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter KID\" [(ngModel)]=\"formData.kid\" [disabled]=\"useStrictReadonlyForm\"/>\n </div>\n </div>\n }\n </lib-form-field-container>\n <lib-form-field-container\n [showHeader]=\"false\"\n >\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">ENABLED</div>\n <div\n (click)=\"!useStrictReadonlyForm && toggleEnabled()\"\n [ngClass]=\"{ on: formData.enabled, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"!formData.enabled\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.enabled\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n @if (showApiCalls) {\n <lib-form-field-container\n [title]=\"'API Calls'\"\n [headerActions]=\"apiOptions\"\n (actionRequested)=\"apiActionRequested($event)\"\n [class]=\"'api-data-no-wrap'\"\n >\n <div class=\"form-row\">\n <input class=\"form-field-input\" [value]=\"apiCallURL\"/>\n <div class=\"icon-copy copy-text\" (click)=\"copyToClipboard(apiCallURL)\"></div>\n </div>\n @if (formData) {\n <lib-json-view [showLineNumbers]=\"false\" [(data)]=\"apiData\" [readOnly]=\"true\" [showCopy]=\"true\"></lib-json-view>\n }\n </lib-form-field-container>\n }\n </div>\n </div>\n @if (formView === 'raw') {\n <div class=\"form-group-column\">\n @if (formData) {\n <lib-json-view [(data)]=\"formData\" [readOnly]=\"useStrictReadonlyForm\"></lib-json-view>\n }\n </div>\n }\n </div>\n</div>\n@if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n}\n<lib-side-modal [(open)]=\"showAutoEnrollHelpModal\" [showClose]=\"true\">\n <div class=\"help-panel-content\">\n <h2 class=\"help-panel-title\">What is Auto Enrollment?</h2>\n <p class=\"help-panel-intro\">\n Auto Enrollment enables automatic identity creation when users authenticate via JWT tokens.\n </p>\n\n <div class=\"help-panel-section\">\n <h3 class=\"help-panel-subtitle\">\n <span class=\"icon-certificate\"></span>\n Auto-Enroll to Cert\n </h3>\n <p class=\"help-panel-text\">\n When enabled, the controller will automatically create an identity and enroll it with a certificate when a user authenticates using a JWT token. This is useful for certificate-based authentication workflows.\n </p>\n </div>\n\n <div class=\"help-panel-section\">\n <h3 class=\"help-panel-subtitle\">\n <span class=\"icon-key\"></span>\n Auto-Enroll to Token\n </h3>\n <p class=\"help-panel-text\">\n When enabled, the controller will automatically create an identity and enroll it with an authentication token when a user authenticates using a JWT token. This is useful for token-based authentication workflows.\n </p>\n </div>\n\n <div class=\"help-panel-note\">\n <strong>Note:</strong> These options are only available in Ziti 2.0 and later versions.\n </div>\n </div>\n</lib-side-modal>\n", styles: ["@charset \"UTF-8\";.jwt-signer-form-container{display:flex;flex-direction:column;height:100%;width:100%;align-items:center;gap:var(--gapXL)}.jwt-signer-form-container .select-file-button{padding-left:var(--paddingMedium);padding-right:var(--paddingMedium);height:1.5625rem;width:fit-content;white-space:nowrap;display:flex;align-items:center;justify-content:center;font-weight:600;font-size:.875rem;background:var(--primary);border-style:solid;border-width:.0625rem;border-color:var(--stroke);color:var(--white);cursor:pointer;border-radius:var(--inputBorderRadius);gap:var(--marginMedium);margin-top:-.3125rem}.jwt-signer-form-container .select-file-button:hover{filter:brightness(.8)}.jwt-signer-form-container .select-file-button:active{filter:grayscale(1);transform:translateY(.0625rem)}.jwt-signer-form-container .select-file-button .spinner{display:inline-block;width:.5rem;height:.5rem;border-radius:50%;background:transparent;border-top:.125rem solid white;border-right:.125rem solid white;border-bottom:.125rem solid transparent;border-left:.125rem solid transparent;-webkit-animation:loading .5s infinite linear;animation:loading .5s infinite linear}.jwt-signer-form-container .select-file-label-container{gap:var(--marginMedium)}::ng-deep .jwt-signer-form-container select:disabled,::ng-deep .jwt-signer-form-container input:disabled{opacity:.75!important;cursor:text;background-color:var(--stroke)!important}::ng-deep .api-data-no-wrap .jse-text-mode{width:100%}::ng-deep .api-data-no-wrap .jse-text-mode .cm-editor{width:100%}::ng-deep .api-data-no-wrap .jse-text-mode .cm-editor .cm-scroller{width:100%}::ng-deep .api-data-no-wrap .jse-text-mode .cm-editor .cm-scroller .cm-content{width:100%;overflow:auto}::ng-deep .api-data-no-wrap .jse-text-mode .cm-editor .cm-scroller .cm-content .\\37cr{white-space:nowrap}.radio-group-container{display:flex;gap:var(--marginLarge)}.radio-group-container:focus .radio-button-container .radio-button-circle{border-color:var(--primaryColor)}.radio-group-container .radio-button-container{display:flex;flex-direction:row;align-items:center;background-color:var(--formGroup);border-radius:var(--inputBorderRadius);width:100%;padding-left:var(--paddingMedium);padding-right:var(--paddingMedium);padding-top:var(--paddingLarge);padding-bottom:var(--paddingLarge);cursor:pointer;gap:var(--marginMedium)}.radio-group-container .radio-button-container .radio-button-circle{display:flex;width:1.3125rem;height:1.3125rem;border-radius:1.3125rem;border-width:.125rem;border-style:solid;border-color:var(--offWhite_ALWAYS);align-items:center;justify-content:center;background-color:var(--offWhite)}.radio-group-container .radio-button-container .radio-button-circle .radio-button-inner-circle{display:none;width:.9375rem;height:.9375rem;border-radius:.9375rem;align-items:center;justify-content:center;background-color:var(--primaryColor)}.radio-group-container .radio-button-container.selected .radio-button-circle .radio-button-inner-circle{display:flex}.radio-group-container .radio-button-container:active .radio-button-circle .radio-button-inner-circle{display:flex;background-color:var(--menu)}.radio-group-container .radio-button-container .radio-button-label{color:var(--offWhite_ALWAYS);font-size:.875rem;font-weight:600}.radio-button-container.toggle-readonly{cursor:default;pointer-events:none;opacity:.92}.select-file-button.toggle-readonly{cursor:default;pointer-events:none;opacity:.85}.test-oauth-button{color:var(--offWhite_ALWAYS);height:var(--defaultInputHeight);border-style:solid;border-color:var(--inputBorder);border-radius:var(--inputBorderRadius);vertical-align:middle;text-align:center;position:relative;display:flex;align-items:center;justify-content:center;overflow:hidden;transition:color .2s;font-size:.9375rem;font-weight:600;font-family:Open Sans,sans-serif;text-transform:uppercase;cursor:pointer;width:100%;background-color:var(--primaryColor);margin-top:1.5rem;max-width:15rem}.test-oauth-button.oidc-verified{filter:brightness(.95);color:var(--offWhite_ALWAYS);background-color:var(--green)}.test-oauth-button .spinner{display:inline-block;width:var(--spinnerSizeMedium);height:var(--spinnerSizeMedium);border-radius:50%;background:transparent;border-top:var(--spinnerBorderTopMedium);border-right:var(--spinnerBorderRightMedium);border-bottom:var(--spinnerBorderBottomMedium);border-left:var(--spinnerBorderLeftMedium);animation:loading .5s infinite linear;margin-left:var(--marginSmall)}.test-oauth-button:hover{filter:brightness(.95)}.test-oauth-button:active{transform:translateY(1px)}.success-check{width:1.0625rem;height:1.0625rem;cursor:pointer;background-color:var(--navigation);margin:0;margin-top:.25rem;border-radius:2rem;margin-left:var(--marginSmall)}.success-check:checked{background-color:var(--green)!important;background-image:url(/assets/images/check.png);background-size:.675rem;background-position:center center;background-repeat:no-repeat;border:none}.success-check:checked:disabled{opacity:1!important}.oidc-token-claims-container{background-color:var(--backgroundMuted);height:fit-content;display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;padding:var(--paddingMedium);border-radius:var(--inputBorderRadius);color:var(--tableText);font-weight:600;overflow:auto}.oidc-token-claims-container .oidc-token-claims-title{font-weight:600;font-size:1rem;white-space:nowrap;margin-bottom:var(--marginMedium)}.oidc-error-message-container{background-color:var(--lightRed);height:fit-content;display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;padding:var(--paddingMedium);border-radius:var(--inputBorderRadius);color:var(--red);font-weight:600;overflow:auto}.oidc-error-message-container .oidc-error-message-source{font-weight:600;font-size:1rem;white-space:nowrap}.oidc-error-message-container .oidc-error-message-detail2,.oidc-error-message-container .oidc-error-message-detail{margin-top:var(--marginSmall);font-weight:400;white-space:nowrap}.oidc-error-message-container::-webkit-scrollbar{height:6px;width:6px}.oidc-error-message-container::-webkit-scrollbar-thumb{background-color:var(--menu);border-radius:10px}.oidc-error-message-container:hover::-webkit-scrollbar-thumb{background-clip:unset!important}.read-only-2{display:flex;overflow:auto;white-space:nowrap;opacity:1!important;cursor:pointer;padding:var(--paddingMedium);padding-bottom:var(--paddingSmall)}.read-only-2::-webkit-scrollbar{height:6px;width:6px}.read-only-2::-webkit-scrollbar-thumb{background-color:var(--menu);border-radius:10px}.read-only-2:hover{text-decoration:underline}.read-only-2:hover::-webkit-scrollbar-thumb{background-clip:unset}.copy-link-container{display:flex;flex-direction:row;justify-content:space-between;align-items:center;opacity:1!important;cursor:text;border-radius:var(--inputBorderRadius);height:var(--defaultInputHeight);background-color:var(--backgroundMuted)!important}.copy-button{display:flex;align-items:center;justify-content:center;height:100%;width:2.575rem;margin-left:.125rem;background:url(/assets/svgs/copy.svg) no-repeat;background-size:1.125rem 1.125rem;background-position:center;cursor:pointer;border-top-right-radius:var(--inputBorderRadius);border-bottom-right-radius:var(--inputBorderRadius);background-color:#0000000f}.copy-button:hover{filter:brightness(.95);background-color:#0000001f}.help-panel-content{display:flex;flex-direction:column;padding-top:3rem}.help-panel-content .help-panel-title{margin:0 0 .25rem;color:var(--primary);font-size:1.5rem;font-weight:600;line-height:1.3;padding-right:2rem}.help-panel-content .help-panel-intro{line-height:1.6;margin-bottom:1.5rem;color:var(--tableText);font-size:.9375rem}.help-panel-content .help-panel-section{margin-bottom:1.5rem;padding:1rem;background-color:var(--navigation);border-radius:var(--inputBorderRadius);border-left:.25rem solid var(--primary)}.help-panel-content .help-panel-section .help-panel-subtitle{margin:0 0 .75rem;color:var(--primary);font-size:1.125rem;font-weight:600;display:flex;align-items:center;gap:.5rem}.help-panel-content .help-panel-section .help-panel-subtitle span[class^=icon-]{font-size:1rem;opacity:.8}.help-panel-content .help-panel-section .help-panel-text{margin:0;line-height:1.6;color:var(--tableText);font-size:.875rem}.help-panel-content .help-panel-note{padding:1rem;background-color:#ffc1071a;border-radius:var(--inputBorderRadius);border-left:.25rem solid #ffc107;color:var(--tableText);font-size:.875rem;line-height:1.6}.help-panel-content .help-panel-note strong{color:#ffc107}\n"] }]
29493
+ args: [{ selector: 'lib-configuration', standalone: false, template: "<div class=\"projectable-form-wrapper\"\n (keyup.enter)=\"save($event)\"\n (keyup.escape)=\"closeModal(false)\"\n tabindex=\"0\"\n >\n <lib-form-header\n [data]=\"formData\"\n [title]=\"(formData.id ? 'JWT Signer Details' : 'Create JWT Signer')\"\n [moreActions]=\"moreActions\"\n [multiActions]=\"multiActions\"\n (actionRequested)=\"headerActionRequested($event)\"\n (multiActionRequested)=\"multiActionRequested($event)\"\n [(formView)]=\"formView\"\n [saveDisabled]=\"svc.saveDisabled || formDataInvalid || formPermissionSaveDisabled\"\n [saveTooltip]=\"formPermissionSaveTooltip\"\n [strictReadonlyUi]=\"useStrictReadonlyForm\"\n ></lib-form-header>\n <div class=\"jwt-signer-form-container projectable-form-container\" #scrollContainer>\n <div class=\"projectable-form-main-column form-group-row\" [hidden]=\"formView !== 'simple'\">\n <div class=\"form-group-column three-fifths\">\n <lib-form-field-container\n [title]=\"'Name'\"\n [label]=\"useStrictReadonlyForm ? '' : 'REQUIRED'\"\n >\n @if (!useStrictReadonlyForm) {\n <input class=\"form-field-input\" [(ngModel)]=\"formData.name\" [ngClass]=\"{error: errors['name']}\" placeholder=\"Name this JWT Signer\" #nameFieldInput/>\n } @else {\n <div class=\"form-field-readonly-value\">{{ formData.name }}</div>\n }\n </lib-form-field-container>\n <lib-form-field-container\n [layout]=\"'column'\"\n [showHeader]=\"false\"\n >\n <div class=\"form-field-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Issuer</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The &quot;issuer&quot; field on the JWT Signer must match the &quot;iss&quot; field on the JWT\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <span class=\"form-field-label\" >{{ useStrictReadonlyForm ? '' : 'REQUIRED' }}</span>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter issuer\" [(ngModel)]=\"formData.issuer\" [ngClass]=\"{error: errors['issuer']}\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Audience</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The expected &quot;aud&quot; (audience) field on the JWT\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter audience\" [(ngModel)]=\"formData.audience\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-row\">\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Claims Property</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The field on the JWT for mapping to an identity. Matches the identity's id or externalId if &quot;Use External Id&quot; is set to true. Defaults to &quot;sub&quot;\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter claims property\" [(ngModel)]=\"formData.claimsProperty\" (keyup)=\"checkExternalIdToggle()\" [ngClass]=\"{error: errors['claimsProperty']}\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <div class=\"config-container-label\" style=\"font-weight: 600; font-size: 14px;\">External ID</div>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"If enabled, will match the &quot;claimsProperty&quot; to an identity's &quot;externalId&quot; field. If false, it will match to the identity's id field.\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n </div>\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"form-field-title-container\">\n <div class=\"config-container-label\">Use External ID</div>\n </div>\n <div\n (click)=\"!useStrictReadonlyForm && toggleUseExternalId()\"\n [ngClass]=\"{ on: formData.useExternalId, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"!formData.useExternalId\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.useExternalId\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n <lib-form-field-container\n [showHeader]=\"false\"\n >\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Client ID</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The client id to use when authenticating to the IDP\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter client ID\" [(ngModel)]=\"formData.clientId\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">External Auth Url</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The URL that users are directed to, to obtain a JWT\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter external auth URL\" [(ngModel)]=\"formData.externalAuthUrl\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Scopes</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"Enter as a comma or space seperated list of claims/permissions\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <lib-chips-input\n (keyup)=\"scopesOnKeyup($event)\"\n [(ngModel)]=\"formData.scopes\"\n [allowDuplicate]=\"false\"\n placeholder=\"Enter scopes\"\n [addOnBlur]=\"true\"\n separator=\",\"\n [disabled]=\"useStrictReadonlyForm\"\n ></lib-chips-input>\n </div>\n </lib-form-field-container>\n @if (showAutoEnrollment) {\n <lib-form-field-container\n [title]=\"'Auto Enrollment'\"\n [helpText]=\"'Click for more information'\"\n (helpClicked)=\"showAutoEnrollHelp()\"\n >\n <div class=\"form-field-row\">\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">Auto-Enroll to Cert</div>\n <div\n (click)=\"toggleEnrollToCertEnabled()\"\n [ngClass]=\"{ on: formData.enrollToCertEnabled, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"!formData.enrollToCertEnabled\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.enrollToCertEnabled\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">Auto-Enroll to Token</div>\n <div\n (click)=\"toggleEnrollToTokenEnabled()\"\n [ngClass]=\"{ on: formData.enrollToTokenEnabled, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"!formData.enrollToTokenEnabled\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.enrollToTokenEnabled\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n }\n <lib-form-field-toggle [(toggleOn)]=\"showMore\" (toggleOnChange)=\"showMoreChanged($event)\" style=\"margin: 0px 10px\"></lib-form-field-toggle>\n @if (showMore && showTokenType) {\n <lib-form-field-container\n [title]=\"'Target Token Type'\"\n [label]=\"'Required'\"\n [helpText]=\"'The target token type. Can be either Access or ID'\"\n class=\"form-field-advanced\"\n >\n <select\n [(ngModel)]=\"targetToken\"\n id=\"authPolicyId\"\n class=\"form-field-dropdown\"\n [disabled]=\"useStrictReadonlyForm\"\n >\n <option value=\"ACCESS\">Access</option>\n <option value=\"ID\">ID</option>\n </select>\n </lib-form-field-container>\n }\n <div [hidden]=\"!showMore\" class=\"form-group-column\">\n @if (showAutoEnrollment) {\n <lib-form-field-container\n [title]=\"'Auto Enrollment Options'\"\n class=\"form-field-advanced\"\n >\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Enrollment Auth Policy</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The authentication policy assigned to identities created through auto-enrollment. Defaults to the &quot;default&quot; policy.\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <select\n [(ngModel)]=\"enrollAuthPolicyId\"\n class=\"form-field-dropdown\"\n [disabled]=\"useStrictReadonlyForm\"\n >\n <option value=\"\">Select an Authentication Policy</option>\n @for (policy of authPolicies; track policy.id) {\n <option [value]=\"policy.id\">{{ policy.name }}</option>\n }\n </select>\n </div>\n <div class=\"form-field-row\">\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Name Claims Selector</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The claims JSON pointer selector or top level property used for the name of enrolling identities. Defaults to /sub\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter name claims selector\" [(ngModel)]=\"formData.enrollNameClaimsSelector\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">Attribute Claims Selector</span>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"The claims JSON pointer selector or top level property used for the attributes of enrolling identities.\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter attribute claims selector\" [(ngModel)]=\"formData.enrollAttributeClaimsSelector\" [disabled]=\"useStrictReadonlyForm\" />\n </div>\n </div>\n </lib-form-field-container>\n }\n <lib-form-field-container\n [title]=\"'Verify OIDC Authentication'\"\n class=\"form-field-advanced\"\n [ngClass]=\"{'formFieldError': !oidcVerified && oidcErrorMessageDetail, 'formFieldSuccess': oidcVerified}\"\n >\n <div class=\"form-field-row\" #oidcVerification>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-title-container\">\n <div class=\"form-field-title\">Callback URL</div>\n <div\n class=\"form-field-info infoicon\"\n matTooltip=\"This URL must be added to the list of allowed callback URLs in your OAuth provider configuration\"\n matTooltipPosition=\"below\"\n ></div>\n </div>\n <div class=\"copy-link-container\">\n <pre class=\"read-only-2\" (click)=\"copyCallbackURL()\">{{ callbackURL }}</pre>\n <div class=\"copy-button\" (click)=\"copyCallbackURL()\"></div>\n </div>\n </div>\n @if (!useStrictReadonlyForm) {\n <div class=\"test-oauth-button\"\n [ngClass]=\"{'oidc-verified': oidcVerified}\"\n (click)=\"testOIDCAuthentication()\"\n >\n {{oidcVerified ? 'Verified' : 'Authenticate' }}\n @if (oauthLoading) {\n <div class=\"spinner\"></div>\n }\n @if (oidcVerified) {\n <input type=\"checkbox\" [disabled]=\"true\" [checked]=\"true\" class=\"success-check\">\n }\n </div>\n }\n </div>\n @if (oidcErrorMessageDetail) {\n <div class=\"oidc-error-message-container\">\n <code class=\"oidc-error-message-source\">{{oidcErrorMessageSource}}: </code>\n <code class=\"oidc-error-message-detail\">{{oidcErrorMessageDetail}}</code>\n @if (oidcErrorMessageDetail2) {\n <code class=\"oidc-error-message-detail2\">{{oidcErrorMessageDetail2}}</code>\n }\n @if (oidcErrorMessageDetail3) {\n <code class=\"oidc-error-message-detail2\">{{oidcErrorMessageDetail3}}</code>\n }\n </div>\n }\n @if (oidcAuthTokenClaims) {\n <div class=\"oidc-token-claims-container\">\n <code class=\"oidc-token-claims-title\">OAuth Token Claims: </code>\n <lib-json-view [showLineNumbers]=\"false\" [(data)]=\"oidcAuthTokenClaims\" [readOnly]=\"useStrictReadonlyForm\"></lib-json-view>\n </div>\n }\n </lib-form-field-container>\n <lib-form-field-container\n [title]=\"'Custom Tags'\"\n [label]=\"'OPTIONAL'\"\n class=\"form-field-advanced\"\n >\n <lib-custom-tags [(tags)]=\"formData.tags\" [readOnly]=\"useStrictReadonlyForm\"></lib-custom-tags>\n </lib-form-field-container>\n </div>\n </div>\n <div class=\"form-group-column two-fifths\">\n <lib-form-field-container\n [title]=\"'Verification'\"\n [label]=\"useStrictReadonlyForm ? '' : 'REQUIRED'\"\n >\n <div\n class=\"radio-group-container\"\n [class.radio-group-readonly]=\"useStrictReadonlyForm\"\n (keydown)=\"radioKeyDownHandler($event)\"\n [attr.tabindex]=\"useStrictReadonlyForm ? -1 : 0\"\n >\n <div\n class=\"radio-button-container\"\n [ngClass]=\"{'selected': signatureMethod === 'JWKS_ENDPOINT', 'toggle-readonly': useStrictReadonlyForm}\"\n (click)=\"!useStrictReadonlyForm && selectSignatureMethod('JWKS_ENDPOINT')\"\n >\n <div class=\"radio-button-circle\">\n <div class=\"radio-button-inner-circle\"></div>\n </div>\n <span class=\"radio-button-label\">JWKS Endpoint</span>\n </div>\n <div\n class=\"radio-button-container\"\n [ngClass]=\"{'selected': signatureMethod === 'CERT_PEM', 'toggle-readonly': useStrictReadonlyForm}\"\n (click)=\"!useStrictReadonlyForm && selectSignatureMethod('CERT_PEM')\"\n >\n <div class=\"radio-button-circle\">\n <div class=\"radio-button-inner-circle\"></div>\n </div>\n <span class=\"radio-button-label\">Cert PEM</span>\n </div>\n </div>\n @if (signatureMethod === 'JWKS_ENDPOINT') {\n <div class=\"form-field-input-group\">\n <div class=\"form-field-label-container select-file-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">JWKS Endpoint</span>\n </div>\n </div>\n <input\n class=\"form-field-input\"\n placeholder=\"Enter endpoint URL\"\n [(ngModel)]=\"formData.jwksEndpoint\"\n [ngClass]=\"{error: errors['jwksEndpoint']}\"\n [disabled]=\"useStrictReadonlyForm\"\n />\n </div>\n </div>\n }\n @if (signatureMethod === 'CERT_PEM') {\n <div class=\"form-field-input-group\">\n <div class=\"form-field-label-container select-file-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">PEM</span>\n </div>\n <span\n class=\"select-file-button\"\n [class.toggle-readonly]=\"useStrictReadonlyForm\"\n (click)=\"!useStrictReadonlyForm && openFileSelect($event)\"\n >\n Select File\n @if (fileSelectOpening) {\n <div class=\"spinner\"></div>\n }\n </span>\n <input\n #fileSelect\n type=\"file\"\n style=\"display:none\"\n [disabled]=\"useStrictReadonlyForm\"\n (change)=\"selectPemFile($event)\"\n >\n </div>\n <textarea\n placeholder=\"Paste pem contents or select file\"\n [(ngModel)]=\"formData.certPem\"\n [ngClass]=\"{error: errors['certPem']}\"\n [disabled]=\"useStrictReadonlyForm\"\n ></textarea>\n </div>\n <div class=\"form-field-label-container\">\n <div class=\"form-field-header\">\n <div class=\"form-field-title-container\">\n <span class=\"form-field-title\">KID</span>\n </div>\n <span class=\"form-field-label\" >Optional</span>\n </div>\n <input class=\"form-field-input\" placeholder=\"Enter KID\" [(ngModel)]=\"formData.kid\" [disabled]=\"useStrictReadonlyForm\"/>\n </div>\n </div>\n }\n </lib-form-field-container>\n <lib-form-field-container\n [showHeader]=\"false\"\n >\n <div class=\"config-item\">\n <div class=\"config-container toggle-container\">\n <div class=\"config-container-label\">ENABLED</div>\n <div\n (click)=\"!useStrictReadonlyForm && toggleEnabled()\"\n [ngClass]=\"{ on: formData.enabled, 'toggle-readonly': useStrictReadonlyForm }\"\n class=\"toggle\"\n >\n <span [hidden]=\"!formData.enabled\" class=\"on-label\">YES</span>\n <span [hidden]=\"formData.enabled\" class=\"off-label\">NO</span>\n <div class=\"switch\"></div>\n </div>\n </div>\n </div>\n </lib-form-field-container>\n @if (showApiCalls) {\n <lib-form-field-container\n [title]=\"'API Calls'\"\n [headerActions]=\"apiOptions\"\n (actionRequested)=\"apiActionRequested($event)\"\n [class]=\"'api-data-no-wrap'\"\n >\n <div class=\"form-row\">\n <input class=\"form-field-input\" [value]=\"apiCallURL\"/>\n <div class=\"icon-copy copy-text\" (click)=\"copyToClipboard(apiCallURL)\"></div>\n </div>\n @if (formData) {\n <lib-json-view [showLineNumbers]=\"false\" [(data)]=\"apiData\" [readOnly]=\"true\" [showCopy]=\"true\"></lib-json-view>\n }\n </lib-form-field-container>\n }\n </div>\n </div>\n @if (formView === 'raw') {\n <div class=\"form-group-column\">\n @if (formData) {\n <lib-json-view [(data)]=\"formData\" [readOnly]=\"useStrictReadonlyForm\"></lib-json-view>\n }\n </div>\n }\n </div>\n</div>\n@if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n}\n<lib-side-modal [(open)]=\"showAutoEnrollHelpModal\" [showClose]=\"true\">\n <div class=\"help-panel-content\">\n <h2 class=\"help-panel-title\">What is Auto Enrollment?</h2>\n <p class=\"help-panel-intro\">\n Auto Enrollment enables automatic identity creation when users authenticate via JWT tokens.\n </p>\n\n <div class=\"help-panel-section\">\n <h3 class=\"help-panel-subtitle\">\n <span class=\"icon-certificate\"></span>\n Auto-Enroll to Cert\n </h3>\n <p class=\"help-panel-text\">\n When enabled, the controller will automatically create an identity and enroll it with a certificate when a user authenticates using a JWT token. This is useful for certificate-based authentication workflows.\n </p>\n </div>\n\n <div class=\"help-panel-section\">\n <h3 class=\"help-panel-subtitle\">\n <span class=\"icon-key\"></span>\n Auto-Enroll to Token\n </h3>\n <p class=\"help-panel-text\">\n When enabled, the controller will automatically create an identity and enroll it with an authentication token when a user authenticates using a JWT token. This is useful for token-based authentication workflows.\n </p>\n </div>\n\n <div class=\"help-panel-section\">\n <h3 class=\"help-panel-subtitle\">\n <span class=\"icon-lock\"></span>\n Enrollment Auth Policy\n </h3>\n <p class=\"help-panel-text\">\n The authentication policy assigned to identities that are created through auto-enrollment. When left unset, the controller's \"default\" authentication policy is used.\n </p>\n </div>\n\n <div class=\"help-panel-section\">\n <h3 class=\"help-panel-subtitle\">\n <span class=\"icon-identity\"></span>\n Name Claims Selector\n </h3>\n <p class=\"help-panel-text\">\n A JSON pointer selector (or top-level claim property) identifying which JWT claim supplies the name for enrolling identities. Defaults to <code>/sub</code> when unset.\n </p>\n </div>\n\n <div class=\"help-panel-section\">\n <h3 class=\"help-panel-subtitle\">\n <span class=\"icon-identity\"></span>\n Attribute Claims Selector\n </h3>\n <p class=\"help-panel-text\">\n A JSON pointer selector (or top-level claim property) identifying which JWT claim supplies the role attributes for enrolling identities.\n </p>\n </div>\n\n <div class=\"help-panel-note\">\n <strong>Note:</strong> These options are only available in Ziti 2.0 and later versions.\n </div>\n </div>\n</lib-side-modal>\n", styles: ["@charset \"UTF-8\";.jwt-signer-form-container{display:flex;flex-direction:column;height:100%;width:100%;align-items:center;gap:var(--gapXL)}.jwt-signer-form-container .select-file-button{padding-left:var(--paddingMedium);padding-right:var(--paddingMedium);height:1.5625rem;width:fit-content;white-space:nowrap;display:flex;align-items:center;justify-content:center;font-weight:600;font-size:.875rem;background:var(--primary);border-style:solid;border-width:.0625rem;border-color:var(--stroke);color:var(--white);cursor:pointer;border-radius:var(--inputBorderRadius);gap:var(--marginMedium);margin-top:-.3125rem}.jwt-signer-form-container .select-file-button:hover{filter:brightness(.8)}.jwt-signer-form-container .select-file-button:active{filter:grayscale(1);transform:translateY(.0625rem)}.jwt-signer-form-container .select-file-button .spinner{display:inline-block;width:.5rem;height:.5rem;border-radius:50%;background:transparent;border-top:.125rem solid white;border-right:.125rem solid white;border-bottom:.125rem solid transparent;border-left:.125rem solid transparent;-webkit-animation:loading .5s infinite linear;animation:loading .5s infinite linear}.jwt-signer-form-container .select-file-label-container{gap:var(--marginMedium)}::ng-deep .jwt-signer-form-container select:disabled,::ng-deep .jwt-signer-form-container input:disabled{opacity:.75!important;cursor:text;background-color:var(--stroke)!important}::ng-deep .api-data-no-wrap .jse-text-mode{width:100%}::ng-deep .api-data-no-wrap .jse-text-mode .cm-editor{width:100%}::ng-deep .api-data-no-wrap .jse-text-mode .cm-editor .cm-scroller{width:100%}::ng-deep .api-data-no-wrap .jse-text-mode .cm-editor .cm-scroller .cm-content{width:100%;overflow:auto}::ng-deep .api-data-no-wrap .jse-text-mode .cm-editor .cm-scroller .cm-content .\\37cr{white-space:nowrap}.radio-group-container{display:flex;gap:var(--marginLarge)}.radio-group-container:focus .radio-button-container .radio-button-circle{border-color:var(--primaryColor)}.radio-group-container .radio-button-container{display:flex;flex-direction:row;align-items:center;background-color:var(--formGroup);border-radius:var(--inputBorderRadius);width:100%;padding-left:var(--paddingMedium);padding-right:var(--paddingMedium);padding-top:var(--paddingLarge);padding-bottom:var(--paddingLarge);cursor:pointer;gap:var(--marginMedium)}.radio-group-container .radio-button-container .radio-button-circle{display:flex;width:1.3125rem;height:1.3125rem;border-radius:1.3125rem;border-width:.125rem;border-style:solid;border-color:var(--offWhite_ALWAYS);align-items:center;justify-content:center;background-color:var(--offWhite)}.radio-group-container .radio-button-container .radio-button-circle .radio-button-inner-circle{display:none;width:.9375rem;height:.9375rem;border-radius:.9375rem;align-items:center;justify-content:center;background-color:var(--primaryColor)}.radio-group-container .radio-button-container.selected .radio-button-circle .radio-button-inner-circle{display:flex}.radio-group-container .radio-button-container:active .radio-button-circle .radio-button-inner-circle{display:flex;background-color:var(--menu)}.radio-group-container .radio-button-container .radio-button-label{color:var(--offWhite_ALWAYS);font-size:.875rem;font-weight:600}.radio-button-container.toggle-readonly{cursor:default;pointer-events:none;opacity:.92}.select-file-button.toggle-readonly{cursor:default;pointer-events:none;opacity:.85}.test-oauth-button{color:var(--offWhite_ALWAYS);height:var(--defaultInputHeight);border-style:solid;border-color:var(--inputBorder);border-radius:var(--inputBorderRadius);vertical-align:middle;text-align:center;position:relative;display:flex;align-items:center;justify-content:center;overflow:hidden;transition:color .2s;font-size:.9375rem;font-weight:600;font-family:Open Sans,sans-serif;text-transform:uppercase;cursor:pointer;width:100%;background-color:var(--primaryColor);margin-top:1.5rem;max-width:15rem}.test-oauth-button.oidc-verified{filter:brightness(.95);color:var(--offWhite_ALWAYS);background-color:var(--green)}.test-oauth-button .spinner{display:inline-block;width:var(--spinnerSizeMedium);height:var(--spinnerSizeMedium);border-radius:50%;background:transparent;border-top:var(--spinnerBorderTopMedium);border-right:var(--spinnerBorderRightMedium);border-bottom:var(--spinnerBorderBottomMedium);border-left:var(--spinnerBorderLeftMedium);animation:loading .5s infinite linear;margin-left:var(--marginSmall)}.test-oauth-button:hover{filter:brightness(.95)}.test-oauth-button:active{transform:translateY(1px)}.success-check{width:1.0625rem;height:1.0625rem;cursor:pointer;background-color:var(--navigation);margin:0;margin-top:.25rem;border-radius:2rem;margin-left:var(--marginSmall)}.success-check:checked{background-color:var(--green)!important;background-image:url(/assets/images/check.png);background-size:.675rem;background-position:center center;background-repeat:no-repeat;border:none}.success-check:checked:disabled{opacity:1!important}.oidc-token-claims-container{background-color:var(--backgroundMuted);height:fit-content;display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;padding:var(--paddingMedium);border-radius:var(--inputBorderRadius);color:var(--tableText);font-weight:600;overflow:auto}.oidc-token-claims-container .oidc-token-claims-title{font-weight:600;font-size:1rem;white-space:nowrap;margin-bottom:var(--marginMedium)}.oidc-error-message-container{background-color:var(--lightRed);height:fit-content;display:flex;flex-direction:column;align-items:flex-start;justify-content:flex-start;padding:var(--paddingMedium);border-radius:var(--inputBorderRadius);color:var(--red);font-weight:600;overflow:auto}.oidc-error-message-container .oidc-error-message-source{font-weight:600;font-size:1rem;white-space:nowrap}.oidc-error-message-container .oidc-error-message-detail2,.oidc-error-message-container .oidc-error-message-detail{margin-top:var(--marginSmall);font-weight:400;white-space:nowrap}.oidc-error-message-container::-webkit-scrollbar{height:6px;width:6px}.oidc-error-message-container::-webkit-scrollbar-thumb{background-color:var(--menu);border-radius:10px}.oidc-error-message-container:hover::-webkit-scrollbar-thumb{background-clip:unset!important}.read-only-2{display:flex;overflow:auto;white-space:nowrap;opacity:1!important;cursor:pointer;padding:var(--paddingMedium);padding-bottom:var(--paddingSmall)}.read-only-2::-webkit-scrollbar{height:6px;width:6px}.read-only-2::-webkit-scrollbar-thumb{background-color:var(--menu);border-radius:10px}.read-only-2:hover{text-decoration:underline}.read-only-2:hover::-webkit-scrollbar-thumb{background-clip:unset}.copy-link-container{display:flex;flex-direction:row;justify-content:space-between;align-items:center;opacity:1!important;cursor:text;border-radius:var(--inputBorderRadius);height:var(--defaultInputHeight);background-color:var(--backgroundMuted)!important}.copy-button{display:flex;align-items:center;justify-content:center;height:100%;width:2.575rem;margin-left:.125rem;background:url(/assets/svgs/copy.svg) no-repeat;background-size:1.125rem 1.125rem;background-position:center;cursor:pointer;border-top-right-radius:var(--inputBorderRadius);border-bottom-right-radius:var(--inputBorderRadius);background-color:#0000000f}.copy-button:hover{filter:brightness(.95);background-color:#0000001f}.help-panel-content{display:flex;flex-direction:column;padding-top:3rem}.help-panel-content .help-panel-title{margin:0 0 .25rem;color:var(--primary);font-size:1.5rem;font-weight:600;line-height:1.3;padding-right:2rem}.help-panel-content .help-panel-intro{line-height:1.6;margin-bottom:1.5rem;color:var(--tableText);font-size:.9375rem}.help-panel-content .help-panel-section{margin-bottom:1.5rem;padding:1rem;background-color:var(--navigation);border-radius:var(--inputBorderRadius);border-left:.25rem solid var(--primary)}.help-panel-content .help-panel-section .help-panel-subtitle{margin:0 0 .75rem;color:var(--primary);font-size:1.125rem;font-weight:600;display:flex;align-items:center;gap:.5rem}.help-panel-content .help-panel-section .help-panel-subtitle span[class^=icon-]{font-size:1rem;opacity:.8}.help-panel-content .help-panel-section .help-panel-text{margin:0;line-height:1.6;color:var(--tableText);font-size:.875rem}.help-panel-content .help-panel-note{padding:1rem;background-color:#ffc1071a;border-radius:var(--inputBorderRadius);border-left:.25rem solid #ffc107;color:var(--tableText);font-size:.875rem;line-height:1.6}.help-panel-content .help-panel-note strong{color:#ffc107}\n"] }]
27099
29494
  }], ctorParameters: () => [{ type: JwtSignerFormService }, { type: SchemaService }, { type: GrowlerService }, { type: undefined, decorators: [{
27100
29495
  type: Inject,
27101
29496
  args: [SHAREDZ_EXTENSION]
@@ -31923,11 +34318,13 @@ class SettingsComponent {
31923
34318
  this.hideTags = false;
31924
34319
  this.transitions = true;
31925
34320
  this.isFilterAfterSave = false;
34321
+ this.betaFeatures = false;
31926
34322
  }
31927
34323
  ngOnInit() {
31928
34324
  this.isDark = localStorage.getItem('mode') === 'dark';
31929
34325
  this.hideTags = localStorage.getItem('hideTags') === 'yes';
31930
34326
  this.isFilterAfterSave = localStorage.getItem('filterAfterSave') === 'on';
34327
+ this.betaFeatures = localStorage.getItem('betaFeatures') === 'on';
31931
34328
  const transitions = localStorage.getItem('Transitions');
31932
34329
  this.transitions = transitions !== 'off';
31933
34330
  this.updateTransitions();
@@ -31945,6 +34342,10 @@ class SettingsComponent {
31945
34342
  const mode = this.isFilterAfterSave ? 'on' : 'off';
31946
34343
  localStorage.setItem('filterAfterSave', mode);
31947
34344
  }
34345
+ betaFeaturesChanged() {
34346
+ const mode = this.betaFeatures ? 'on' : 'off';
34347
+ localStorage.setItem('betaFeatures', mode);
34348
+ }
31948
34349
  transitionsChange() {
31949
34350
  const mode = this.transitions ? 'on' : 'off';
31950
34351
  localStorage.setItem('Transitions', mode);
@@ -31967,11 +34368,11 @@ class SettingsComponent {
31967
34368
  document.getElementsByTagName('html')[0].style.setProperty('--secondary', color);
31968
34369
  }
31969
34370
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SettingsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
31970
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: SettingsComponent, isStandalone: false, selector: "lib-settings", ngImport: i0, template: "<div class=\"projectable-form-wrapper\">\n <div class=\"header\">\n <div class=\"title\">Manage <span>{{pageTitle}}</span></div>\n <div class=\"line\"></div>\n <div class=\"tabs\">\n <div class=\"tab selected\">Manage <span>{{pageTitle}}</span></div>\n <div class=\"tab\" routerLink=\"/profile\">Manage Profile</div>\n </div>\n <div class=\"constrained\">\n <div class=\"grid split\">\n <div>\n <label>Filter after Save</label>\n <div class=\"toggle\" [class.on]=\"isFilterAfterSave\" (click)=\"isFilterAfterSave = !isFilterAfterSave; filterAfter()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n <div>\n <label>Dark mode</label>\n <div class=\"toggle\" [class.on]=\"isDark\" (click)=\"isDark = !isDark; swap()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n <div>\n <label>Hide Tags</label>\n <div class=\"toggle\" [class.on]=\"hideTags\" (click)=\"hideTags = !hideTags; hide()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n <div>\n <label>Transitions</label>\n <div class=\"toggle\" [class.on]=\"transitions\" (click)=\"transitions = !transitions; transitionsChange()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: [".header .constrained .grid.split{grid-template-columns:1fr 1fr}label{color:var(--dark);text-transform:uppercase;margin-top:var(--marginMedium);position:relative;display:inline-block;width:100%;margin-bottom:.1875rem;font-size:.875rem;white-space:nowrap;font-weight:600;font-family:Open Sans;overflow:hidden;text-overflow:ellipsis;line-height:var(--defaultLineHeight)}.toggle{width:3.75rem;height:1.875rem;background-color:var(--black);color:var(--white);cursor:pointer;border-radius:6.25rem;position:relative;display:inline-block;margin-top:var(--marginMedium);transition:var(--transition);float:left;margin-right:var(--marginXL)}.toggle.on{background-color:var(--green)}.toggle.on .switch{left:2.1875rem}.toggle.on .label{left:.625rem;content:\"YES\"}.toggle.on .label:before{content:\"YES\"}.toggle .switch{width:1.25rem;height:1.25rem;border-radius:.625rem;background-color:var(--white);transition:var(--transition);position:absolute;left:.3125rem;top:.3125rem;z-index:10}.toggle .label{right:.625rem;line-height:1.875rem;font-size:.75rem;color:var(--white);position:absolute;z-index:8;transition:var(--transition);text-transform:uppercase}.toggleLabel{line-height:3.125rem}.toggle .label:before{content:\"NO\"}.constrained{width:37.5rem;position:relative;margin-left:auto;margin-right:auto}.split{display:grid;grid-gap:.625rem;grid-template-columns:auto 50%}.header{position:relative;display:inline-block;width:100%}.line{position:absolute;width:100%;top:2.5rem;height:.25rem;background-color:var(--stroke)}.tabs{position:relative;display:inline-block;width:100%;font-size:.875rem;min-height:2.5rem;padding-top:.9375rem}.tabs .tab{position:relative;float:left;margin-right:1.25rem;padding-bottom:.3125rem;margin-bottom:.625rem;font-size:.875rem;font-weight:600;cursor:pointer;transition:var(--transition)}.tabs .tab.selected{border-bottom-width:.25rem;border-bottom-color:var(--primary);border-bottom-style:solid}\n"], dependencies: [{ kind: "directive", type: i4.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); }
34371
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: SettingsComponent, isStandalone: false, selector: "lib-settings", ngImport: i0, template: "<div class=\"projectable-form-wrapper\">\n <div class=\"header\">\n <div class=\"title\">Manage <span>{{pageTitle}}</span></div>\n <div class=\"line\"></div>\n <div class=\"tabs\">\n <div class=\"tab selected\">Manage <span>{{pageTitle}}</span></div>\n <div class=\"tab\" routerLink=\"/profile\">Manage Profile</div>\n </div>\n <div class=\"constrained\">\n <div class=\"grid split\">\n <div>\n <label>Filter after Save</label>\n <div class=\"toggle\" [class.on]=\"isFilterAfterSave\" (click)=\"isFilterAfterSave = !isFilterAfterSave; filterAfter()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n <div>\n <label>Dark mode</label>\n <div class=\"toggle\" [class.on]=\"isDark\" (click)=\"isDark = !isDark; swap()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n <div>\n <label>Hide Tags</label>\n <div class=\"toggle\" [class.on]=\"hideTags\" (click)=\"hideTags = !hideTags; hide()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n <div>\n <label>Transitions</label>\n <div class=\"toggle\" [class.on]=\"transitions\" (click)=\"transitions = !transitions; transitionsChange()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n <div>\n <label>Beta Features</label>\n <div class=\"toggle\" [class.on]=\"betaFeatures\" (click)=\"betaFeatures = !betaFeatures; betaFeaturesChanged()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: [".header .constrained .grid.split{grid-template-columns:1fr 1fr}label{color:var(--dark);text-transform:uppercase;margin-top:var(--marginMedium);position:relative;display:inline-block;width:100%;margin-bottom:.1875rem;font-size:.875rem;white-space:nowrap;font-weight:600;font-family:Open Sans;overflow:hidden;text-overflow:ellipsis;line-height:var(--defaultLineHeight)}.toggle{width:3.75rem;height:1.875rem;background-color:var(--black);color:var(--white);cursor:pointer;border-radius:6.25rem;position:relative;display:inline-block;margin-top:var(--marginMedium);transition:var(--transition);float:left;margin-right:var(--marginXL)}.toggle.on{background-color:var(--green)}.toggle.on .switch{left:2.1875rem}.toggle.on .label{left:.625rem;content:\"YES\"}.toggle.on .label:before{content:\"YES\"}.toggle .switch{width:1.25rem;height:1.25rem;border-radius:.625rem;background-color:var(--white);transition:var(--transition);position:absolute;left:.3125rem;top:.3125rem;z-index:10}.toggle .label{right:.625rem;line-height:1.875rem;font-size:.75rem;color:var(--white);position:absolute;z-index:8;transition:var(--transition);text-transform:uppercase}.toggleLabel{line-height:3.125rem}.toggle .label:before{content:\"NO\"}.constrained{width:37.5rem;position:relative;margin-left:auto;margin-right:auto}.split{display:grid;grid-gap:.625rem;grid-template-columns:auto 50%}.header{position:relative;display:inline-block;width:100%}.line{position:absolute;width:100%;top:2.5rem;height:.25rem;background-color:var(--stroke)}.tabs{position:relative;display:inline-block;width:100%;font-size:.875rem;min-height:2.5rem;padding-top:.9375rem}.tabs .tab{position:relative;float:left;margin-right:1.25rem;padding-bottom:.3125rem;margin-bottom:.625rem;font-size:.875rem;font-weight:600;cursor:pointer;transition:var(--transition)}.tabs .tab.selected{border-bottom-width:.25rem;border-bottom-color:var(--primary);border-bottom-style:solid}\n"], dependencies: [{ kind: "directive", type: i4.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); }
31971
34372
  }
31972
34373
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SettingsComponent, decorators: [{
31973
34374
  type: Component,
31974
- args: [{ selector: 'lib-settings', standalone: false, template: "<div class=\"projectable-form-wrapper\">\n <div class=\"header\">\n <div class=\"title\">Manage <span>{{pageTitle}}</span></div>\n <div class=\"line\"></div>\n <div class=\"tabs\">\n <div class=\"tab selected\">Manage <span>{{pageTitle}}</span></div>\n <div class=\"tab\" routerLink=\"/profile\">Manage Profile</div>\n </div>\n <div class=\"constrained\">\n <div class=\"grid split\">\n <div>\n <label>Filter after Save</label>\n <div class=\"toggle\" [class.on]=\"isFilterAfterSave\" (click)=\"isFilterAfterSave = !isFilterAfterSave; filterAfter()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n <div>\n <label>Dark mode</label>\n <div class=\"toggle\" [class.on]=\"isDark\" (click)=\"isDark = !isDark; swap()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n <div>\n <label>Hide Tags</label>\n <div class=\"toggle\" [class.on]=\"hideTags\" (click)=\"hideTags = !hideTags; hide()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n <div>\n <label>Transitions</label>\n <div class=\"toggle\" [class.on]=\"transitions\" (click)=\"transitions = !transitions; transitionsChange()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: [".header .constrained .grid.split{grid-template-columns:1fr 1fr}label{color:var(--dark);text-transform:uppercase;margin-top:var(--marginMedium);position:relative;display:inline-block;width:100%;margin-bottom:.1875rem;font-size:.875rem;white-space:nowrap;font-weight:600;font-family:Open Sans;overflow:hidden;text-overflow:ellipsis;line-height:var(--defaultLineHeight)}.toggle{width:3.75rem;height:1.875rem;background-color:var(--black);color:var(--white);cursor:pointer;border-radius:6.25rem;position:relative;display:inline-block;margin-top:var(--marginMedium);transition:var(--transition);float:left;margin-right:var(--marginXL)}.toggle.on{background-color:var(--green)}.toggle.on .switch{left:2.1875rem}.toggle.on .label{left:.625rem;content:\"YES\"}.toggle.on .label:before{content:\"YES\"}.toggle .switch{width:1.25rem;height:1.25rem;border-radius:.625rem;background-color:var(--white);transition:var(--transition);position:absolute;left:.3125rem;top:.3125rem;z-index:10}.toggle .label{right:.625rem;line-height:1.875rem;font-size:.75rem;color:var(--white);position:absolute;z-index:8;transition:var(--transition);text-transform:uppercase}.toggleLabel{line-height:3.125rem}.toggle .label:before{content:\"NO\"}.constrained{width:37.5rem;position:relative;margin-left:auto;margin-right:auto}.split{display:grid;grid-gap:.625rem;grid-template-columns:auto 50%}.header{position:relative;display:inline-block;width:100%}.line{position:absolute;width:100%;top:2.5rem;height:.25rem;background-color:var(--stroke)}.tabs{position:relative;display:inline-block;width:100%;font-size:.875rem;min-height:2.5rem;padding-top:.9375rem}.tabs .tab{position:relative;float:left;margin-right:1.25rem;padding-bottom:.3125rem;margin-bottom:.625rem;font-size:.875rem;font-weight:600;cursor:pointer;transition:var(--transition)}.tabs .tab.selected{border-bottom-width:.25rem;border-bottom-color:var(--primary);border-bottom-style:solid}\n"] }]
34375
+ args: [{ selector: 'lib-settings', standalone: false, template: "<div class=\"projectable-form-wrapper\">\n <div class=\"header\">\n <div class=\"title\">Manage <span>{{pageTitle}}</span></div>\n <div class=\"line\"></div>\n <div class=\"tabs\">\n <div class=\"tab selected\">Manage <span>{{pageTitle}}</span></div>\n <div class=\"tab\" routerLink=\"/profile\">Manage Profile</div>\n </div>\n <div class=\"constrained\">\n <div class=\"grid split\">\n <div>\n <label>Filter after Save</label>\n <div class=\"toggle\" [class.on]=\"isFilterAfterSave\" (click)=\"isFilterAfterSave = !isFilterAfterSave; filterAfter()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n <div>\n <label>Dark mode</label>\n <div class=\"toggle\" [class.on]=\"isDark\" (click)=\"isDark = !isDark; swap()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n <div>\n <label>Hide Tags</label>\n <div class=\"toggle\" [class.on]=\"hideTags\" (click)=\"hideTags = !hideTags; hide()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n <div>\n <label>Transitions</label>\n <div class=\"toggle\" [class.on]=\"transitions\" (click)=\"transitions = !transitions; transitionsChange()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n <div>\n <label>Beta Features</label>\n <div class=\"toggle\" [class.on]=\"betaFeatures\" (click)=\"betaFeatures = !betaFeatures; betaFeaturesChanged()\">\n <div class=\"switch\"></div>\n <div class=\"label\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</div>\n", styles: [".header .constrained .grid.split{grid-template-columns:1fr 1fr}label{color:var(--dark);text-transform:uppercase;margin-top:var(--marginMedium);position:relative;display:inline-block;width:100%;margin-bottom:.1875rem;font-size:.875rem;white-space:nowrap;font-weight:600;font-family:Open Sans;overflow:hidden;text-overflow:ellipsis;line-height:var(--defaultLineHeight)}.toggle{width:3.75rem;height:1.875rem;background-color:var(--black);color:var(--white);cursor:pointer;border-radius:6.25rem;position:relative;display:inline-block;margin-top:var(--marginMedium);transition:var(--transition);float:left;margin-right:var(--marginXL)}.toggle.on{background-color:var(--green)}.toggle.on .switch{left:2.1875rem}.toggle.on .label{left:.625rem;content:\"YES\"}.toggle.on .label:before{content:\"YES\"}.toggle .switch{width:1.25rem;height:1.25rem;border-radius:.625rem;background-color:var(--white);transition:var(--transition);position:absolute;left:.3125rem;top:.3125rem;z-index:10}.toggle .label{right:.625rem;line-height:1.875rem;font-size:.75rem;color:var(--white);position:absolute;z-index:8;transition:var(--transition);text-transform:uppercase}.toggleLabel{line-height:3.125rem}.toggle .label:before{content:\"NO\"}.constrained{width:37.5rem;position:relative;margin-left:auto;margin-right:auto}.split{display:grid;grid-gap:.625rem;grid-template-columns:auto 50%}.header{position:relative;display:inline-block;width:100%}.line{position:absolute;width:100%;top:2.5rem;height:.25rem;background-color:var(--stroke)}.tabs{position:relative;display:inline-block;width:100%;font-size:.875rem;min-height:2.5rem;padding-top:.9375rem}.tabs .tab{position:relative;float:left;margin-right:1.25rem;padding-bottom:.3125rem;margin-bottom:.625rem;font-size:.875rem;font-weight:600;cursor:pointer;transition:var(--transition)}.tabs .tab.selected{border-bottom-width:.25rem;border-bottom-color:var(--primary);border-bottom-style:solid}\n"] }]
31975
34376
  }], ctorParameters: () => [] });
31976
34377
 
31977
34378
  class ProfileComponent {
@@ -39542,12 +41943,24 @@ class OpenZitiConsoleLibModule {
39542
41943
  ConfigTypesPageComponent,
39543
41944
  ConfigTypeFormComponent,
39544
41945
  ListPageHeaderComponent,
41946
+ ListTableHeaderComponent,
39545
41947
  FilterBarComponent,
39546
41948
  ListPageFormComponent,
39547
41949
  IdentitiesPageComponent,
41950
+ IdentitiesListPageComponent,
39548
41951
  EdgeRoutersPageComponent,
39549
41952
  TransitRoutersPageComponent,
39550
41953
  DataTableComponent,
41954
+ ListTableComponent,
41955
+ ListMenuComponent,
41956
+ NameCellComponent,
41957
+ SelectCellComponent,
41958
+ ListHeaderCellComponent,
41959
+ ListFilterCellComponent,
41960
+ OsInfoTooltipComponent,
41961
+ SdkInfoTooltipComponent,
41962
+ BooleanCellComponent,
41963
+ TokenCellComponent,
39551
41964
  TableCellSelectComponent,
39552
41965
  TableColumnSelectComponent,
39553
41966
  TableCellMenuComponent,
@@ -39650,6 +42063,7 @@ class OpenZitiConsoleLibModule {
39650
42063
  MatButtonModule,
39651
42064
  MatIconModule,
39652
42065
  OverlayModule,
42066
+ DragDropModule,
39653
42067
  AgGridModule,
39654
42068
  QRCodeComponent,
39655
42069
  ClickOutsideModule,
@@ -39678,6 +42092,7 @@ class OpenZitiConsoleLibModule {
39678
42092
  EdgeRouterFormComponent,
39679
42093
  TransitRouterFormComponent,
39680
42094
  IdentitiesPageComponent,
42095
+ IdentitiesListPageComponent,
39681
42096
  EdgeRoutersPageComponent,
39682
42097
  TransitRoutersPageComponent,
39683
42098
  ServicesPageComponent,
@@ -39721,7 +42136,8 @@ class OpenZitiConsoleLibModule {
39721
42136
  SideNavigatorComponent,
39722
42137
  FilterSelectComponent,
39723
42138
  FilterSelectOptionTemplateDirective,
39724
- FilterSelectTriggerTemplateDirective] }); }
42139
+ FilterSelectTriggerTemplateDirective,
42140
+ ListTableComponent] }); }
39725
42141
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: OpenZitiConsoleLibModule, providers: [
39726
42142
  { provide: SHAREDZ_EXTENSION, useClass: ExtensionsNoopService },
39727
42143
  { provide: ZITI_NAVIGATOR, useValue: {} },
@@ -39746,6 +42162,7 @@ class OpenZitiConsoleLibModule {
39746
42162
  MatButtonModule,
39747
42163
  MatIconModule,
39748
42164
  OverlayModule,
42165
+ DragDropModule,
39749
42166
  AgGridModule,
39750
42167
  QRCodeComponent,
39751
42168
  ClickOutsideModule,
@@ -39777,12 +42194,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
39777
42194
  ConfigTypesPageComponent,
39778
42195
  ConfigTypeFormComponent,
39779
42196
  ListPageHeaderComponent,
42197
+ ListTableHeaderComponent,
39780
42198
  FilterBarComponent,
39781
42199
  ListPageFormComponent,
39782
42200
  IdentitiesPageComponent,
42201
+ IdentitiesListPageComponent,
39783
42202
  EdgeRoutersPageComponent,
39784
42203
  TransitRoutersPageComponent,
39785
42204
  DataTableComponent,
42205
+ ListTableComponent,
42206
+ ListMenuComponent,
42207
+ NameCellComponent,
42208
+ SelectCellComponent,
42209
+ ListHeaderCellComponent,
42210
+ ListFilterCellComponent,
42211
+ OsInfoTooltipComponent,
42212
+ SdkInfoTooltipComponent,
42213
+ BooleanCellComponent,
42214
+ TokenCellComponent,
39786
42215
  TableCellSelectComponent,
39787
42216
  TableColumnSelectComponent,
39788
42217
  TableCellMenuComponent,
@@ -39896,6 +42325,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
39896
42325
  EdgeRouterFormComponent,
39897
42326
  TransitRouterFormComponent,
39898
42327
  IdentitiesPageComponent,
42328
+ IdentitiesListPageComponent,
39899
42329
  EdgeRoutersPageComponent,
39900
42330
  TransitRoutersPageComponent,
39901
42331
  ServicesPageComponent,
@@ -39939,7 +42369,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
39939
42369
  SideNavigatorComponent,
39940
42370
  FilterSelectComponent,
39941
42371
  FilterSelectOptionTemplateDirective,
39942
- FilterSelectTriggerTemplateDirective
42372
+ FilterSelectTriggerTemplateDirective,
42373
+ ListTableComponent
39943
42374
  ], imports: [
39944
42375
  CommonModule,
39945
42376
  FormsModule,
@@ -39954,6 +42385,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
39954
42385
  MatButtonModule,
39955
42386
  MatIconModule,
39956
42387
  OverlayModule,
42388
+ DragDropModule,
39957
42389
  AgGridModule,
39958
42390
  QRCodeComponent,
39959
42391
  ClickOutsideModule,
@@ -41410,8 +43842,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
41410
43842
  args: [SETTINGS_SERVICE]
41411
43843
  }] }, { type: ZitiOidcService }, { type: GrowlerService }, { type: i0.Injector }] });
41412
43844
 
43845
+ /*
43846
+ Copyright NetFoundry Inc.
43847
+
43848
+ Licensed under the Apache License, Version 2.0 (the "License");
43849
+ you may not use this file except in compliance with the License.
43850
+ You may obtain a copy of the License at
43851
+
43852
+ https://www.apache.org/licenses/LICENSE-2.0
43853
+
43854
+ Unless required by applicable law or agreed to in writing, software
43855
+ distributed under the License is distributed on an "AS IS" BASIS,
43856
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
43857
+ See the License for the specific language governing permissions and
43858
+ limitations under the License.
43859
+ */
43860
+
41413
43861
  const ZAC_VERSION = {
41414
- "version": "4.5.2"
43862
+ "version": "4.5.3"
41415
43863
  };
41416
43864
 
41417
43865
  /*
@@ -41422,5 +43870,5 @@ const ZAC_VERSION = {
41422
43870
  * Generated bundle index. Do not edit.
41423
43871
  */
41424
43872
 
41425
- export { APISessionFormComponent, APISessionsPageComponent, API_RESOURCE_TO_ENTITY_PERMISSION, ApiSessionsPageService, AttributesComponent, AuthPoliciesPageComponent, AuthPoliciesPageService, AuthPolicyFormComponent, AuthPolicyFormService, AuthService, BooleanToggleInputComponent, CallbackComponent, CardListComponent, CertificateAuthoritiesPageComponent, CertificateAuthoritiesPageService, CertificateAuthorityFormComponent, CertificateAuthorityFormService, CheckboxListInputComponent, ChipsInputComponent, ConfigEditorComponent, ConfigTypeFormComponent, ConfigTypesPageComponent, ConfigurationFormComponent, ConfigurationService, ConfigurationsPageComponent, CsvDownloadService, DEACTIVATE_GUARD, DEFAULT_APP_CONFIG, DataTableFilterService, DeactivateGuardService, EDGE_ROUTER_EXTENSION_SERVICE, EDGE_ROUTER_POLICY_EXTENSION_SERVICE, EdgeRouter, EdgeRouterFormComponent, EdgeRouterFormService, EdgeRouterPoliciesPageComponent, EdgeRouterPoliciesPageService, EdgeRouterPolicyFormComponent, EdgeRouterPolicyFormService, EdgeRoutersPageComponent, EdgeRoutersPageService, Entity, ExtendableComponent, ExtensionsNoopService, FilterSelectComponent, FilterSelectOptionTemplateDirective, FilterSelectTriggerTemplateDirective, FormFieldContainerComponent, FormFieldToggleComponent, FormHeaderComponent, GeolocateComponent, GrowlerComponent, GrowlerModel, GrowlerModule, GrowlerService, HAControllerService, HaStatusIndicatorComponent, HeaderBarComponent, IDENTITY_EXTENSION_SERVICE, IdentitiesPageComponent, IdentitiesPageService, Identity$1 as Identity, IdentityFormComponent, IdentityFormService, IdentityServicePathComponent, InsecureContextError, JsonViewComponent, JwtSignerFormComponent, JwtSignerFormService, JwtSignersPageComponent, JwtSignersPageService, KEY_CODES, ListPageComponent, ListPageServiceClass, LoadingIndicatorComponent, LoggerService, LoginDialogComponent, LoginServiceClass, ManagementPermissionsService, NetworkVisualizerComponent, NodeDataService, NoopLoginService, NoopTabInterceptorService, NumberInputComponent, OIDC_CLIENT_ID, OIDC_CLIENT_ID_LEGACY, ObjectComponent, OidcEnvironmentalError, OidcSessionUnrecoverableError, OpenZitiConsoleLibModule, PasswordInputComponent, PostureCheckFormComponent, PostureChecksPageComponent, PostureChecksPageService, PreviewListComponent, ProfileComponent, ProgressMeterComponent, ProgressMeterStep, ProgressWizardComponent, ProgressWizardStep, ProjectableForm, ProtocolAddressPortInputComponent, QuickAddComponent, ResetEnrollmentService, SAVE_DENIED_BY_PERMISSION_TOOLTIP, SERVICE_EDGE_ROUTER_POLICY_EXTENSION_SERVICE, SERVICE_EXTENSION_SERVICE, SERVICE_POLICY_EXTENSION_SERVICE, SETTINGS_SERVICE, SHAREDZ_EXTENSION, SelectorInputComponent, ServiceEdgeRouterPoliciesPageComponent, ServiceEdgeRouterPoliciesPageService, ServiceEdgeRouterPolicyFormComponent, ServiceEdgeRouterPolicyFormService, ServiceFormComponent, ServiceFormService, ServicePoliciesPageComponent, ServicePoliciesPageService, ServicePolicyFormComponent, ServicePolicyFormService, ServicesPageComponent, ServicesPageService, Session, SessionFormComponent, SessionRefreshService, SessionsPageComponent, SessionsPageService, SettingsComponent, SettingsService, SettingsServiceClass, SideModalComponent, SideNavbarComponent, SideNavigatorComponent, SimpleServiceComponent, StringInputComponent, TabNameService, TagSelectorComponent, TerminatorFormComponent, TerminatorFormService, TerminatorsPageComponent, TerminatorsPageService, TextListInputComponent, TransitRouterFormComponent, TransitRouterFormService, TransitRoutersPageComponent, TransitRoutersPageService, ValidationService, VerifyCertificateComponent, VisualizerServiceClass, ZAC_LOGIN_SERVICE, ZAC_VERSION, ZITI_DATA_SERVICE, ZITI_DOMAIN_CONTROLLER, ZITI_NAVIGATOR, ZITI_TAB_OVERRIDES, ZITI_URLS, ZacRoutingModule, ZitiControllerDataService, ZitiDataService, ZitiOidcService, decodeJwtPayload, jwtExpMs, jwtIatMs, pkceChallengeS256, playerFactory, randomUrlSafeString };
43873
+ export { APISessionFormComponent, APISessionsPageComponent, API_RESOURCE_TO_ENTITY_PERMISSION, ApiSessionsPageService, AttributesComponent, AuthPoliciesPageComponent, AuthPoliciesPageService, AuthPolicyFormComponent, AuthPolicyFormService, AuthService, BooleanCellComponent, BooleanToggleInputComponent, CallbackComponent, CardListComponent, CertificateAuthoritiesPageComponent, CertificateAuthoritiesPageService, CertificateAuthorityFormComponent, CertificateAuthorityFormService, CheckboxListInputComponent, ChipsInputComponent, ConfigEditorComponent, ConfigTypeFormComponent, ConfigTypesPageComponent, ConfigurationFormComponent, ConfigurationService, ConfigurationsPageComponent, CsvDownloadService, DEACTIVATE_GUARD, DEFAULT_APP_CONFIG, DataTableFilterService, DeactivateGuardService, EDGE_ROUTER_EXTENSION_SERVICE, EDGE_ROUTER_POLICY_EXTENSION_SERVICE, EdgeRouter, EdgeRouterFormComponent, EdgeRouterFormService, EdgeRouterPoliciesPageComponent, EdgeRouterPoliciesPageService, EdgeRouterPolicyFormComponent, EdgeRouterPolicyFormService, EdgeRoutersPageComponent, EdgeRoutersPageService, Entity, ExtendableComponent, ExtensionsNoopService, FilterSelectComponent, FilterSelectOptionTemplateDirective, FilterSelectTriggerTemplateDirective, FormFieldContainerComponent, FormFieldToggleComponent, FormHeaderComponent, GeolocateComponent, GrowlerComponent, GrowlerModel, GrowlerModule, GrowlerService, HAControllerService, HaStatusIndicatorComponent, HeaderBarComponent, IDENTITY_EXTENSION_SERVICE, IdentitiesListPageComponent, IdentitiesPageComponent, IdentitiesPageService, Identity$1 as Identity, IdentityFormComponent, IdentityFormService, IdentityServicePathComponent, InsecureContextError, JsonViewComponent, JwtSignerFormComponent, JwtSignerFormService, JwtSignersPageComponent, JwtSignersPageService, KEY_CODES, ListFilterCellComponent, ListHeaderCellComponent, ListMenuComponent, ListPageComponent, ListPageServiceClass, ListTableComponent, ListTableService, LoadingIndicatorComponent, LoggerService, LoginDialogComponent, LoginServiceClass, ManagementPermissionsService, NameCellComponent, NetworkVisualizerComponent, NodeDataService, NoopLoginService, NoopTabInterceptorService, NumberInputComponent, OIDC_CLIENT_ID, OIDC_CLIENT_ID_LEGACY, ObjectComponent, OidcEnvironmentalError, OidcSessionUnrecoverableError, OpenZitiConsoleLibModule, OsInfoTooltipComponent, PasswordInputComponent, PostureCheckFormComponent, PostureChecksPageComponent, PostureChecksPageService, PreviewListComponent, ProfileComponent, ProgressMeterComponent, ProgressMeterStep, ProgressWizardComponent, ProgressWizardStep, ProjectableForm, ProtocolAddressPortInputComponent, QuickAddComponent, ResetEnrollmentService, SAVE_DENIED_BY_PERMISSION_TOOLTIP, SERVICE_EDGE_ROUTER_POLICY_EXTENSION_SERVICE, SERVICE_EXTENSION_SERVICE, SERVICE_POLICY_EXTENSION_SERVICE, SETTINGS_SERVICE, SHAREDZ_EXTENSION, SdkInfoTooltipComponent, SelectCellComponent, SelectorInputComponent, ServiceEdgeRouterPoliciesPageComponent, ServiceEdgeRouterPoliciesPageService, ServiceEdgeRouterPolicyFormComponent, ServiceEdgeRouterPolicyFormService, ServiceFormComponent, ServiceFormService, ServicePoliciesPageComponent, ServicePoliciesPageService, ServicePolicyFormComponent, ServicePolicyFormService, ServicesPageComponent, ServicesPageService, Session, SessionFormComponent, SessionRefreshService, SessionsPageComponent, SessionsPageService, SettingsComponent, SettingsService, SettingsServiceClass, SideModalComponent, SideNavbarComponent, SideNavigatorComponent, SimpleServiceComponent, StringInputComponent, TabNameService, TagSelectorComponent, TerminatorFormComponent, TerminatorFormService, TerminatorsPageComponent, TerminatorsPageService, TextListInputComponent, TokenCellComponent, TransitRouterFormComponent, TransitRouterFormService, TransitRoutersPageComponent, TransitRoutersPageService, ValidationService, VerifyCertificateComponent, VisualizerServiceClass, ZAC_LOGIN_SERVICE, ZAC_VERSION, ZITI_DATA_SERVICE, ZITI_DOMAIN_CONTROLLER, ZITI_NAVIGATOR, ZITI_TAB_OVERRIDES, ZITI_URLS, ZacRoutingModule, ZitiControllerDataService, ZitiDataService, ZitiOidcService, decodeJwtPayload, jwtExpMs, jwtIatMs, pkceChallengeS256, playerFactory, randomUrlSafeString };
41426
43874
  //# sourceMappingURL=openziti-ziti-console-lib.mjs.map