@openziti/ziti-console-lib 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6955,7 +6955,7 @@ class EdgeRouterFormService {
6955
6955
  this.growlerService.show(growlerData);
6956
6956
  }
6957
6957
  getAssociatedServices(id) {
6958
- this.zitiService.getSubdata('edge-routers', id, 'services').then((result) => {
6958
+ this.zitiService.getSubdata('edge-routers', id, 'services', this.zitiService.DEFAULT_PAGING).then((result) => {
6959
6959
  this.associatedServices = result.data;
6960
6960
  this.associatedServiceNames = this.associatedServices.map((svc) => {
6961
6961
  return svc.name;
@@ -6963,7 +6963,7 @@ class EdgeRouterFormService {
6963
6963
  });
6964
6964
  }
6965
6965
  getAssociatedIdentities(id) {
6966
- this.zitiService.getSubdata('edge-routers', id, 'identities').then((result) => {
6966
+ this.zitiService.getSubdata('edge-routers', id, 'identities', this.zitiService.DEFAULT_PAGING).then((result) => {
6967
6967
  this.associatedIdentities = result.data;
6968
6968
  this.associatedIdentityNames = this.associatedIdentities.map((policy) => {
6969
6969
  return policy.name;
@@ -10742,6 +10742,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
10742
10742
 
10743
10743
  class Link {
10744
10744
  constructor() {
10745
+ this.statusReason = null;
10745
10746
  this.linkType = 'inferred';
10746
10747
  }
10747
10748
  }
@@ -10829,6 +10830,7 @@ class IdentityServicePathHelper {
10829
10830
  srNode.id = selectedServiceOb.id;
10830
10831
  srNode.name = selectedServiceOb.name;
10831
10832
  srNode.intercept = serviceConfigAlias(serviceConfigs);
10833
+ buildServiceDetails(srNode, selectedServiceOb, serviceConfigs);
10832
10834
  srNode.type = 'Config Service';
10833
10835
  srNode.group = '4';
10834
10836
  srNode.status = '1';
@@ -10932,6 +10934,36 @@ class IdentityServicePathHelper {
10932
10934
  }
10933
10935
  }
10934
10936
  });
10937
+ // Per-host connectivity is derived from the identity's own controller-reported state
10938
+ // (hasEdgeRouterConnection / edgeRouterConnectionStatus -> routerConnection/apiSession),
10939
+ // NOT from terminators: real ziti terminators carry no hosting-identity id (identity:"",
10940
+ // no hostId), so a terminator can't be mapped back to the host that created it. Terminator
10941
+ // COUNT remains a service-level health hint only.
10942
+ // Annotate node connectivity so the renderer can pick a broken icon and the tooltip can
10943
+ // explain the cause. A hosting tunneler that is enrolled but unreachable, or expected to
10944
+ // host (Bind policy) yet has no terminator, is "broken".
10945
+ group3Nodes.forEach((node) => {
10946
+ if (node.status === 'Un-Registered') {
10947
+ node.brokenCause = 'misconfigured';
10948
+ node.connectivity = 'Not enrolled';
10949
+ }
10950
+ else if (node.apiSession === 'No' || node.routerConnection === 'No') {
10951
+ node.brokenCause = 'tunneler-unreachable';
10952
+ node.connectivity = 'Configured but not connected (offline or blocked)';
10953
+ }
10954
+ else {
10955
+ node.connectivity = 'Connected';
10956
+ }
10957
+ });
10958
+ if (endpointNodeOb && endpointNodeOb.status !== 'Un-Registered') {
10959
+ if (endpointNodeOb.apiSession === 'No' || endpointNodeOb.routerConnection === 'No') {
10960
+ endpointNodeOb.brokenCause = 'tunneler-unreachable';
10961
+ endpointNodeOb.connectivity = 'Configured but not connected (offline or blocked)';
10962
+ }
10963
+ else {
10964
+ endpointNodeOb.connectivity = 'Connected';
10965
+ }
10966
+ }
10935
10967
  // Build fabric link lookup
10936
10968
  const fabricLinkSet = new Set();
10937
10969
  fabricLinks.forEach((link) => {
@@ -10950,8 +10982,7 @@ class IdentityServicePathHelper {
10950
10982
  lnk.sourceName = endpointNodeOb.name;
10951
10983
  lnk.target = nd.id;
10952
10984
  lnk.targetName = nd.name;
10953
- lnk.status = getEndpointToRouterLinkState(endpointNodeOb, nd);
10954
- lnk.weight = getWeight(lnk.status);
10985
+ applyLinkState(lnk, getEndpointToRouterLinkState(endpointNodeOb, nd));
10955
10986
  lnk.linkType = activeCircuitHops.has(hopKey(endpointNodeOb.id, nd.id))
10956
10987
  ? 'active-circuit' : 'endpoint-connection';
10957
10988
  rootOb.addLink(lnk);
@@ -11055,11 +11086,17 @@ class IdentityServicePathHelper {
11055
11086
  lnk.target = r2.id;
11056
11087
  lnk.targetName = r2.name;
11057
11088
  lnk.linkType = isActiveCircuit ? 'active-circuit' : 'fabric-link';
11058
- lnk.status = (r1.online === 'Yes' || r1.status === 'PROVISIONED') &&
11059
- (r2.online === 'Yes' || r2.status === 'PROVISIONED') ? 1 : 0;
11060
- if (isActiveCircuit)
11061
- lnk.status = 1;
11062
- lnk.weight = getWeight(lnk.status);
11089
+ const r1Up = r1.online === 'Yes' || r1.status === 'PROVISIONED';
11090
+ const r2Up = r2.online === 'Yes' || r2.status === 'PROVISIONED';
11091
+ if (isActiveCircuit) {
11092
+ applyLinkState(lnk, { state: 1, reason: null });
11093
+ }
11094
+ else if (!r1Up || !r2Up) {
11095
+ applyLinkState(lnk, { state: 2, reason: 'router-down' });
11096
+ }
11097
+ else {
11098
+ applyLinkState(lnk, { state: 1, reason: null });
11099
+ }
11063
11100
  rootOb.addLink(lnk);
11064
11101
  });
11065
11102
  // If no fabric links connect dial to host side, add inferred connections
@@ -11108,8 +11145,7 @@ class IdentityServicePathHelper {
11108
11145
  lnk.sourceName = routerNode.name;
11109
11146
  lnk.target = hostNode.id;
11110
11147
  lnk.targetName = hostNode.name;
11111
- lnk.status = getEndpointToRouterLinkState(hostNode, routerNode);
11112
- lnk.weight = getWeight(lnk.status);
11148
+ applyLinkState(lnk, getEndpointToRouterLinkState(hostNode, routerNode));
11113
11149
  lnk.linkType = activeCircuitHops.has(hopKey(routerNode.id, hostNode.id))
11114
11150
  ? 'active-circuit' : 'endpoint-connection';
11115
11151
  rootOb.addLink(lnk);
@@ -11126,8 +11162,7 @@ class IdentityServicePathHelper {
11126
11162
  lnk.sourceName = routerNode.name;
11127
11163
  lnk.target = hostNode.id;
11128
11164
  lnk.targetName = hostNode.name;
11129
- lnk.status = getEndpointToRouterLinkState(hostNode, routerNode);
11130
- lnk.weight = getWeight(lnk.status);
11165
+ applyLinkState(lnk, getEndpointToRouterLinkState(hostNode, routerNode));
11131
11166
  lnk.linkType = activeCircuitHops.has(hopKey(routerNode.id, hostNode.id))
11132
11167
  ? 'active-circuit'
11133
11168
  : (lnk.status === 1 ? 'endpoint-connection' : 'inferred');
@@ -11150,8 +11185,7 @@ class IdentityServicePathHelper {
11150
11185
  lnk.sourceName = dialRouter.name;
11151
11186
  lnk.target = hostNode.id;
11152
11187
  lnk.targetName = hostNode.name;
11153
- lnk.status = getEndpointToRouterLinkState(hostNode, dialRouter);
11154
- lnk.weight = getWeight(lnk.status);
11188
+ applyLinkState(lnk, getEndpointToRouterLinkState(hostNode, dialRouter));
11155
11189
  lnk.linkType = lnk.status === 1 ? 'endpoint-connection' : 'inferred';
11156
11190
  rootOb.addLink(lnk);
11157
11191
  }
@@ -11178,8 +11212,7 @@ class IdentityServicePathHelper {
11178
11212
  lnk.target = serviceNode.id;
11179
11213
  lnk.targetName = serviceNode.name;
11180
11214
  lnk.weight = 1;
11181
- lnk.status = getServiceToEndpointLinkState(hostNode, serviceNode);
11182
- lnk.weight = getWeight(lnk.status);
11215
+ applyLinkState(lnk, getServiceToEndpointLinkState(hostNode, serviceNode));
11183
11216
  lnk.linkType = activeCircuitHops.has(hopKey(hostNode.id, serviceNode.id))
11184
11217
  ? 'active-circuit' : 'endpoint-connection';
11185
11218
  rootOb.addLink(lnk);
@@ -11199,6 +11232,28 @@ class IdentityServicePathHelper {
11199
11232
  return false;
11200
11233
  return true;
11201
11234
  });
11235
+ // Collapse parallel links between the same node pair. A router that is on BOTH the dial
11236
+ // and host side can otherwise produce two links to the same node — e.g. an 'available'
11237
+ // inferred link and a 'router-down' link — which overlap and render as mixed dashed/dotted.
11238
+ // Keep the single most-significant link per pair.
11239
+ const linkSeverity = (l) => {
11240
+ if (l.linkType === 'active-circuit')
11241
+ return 4; // live traffic wins
11242
+ if (l.status === 2)
11243
+ return 3; // broken (router-down / tunneler-unreachable)
11244
+ if (l.status === 1)
11245
+ return 2; // connected
11246
+ return 1; // available / inferred
11247
+ };
11248
+ const bestLinkByPair = new Map();
11249
+ rootOb.links.forEach((lnk) => {
11250
+ const key = [lnk.source, lnk.target].sort().join('::');
11251
+ const existing = bestLinkByPair.get(key);
11252
+ if (!existing || linkSeverity(lnk) > linkSeverity(existing)) {
11253
+ bestLinkByPair.set(key, lnk);
11254
+ }
11255
+ });
11256
+ rootOb.links = Array.from(bestLinkByPair.values());
11202
11257
  return rootOb;
11203
11258
  }
11204
11259
  /**
@@ -11236,6 +11291,7 @@ class IdentityServicePathHelper {
11236
11291
  srNode.id = selectedServiceOb.id;
11237
11292
  srNode.name = selectedServiceOb.name;
11238
11293
  srNode.intercept = serviceConfigAlias(serviceConfigs);
11294
+ buildServiceDetails(srNode, selectedServiceOb, serviceConfigs);
11239
11295
  srNode.type = 'Config Service';
11240
11296
  srNode.group = '4';
11241
11297
  srNode.status = '1';
@@ -11335,8 +11391,7 @@ class IdentityServicePathHelper {
11335
11391
  lnk.sourceName = endpointNodeOb.name;
11336
11392
  lnk.target = nd.id;
11337
11393
  lnk.targetName = nd.name;
11338
- lnk.status = getEndpointToRouterLinkState(endpointNodeOb, nd);
11339
- lnk.weight = getWeight(lnk.status);
11394
+ applyLinkState(lnk, getEndpointToRouterLinkState(endpointNodeOb, nd));
11340
11395
  lnk.linkType = 'endpoint-connection';
11341
11396
  rootOb.addLink(lnk);
11342
11397
  }
@@ -11361,9 +11416,10 @@ class IdentityServicePathHelper {
11361
11416
  nd2.status === 'PROVISIONED' &&
11362
11417
  nd2.online === 'Yes'
11363
11418
  ? 1 : 0;
11419
+ lnk.statusReason = lnk.status === 1 ? null : 'router-down';
11364
11420
  }
11365
11421
  else {
11366
- lnk.status = getEndpointToRouterLinkState(nd2, nd1);
11422
+ applyLinkState(lnk, getEndpointToRouterLinkState(nd2, nd1));
11367
11423
  }
11368
11424
  lnk.weight = getWeight(lnk.status);
11369
11425
  lnk.linkType = 'inferred';
@@ -11386,8 +11442,7 @@ class IdentityServicePathHelper {
11386
11442
  lnk.status = foundNd.online === 'Yes' ? 1 : -1;
11387
11443
  }
11388
11444
  else {
11389
- lnk.status = getServiceToEndpointLinkState(group3Nodes[k1], group4Nodes[k0]);
11390
- lnk.weight = getWeight(lnk.status);
11445
+ applyLinkState(lnk, getServiceToEndpointLinkState(group3Nodes[k1], group4Nodes[k0]));
11391
11446
  }
11392
11447
  lnk.linkType = 'endpoint-connection';
11393
11448
  rootOb.addLink(lnk);
@@ -11455,6 +11510,44 @@ function serviceConfigAlias(configs) {
11455
11510
  });
11456
11511
  return intercept;
11457
11512
  }
11513
+ // Annotates the service node with labeled, purpose-specific details so the hover card can explain
11514
+ // what each address is for and surface the service's configuration. Handles services that are
11515
+ // intercept+host, host-only, intercept-only, or SDK-defined (no address configs at all).
11516
+ function buildServiceDetails(srNode, service, configs) {
11517
+ srNode.encryptionRequired = service ? service.encryptionRequired : undefined;
11518
+ srNode.terminatorStrategy = service ? service.terminatorStrategy : undefined;
11519
+ srNode.serviceRoleAttributes = _.isArray(service && service.roleAttributes) ? service.roleAttributes : [];
11520
+ srNode.permissions = _.isArray(service && service.permissions) ? service.permissions : [];
11521
+ const fmtPorts = (portRanges, port) => {
11522
+ if (_.isArray(portRanges)) {
11523
+ return portRanges.map((r) => (r.low === r.high ? `${r.low}` : `${r.low}-${r.high}`));
11524
+ }
11525
+ if (port !== undefined && port !== null)
11526
+ return [String(port)];
11527
+ return [];
11528
+ };
11529
+ const fmtEndpoint = (addrs, ports, protos) => {
11530
+ const a = (addrs || []).filter(Boolean).join(', ');
11531
+ const detail = [(protos || []).join('/'), (ports || []).join(',')].filter(Boolean).join('/');
11532
+ return detail ? `${a} ${detail}` : a;
11533
+ };
11534
+ const types = [];
11535
+ _.isArray(configs) && configs.forEach((conf) => {
11536
+ const t = ((conf && conf.configType && conf.configType.name) || (conf && conf.type) || '').toString();
11537
+ const d = (conf && conf.data) || {};
11538
+ if (t)
11539
+ types.push(t);
11540
+ if (t.indexOf('intercept') === 0) {
11541
+ const addrs = d.addresses || (d.address ? [d.address] : []);
11542
+ srNode.interceptSummary = fmtEndpoint(addrs, fmtPorts(d.portRanges, d.port), d.protocols || (d.protocol ? [d.protocol] : []));
11543
+ }
11544
+ else if (t.indexOf('host') === 0) {
11545
+ const addrs = d.address ? [d.address] : (d.forwardAddress ? ['(forwarded)'] : []);
11546
+ srNode.hostSummary = fmtEndpoint(addrs, fmtPorts(d.allowedPortRanges, d.port), d.protocol ? [d.protocol] : (d.protocols || (d.forwardProtocol ? ['(forwarded)'] : [])));
11547
+ }
11548
+ });
11549
+ srNode.configTypeNames = types;
11550
+ }
11458
11551
  function findEndpoint(endpointId, endpoints) {
11459
11552
  if (endpointId === null) {
11460
11553
  return null;
@@ -11474,7 +11567,10 @@ function createEndpoint(ePoint) {
11474
11567
  node.id = ePoint.id;
11475
11568
  node.name = ePoint.name;
11476
11569
  node.type = 'Identity';
11477
- if (_.has(ePoint, 'edgeRouterConnectionStatus')) {
11570
+ // Prefer the explicit booleans, which controllers populate reliably. Only fall back to the
11571
+ // edgeRouterConnectionStatus string when it actually carries a value — note its key can be
11572
+ // PRESENT-BUT-NULL, so `_.has` is not a safe guard (that bug made every identity look "Yes").
11573
+ if (ePoint.edgeRouterConnectionStatus != null) {
11478
11574
  node.routerConnection = ePoint.edgeRouterConnectionStatus === 'offline' ? 'No' : 'Yes';
11479
11575
  node.apiSession = node.routerConnection;
11480
11576
  }
@@ -11484,7 +11580,27 @@ function createEndpoint(ePoint) {
11484
11580
  }
11485
11581
  node.os = ePoint.envInfo ? ePoint.envInfo.os : '';
11486
11582
  node.mfaEnabled = ePoint.isMfaEnabled;
11487
- node.status = ePoint.envInfo && ePoint.envInfo.os !== null ? 'Registered' : 'Un-Registered';
11583
+ // "Registered" means the identity has enrolled and run (it has reported env info). Guard on a
11584
+ // truthy os: the controller returns envInfo as an empty object {} for a created-but-never-
11585
+ // enrolled identity, and `{}.os !== null` is true — which previously mis-classed it Registered.
11586
+ node.status = (ePoint.envInfo && ePoint.envInfo.os) ? 'Registered' : 'Un-Registered';
11587
+ // Node status indicator. Mirrors the controller's edgeRouterConnectionStatus enum
11588
+ // (online | offline | unknown) when present, falling back to the hasEdgeRouterConnection
11589
+ // boolean otherwise. An identity that hasn't enrolled is "unenrolled" (its own state, not an
11590
+ // error). There is no "error" value in the API, so we don't synthesize one.
11591
+ if (node.status === 'Un-Registered') {
11592
+ node.connState = 'unenrolled';
11593
+ }
11594
+ else {
11595
+ const ers = ePoint.edgeRouterConnectionStatus;
11596
+ if (ers === 'online' || ers === 'offline' || ers === 'unknown') {
11597
+ node.connState = ers;
11598
+ }
11599
+ else {
11600
+ node.connState = (ePoint.hasEdgeRouterConnection === true || ePoint.hasApiSession === true)
11601
+ ? 'online' : 'offline';
11602
+ }
11603
+ }
11488
11604
  return node;
11489
11605
  }
11490
11606
  function createERNode(erouter) {
@@ -11509,38 +11625,58 @@ function createFabricRouterNode(fabricRouter, routerType) {
11509
11625
  nodeOb.routerType = routerType;
11510
11626
  return nodeOb;
11511
11627
  }
11628
+ // Determines the state of an identity/tunneler <-> edge-router link.
11629
+ // A router-reported problem (offline/errored router) is distinguished from a tunneler-side
11630
+ // problem (enrolled identity that cannot establish a connection to the router, e.g. blocked
11631
+ // by a firewall) so the visualizer can render the two causes differently.
11512
11632
  function getEndpointToRouterLinkState(endpointNode, routerNode) {
11513
- let linkstate = -1;
11514
- if (routerNode.status === 'ERROR') {
11515
- linkstate = 2; // router is broken — warning
11633
+ if (routerNode.status === 'ERROR' || routerNode.online === 'No') {
11634
+ return { state: 2, reason: 'router-down' }; // router-reported issue
11516
11635
  }
11517
- else if (endpointNode.status === 'Un-Registered') {
11518
- linkstate = -1;
11519
- }
11520
- else if (endpointNode.apiSession === 'No' || endpointNode.routerConnection === 'No') {
11521
- // No session or no connection = offline, not error
11522
- linkstate = -1;
11636
+ if (endpointNode.status === 'Un-Registered') {
11637
+ // Never enrolled — the path through this identity can't carry traffic, so it's not
11638
+ // "available". Marked unavailable (the node still shows the specific "Not enrolled" cause).
11639
+ return { state: 2, reason: 'misconfigured' };
11523
11640
  }
11524
- else if (routerNode.online === 'Yes' && endpointNode.routerConnection === 'Yes') {
11525
- linkstate = 1;
11641
+ if (endpointNode.apiSession === 'No' || endpointNode.routerConnection === 'No') {
11642
+ // Enrolled but cannot reach the edge router — tunneler-side broken (was previously
11643
+ // shown as a benign "available" link, which is exactly what we're fixing).
11644
+ return { state: 2, reason: 'tunneler-unreachable' };
11526
11645
  }
11527
- else {
11528
- linkstate = -1;
11646
+ if (routerNode.online === 'Yes' && endpointNode.routerConnection === 'Yes') {
11647
+ return { state: 1, reason: null }; // active/available
11529
11648
  }
11530
- return linkstate;
11649
+ return { state: -1, reason: null };
11531
11650
  }
11532
- function getServiceToEndpointLinkState(endpointNode, serviceNode) {
11533
- // status 1 = active, -1 = offline/available, 2 = warning/misconfigured
11534
- if (endpointNode.status === 'Un-Registered') {
11535
- return 2; // hosting identity never enrolled — misconfigured
11651
+ // Determines the state of a hosting identity/router <-> service link.
11652
+ // hasTerminator is the authoritative signal that the host actually established its hosting
11653
+ // connection: a Bind policy makes a host "expected", but only a terminator proves it connected.
11654
+ function getServiceToEndpointLinkState(hostNode, serviceNode) {
11655
+ const isRouter = !!(hostNode.type && String(hostNode.type).includes('Router'));
11656
+ if (isRouter) {
11657
+ if (hostNode.status === 'ERROR' || hostNode.online === 'No') {
11658
+ return { state: 2, reason: 'router-down' };
11659
+ }
11660
+ return { state: 1, reason: null };
11661
+ }
11662
+ // Identity host (tunneler). Broken-detection uses the host's own connection state, since
11663
+ // terminators can't be attributed to a specific host (see note in getEndpointGraphObj).
11664
+ if (hostNode.status === 'Un-Registered') {
11665
+ return { state: 2, reason: 'misconfigured' }; // never enrolled
11536
11666
  }
11537
- else if (endpointNode.apiSession === 'No' || endpointNode.routerConnection === 'No') {
11538
- return -1; // just offline
11667
+ if (hostNode.apiSession === 'No' || hostNode.routerConnection === 'No') {
11668
+ return { state: 2, reason: 'tunneler-unreachable' }; // enrolled but unreachable (e.g. firewall)
11539
11669
  }
11540
- else if (endpointNode.status === 'Registered' && endpointNode.routerConnection === 'Yes') {
11541
- return 1; // active
11670
+ if (hostNode.status === 'Registered' && hostNode.routerConnection === 'Yes') {
11671
+ return { state: 1, reason: null }; // active/available
11542
11672
  }
11543
- return -1;
11673
+ return { state: -1, reason: null };
11674
+ }
11675
+ // Applies a computed LinkState to a link, including its derived weight.
11676
+ function applyLinkState(lnk, ls) {
11677
+ lnk.status = ls.state;
11678
+ lnk.statusReason = ls.reason;
11679
+ lnk.weight = getWeight(ls.state);
11544
11680
  }
11545
11681
  function getWeight(linkState) {
11546
11682
  return linkState === -1 ? 5 : 1;
@@ -11747,10 +11883,16 @@ class IdentityServicePathComponent {
11747
11883
  this.isDarkMode = false;
11748
11884
  this.refreshRotate = false;
11749
11885
  this.closeRotate = true;
11886
+ this.lastRenderContext = null;
11887
+ this.zoomBehavior = null;
11888
+ this.hasInitialFit = false;
11889
+ this.viewBoxWidth = 1050;
11890
+ this.viewBoxHeight = 500;
11750
11891
  this.filterText = 'fetching services..';
11751
11892
  this.noSearchResults = false;
11752
11893
  this.autocompleteOptions = [];
11753
11894
  this.fullScreen = false;
11895
+ this.legendCollapsed = false;
11754
11896
  this.endpoints = [];
11755
11897
  this.selectedService = '';
11756
11898
  this.doEndpointHasAppwans = false;
@@ -11901,6 +12043,8 @@ class IdentityServicePathComponent {
11901
12043
  this.serviceChanged();
11902
12044
  }
11903
12045
  serviceChanged() {
12046
+ // Service change implies new topology — re-fit on next render.
12047
+ this.hasInitialFit = false;
11904
12048
  this.isLoading = true;
11905
12049
  const allPromises = [];
11906
12050
  let serviceOb = null;
@@ -12013,12 +12157,57 @@ class IdentityServicePathComponent {
12013
12157
  });
12014
12158
  }
12015
12159
  }
12160
+ this.lastRenderContext = {
12161
+ bindIdnetities,
12162
+ serviceOb,
12163
+ serviceConfigs: service_configs,
12164
+ hostEdgeRouters,
12165
+ };
12016
12166
  this.startVisProcess(bindIdnetities, serviceOb, service_configs, hostEdgeRouters, serviceTerminators, serviceCircuits);
12017
12167
  this.isLoading = false;
12018
12168
  });
12019
12169
  });
12020
12170
  });
12021
12171
  }
12172
+ async refreshFabricData() {
12173
+ if (this.refreshRotate) {
12174
+ return;
12175
+ }
12176
+ this.refreshRotate = true;
12177
+ try {
12178
+ const results = await Promise.allSettled([
12179
+ this.mapDataService.fetchRouters(),
12180
+ this.mapDataService.fetchLinks(),
12181
+ this.mapDataService.fetchCircuits(),
12182
+ this.mapDataService.fetchTerminators(),
12183
+ ]);
12184
+ const [routersResult, linksResult, circuitsResult, terminatorsResult] = results;
12185
+ const allFailed = results.every((r) => r.status === 'rejected');
12186
+ this.fabricApiAvailable = !allFailed;
12187
+ if (routersResult.status === 'fulfilled') {
12188
+ this.allRouters = routersResult.value.routers || [];
12189
+ this.routerTypesMap = routersResult.value.routerTypes || new Map();
12190
+ }
12191
+ if (linksResult.status === 'fulfilled') {
12192
+ this.allLinks = linksResult.value || [];
12193
+ }
12194
+ if (circuitsResult.status === 'fulfilled') {
12195
+ this.allCircuits = circuitsResult.value || [];
12196
+ }
12197
+ if (terminatorsResult.status === 'fulfilled') {
12198
+ this.allTerminators = terminatorsResult.value || [];
12199
+ }
12200
+ if (this.lastRenderContext) {
12201
+ const { bindIdnetities, serviceOb, serviceConfigs, hostEdgeRouters } = this.lastRenderContext;
12202
+ const serviceTerminators = this.allTerminators.filter((t) => (t.serviceId || t.service?.id || t.service) === serviceOb.id);
12203
+ const serviceCircuits = this.allCircuits.filter((c) => c.service?.id === serviceOb.id);
12204
+ this.startVisProcess(bindIdnetities, serviceOb, serviceConfigs, hostEdgeRouters, serviceTerminators, serviceCircuits);
12205
+ }
12206
+ }
12207
+ finally {
12208
+ this.refreshRotate = false;
12209
+ }
12210
+ }
12022
12211
  addItemToArray(bindIdnetities, ob) {
12023
12212
  if (!bindIdnetities.find((item) => item.id === ob.id)) {
12024
12213
  bindIdnetities.push(ob);
@@ -12076,21 +12265,42 @@ class IdentityServicePathComponent {
12076
12265
  this.dialogRef.close();
12077
12266
  }
12078
12267
  initTopoView() {
12079
- const vbWidth = 1050;
12080
- const vbHeight = 500;
12268
+ const vbWidth = this.viewBoxWidth;
12269
+ const vbHeight = this.viewBoxHeight;
12081
12270
  const colors = d3.scaleOrdinal(d3.schemeCategory10);
12082
12271
  const tooltip = d3.select('#epTooltip');
12083
- let svg = d3.select('#IdentityTopology'), node, link;
12084
- const handleZoom = (e) => svg.attr('transform', e.transform);
12085
- const zoom = d3.zoom().on('zoom', handleZoom);
12272
+ // Hide is deferred so the cursor can travel from the node into the popover (to read/select
12273
+ // its content); entering the popover cancels the hide, leaving it dismisses it.
12274
+ let tooltipHideTimer = null;
12275
+ tooltip.on('mouseenter', () => clearTimeout(tooltipHideTimer));
12276
+ tooltip.on('mouseleave', () => tooltip.style('display', 'none'));
12277
+ const svg = d3.select('#IdentityTopology');
12278
+ const svgEl = svg.node();
12279
+ let node, link;
12086
12280
  svg
12087
12281
  .attr('width', '100%')
12088
12282
  .attr('height', '100%')
12089
- // .call(zoom)
12090
12283
  .attr('viewBox', '-20 -20 ' + (vbWidth + 40) + ' ' + vbHeight)
12091
- .attr('preserveAspectRatio', 'xMidYMin meet')
12092
- .append('g');
12284
+ .attr('preserveAspectRatio', 'xMidYMin meet');
12285
+ // Capture any pre-existing zoom transform (from previous render / user gestures)
12286
+ // so we can re-apply it after recreating the content group.
12287
+ const existingTransform = svgEl
12288
+ ? d3.zoomTransform(svgEl)
12289
+ : d3.zoomIdentity;
12093
12290
  svg.selectAll('g').remove();
12291
+ // Wrap all topology content in a <g> so zoom transforms have something to translate.
12292
+ const contentG = svg.append('g').attr('class', 'topology-content');
12293
+ contentG.attr('transform', existingTransform.toString());
12294
+ const handleZoom = (e) => {
12295
+ contentG.attr('transform', e.transform);
12296
+ };
12297
+ if (!this.zoomBehavior) {
12298
+ this.zoomBehavior = d3.zoom().scaleExtent([0.1, 5]).on('zoom', handleZoom);
12299
+ }
12300
+ else {
12301
+ this.zoomBehavior.on('zoom', handleZoom);
12302
+ }
12303
+ svg.call(this.zoomBehavior);
12094
12304
  const simulation = d3
12095
12305
  .forceSimulation()
12096
12306
  .force('link', d3
@@ -12104,47 +12314,49 @@ class IdentityServicePathComponent {
12104
12314
  .force('center', d3.forceCenter(vbWidth / 2, vbHeight / 2));
12105
12315
  const links = this.graphJsonObj.links;
12106
12316
  const nodes = this.graphJsonObj.nodes;
12107
- link = svg
12317
+ link = contentG
12108
12318
  .selectAll('g.line.line')
12109
12319
  .data(links)
12110
12320
  .enter()
12111
12321
  .append('g')
12112
12322
  .attr('class', 'link')
12113
12323
  .attr('marker-end', 'url(#arrowhead)');
12324
+ // Three link styles, distinguishable by pattern (color-blind-safe) and color. A single
12325
+ // "broken" style covers every failure — which END is the problem is read from the node
12326
+ // colors (an offline router vs an unreachable/un-enrolled endpoint), so the link needn't
12327
+ // re-encode the cause:
12328
+ // active circuit -> green solid
12329
+ // available -> gray dashed
12330
+ // broken -> red dotted
12331
+ const isBroken = (d) => d.status === 2;
12114
12332
  link
12115
12333
  .append('line')
12116
12334
  .style('stroke-width', function (d) {
12117
- if (d.linkType === 'active-circuit') {
12335
+ if (d.linkType === 'active-circuit')
12118
12336
  return 1.5;
12119
- }
12337
+ if (isBroken(d))
12338
+ return 2.25;
12120
12339
  return 1.25;
12340
+ })
12341
+ .style('stroke-linecap', function (d) {
12342
+ // Rounded caps turn the tight broken dash array into actual dots.
12343
+ return isBroken(d) ? 'round' : 'butt';
12121
12344
  })
12122
12345
  .style('stroke', function (d) {
12123
- if (d.linkType === 'active-circuit') {
12346
+ if (d.linkType === 'active-circuit')
12124
12347
  return '#00cd13';
12125
- }
12348
+ if (isBroken(d))
12349
+ return '#e6432e';
12126
12350
  return '#8a8f9a';
12127
12351
  })
12128
12352
  .style('stroke-dasharray', function (d) {
12129
- if (d.linkType === 'active-circuit') {
12130
- return null;
12131
- }
12132
- return '5,5';
12133
- });
12134
- link.each(function (d, i) {
12135
- const _this = d3.select(this);
12136
- if (d.status === 2) {
12137
- // Warning/misconfigured — show link-cut icon
12138
- _this
12139
- .append('image')
12140
- .attr('xlink:href', function () {
12141
- return 'assets/images/visualizer/link-cut.png';
12142
- })
12143
- .attr('width', '20')
12144
- .attr('height', '24');
12145
- }
12353
+ if (d.linkType === 'active-circuit')
12354
+ return null; // solid
12355
+ if (isBroken(d))
12356
+ return '0.5,6'; // dotted
12357
+ return '5,5'; // available: dashed
12146
12358
  });
12147
- node = svg
12359
+ node = contentG
12148
12360
  .selectAll('.node')
12149
12361
  .data(nodes)
12150
12362
  .enter()
@@ -12154,61 +12366,73 @@ class IdentityServicePathComponent {
12154
12366
  .append('image')
12155
12367
  .attr('xlink:href', function (d) {
12156
12368
  const routerImagePath = 'assets/images/visualizer/Routers.png';
12157
- const nonProvisionedRouterImagePath = 'assets/images/visualizer/ER.png';
12158
- const errRouterImagePath = 'assets/images/visualizer/error_er.png';
12159
- const errEndpointImagePath = 'assets/images/visualizer/host-error.png';
12160
12369
  const unregisteredEndpointImagePath = 'assets/images/visualizer/host_unregistered.png';
12161
12370
  const endPointPath = 'assets/images/visualizer/host.png';
12162
- if (d.type.includes('Identity') && d.status === 'Un-Registered') {
12163
- return unregisteredEndpointImagePath;
12164
- }
12165
- else if (d.type.includes('Identity') && d.routerConnection === 'No') {
12166
- return unregisteredEndpointImagePath;
12167
- }
12168
- else if (d.type.includes('Identity')) {
12371
+ if (d.type.includes('Identity')) {
12372
+ // Un-enrolled identities get the distinct "unregistered" icon; otherwise the normal
12373
+ // host icon — the status dot below conveys online/offline/unknown.
12374
+ if (d.connState === 'unenrolled' || d.status === 'Un-Registered') {
12375
+ return unregisteredEndpointImagePath;
12376
+ }
12169
12377
  return endPointPath;
12170
12378
  }
12171
- else if (d.type.includes('Router') &&
12172
- (d.status === 'ERROR' || d.online === 'No')) {
12173
- return errRouterImagePath;
12174
- }
12175
- else if (d.type.includes('Router') && d.status === 'PROVISIONED') {
12379
+ if (d.type.includes('Router')) {
12380
+ // One base router icon; the status dot below carries online/offline (the baked dot in
12381
+ // the PNG is covered by that overlay).
12176
12382
  return routerImagePath;
12177
12383
  }
12178
- else if (d.type.includes('Router')) {
12179
- return nonProvisionedRouterImagePath;
12180
- }
12181
12384
  if (d.group === '4') {
12182
12385
  return 'assets/images/visualizer/service.png';
12183
12386
  }
12184
- else {
12185
- return '';
12186
- }
12387
+ return '';
12187
12388
  })
12188
12389
  .attr('x', -25)
12189
12390
  .attr('y', -25)
12190
12391
  .attr('width', '40')
12191
12392
  .attr('height', '40');
12192
- // Add status circle overlay for identity nodes
12193
- node.filter((d) => d.type.includes('Identity'))
12393
+ // Status dot for identities AND routers. Colors a single dot by the node's real state
12394
+ // (no "error" — that isn't a value the API exposes). Routers use a larger dot positioned to
12395
+ // cover the status dot baked into the router PNG.
12396
+ const statusColor = (d) => {
12397
+ if (d.type.includes('Router')) {
12398
+ return (d.online === 'No' || d.status === 'ERROR') ? '#8a8f9a' : '#08dc5a';
12399
+ }
12400
+ switch (d.connState) {
12401
+ case 'online': return '#08dc5a'; // green
12402
+ case 'unknown': return '#e6a700'; // amber — controller can't determine
12403
+ case 'unenrolled': return '#ffffff'; // hollow (gray ring below)
12404
+ case 'offline':
12405
+ default: return '#8a8f9a'; // gray
12406
+ }
12407
+ };
12408
+ // Positioned to sit directly over the dot baked into each icon PNG (≈ the icon's top-right),
12409
+ // sized just large enough to cover it (the white halo masks the baked dot's soft glow).
12410
+ node.filter((d) => d.type.includes('Identity') || d.type.includes('Router'))
12194
12411
  .append('circle')
12195
- .attr('cx', 10)
12196
- .attr('cy', -20)
12197
- .attr('r', 4.5)
12198
- .attr('fill', function (d) {
12199
- if (d.routerConnection === 'Yes' || d.apiSession === 'Yes')
12200
- return '#08dc5a';
12201
- return '#8a8f9a';
12202
- })
12203
- .attr('stroke', 'white')
12204
- .attr('stroke-width', 1.5);
12205
- // Symmetric labels — truncated, centered below each node
12412
+ .attr('cx', (d) => d.type.includes('Router') ? 6 : 8)
12413
+ .attr('cy', (d) => d.type.includes('Router') ? -20 : -19)
12414
+ .attr('r', 5)
12415
+ .attr('fill', (d) => statusColor(d))
12416
+ // Un-enrolled identities render as a hollow ring (gray border); everything else gets a
12417
+ // white separator ring against the icon.
12418
+ .attr('stroke', (d) => (!d.type.includes('Router') && d.connState === 'unenrolled') ? '#8a8f9a' : 'white')
12419
+ .attr('stroke-width', 2);
12420
+ // Node labels: for compound pipe-delimited names ("<networkId>|<identityId>|Human Name")
12421
+ // show only the last segment so the canvas stays readable; the full name remains in the
12422
+ // hover tooltip.
12423
+ const getDisplayLabel = (fullName) => {
12424
+ if (!fullName)
12425
+ return '';
12426
+ const segments = fullName.split('|').map((s) => s.trim()).filter((s) => s.length > 0);
12427
+ const candidate = segments.length > 0 ? segments[segments.length - 1] : fullName;
12428
+ const maxLen = 28;
12429
+ return candidate.length > maxLen
12430
+ ? candidate.substring(0, maxLen) + '…'
12431
+ : candidate;
12432
+ };
12206
12433
  node.each(function (d) {
12207
12434
  const g = d3.select(this);
12208
- const maxLen = 50;
12209
- const displayName = d.name && d.name.length > maxLen
12210
- ? d.name.substring(0, maxLen) + '\u2026'
12211
- : d.name;
12435
+ const displayName = getDisplayLabel(d.name);
12212
12436
  const text = g.append('text')
12213
12437
  .attr('x', 0)
12214
12438
  .attr('y', 28)
@@ -12311,10 +12535,13 @@ class IdentityServicePathComponent {
12311
12535
  } // end of ticked
12312
12536
  function removeTooltip() {
12313
12537
  if (tooltip) {
12314
- tooltip.style('display', 'none');
12538
+ // Defer the hide so the user can move onto the popover; mouseenter (above) cancels it.
12539
+ clearTimeout(tooltipHideTimer);
12540
+ tooltipHideTimer = setTimeout(() => tooltip.style('display', 'none'), 220);
12315
12541
  }
12316
12542
  }
12317
12543
  function drawTooltip(event, d, endpointUtlizationJson) {
12544
+ clearTimeout(tooltipHideTimer);
12318
12545
  document.getElementById('epTooltip').innerHTML = readKeyValues(d, endpointUtlizationJson);
12319
12546
  tooltip.style('display', 'block');
12320
12547
  tooltip.style('white-space', 'pre-wrap');
@@ -12323,34 +12550,133 @@ class IdentityServicePathComponent {
12323
12550
  tooltip.style('left', x);
12324
12551
  tooltip.style('top', y);
12325
12552
  }
12553
+ // Builds a compact status "card" for a node's hover tooltip: a bold name, a prominent
12554
+ // plain-language status line (with cause), and a few key facts. Makes the connectivity of
12555
+ // customer-hosted tunnelers and routers immediately legible.
12326
12556
  function readKeyValues(d, endpointUtlizationJson) {
12327
- const excludeKeys = new Set([
12328
- 'status', 'posy', 'posx', 'group', 'hostedEdgeId', 'value',
12329
- 'index', 'parent', 'depth', 'x', 'y', 'id', 'x0', 'y0',
12330
- 'vy', 'vx', 'routerType', 'linkType', 'provider', 'usage',
12331
- ]);
12332
- let info = "<div class='tool-tip-container'>";
12333
- // Show name as header
12334
- if (d.name) {
12335
- info += '<div class="tooltip-header">' + d.name + '</div>';
12336
- }
12337
- const keys = Object.keys(d);
12338
- keys.forEach(function (k) {
12339
- if (excludeKeys.has(k) || k === 'name')
12340
- return;
12341
- let val = d[k];
12342
- if (val === null || val === undefined || val === '')
12343
- return;
12344
- const label = k.replace(/([A-Z])/g, ' $1').replace(/^./, s => s.toUpperCase());
12345
- if (isAnObject(val)) {
12346
- val = val.name || JSON.stringify(val);
12557
+ const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));
12558
+ const isRouter = !!(d.type && String(d.type).includes('Router'));
12559
+ const isService = d.group === '4' || !!(d.type && String(d.type).includes('Service'));
12560
+ // The status band is the single source of truth for connectivity/enrollment — it replaces
12561
+ // any separate "Enrolled" row. `caption` is a short fragment, used only where it tells the
12562
+ // operator something the label alone doesn't (i.e. what to go check).
12563
+ // Status pill mirrors the node dot — the component's real state (online / offline /
12564
+ // unknown / not-enrolled), never a synthesized "error". Path problems live on the links.
12565
+ let cls = 'neutral';
12566
+ let label = '';
12567
+ if (isService) {
12568
+ label = 'Service';
12569
+ }
12570
+ else if (isRouter) {
12571
+ // Routers only expose isOnline (no edgeRouterConnectionStatus).
12572
+ if (d.online === 'No' || d.status === 'ERROR') {
12573
+ cls = 'offline';
12574
+ label = 'Offline';
12575
+ }
12576
+ else {
12577
+ cls = 'ok';
12578
+ label = 'Online';
12347
12579
  }
12348
- else if (val instanceof Array) {
12349
- val = val.join(', ');
12580
+ }
12581
+ else {
12582
+ switch (d.connState) {
12583
+ case 'online':
12584
+ cls = 'ok';
12585
+ label = 'Online';
12586
+ break;
12587
+ case 'unknown':
12588
+ cls = 'unknown';
12589
+ label = 'Unknown';
12590
+ break;
12591
+ case 'unenrolled':
12592
+ cls = 'offline';
12593
+ label = 'Not enrolled';
12594
+ break;
12595
+ case 'offline':
12596
+ default:
12597
+ cls = 'offline';
12598
+ label = 'Offline';
12599
+ break;
12350
12600
  }
12351
- info += '<div class="prop-row">'
12352
- + '<span class="prop-label">' + label + '</span>'
12353
- + '<span class="prop-value">' + val + '</span>'
12601
+ }
12602
+ // No role for the service node — the icon/name already make it obvious, and a "Role: Service"
12603
+ // row would just repeat itself.
12604
+ // Role is the policy action (Dial/Bind) the identity has for the service — NOT the host
12605
+ // mechanism, which we can't know (tunneler vs SDK vs router all look the same here).
12606
+ let role = '';
12607
+ if (d.group === '1')
12608
+ role = 'Client (Dial)';
12609
+ else if (d.group === '3')
12610
+ role = 'Host (Bind)';
12611
+ else if (isRouter)
12612
+ role = d.routerType === 'transit-router' ? 'Transit router' : 'Edge router';
12613
+ // Facts that add detail beyond the status pill. The pill summarizes overall state; these
12614
+ // are the granular signals an operator uses to diagnose it (a host can have an API session
12615
+ // yet no router connection). Only the pill's own inputs (Online/Type/Enrolled) are omitted.
12616
+ // Rows are [label, value, optional hover-context]. Service rows are purpose-labeled and
12617
+ // adapt to how the service is configured (intercept+host, host-only, intercept-only, or
12618
+ // SDK-defined with no address config).
12619
+ const rows = [];
12620
+ if (isService) {
12621
+ if (d.interceptSummary) {
12622
+ rows.push(['Intercept', d.interceptSummary]);
12623
+ }
12624
+ if (d.hostSummary) {
12625
+ rows.push(['Host', d.hostSummary]);
12626
+ }
12627
+ if (!d.interceptSummary && !d.hostSummary) {
12628
+ // No intercept/host config. State the facts (the config types present, or none) rather
12629
+ // than inferring "SDK-defined" — there's no property that actually tells us the host type.
12630
+ rows.push(['Config types',
12631
+ (d.configTypeNames && d.configTypeNames.length) ? d.configTypeNames.join(', ') : 'None']);
12632
+ }
12633
+ if (typeof d.encryptionRequired === 'boolean') {
12634
+ rows.push(['Encryption', d.encryptionRequired ? 'Required' : 'Not required']);
12635
+ }
12636
+ if (d.terminatorStrategy) {
12637
+ rows.push(['Terminators', d.terminatorStrategy]);
12638
+ }
12639
+ if (d.serviceRoleAttributes && d.serviceRoleAttributes.length) {
12640
+ rows.push(['Attributes', d.serviceRoleAttributes.join(', ')]);
12641
+ }
12642
+ if (d.permissions && d.permissions.length) {
12643
+ rows.push(['Your access', d.permissions.join(', ')]);
12644
+ }
12645
+ }
12646
+ else {
12647
+ if (role)
12648
+ rows.push(['Role', role]);
12649
+ if (isRouter) {
12650
+ if (d.tunnelerEnabled === 'Yes' || d.tunnelerEnabled === 'No') {
12651
+ rows.push(['Tunneler Enabled', d.tunnelerEnabled]);
12652
+ }
12653
+ }
12654
+ else {
12655
+ if (d.routerConnection === 'Yes' || d.routerConnection === 'No') {
12656
+ rows.push(['Router Connection', d.routerConnection]);
12657
+ }
12658
+ if (d.apiSession === 'Yes' || d.apiSession === 'No') {
12659
+ rows.push(['API Session', d.apiSession]);
12660
+ }
12661
+ if (d.os)
12662
+ rows.push(['OS', d.os]);
12663
+ if (d.mfaEnabled === true)
12664
+ rows.push(['MFA', 'Enabled']);
12665
+ }
12666
+ }
12667
+ let info = "<div class='tool-tip-container tooltip-card'>";
12668
+ info += '<div class="tooltip-header">' + esc(d.name || '') + '</div>';
12669
+ // The service node carries no connectivity state, so it gets no status pill.
12670
+ if (!isService) {
12671
+ info += '<div class="tt-status-pill tt-' + cls + '">'
12672
+ + '<span class="tt-status-dot"></span>'
12673
+ + '<span class="tt-status-label">' + esc(label) + '</span>'
12674
+ + '</div>';
12675
+ }
12676
+ rows.forEach(([k, v, t]) => {
12677
+ info += '<div class="prop-row"' + (t ? ' title="' + esc(t) + '"' : '') + '>'
12678
+ + '<span class="prop-label">' + esc(k) + '</span>'
12679
+ + '<span class="prop-value">' + esc(v) + '</span>'
12354
12680
  + '</div>';
12355
12681
  });
12356
12682
  return info + '</div>';
@@ -12359,13 +12685,79 @@ class IdentityServicePathComponent {
12359
12685
  return o instanceof Object && o.constructor === Object;
12360
12686
  }
12361
12687
  this.refreshRotate = false;
12688
+ // After the first render only, fit the graph to the viewBox so big topologies don't
12689
+ // overflow off-screen. Subsequent renders (refresh ticks) preserve the user's transform.
12690
+ if (!this.hasInitialFit) {
12691
+ setTimeout(() => {
12692
+ this.fitToContent();
12693
+ this.hasInitialFit = true;
12694
+ }, 0);
12695
+ }
12362
12696
  } // end of initTopoView
12697
+ fitToContent() {
12698
+ if (!this.zoomBehavior)
12699
+ return;
12700
+ const nodes = this.graphJsonObj?.nodes || [];
12701
+ if (nodes.length === 0)
12702
+ return;
12703
+ const xs = nodes
12704
+ .map((n) => n.posx)
12705
+ .filter((v) => typeof v === 'number' && !isNaN(v));
12706
+ const ys = nodes
12707
+ .map((n) => n.posy)
12708
+ .filter((v) => typeof v === 'number' && !isNaN(v));
12709
+ if (xs.length === 0 || ys.length === 0)
12710
+ return;
12711
+ const padding = 60;
12712
+ const minX = Math.min(...xs);
12713
+ const maxX = Math.max(...xs);
12714
+ const minY = Math.min(...ys);
12715
+ const maxY = Math.max(...ys);
12716
+ const contentWidth = (maxX - minX) + padding * 2;
12717
+ const contentHeight = (maxY - minY) + padding * 2;
12718
+ if (contentWidth <= 0 || contentHeight <= 0)
12719
+ return;
12720
+ // Reserve room in the bottom-right for the legend so the auto-fit doesn't place nodes
12721
+ // underneath it. When the legend is collapsed there's nothing to avoid.
12722
+ const reserveRight = this.legendCollapsed ? 0 : 160;
12723
+ const reserveBottom = this.legendCollapsed ? 0 : 90;
12724
+ const availWidth = this.viewBoxWidth - reserveRight;
12725
+ const availHeight = this.viewBoxHeight - reserveBottom;
12726
+ const scale = Math.min(availWidth / contentWidth, availHeight / contentHeight, 1);
12727
+ // Center the content within the available (legend-free) area, which sits at the top-left,
12728
+ // leaving the reserved margin on the right/bottom for the legend panel.
12729
+ const tx = (availWidth - contentWidth * scale) / 2 - (minX - padding) * scale;
12730
+ const ty = (availHeight - contentHeight * scale) / 2 - (minY - padding) * scale;
12731
+ d3.select('#IdentityTopology')
12732
+ .transition()
12733
+ .duration(300)
12734
+ .call(this.zoomBehavior.transform, d3.zoomIdentity.translate(tx, ty).scale(scale));
12735
+ }
12736
+ zoomIn() {
12737
+ if (!this.zoomBehavior)
12738
+ return;
12739
+ d3.select('#IdentityTopology')
12740
+ .transition()
12741
+ .duration(200)
12742
+ .call(this.zoomBehavior.scaleBy, 1.4);
12743
+ }
12744
+ zoomOut() {
12745
+ if (!this.zoomBehavior)
12746
+ return;
12747
+ d3.select('#IdentityTopology')
12748
+ .transition()
12749
+ .duration(200)
12750
+ .call(this.zoomBehavior.scaleBy, 1 / 1.4);
12751
+ }
12752
+ resetView() {
12753
+ this.fitToContent();
12754
+ }
12363
12755
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: IdentityServicePathComponent, deps: [{ token: IdentityServicePathHelper }, { token: MapDataService }, { token: SETTINGS_SERVICE }, { token: ZITI_DATA_SERVICE }, { token: i4.Router }, { token: i5.MatDialogRef }, { token: MAT_DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component }); }
12364
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: IdentityServicePathComponent, isStandalone: false, selector: "app-identity-visualizer", ngImport: i0, template: "<div [ngClass]=\"{ 'full-screen': fullScreen }\" class=\"modal solid\" id=\"IdentityVisualizer\">\n <div class=\"tp-data-header\">\n <div class=\"modaltitle\">Identity Service Path</div>\n <div (click)=\"hide()\" class=\"buttonBall icon-close\" id=\"EscZEndpointClose\"></div>\n <div (click)=\"toggleModalSize()\" class=\"buttonBall resize\" id=\"ResizeModalButton\"></div>\n </div>\n <div [ngClass]=\"isDarkMode==true?'darkMode':'lightMode'\" class=\"identity-topology-container\">\n <div style=\"width: 100%; height: 80px; float: left\">\n <div class=\"ep-topology-filter-container listBox row\">\n <input\n (keyup)=\"filterSearchArray()\"\n [(ngModel)]=\"selectedService\"\n [matAutocomplete]=\"auto\"\n class=\"topology-filter-input\"\n id=\"filterText\"\n matInput\n placeholder=\"Type to search identity accessible services.\"\n />\n\n @if (filterText) {\n <div\n (click)=\"clearSearchFilter()\"\n class=\"clear-svc-icon icon-close\"\n id=\"ClearSearchFilter\"\n ></div>\n }\n <div [hidden]=\"!noSearchResults\" class=\"no-results-warning\">No Results Found</div>\n <mat-autocomplete #auto=\"matAutocomplete\" (optionSelected)=\"autocompleteSearch($event)\">\n @for (option of autocompleteOptions; track option) {\n <mat-option [value]=\"option.name\">\n {{ option.name }}\n </mat-option>\n }\n </mat-autocomplete>\n </div>\n </div>\n\n <div class=\"topotip\" id=\"epTooltip\"></div>\n\n <svg style='pointer-events:all' height=\"1000\" id=\"IdentityTopology\" width=\"1400\">\n </svg>\n @if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n }\n\n <div class=\"topology-legend\">\n <div class=\"legend-section\">\n <div class=\"legend-section-title\">Nodes</div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><img class=\"legend-node-icon\" src=\"assets/images/visualizer/host.png\" /></div>\n <div class=\"legend-text\">Identity</div>\n </div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><img class=\"legend-node-icon\" src=\"assets/images/visualizer/Routers.png\" /></div>\n <div class=\"legend-text\">Edge Router</div>\n </div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><img class=\"legend-node-icon\" src=\"assets/images/visualizer/service.png\" /></div>\n <div class=\"legend-text\">Service</div>\n </div>\n </div>\n <div class=\"legend-section\">\n <div class=\"legend-section-title\">Status</div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><div class=\"legend-circle registered_in_use\"></div></div>\n <div class=\"legend-text\">Online</div>\n </div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><div class=\"legend-circle unregistered\"></div></div>\n <div class=\"legend-text\">Offline</div>\n </div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><div class=\"legend-circle errored\"></div></div>\n <div class=\"legend-text\">Error</div>\n </div>\n </div>\n @if (fabricApiAvailable) {\n <div class=\"legend-section\">\n <div class=\"legend-section-title\">Path</div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><div class=\"legend-line active-circuit\"></div></div>\n <div class=\"legend-text\">Active Circuit</div>\n </div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><div class=\"legend-line available\"></div></div>\n <div class=\"legend-text\">Available</div>\n </div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><div class=\"legend-icon misconfigured\"></div></div>\n <div class=\"legend-text\">Broken</div>\n </div>\n </div>\n }\n </div>\n\n </div>\n</div>\n", styles: ["#IdentityVisualizer{overflow:hidden;width:75%;height:85%;background-color:var(--background);border-radius:1.5625rem;z-index:1001;margin:auto;inset:0rem;position:fixed;display:flex;flex-direction:column}#IdentityVisualizer .buttonBall{cursor:pointer;font-weight:600}#IdentityVisualizer .buttonBall.icon-close{position:absolute;top:1.25rem;right:1.25rem;font-size:1.4375rem;color:var(--icon)}#IdentityVisualizer .buttonBall:hover{filter:brightness(50%)}#IdentityVisualizer .clear-svc-icon{font-size:1.4375rem;font-weight:600;position:relative;top:.9375rem;right:2.5rem;color:var(--icon);cursor:pointer}#IdentityVisualizer .clear-svc-icon:hover{filter:brightness(50%)}#IdentityVisualizer.full-screen{width:100%;height:100%;border-radius:0}.ep-topology-filter-container{width:37%;display:flex;flex-direction:row;position:absolute;margin:1.25rem;padding:0}.buttonBall.resize{top:1.25rem;width:1.4375rem;height:1.4375rem;right:3.4375rem;background-image:url(/assets/svgs/Resize.svg);background-size:1.4375rem 1.4375rem;position:absolute;cursor:pointer}.identity-topology-container{display:flex;margin-left:-.3125rem;margin-top:.625rem;flex-direction:column;cursor:grab;flex:1;overflow:hidden}.lightMode{background:#f5e9f2;background-image:linear-gradient(#ecd3e6 .125rem,transparent 0),linear-gradient(90deg,#ecd3e6 .125rem,transparent 0),linear-gradient(#ecd3e6 .0625rem,transparent 0),linear-gradient(90deg,#ecd3e6 .0625rem,transparent 0);background-size:2.5rem 2.5rem,2.5rem 2.5rem,.625rem .625rem,.625rem .625rem}.darkMode{background:#000a17;background-image:linear-gradient(#001127 .125rem,transparent 0),linear-gradient(90deg,#001127 .125rem,transparent 0),linear-gradient(#001127 1px,transparent 0),linear-gradient(90deg,#001127 1px,transparent 0);background-size:2.5rem 2.5rem,2.5rem 2.5rem,.625rem .625rem,.625rem .625rem}.tp-data-header{height:2.5rem;font-size:1.5625rem;font-weight:600;margin-left:1.25rem;margin-top:.625rem;margin-bottom:.3125rem;font-family:Russo One;color:var(--primaryColor);font-size:1.875rem;text-transform:uppercase;font-weight:400}.node text{font:.75rem Open Sans,Arial,sans-serif}.modaltitle{padding-top:.375rem;padding-left:.3125rem;color:var(--primaryColor)}.modalbuttons{padding-right:1.25rem}.listBox.row .close{position:fixed;display:flex;flex-direction:column;font-family:icomoon!important;font-style:normal;font-weight:600;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--icon);text-align:center;font-size:2.1875rem;line-height:2.5rem;left:35%;top:17%;width:2.5rem;height:2.5rem;background-image:url(/assets/svgs/close.svg);z-index:600;background-position:center center;background-size:75%;cursor:pointer;transition:.3s;justify-content:end;align-items:center}.tp-topo-data-header{border:0rem solid black;height:75rem;font-size:.75rem;font-weight:400;margin-left:.75rem;margin-top:.1875rem;color:var(--primaryColor)}.services-select{width:25rem;flex-shrink:0;border-radius:1.6875rem;height:3.1875rem;margin-left:1.375rem;margin-top:.625rem;background-color:#fff;border-color:var(--navigation);box-shadow:0 1.25rem 1.4375rem 0 var(--shadow);color:var(--tableText);padding-left:1.25rem}select{background-image:url(/assets/svgs/ArrowDown.svg);background-position:center right .9375rem;background-repeat:no-repeat;background-size:1.125rem;padding-right:2.9375rem}.topology-filter-input{height:3.125rem;width:95%;border-radius:1.5625rem;border-color:var(--navigation);background-color:var(--navigation);box-shadow:0 1.25rem 1.4375rem 0 var(--shadow);padding-left:1.25rem}::ng-deep .topotip{display:none;position:fixed;background:var(--navigation);border-radius:.5rem;pointer-events:none;box-shadow:0 .25rem 1rem 0 var(--shadow);color:var(--tableText);z-index:2000;padding:0;max-width:24rem;overflow:hidden}.topology-legend{width:13rem;border-radius:.5rem;position:absolute;bottom:1rem;right:.625rem;background:var(--navigation);box-shadow:0 .25rem .75rem 0 var(--shadow);padding:.9rem}.legend-section{padding:.3rem 0}.legend-section:first-child{padding-top:0}.legend-section:last-child{padding-bottom:0;margin-bottom:-.5rem}.legend-section:not(:last-child){border-bottom:1px solid var(--shadow)}.legend-section-title{font-family:Russo One,sans-serif;font-size:.9rem;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);margin-bottom:.25rem}.legend-item{display:flex;align-items:center;gap:0;padding:.225rem 0}.legend-icon-col{width:2.5rem;min-width:2.5rem;display:flex;align-items:center;justify-content:center;margin-right:.4rem}.legend-circle{height:.75rem;width:.75rem;min-width:.75rem;border-radius:50%}.legend-circle.unregistered{background-color:#8a8f9a}.legend-circle.registered_in_use{background-color:#08dc5a}.legend-circle.errored{background-color:#fb0d3d}.legend-line{height:.225rem;width:1.5rem}.legend-line.active-circuit{background-color:#00cd13}.legend-line.available{background:repeating-linear-gradient(to right,#8a8f9a 0,#8a8f9a .25rem,transparent .25rem,transparent .5rem)}.legend-text{font-size:.975rem;font-weight:500;color:var(--tableText);white-space:nowrap}.legend-node-icon{width:2rem;height:2rem;min-width:2rem;object-fit:contain}.legend-icon.misconfigured{width:1.2rem;height:1.2rem;min-width:1.2rem;background-image:url(/assets/images/visualizer/link-cut.png);background-size:contain;background-repeat:no-repeat}.legend-text2{display:inline-block;font-size:.625rem;font-weight:400;color:var(--tableText);padding-left:.125rem;padding-bottom:.25rem}@-webkit-keyframes rotating{0%{-webkit-transform:rotate(0deg);-o-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes rotating{0%{-ms-transform:rotate(0deg);-moz-transform:rotate(0deg);-webkit-transform:rotate(0deg);-o-transform:rotate(0deg);transform:rotate(0)}to{-ms-transform:rotate(360deg);-moz-transform:rotate(360deg);-webkit-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}.rotating{-webkit-animation:rotating 2s linear infinite;-moz-animation:rotating 2s linear infinite;-ms-animation:rotating 2s linear infinite;-o-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}::ng-deep .tool-tip-container{padding:0}::ng-deep .tool-tip-container .tooltip-header{font-family:Russo One,sans-serif;font-size:.975rem;color:var(--primary);padding:.75rem .9rem .45rem;border-bottom:1px solid var(--shadow)}::ng-deep .tool-tip-container .prop-row{display:flex;justify-content:space-between;gap:1.2rem;padding:.375rem .9rem;font-size:.9rem}::ng-deep .tool-tip-container .prop-row:nth-child(2n){background-color:var(--formBackground)}::ng-deep .tool-tip-container .prop-row:last-child{padding-bottom:.6rem}::ng-deep .tool-tip-container .prop-label{color:var(--tableText);opacity:.6;font-weight:500;white-space:nowrap}::ng-deep .tool-tip-container .prop-value{color:var(--tableText);font-weight:600;text-align:right}::ng-deep .cdk-overlay-pane:not(.mat-mdc-autocomplete-panel-above) div.mat-mdc-autocomplete-panel{border-radius:14px;background:var(--background);color:var(--tableText)}::ng-deep .mat-mdc-option.mdc-list-item{align-items:center;color:var(--tableText)}::ng-deep .mat-mdc-option.mdc-list-item:hover:not(.mat-option-disabled),::ng-deep .mat-mdc-option.mdc-list-item:focus:not(.mat-option-disabled){border-color:var(--primaryColor);border-radius:14px;border:.01rem solid var(--primaryColor)}::ng-deep .mat-mdc-raised-button .mdc-button__ripple{border:.1rem solid var(--primaryColor)}::ng-deep .p-button.p-button-icon-only{width:1.357rem}\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: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i5$3.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i9.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "directive", type: i9.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "lib-loading-indicator", inputs: ["isLoading"] }] }); }
12756
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: IdentityServicePathComponent, isStandalone: false, selector: "app-identity-visualizer", ngImport: i0, template: "<div [ngClass]=\"{ 'full-screen': fullScreen }\" class=\"modal solid\" id=\"IdentityVisualizer\">\n <div class=\"tp-data-header\">\n <div class=\"modaltitle\">Identity Service Path</div>\n <div (click)=\"hide()\" class=\"buttonBall icon-close\" id=\"EscZEndpointClose\"></div>\n <div (click)=\"toggleModalSize()\" class=\"buttonBall resize\" id=\"ResizeModalButton\"></div>\n </div>\n <div [ngClass]=\"isDarkMode==true?'darkMode':'lightMode'\" class=\"identity-topology-container\">\n <div class=\"topology-toolbar\">\n <div class=\"ep-topology-filter-container listBox row\">\n <input\n (keyup)=\"filterSearchArray()\"\n [(ngModel)]=\"selectedService\"\n [matAutocomplete]=\"auto\"\n class=\"topology-filter-input\"\n id=\"filterText\"\n matInput\n placeholder=\"Type to search identity accessible services.\"\n />\n\n @if (filterText) {\n <div\n (click)=\"clearSearchFilter()\"\n class=\"clear-svc-icon icon-close\"\n id=\"ClearSearchFilter\"\n ></div>\n }\n <div [hidden]=\"!noSearchResults\" class=\"no-results-warning\">No Results Found</div>\n <mat-autocomplete #auto=\"matAutocomplete\" (optionSelected)=\"autocompleteSearch($event)\">\n @for (option of autocompleteOptions; track option) {\n <mat-option [value]=\"option.name\">\n {{ option.name }}\n </mat-option>\n }\n </mat-autocomplete>\n </div>\n <div class=\"topology-refresh-controls\">\n <div\n (click)=\"refreshFabricData()\"\n [ngClass]=\"{ rotating: refreshRotate }\"\n class=\"refresh-button\"\n id=\"RefreshFabricButton\"\n title=\"Refresh fabric data\"\n >\n <div class=\"refresh-icon\"></div>\n </div>\n </div>\n </div>\n\n <div class=\"topotip\" id=\"epTooltip\"></div>\n\n <svg style='pointer-events:all' height=\"1000\" id=\"IdentityTopology\" width=\"1400\">\n </svg>\n <div class=\"topology-zoom-controls\">\n <div (click)=\"zoomIn()\" class=\"zoom-btn\" title=\"Zoom in\" id=\"ZoomInButton\">+</div>\n <div (click)=\"zoomOut()\" class=\"zoom-btn\" title=\"Zoom out\" id=\"ZoomOutButton\">&#8722;</div>\n <div (click)=\"resetView()\" class=\"zoom-btn zoom-btn-text\" title=\"Fit to content\" id=\"FitToContentButton\">Fit</div>\n </div>\n @if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n }\n\n <div class=\"topology-legend\" [class.collapsed]=\"legendCollapsed\">\n <div class=\"legend-header\" (click)=\"legendCollapsed = !legendCollapsed\"\n [title]=\"legendCollapsed ? 'Show legend' : 'Hide legend'\">\n <span class=\"legend-header-title\">Legend</span>\n <span class=\"legend-header-toggle\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 16 16\" width=\"13\" height=\"13\">\n <path d=\"M3.5 6 L8 10.5 L12.5 6\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n </span>\n </div>\n @if (!legendCollapsed) {\n <div class=\"legend-body\">\n <div class=\"legend-section\">\n <div class=\"legend-section-title\">Nodes</div>\n <div class=\"legend-item\" title=\"An endpoint identity \u2014 a client that dials the service or a tunneler that hosts it.\">\n <div class=\"legend-icon-col\"><img class=\"legend-node-icon\" src=\"assets/images/visualizer/host.png\" /></div>\n <div class=\"legend-text\">Identity</div>\n </div>\n <div class=\"legend-item\" title=\"A Ziti edge router that relays traffic between identities.\">\n <div class=\"legend-icon-col\"><img class=\"legend-node-icon\" src=\"assets/images/visualizer/Routers.png\" /></div>\n <div class=\"legend-text\">Edge Router</div>\n </div>\n <div class=\"legend-item\" title=\"The service being dialed and hosted.\">\n <div class=\"legend-icon-col\"><img class=\"legend-node-icon\" src=\"assets/images/visualizer/service.png\" /></div>\n <div class=\"legend-text\">Service</div>\n </div>\n </div>\n <div class=\"legend-section\">\n <div class=\"legend-section-title\">Status</div>\n <div class=\"legend-item\" title=\"Connected to an edge router.\">\n <div class=\"legend-icon-col\"><div class=\"legend-circle online\"></div></div>\n <div class=\"legend-text\">Online</div>\n </div>\n <div class=\"legend-item\" title=\"Enrolled but not currently connected (e.g. powered off or idle).\">\n <div class=\"legend-icon-col\"><div class=\"legend-circle offline\"></div></div>\n <div class=\"legend-text\">Offline</div>\n </div>\n <div class=\"legend-item\" title=\"Identity-only: the controller can't currently determine the connection state.\">\n <div class=\"legend-icon-col\"><div class=\"legend-circle unknown\"></div></div>\n <div class=\"legend-text\">Unknown</div>\n </div>\n <div class=\"legend-item\" title=\"Identity has not completed enrollment.\">\n <div class=\"legend-icon-col\"><div class=\"legend-circle notenrolled\"></div></div>\n <div class=\"legend-text\">Not enrolled</div>\n </div>\n </div>\n @if (fabricApiAvailable) {\n <div class=\"legend-section\">\n <div class=\"legend-section-title\">Path</div>\n <div class=\"legend-item\" title=\"Live traffic is flowing over this hop right now.\">\n <div class=\"legend-icon-col\"><div class=\"legend-line active-circuit\"></div></div>\n <div class=\"legend-text\">Active Circuit</div>\n </div>\n <div class=\"legend-item\" title=\"Reachable and authorized, but no active circuit at the moment.\">\n <div class=\"legend-icon-col\"><div class=\"legend-line available\"></div></div>\n <div class=\"legend-text\">Available</div>\n </div>\n <div class=\"legend-item\" title=\"This hop can't carry traffic right now. The node colors show which end is the problem \u2014 an offline router, or an unreachable / un-enrolled endpoint.\">\n <div class=\"legend-icon-col\"><div class=\"legend-line broken\"></div></div>\n <div class=\"legend-text\">Unavailable</div>\n </div>\n </div>\n }\n </div>\n }\n </div>\n\n </div>\n</div>\n", styles: ["#IdentityVisualizer{overflow:hidden;width:75%;height:85%;background-color:var(--background);border-radius:1.5625rem;z-index:1001;margin:auto;inset:0rem;position:fixed;display:flex;flex-direction:column}#IdentityVisualizer .buttonBall{cursor:pointer;font-weight:600}#IdentityVisualizer .buttonBall.icon-close{position:absolute;top:1.25rem;right:1.25rem;font-size:1.4375rem;color:var(--icon)}#IdentityVisualizer .buttonBall:hover{filter:brightness(50%)}#IdentityVisualizer .clear-svc-icon{position:absolute;top:50%;right:.55rem;transform:translateY(-50%);width:1.65rem;height:1.65rem;display:flex;align-items:center;justify-content:center;border-radius:50%;font-size:.95rem;color:var(--icon);cursor:pointer;transition:background .12s ease,color .12s ease}#IdentityVisualizer .clear-svc-icon:hover{background:color-mix(in srgb,var(--tableText) 12%,transparent);color:var(--tableText)}#IdentityVisualizer.full-screen{width:100%;height:100%;border-radius:0}.ep-topology-filter-container{width:37%;display:flex;flex-direction:row;position:absolute;margin:1.25rem;padding:0}.topology-toolbar .ep-topology-filter-container{position:relative;margin:0;flex:1 1 auto;min-width:0;max-width:28rem}.topology-toolbar{display:flex;align-items:center;gap:1rem;padding:1.25rem 1.25rem .625rem;box-sizing:border-box;width:100%;flex-shrink:0;position:relative;z-index:4}.buttonBall.resize{top:1.25rem;width:1.4375rem;height:1.4375rem;right:3.4375rem;background-image:url(/assets/svgs/Resize.svg);background-size:1.4375rem 1.4375rem;position:absolute;cursor:pointer}.topology-refresh-controls{display:flex;align-items:center;gap:.5rem;flex:0 0 auto}.topology-refresh-controls .refresh-button{width:2.5rem;height:2.5rem;border-radius:50%;background-color:var(--navigation);box-shadow:0 .375rem 1rem 0 var(--shadow),0 0 0 .0625rem var(--shadow);display:flex;align-items:center;justify-content:center;cursor:pointer;transition:filter .15s ease,transform .1s ease,box-shadow .15s ease}.topology-refresh-controls .refresh-button .refresh-icon{width:1.125rem;height:1.125rem;background-color:var(--tableText);opacity:.85;-webkit-mask:url(/assets/svgs/refresh.svg) center/contain no-repeat;mask:url(/assets/svgs/refresh.svg) center/contain no-repeat;transition:opacity .15s ease}.topology-refresh-controls .refresh-button:hover{box-shadow:0 .5rem 1.25rem 0 var(--shadow),0 0 0 .0625rem var(--primaryColor)}.topology-refresh-controls .refresh-button:hover .refresh-icon{opacity:1}.topology-refresh-controls .refresh-button:active{transform:scale(.95)}.topology-refresh-controls .refresh-button.rotating .refresh-icon{animation:rotating 1s linear infinite}.topology-zoom-controls{position:absolute;bottom:1rem;left:1rem;display:flex;flex-direction:column;gap:.375rem;z-index:5}.topology-zoom-controls .zoom-btn{width:2.25rem;height:2.25rem;display:flex;align-items:center;justify-content:center;border-radius:.5rem;background-color:var(--navigation);color:var(--tableText);font-family:Open Sans,Arial,sans-serif;font-size:1.25rem;font-weight:600;line-height:1;cursor:pointer;-webkit-user-select:none;user-select:none;box-shadow:0 .5rem 1.25rem 0 var(--shadow);transition:filter .15s ease,transform .15s ease}.topology-zoom-controls .zoom-btn.zoom-btn-text{font-size:.75rem;text-transform:uppercase;letter-spacing:.04em}.topology-zoom-controls .zoom-btn:hover{filter:brightness(85%)}.topology-zoom-controls .zoom-btn:active{transform:scale(.95)}.identity-topology-container{display:flex;margin-left:-.3125rem;margin-top:.625rem;flex-direction:column;flex:1;overflow:hidden;position:relative}#IdentityTopology{cursor:grab;flex:1;min-height:0}#IdentityTopology:active{cursor:grabbing}.lightMode{background:#f5e9f2;background-image:linear-gradient(#ecd3e6 .125rem,transparent 0),linear-gradient(90deg,#ecd3e6 .125rem,transparent 0),linear-gradient(#ecd3e6 .0625rem,transparent 0),linear-gradient(90deg,#ecd3e6 .0625rem,transparent 0);background-size:2.5rem 2.5rem,2.5rem 2.5rem,.625rem .625rem,.625rem .625rem}.darkMode{background:#000a17;background-image:linear-gradient(#001127 .125rem,transparent 0),linear-gradient(90deg,#001127 .125rem,transparent 0),linear-gradient(#001127 1px,transparent 0),linear-gradient(90deg,#001127 1px,transparent 0);background-size:2.5rem 2.5rem,2.5rem 2.5rem,.625rem .625rem,.625rem .625rem}.tp-data-header{height:2.5rem;font-size:1.5625rem;font-weight:600;margin-left:1.25rem;margin-top:.625rem;margin-bottom:.3125rem;font-family:Russo One;color:var(--primaryColor);font-size:1.875rem;text-transform:uppercase;font-weight:400}.node text{font:.75rem Open Sans,Arial,sans-serif}.modaltitle{padding-top:.375rem;padding-left:.3125rem;color:var(--primaryColor)}.modalbuttons{padding-right:1.25rem}.listBox.row .close{position:fixed;display:flex;flex-direction:column;font-family:icomoon!important;font-style:normal;font-weight:600;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--icon);text-align:center;font-size:2.1875rem;line-height:2.5rem;left:35%;top:17%;width:2.5rem;height:2.5rem;background-image:url(/assets/svgs/close.svg);z-index:600;background-position:center center;background-size:75%;cursor:pointer;transition:.3s;justify-content:end;align-items:center}.tp-topo-data-header{border:0rem solid black;height:75rem;font-size:.75rem;font-weight:400;margin-left:.75rem;margin-top:.1875rem;color:var(--primaryColor)}.services-select{width:25rem;flex-shrink:0;border-radius:1.6875rem;height:3.1875rem;margin-left:1.375rem;margin-top:.625rem;background-color:#fff;border-color:var(--navigation);box-shadow:0 1.25rem 1.4375rem 0 var(--shadow);color:var(--tableText);padding-left:1.25rem}select{background-image:url(/assets/svgs/ArrowDown.svg);background-position:center right .9375rem;background-repeat:no-repeat;background-size:1.125rem;padding-right:2.9375rem}.topology-filter-input{height:2.75rem;width:100%;box-sizing:border-box;border-radius:.75rem;border:1px solid var(--shadow);background-color:var(--background);color:var(--tableText);box-shadow:0 .0625rem .125rem #0000000f;padding:0 2.6rem 0 1rem;font-size:.95rem;outline:none;transition:border-color .15s ease,box-shadow .15s ease}.topology-filter-input::placeholder{color:color-mix(in srgb,var(--tableText) 50%,transparent)}.topology-filter-input:hover{border-color:color-mix(in srgb,var(--tableText) 28%,transparent)}.topology-filter-input:focus{border-color:var(--primary);box-shadow:0 0 0 .1875rem color-mix(in srgb,var(--primary) 18%,transparent)}::ng-deep .topotip{display:none;position:fixed;background:var(--navigation);border-radius:.5rem;pointer-events:auto;box-shadow:0 .25rem 1rem 0 var(--shadow);color:var(--tableText);z-index:2000;padding:0;max-width:24rem;overflow:hidden}.topology-legend{width:15rem;border-radius:.5rem;position:absolute;bottom:1rem;right:.625rem;background:var(--navigation);box-shadow:0 .25rem .75rem 0 var(--shadow);padding:.9rem}.topology-legend.collapsed{width:auto}.legend-header{display:flex;align-items:center;justify-content:space-between;gap:.75rem;cursor:pointer;-webkit-user-select:none;user-select:none}.legend-header-title{font-family:Russo One,sans-serif;font-size:.9rem;text-transform:uppercase;letter-spacing:.05em;color:var(--primary)}.legend-header-toggle{color:var(--primary);display:flex;align-items:center}.legend-header-toggle svg{display:block;transition:transform .15s ease}.topology-legend.collapsed .legend-header-toggle svg{transform:rotate(180deg)}.legend-body{padding-top:.6rem}.legend-section{padding:.3rem 0}.legend-section:first-child{padding-top:0}.legend-section:last-child{padding-bottom:0;margin-bottom:-.5rem}.legend-section:not(:last-child){border-bottom:1px solid var(--shadow)}.legend-section-title{font-family:Russo One,sans-serif;font-size:.9rem;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);margin-bottom:.25rem}.legend-item{display:flex;align-items:center;gap:0;padding:.225rem 0;cursor:help}.legend-icon-col{width:2.5rem;min-width:2.5rem;display:flex;align-items:center;justify-content:center;margin-right:.4rem}.legend-circle{height:.75rem;width:.75rem;min-width:.75rem;border-radius:50%}.legend-circle.online{background-color:#08dc5a}.legend-circle.offline{background-color:#8a8f9a}.legend-circle.unknown{background-color:#e6a700}.legend-circle.notenrolled{background-color:var(--background);border:.125rem solid #8a8f9a}.legend-line{height:.225rem;width:1.5rem}.legend-line.active-circuit{background-color:#00cd13}.legend-line.available{background:repeating-linear-gradient(to right,#8a8f9a 0,#8a8f9a .25rem,transparent .25rem,transparent .5rem)}.legend-line.broken{height:0;background:none;border-top:.28rem dotted #e6432e}.legend-text{font-size:.975rem;font-weight:500;color:var(--tableText);white-space:nowrap}.legend-node-icon{width:2rem;height:2rem;min-width:2rem;object-fit:contain}.legend-icon.misconfigured,.legend-icon.broken-link{width:1.2rem;height:1.2rem;min-width:1.2rem;background-image:url(/assets/images/visualizer/link-cut.png);background-size:contain;background-repeat:no-repeat}.legend-text2{display:inline-block;font-size:.625rem;font-weight:400;color:var(--tableText);padding-left:.125rem;padding-bottom:.25rem}@-webkit-keyframes rotating{0%{-webkit-transform:rotate(0deg);-o-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes rotating{0%{-ms-transform:rotate(0deg);-moz-transform:rotate(0deg);-webkit-transform:rotate(0deg);-o-transform:rotate(0deg);transform:rotate(0)}to{-ms-transform:rotate(360deg);-moz-transform:rotate(360deg);-webkit-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}.rotating{-webkit-animation:rotating 2s linear infinite;-moz-animation:rotating 2s linear infinite;-ms-animation:rotating 2s linear infinite;-o-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}::ng-deep .tool-tip-container{padding:0}::ng-deep .tool-tip-container .tooltip-header{font-family:Russo One,sans-serif;font-size:.975rem;color:var(--primary);padding:.7rem .9rem .15rem}::ng-deep .tool-tip-container .prop-row{display:flex;justify-content:space-between;gap:1.2rem;padding:.375rem .9rem;font-size:.9rem}::ng-deep .tool-tip-container .prop-row:nth-child(2n){background-color:var(--formBackground)}::ng-deep .tool-tip-container .prop-row:last-child{padding-bottom:.6rem}::ng-deep .tool-tip-container .prop-label{color:var(--tableText);opacity:.6;font-weight:500;white-space:nowrap}::ng-deep .tool-tip-container .prop-value{color:var(--tableText);font-weight:600;text-align:right}::ng-deep .tool-tip-container .tt-status-pill{display:inline-flex;align-items:center;gap:.35rem;margin:0 .9rem .55rem;padding:.14rem .55rem .14rem .45rem;border-radius:999px;font-size:.72rem;font-weight:600;letter-spacing:.02em}::ng-deep .tool-tip-container .tt-status-dot{width:.45rem;height:.45rem;min-width:.45rem;border-radius:50%}::ng-deep .tool-tip-container .prop-row:first-of-type{border-top:1px solid var(--shadow);padding-top:.5rem}::ng-deep .tool-tip-container .tt-ok{background:#08dc5a1f}::ng-deep .tool-tip-container .tt-ok .tt-status-dot{background:#08dc5a}::ng-deep .tool-tip-container .tt-ok .tt-status-label{color:#0a9d4a}::ng-deep .tool-tip-container .tt-broken{background:#e6432e1f}::ng-deep .tool-tip-container .tt-broken .tt-status-dot{background:#e6432e}::ng-deep .tool-tip-container .tt-broken .tt-status-label{color:#d23b27}::ng-deep .tool-tip-container .tt-offline{background:#8a8f9a29}::ng-deep .tool-tip-container .tt-offline .tt-status-dot{background:#8a8f9a}::ng-deep .tool-tip-container .tt-offline .tt-status-label{color:#6f7480}::ng-deep .tool-tip-container .tt-unknown{background:#e6a70024}::ng-deep .tool-tip-container .tt-unknown .tt-status-dot{background:#e6a700}::ng-deep .tool-tip-container .tt-unknown .tt-status-label{color:#b07f00}::ng-deep .tool-tip-container .tt-neutral{background:#6e78c81f}::ng-deep .tool-tip-container .tt-neutral .tt-status-dot{background:var(--primary)}::ng-deep .tool-tip-container .tt-neutral .tt-status-label{color:var(--primary)}::ng-deep .cdk-overlay-pane .mat-mdc-autocomplete-panel{border-radius:.75rem!important;background:var(--background);color:var(--tableText);padding:.35rem;border:1px solid var(--shadow);box-shadow:0 .5rem 1.5rem #00000029!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option.mdc-list-item{align-items:center;min-height:2.5rem;margin:.1rem 0;border-radius:.5rem;color:var(--tableText);transition:background-color .12s ease,color .12s ease}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option.mdc-list-item:hover:not(.mdc-list-item--disabled),::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option.mat-mdc-option-active:not(.mdc-list-item--disabled){background-color:color-mix(in srgb,var(--primary) 9%,transparent)!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option.mdc-list-item--selected:not(.mdc-list-item--disabled){background-color:color-mix(in srgb,var(--primary) 14%,transparent)!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option.mdc-list-item--selected .mdc-list-item__primary-text{color:var(--primary);font-weight:600}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .mat-mdc-option-ripple,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .mat-ripple{display:none}::ng-deep .mat-mdc-autocomplete-panel .mat-pseudo-checkbox-minimal{color:var(--primary)}::ng-deep .mat-mdc-raised-button .mdc-button__ripple{border:.1rem solid var(--primaryColor)}::ng-deep .p-button.p-button-icon-only{width:1.357rem}\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: i7.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i5$3.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i9.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "directive", type: i9.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "component", type: LoadingIndicatorComponent, selector: "lib-loading-indicator", inputs: ["isLoading"] }] }); }
12365
12757
  }
12366
12758
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: IdentityServicePathComponent, decorators: [{
12367
12759
  type: Component,
12368
- args: [{ selector: 'app-identity-visualizer', standalone: false, template: "<div [ngClass]=\"{ 'full-screen': fullScreen }\" class=\"modal solid\" id=\"IdentityVisualizer\">\n <div class=\"tp-data-header\">\n <div class=\"modaltitle\">Identity Service Path</div>\n <div (click)=\"hide()\" class=\"buttonBall icon-close\" id=\"EscZEndpointClose\"></div>\n <div (click)=\"toggleModalSize()\" class=\"buttonBall resize\" id=\"ResizeModalButton\"></div>\n </div>\n <div [ngClass]=\"isDarkMode==true?'darkMode':'lightMode'\" class=\"identity-topology-container\">\n <div style=\"width: 100%; height: 80px; float: left\">\n <div class=\"ep-topology-filter-container listBox row\">\n <input\n (keyup)=\"filterSearchArray()\"\n [(ngModel)]=\"selectedService\"\n [matAutocomplete]=\"auto\"\n class=\"topology-filter-input\"\n id=\"filterText\"\n matInput\n placeholder=\"Type to search identity accessible services.\"\n />\n\n @if (filterText) {\n <div\n (click)=\"clearSearchFilter()\"\n class=\"clear-svc-icon icon-close\"\n id=\"ClearSearchFilter\"\n ></div>\n }\n <div [hidden]=\"!noSearchResults\" class=\"no-results-warning\">No Results Found</div>\n <mat-autocomplete #auto=\"matAutocomplete\" (optionSelected)=\"autocompleteSearch($event)\">\n @for (option of autocompleteOptions; track option) {\n <mat-option [value]=\"option.name\">\n {{ option.name }}\n </mat-option>\n }\n </mat-autocomplete>\n </div>\n </div>\n\n <div class=\"topotip\" id=\"epTooltip\"></div>\n\n <svg style='pointer-events:all' height=\"1000\" id=\"IdentityTopology\" width=\"1400\">\n </svg>\n @if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n }\n\n <div class=\"topology-legend\">\n <div class=\"legend-section\">\n <div class=\"legend-section-title\">Nodes</div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><img class=\"legend-node-icon\" src=\"assets/images/visualizer/host.png\" /></div>\n <div class=\"legend-text\">Identity</div>\n </div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><img class=\"legend-node-icon\" src=\"assets/images/visualizer/Routers.png\" /></div>\n <div class=\"legend-text\">Edge Router</div>\n </div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><img class=\"legend-node-icon\" src=\"assets/images/visualizer/service.png\" /></div>\n <div class=\"legend-text\">Service</div>\n </div>\n </div>\n <div class=\"legend-section\">\n <div class=\"legend-section-title\">Status</div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><div class=\"legend-circle registered_in_use\"></div></div>\n <div class=\"legend-text\">Online</div>\n </div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><div class=\"legend-circle unregistered\"></div></div>\n <div class=\"legend-text\">Offline</div>\n </div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><div class=\"legend-circle errored\"></div></div>\n <div class=\"legend-text\">Error</div>\n </div>\n </div>\n @if (fabricApiAvailable) {\n <div class=\"legend-section\">\n <div class=\"legend-section-title\">Path</div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><div class=\"legend-line active-circuit\"></div></div>\n <div class=\"legend-text\">Active Circuit</div>\n </div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><div class=\"legend-line available\"></div></div>\n <div class=\"legend-text\">Available</div>\n </div>\n <div class=\"legend-item\">\n <div class=\"legend-icon-col\"><div class=\"legend-icon misconfigured\"></div></div>\n <div class=\"legend-text\">Broken</div>\n </div>\n </div>\n }\n </div>\n\n </div>\n</div>\n", styles: ["#IdentityVisualizer{overflow:hidden;width:75%;height:85%;background-color:var(--background);border-radius:1.5625rem;z-index:1001;margin:auto;inset:0rem;position:fixed;display:flex;flex-direction:column}#IdentityVisualizer .buttonBall{cursor:pointer;font-weight:600}#IdentityVisualizer .buttonBall.icon-close{position:absolute;top:1.25rem;right:1.25rem;font-size:1.4375rem;color:var(--icon)}#IdentityVisualizer .buttonBall:hover{filter:brightness(50%)}#IdentityVisualizer .clear-svc-icon{font-size:1.4375rem;font-weight:600;position:relative;top:.9375rem;right:2.5rem;color:var(--icon);cursor:pointer}#IdentityVisualizer .clear-svc-icon:hover{filter:brightness(50%)}#IdentityVisualizer.full-screen{width:100%;height:100%;border-radius:0}.ep-topology-filter-container{width:37%;display:flex;flex-direction:row;position:absolute;margin:1.25rem;padding:0}.buttonBall.resize{top:1.25rem;width:1.4375rem;height:1.4375rem;right:3.4375rem;background-image:url(/assets/svgs/Resize.svg);background-size:1.4375rem 1.4375rem;position:absolute;cursor:pointer}.identity-topology-container{display:flex;margin-left:-.3125rem;margin-top:.625rem;flex-direction:column;cursor:grab;flex:1;overflow:hidden}.lightMode{background:#f5e9f2;background-image:linear-gradient(#ecd3e6 .125rem,transparent 0),linear-gradient(90deg,#ecd3e6 .125rem,transparent 0),linear-gradient(#ecd3e6 .0625rem,transparent 0),linear-gradient(90deg,#ecd3e6 .0625rem,transparent 0);background-size:2.5rem 2.5rem,2.5rem 2.5rem,.625rem .625rem,.625rem .625rem}.darkMode{background:#000a17;background-image:linear-gradient(#001127 .125rem,transparent 0),linear-gradient(90deg,#001127 .125rem,transparent 0),linear-gradient(#001127 1px,transparent 0),linear-gradient(90deg,#001127 1px,transparent 0);background-size:2.5rem 2.5rem,2.5rem 2.5rem,.625rem .625rem,.625rem .625rem}.tp-data-header{height:2.5rem;font-size:1.5625rem;font-weight:600;margin-left:1.25rem;margin-top:.625rem;margin-bottom:.3125rem;font-family:Russo One;color:var(--primaryColor);font-size:1.875rem;text-transform:uppercase;font-weight:400}.node text{font:.75rem Open Sans,Arial,sans-serif}.modaltitle{padding-top:.375rem;padding-left:.3125rem;color:var(--primaryColor)}.modalbuttons{padding-right:1.25rem}.listBox.row .close{position:fixed;display:flex;flex-direction:column;font-family:icomoon!important;font-style:normal;font-weight:600;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--icon);text-align:center;font-size:2.1875rem;line-height:2.5rem;left:35%;top:17%;width:2.5rem;height:2.5rem;background-image:url(/assets/svgs/close.svg);z-index:600;background-position:center center;background-size:75%;cursor:pointer;transition:.3s;justify-content:end;align-items:center}.tp-topo-data-header{border:0rem solid black;height:75rem;font-size:.75rem;font-weight:400;margin-left:.75rem;margin-top:.1875rem;color:var(--primaryColor)}.services-select{width:25rem;flex-shrink:0;border-radius:1.6875rem;height:3.1875rem;margin-left:1.375rem;margin-top:.625rem;background-color:#fff;border-color:var(--navigation);box-shadow:0 1.25rem 1.4375rem 0 var(--shadow);color:var(--tableText);padding-left:1.25rem}select{background-image:url(/assets/svgs/ArrowDown.svg);background-position:center right .9375rem;background-repeat:no-repeat;background-size:1.125rem;padding-right:2.9375rem}.topology-filter-input{height:3.125rem;width:95%;border-radius:1.5625rem;border-color:var(--navigation);background-color:var(--navigation);box-shadow:0 1.25rem 1.4375rem 0 var(--shadow);padding-left:1.25rem}::ng-deep .topotip{display:none;position:fixed;background:var(--navigation);border-radius:.5rem;pointer-events:none;box-shadow:0 .25rem 1rem 0 var(--shadow);color:var(--tableText);z-index:2000;padding:0;max-width:24rem;overflow:hidden}.topology-legend{width:13rem;border-radius:.5rem;position:absolute;bottom:1rem;right:.625rem;background:var(--navigation);box-shadow:0 .25rem .75rem 0 var(--shadow);padding:.9rem}.legend-section{padding:.3rem 0}.legend-section:first-child{padding-top:0}.legend-section:last-child{padding-bottom:0;margin-bottom:-.5rem}.legend-section:not(:last-child){border-bottom:1px solid var(--shadow)}.legend-section-title{font-family:Russo One,sans-serif;font-size:.9rem;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);margin-bottom:.25rem}.legend-item{display:flex;align-items:center;gap:0;padding:.225rem 0}.legend-icon-col{width:2.5rem;min-width:2.5rem;display:flex;align-items:center;justify-content:center;margin-right:.4rem}.legend-circle{height:.75rem;width:.75rem;min-width:.75rem;border-radius:50%}.legend-circle.unregistered{background-color:#8a8f9a}.legend-circle.registered_in_use{background-color:#08dc5a}.legend-circle.errored{background-color:#fb0d3d}.legend-line{height:.225rem;width:1.5rem}.legend-line.active-circuit{background-color:#00cd13}.legend-line.available{background:repeating-linear-gradient(to right,#8a8f9a 0,#8a8f9a .25rem,transparent .25rem,transparent .5rem)}.legend-text{font-size:.975rem;font-weight:500;color:var(--tableText);white-space:nowrap}.legend-node-icon{width:2rem;height:2rem;min-width:2rem;object-fit:contain}.legend-icon.misconfigured{width:1.2rem;height:1.2rem;min-width:1.2rem;background-image:url(/assets/images/visualizer/link-cut.png);background-size:contain;background-repeat:no-repeat}.legend-text2{display:inline-block;font-size:.625rem;font-weight:400;color:var(--tableText);padding-left:.125rem;padding-bottom:.25rem}@-webkit-keyframes rotating{0%{-webkit-transform:rotate(0deg);-o-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes rotating{0%{-ms-transform:rotate(0deg);-moz-transform:rotate(0deg);-webkit-transform:rotate(0deg);-o-transform:rotate(0deg);transform:rotate(0)}to{-ms-transform:rotate(360deg);-moz-transform:rotate(360deg);-webkit-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}.rotating{-webkit-animation:rotating 2s linear infinite;-moz-animation:rotating 2s linear infinite;-ms-animation:rotating 2s linear infinite;-o-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}::ng-deep .tool-tip-container{padding:0}::ng-deep .tool-tip-container .tooltip-header{font-family:Russo One,sans-serif;font-size:.975rem;color:var(--primary);padding:.75rem .9rem .45rem;border-bottom:1px solid var(--shadow)}::ng-deep .tool-tip-container .prop-row{display:flex;justify-content:space-between;gap:1.2rem;padding:.375rem .9rem;font-size:.9rem}::ng-deep .tool-tip-container .prop-row:nth-child(2n){background-color:var(--formBackground)}::ng-deep .tool-tip-container .prop-row:last-child{padding-bottom:.6rem}::ng-deep .tool-tip-container .prop-label{color:var(--tableText);opacity:.6;font-weight:500;white-space:nowrap}::ng-deep .tool-tip-container .prop-value{color:var(--tableText);font-weight:600;text-align:right}::ng-deep .cdk-overlay-pane:not(.mat-mdc-autocomplete-panel-above) div.mat-mdc-autocomplete-panel{border-radius:14px;background:var(--background);color:var(--tableText)}::ng-deep .mat-mdc-option.mdc-list-item{align-items:center;color:var(--tableText)}::ng-deep .mat-mdc-option.mdc-list-item:hover:not(.mat-option-disabled),::ng-deep .mat-mdc-option.mdc-list-item:focus:not(.mat-option-disabled){border-color:var(--primaryColor);border-radius:14px;border:.01rem solid var(--primaryColor)}::ng-deep .mat-mdc-raised-button .mdc-button__ripple{border:.1rem solid var(--primaryColor)}::ng-deep .p-button.p-button-icon-only{width:1.357rem}\n"] }]
12760
+ args: [{ selector: 'app-identity-visualizer', standalone: false, template: "<div [ngClass]=\"{ 'full-screen': fullScreen }\" class=\"modal solid\" id=\"IdentityVisualizer\">\n <div class=\"tp-data-header\">\n <div class=\"modaltitle\">Identity Service Path</div>\n <div (click)=\"hide()\" class=\"buttonBall icon-close\" id=\"EscZEndpointClose\"></div>\n <div (click)=\"toggleModalSize()\" class=\"buttonBall resize\" id=\"ResizeModalButton\"></div>\n </div>\n <div [ngClass]=\"isDarkMode==true?'darkMode':'lightMode'\" class=\"identity-topology-container\">\n <div class=\"topology-toolbar\">\n <div class=\"ep-topology-filter-container listBox row\">\n <input\n (keyup)=\"filterSearchArray()\"\n [(ngModel)]=\"selectedService\"\n [matAutocomplete]=\"auto\"\n class=\"topology-filter-input\"\n id=\"filterText\"\n matInput\n placeholder=\"Type to search identity accessible services.\"\n />\n\n @if (filterText) {\n <div\n (click)=\"clearSearchFilter()\"\n class=\"clear-svc-icon icon-close\"\n id=\"ClearSearchFilter\"\n ></div>\n }\n <div [hidden]=\"!noSearchResults\" class=\"no-results-warning\">No Results Found</div>\n <mat-autocomplete #auto=\"matAutocomplete\" (optionSelected)=\"autocompleteSearch($event)\">\n @for (option of autocompleteOptions; track option) {\n <mat-option [value]=\"option.name\">\n {{ option.name }}\n </mat-option>\n }\n </mat-autocomplete>\n </div>\n <div class=\"topology-refresh-controls\">\n <div\n (click)=\"refreshFabricData()\"\n [ngClass]=\"{ rotating: refreshRotate }\"\n class=\"refresh-button\"\n id=\"RefreshFabricButton\"\n title=\"Refresh fabric data\"\n >\n <div class=\"refresh-icon\"></div>\n </div>\n </div>\n </div>\n\n <div class=\"topotip\" id=\"epTooltip\"></div>\n\n <svg style='pointer-events:all' height=\"1000\" id=\"IdentityTopology\" width=\"1400\">\n </svg>\n <div class=\"topology-zoom-controls\">\n <div (click)=\"zoomIn()\" class=\"zoom-btn\" title=\"Zoom in\" id=\"ZoomInButton\">+</div>\n <div (click)=\"zoomOut()\" class=\"zoom-btn\" title=\"Zoom out\" id=\"ZoomOutButton\">&#8722;</div>\n <div (click)=\"resetView()\" class=\"zoom-btn zoom-btn-text\" title=\"Fit to content\" id=\"FitToContentButton\">Fit</div>\n </div>\n @if (isLoading) {\n <lib-loading-indicator [isLoading]=\"isLoading\"></lib-loading-indicator>\n }\n\n <div class=\"topology-legend\" [class.collapsed]=\"legendCollapsed\">\n <div class=\"legend-header\" (click)=\"legendCollapsed = !legendCollapsed\"\n [title]=\"legendCollapsed ? 'Show legend' : 'Hide legend'\">\n <span class=\"legend-header-title\">Legend</span>\n <span class=\"legend-header-toggle\" aria-hidden=\"true\">\n <svg viewBox=\"0 0 16 16\" width=\"13\" height=\"13\">\n <path d=\"M3.5 6 L8 10.5 L12.5 6\" fill=\"none\" stroke=\"currentColor\"\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n </span>\n </div>\n @if (!legendCollapsed) {\n <div class=\"legend-body\">\n <div class=\"legend-section\">\n <div class=\"legend-section-title\">Nodes</div>\n <div class=\"legend-item\" title=\"An endpoint identity \u2014 a client that dials the service or a tunneler that hosts it.\">\n <div class=\"legend-icon-col\"><img class=\"legend-node-icon\" src=\"assets/images/visualizer/host.png\" /></div>\n <div class=\"legend-text\">Identity</div>\n </div>\n <div class=\"legend-item\" title=\"A Ziti edge router that relays traffic between identities.\">\n <div class=\"legend-icon-col\"><img class=\"legend-node-icon\" src=\"assets/images/visualizer/Routers.png\" /></div>\n <div class=\"legend-text\">Edge Router</div>\n </div>\n <div class=\"legend-item\" title=\"The service being dialed and hosted.\">\n <div class=\"legend-icon-col\"><img class=\"legend-node-icon\" src=\"assets/images/visualizer/service.png\" /></div>\n <div class=\"legend-text\">Service</div>\n </div>\n </div>\n <div class=\"legend-section\">\n <div class=\"legend-section-title\">Status</div>\n <div class=\"legend-item\" title=\"Connected to an edge router.\">\n <div class=\"legend-icon-col\"><div class=\"legend-circle online\"></div></div>\n <div class=\"legend-text\">Online</div>\n </div>\n <div class=\"legend-item\" title=\"Enrolled but not currently connected (e.g. powered off or idle).\">\n <div class=\"legend-icon-col\"><div class=\"legend-circle offline\"></div></div>\n <div class=\"legend-text\">Offline</div>\n </div>\n <div class=\"legend-item\" title=\"Identity-only: the controller can't currently determine the connection state.\">\n <div class=\"legend-icon-col\"><div class=\"legend-circle unknown\"></div></div>\n <div class=\"legend-text\">Unknown</div>\n </div>\n <div class=\"legend-item\" title=\"Identity has not completed enrollment.\">\n <div class=\"legend-icon-col\"><div class=\"legend-circle notenrolled\"></div></div>\n <div class=\"legend-text\">Not enrolled</div>\n </div>\n </div>\n @if (fabricApiAvailable) {\n <div class=\"legend-section\">\n <div class=\"legend-section-title\">Path</div>\n <div class=\"legend-item\" title=\"Live traffic is flowing over this hop right now.\">\n <div class=\"legend-icon-col\"><div class=\"legend-line active-circuit\"></div></div>\n <div class=\"legend-text\">Active Circuit</div>\n </div>\n <div class=\"legend-item\" title=\"Reachable and authorized, but no active circuit at the moment.\">\n <div class=\"legend-icon-col\"><div class=\"legend-line available\"></div></div>\n <div class=\"legend-text\">Available</div>\n </div>\n <div class=\"legend-item\" title=\"This hop can't carry traffic right now. The node colors show which end is the problem \u2014 an offline router, or an unreachable / un-enrolled endpoint.\">\n <div class=\"legend-icon-col\"><div class=\"legend-line broken\"></div></div>\n <div class=\"legend-text\">Unavailable</div>\n </div>\n </div>\n }\n </div>\n }\n </div>\n\n </div>\n</div>\n", styles: ["#IdentityVisualizer{overflow:hidden;width:75%;height:85%;background-color:var(--background);border-radius:1.5625rem;z-index:1001;margin:auto;inset:0rem;position:fixed;display:flex;flex-direction:column}#IdentityVisualizer .buttonBall{cursor:pointer;font-weight:600}#IdentityVisualizer .buttonBall.icon-close{position:absolute;top:1.25rem;right:1.25rem;font-size:1.4375rem;color:var(--icon)}#IdentityVisualizer .buttonBall:hover{filter:brightness(50%)}#IdentityVisualizer .clear-svc-icon{position:absolute;top:50%;right:.55rem;transform:translateY(-50%);width:1.65rem;height:1.65rem;display:flex;align-items:center;justify-content:center;border-radius:50%;font-size:.95rem;color:var(--icon);cursor:pointer;transition:background .12s ease,color .12s ease}#IdentityVisualizer .clear-svc-icon:hover{background:color-mix(in srgb,var(--tableText) 12%,transparent);color:var(--tableText)}#IdentityVisualizer.full-screen{width:100%;height:100%;border-radius:0}.ep-topology-filter-container{width:37%;display:flex;flex-direction:row;position:absolute;margin:1.25rem;padding:0}.topology-toolbar .ep-topology-filter-container{position:relative;margin:0;flex:1 1 auto;min-width:0;max-width:28rem}.topology-toolbar{display:flex;align-items:center;gap:1rem;padding:1.25rem 1.25rem .625rem;box-sizing:border-box;width:100%;flex-shrink:0;position:relative;z-index:4}.buttonBall.resize{top:1.25rem;width:1.4375rem;height:1.4375rem;right:3.4375rem;background-image:url(/assets/svgs/Resize.svg);background-size:1.4375rem 1.4375rem;position:absolute;cursor:pointer}.topology-refresh-controls{display:flex;align-items:center;gap:.5rem;flex:0 0 auto}.topology-refresh-controls .refresh-button{width:2.5rem;height:2.5rem;border-radius:50%;background-color:var(--navigation);box-shadow:0 .375rem 1rem 0 var(--shadow),0 0 0 .0625rem var(--shadow);display:flex;align-items:center;justify-content:center;cursor:pointer;transition:filter .15s ease,transform .1s ease,box-shadow .15s ease}.topology-refresh-controls .refresh-button .refresh-icon{width:1.125rem;height:1.125rem;background-color:var(--tableText);opacity:.85;-webkit-mask:url(/assets/svgs/refresh.svg) center/contain no-repeat;mask:url(/assets/svgs/refresh.svg) center/contain no-repeat;transition:opacity .15s ease}.topology-refresh-controls .refresh-button:hover{box-shadow:0 .5rem 1.25rem 0 var(--shadow),0 0 0 .0625rem var(--primaryColor)}.topology-refresh-controls .refresh-button:hover .refresh-icon{opacity:1}.topology-refresh-controls .refresh-button:active{transform:scale(.95)}.topology-refresh-controls .refresh-button.rotating .refresh-icon{animation:rotating 1s linear infinite}.topology-zoom-controls{position:absolute;bottom:1rem;left:1rem;display:flex;flex-direction:column;gap:.375rem;z-index:5}.topology-zoom-controls .zoom-btn{width:2.25rem;height:2.25rem;display:flex;align-items:center;justify-content:center;border-radius:.5rem;background-color:var(--navigation);color:var(--tableText);font-family:Open Sans,Arial,sans-serif;font-size:1.25rem;font-weight:600;line-height:1;cursor:pointer;-webkit-user-select:none;user-select:none;box-shadow:0 .5rem 1.25rem 0 var(--shadow);transition:filter .15s ease,transform .15s ease}.topology-zoom-controls .zoom-btn.zoom-btn-text{font-size:.75rem;text-transform:uppercase;letter-spacing:.04em}.topology-zoom-controls .zoom-btn:hover{filter:brightness(85%)}.topology-zoom-controls .zoom-btn:active{transform:scale(.95)}.identity-topology-container{display:flex;margin-left:-.3125rem;margin-top:.625rem;flex-direction:column;flex:1;overflow:hidden;position:relative}#IdentityTopology{cursor:grab;flex:1;min-height:0}#IdentityTopology:active{cursor:grabbing}.lightMode{background:#f5e9f2;background-image:linear-gradient(#ecd3e6 .125rem,transparent 0),linear-gradient(90deg,#ecd3e6 .125rem,transparent 0),linear-gradient(#ecd3e6 .0625rem,transparent 0),linear-gradient(90deg,#ecd3e6 .0625rem,transparent 0);background-size:2.5rem 2.5rem,2.5rem 2.5rem,.625rem .625rem,.625rem .625rem}.darkMode{background:#000a17;background-image:linear-gradient(#001127 .125rem,transparent 0),linear-gradient(90deg,#001127 .125rem,transparent 0),linear-gradient(#001127 1px,transparent 0),linear-gradient(90deg,#001127 1px,transparent 0);background-size:2.5rem 2.5rem,2.5rem 2.5rem,.625rem .625rem,.625rem .625rem}.tp-data-header{height:2.5rem;font-size:1.5625rem;font-weight:600;margin-left:1.25rem;margin-top:.625rem;margin-bottom:.3125rem;font-family:Russo One;color:var(--primaryColor);font-size:1.875rem;text-transform:uppercase;font-weight:400}.node text{font:.75rem Open Sans,Arial,sans-serif}.modaltitle{padding-top:.375rem;padding-left:.3125rem;color:var(--primaryColor)}.modalbuttons{padding-right:1.25rem}.listBox.row .close{position:fixed;display:flex;flex-direction:column;font-family:icomoon!important;font-style:normal;font-weight:600;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--icon);text-align:center;font-size:2.1875rem;line-height:2.5rem;left:35%;top:17%;width:2.5rem;height:2.5rem;background-image:url(/assets/svgs/close.svg);z-index:600;background-position:center center;background-size:75%;cursor:pointer;transition:.3s;justify-content:end;align-items:center}.tp-topo-data-header{border:0rem solid black;height:75rem;font-size:.75rem;font-weight:400;margin-left:.75rem;margin-top:.1875rem;color:var(--primaryColor)}.services-select{width:25rem;flex-shrink:0;border-radius:1.6875rem;height:3.1875rem;margin-left:1.375rem;margin-top:.625rem;background-color:#fff;border-color:var(--navigation);box-shadow:0 1.25rem 1.4375rem 0 var(--shadow);color:var(--tableText);padding-left:1.25rem}select{background-image:url(/assets/svgs/ArrowDown.svg);background-position:center right .9375rem;background-repeat:no-repeat;background-size:1.125rem;padding-right:2.9375rem}.topology-filter-input{height:2.75rem;width:100%;box-sizing:border-box;border-radius:.75rem;border:1px solid var(--shadow);background-color:var(--background);color:var(--tableText);box-shadow:0 .0625rem .125rem #0000000f;padding:0 2.6rem 0 1rem;font-size:.95rem;outline:none;transition:border-color .15s ease,box-shadow .15s ease}.topology-filter-input::placeholder{color:color-mix(in srgb,var(--tableText) 50%,transparent)}.topology-filter-input:hover{border-color:color-mix(in srgb,var(--tableText) 28%,transparent)}.topology-filter-input:focus{border-color:var(--primary);box-shadow:0 0 0 .1875rem color-mix(in srgb,var(--primary) 18%,transparent)}::ng-deep .topotip{display:none;position:fixed;background:var(--navigation);border-radius:.5rem;pointer-events:auto;box-shadow:0 .25rem 1rem 0 var(--shadow);color:var(--tableText);z-index:2000;padding:0;max-width:24rem;overflow:hidden}.topology-legend{width:15rem;border-radius:.5rem;position:absolute;bottom:1rem;right:.625rem;background:var(--navigation);box-shadow:0 .25rem .75rem 0 var(--shadow);padding:.9rem}.topology-legend.collapsed{width:auto}.legend-header{display:flex;align-items:center;justify-content:space-between;gap:.75rem;cursor:pointer;-webkit-user-select:none;user-select:none}.legend-header-title{font-family:Russo One,sans-serif;font-size:.9rem;text-transform:uppercase;letter-spacing:.05em;color:var(--primary)}.legend-header-toggle{color:var(--primary);display:flex;align-items:center}.legend-header-toggle svg{display:block;transition:transform .15s ease}.topology-legend.collapsed .legend-header-toggle svg{transform:rotate(180deg)}.legend-body{padding-top:.6rem}.legend-section{padding:.3rem 0}.legend-section:first-child{padding-top:0}.legend-section:last-child{padding-bottom:0;margin-bottom:-.5rem}.legend-section:not(:last-child){border-bottom:1px solid var(--shadow)}.legend-section-title{font-family:Russo One,sans-serif;font-size:.9rem;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);margin-bottom:.25rem}.legend-item{display:flex;align-items:center;gap:0;padding:.225rem 0;cursor:help}.legend-icon-col{width:2.5rem;min-width:2.5rem;display:flex;align-items:center;justify-content:center;margin-right:.4rem}.legend-circle{height:.75rem;width:.75rem;min-width:.75rem;border-radius:50%}.legend-circle.online{background-color:#08dc5a}.legend-circle.offline{background-color:#8a8f9a}.legend-circle.unknown{background-color:#e6a700}.legend-circle.notenrolled{background-color:var(--background);border:.125rem solid #8a8f9a}.legend-line{height:.225rem;width:1.5rem}.legend-line.active-circuit{background-color:#00cd13}.legend-line.available{background:repeating-linear-gradient(to right,#8a8f9a 0,#8a8f9a .25rem,transparent .25rem,transparent .5rem)}.legend-line.broken{height:0;background:none;border-top:.28rem dotted #e6432e}.legend-text{font-size:.975rem;font-weight:500;color:var(--tableText);white-space:nowrap}.legend-node-icon{width:2rem;height:2rem;min-width:2rem;object-fit:contain}.legend-icon.misconfigured,.legend-icon.broken-link{width:1.2rem;height:1.2rem;min-width:1.2rem;background-image:url(/assets/images/visualizer/link-cut.png);background-size:contain;background-repeat:no-repeat}.legend-text2{display:inline-block;font-size:.625rem;font-weight:400;color:var(--tableText);padding-left:.125rem;padding-bottom:.25rem}@-webkit-keyframes rotating{0%{-webkit-transform:rotate(0deg);-o-transform:rotate(0deg);transform:rotate(0)}to{-webkit-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes rotating{0%{-ms-transform:rotate(0deg);-moz-transform:rotate(0deg);-webkit-transform:rotate(0deg);-o-transform:rotate(0deg);transform:rotate(0)}to{-ms-transform:rotate(360deg);-moz-transform:rotate(360deg);-webkit-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}.rotating{-webkit-animation:rotating 2s linear infinite;-moz-animation:rotating 2s linear infinite;-ms-animation:rotating 2s linear infinite;-o-animation:rotating 2s linear infinite;animation:rotating 2s linear infinite}::ng-deep .tool-tip-container{padding:0}::ng-deep .tool-tip-container .tooltip-header{font-family:Russo One,sans-serif;font-size:.975rem;color:var(--primary);padding:.7rem .9rem .15rem}::ng-deep .tool-tip-container .prop-row{display:flex;justify-content:space-between;gap:1.2rem;padding:.375rem .9rem;font-size:.9rem}::ng-deep .tool-tip-container .prop-row:nth-child(2n){background-color:var(--formBackground)}::ng-deep .tool-tip-container .prop-row:last-child{padding-bottom:.6rem}::ng-deep .tool-tip-container .prop-label{color:var(--tableText);opacity:.6;font-weight:500;white-space:nowrap}::ng-deep .tool-tip-container .prop-value{color:var(--tableText);font-weight:600;text-align:right}::ng-deep .tool-tip-container .tt-status-pill{display:inline-flex;align-items:center;gap:.35rem;margin:0 .9rem .55rem;padding:.14rem .55rem .14rem .45rem;border-radius:999px;font-size:.72rem;font-weight:600;letter-spacing:.02em}::ng-deep .tool-tip-container .tt-status-dot{width:.45rem;height:.45rem;min-width:.45rem;border-radius:50%}::ng-deep .tool-tip-container .prop-row:first-of-type{border-top:1px solid var(--shadow);padding-top:.5rem}::ng-deep .tool-tip-container .tt-ok{background:#08dc5a1f}::ng-deep .tool-tip-container .tt-ok .tt-status-dot{background:#08dc5a}::ng-deep .tool-tip-container .tt-ok .tt-status-label{color:#0a9d4a}::ng-deep .tool-tip-container .tt-broken{background:#e6432e1f}::ng-deep .tool-tip-container .tt-broken .tt-status-dot{background:#e6432e}::ng-deep .tool-tip-container .tt-broken .tt-status-label{color:#d23b27}::ng-deep .tool-tip-container .tt-offline{background:#8a8f9a29}::ng-deep .tool-tip-container .tt-offline .tt-status-dot{background:#8a8f9a}::ng-deep .tool-tip-container .tt-offline .tt-status-label{color:#6f7480}::ng-deep .tool-tip-container .tt-unknown{background:#e6a70024}::ng-deep .tool-tip-container .tt-unknown .tt-status-dot{background:#e6a700}::ng-deep .tool-tip-container .tt-unknown .tt-status-label{color:#b07f00}::ng-deep .tool-tip-container .tt-neutral{background:#6e78c81f}::ng-deep .tool-tip-container .tt-neutral .tt-status-dot{background:var(--primary)}::ng-deep .tool-tip-container .tt-neutral .tt-status-label{color:var(--primary)}::ng-deep .cdk-overlay-pane .mat-mdc-autocomplete-panel{border-radius:.75rem!important;background:var(--background);color:var(--tableText);padding:.35rem;border:1px solid var(--shadow);box-shadow:0 .5rem 1.5rem #00000029!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option.mdc-list-item{align-items:center;min-height:2.5rem;margin:.1rem 0;border-radius:.5rem;color:var(--tableText);transition:background-color .12s ease,color .12s ease}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option.mdc-list-item:hover:not(.mdc-list-item--disabled),::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option.mat-mdc-option-active:not(.mdc-list-item--disabled){background-color:color-mix(in srgb,var(--primary) 9%,transparent)!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option.mdc-list-item--selected:not(.mdc-list-item--disabled){background-color:color-mix(in srgb,var(--primary) 14%,transparent)!important}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option.mdc-list-item--selected .mdc-list-item__primary-text{color:var(--primary);font-weight:600}::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .mat-mdc-option-ripple,::ng-deep .mat-mdc-autocomplete-panel .mat-mdc-option .mat-ripple{display:none}::ng-deep .mat-mdc-autocomplete-panel .mat-pseudo-checkbox-minimal{color:var(--primary)}::ng-deep .mat-mdc-raised-button .mdc-button__ripple{border:.1rem solid var(--primaryColor)}::ng-deep .p-button.p-button-icon-only{width:1.357rem}\n"] }]
12369
12761
  }], ctorParameters: () => [{ type: IdentityServicePathHelper }, { type: MapDataService }, { type: SettingsService, decorators: [{
12370
12762
  type: Inject,
12371
12763
  args: [SETTINGS_SERVICE]
@@ -14218,7 +14610,7 @@ class ServicePolicyFormService {
14218
14610
  });
14219
14611
  }
14220
14612
  getAssociatedServicesByRole(id) {
14221
- this.zitiService.getSubdata('service-policies', id, 'services').then((result) => {
14613
+ this.zitiService.getSubdata('service-policies', id, 'services', this.zitiService.DEFAULT_PAGING).then((result) => {
14222
14614
  this.associatedServices = result.data;
14223
14615
  this.associatedServiceNames = this.associatedServices.map((svc) => {
14224
14616
  return svc.name;
@@ -14226,7 +14618,7 @@ class ServicePolicyFormService {
14226
14618
  });
14227
14619
  }
14228
14620
  getAssociatedIdentitiesById(id) {
14229
- return this.zitiService.getSubdata('service-policies', id, 'identities').then((result) => {
14621
+ return this.zitiService.getSubdata('service-policies', id, 'identities', this.zitiService.DEFAULT_PAGING).then((result) => {
14230
14622
  this.associatedIdentities = result.data;
14231
14623
  this.associatedIdentityNames = this.associatedIdentities.map((ident) => {
14232
14624
  return ident.name;
@@ -14858,11 +15250,11 @@ class PreviewListComponent {
14858
15250
  return './' + hrefVal;
14859
15251
  }
14860
15252
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PreviewListComponent, deps: [{ token: i4.Router }], target: i0.ɵɵFactoryTarget.Component }); }
14861
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PreviewListComponent, isStandalone: false, selector: "lib-preview-list", inputs: { label: "label", clickable: "clickable", isLoading: "isLoading", allNames: "allNames", items: "items", allowRemove: "allowRemove", tooltip: "tooltip", showfilter: "showfilter", localFilter: "localFilter" }, outputs: { itemSelected: "itemSelected", itemRemoved: "itemRemoved", filterChanged: "filterChanged" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"listBox row\">\n @if (showfilter) {\n <input (keyup)=\"onKeydownEvent()\" [(ngModel)]=\"filterFor\" placeholder=\"Type to Filter\"/>\n }\n <div class=\"text row\">\n @if (previewItems.length <= 0) {\n <div class=\"listText\">\n <span class=\"preview-name preview-placeholder no-items-preview\">no items to display...</span>\n </div>\n }\n @for (item of previewItems; track item) {\n <div (click)=\"selected(item)\" [ngClass]=\"{ clickable: clickable }\" class=\"listText\">\n @if (allowRemove) {\n <div class=\"icon-clear\" (click)=\"remove(item)\"></div>\n }\n @if (!item.href) {\n <span class=\"preview-name\" matTooltip=\"{{tooltip}}\" matTooltipPosition=\"below\" matTooltipShowDelay=\"1000\">{{ item.name || item }}</span>\n }\n @if (item.href) {\n <a [href]=\"getHrefVal(item.href)\" (click)=\"linkClicked($event, item)\" class=\"{{item.linkClass ? item.linkClass : ''}}\" matTooltip=\"{{tooltip}}\" matTooltipShowDelay=\"1000\" matTooltipPosition=\"below\">\n @if (item.name) {\n <span class=\"preview-name\">{{ item.name }}@if (item.iconClass) {\n <div class=\"preview-item-icon {{item.iconClass}}\" (click)=\"remove(item)\"></div>\n }</span>\n }\n </a>\n }\n </div>\n }\n </div>\n</div>\n@if (isLoading) {\n <div class=\"spinner-container\">\n <span class=\"spinner\"></span>\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;height:100%;position:relative}.counter{background-color:#4c5566;color:var(--white);position:absolute;right:0rem}.listBox{background-color:var(--stroke);padding:.625rem;border-radius:var(--inputBorderRadius);height:100%;min-height:3.75rem}.listBox input{height:2.5rem;border-radius:var(--inputBorderRadius);font-size:.875rem;font-weight:600;background-image:url(/assets/images/Filter.png);background-size:auto 1.3125rem;background-position:top .5rem right .75rem;background-repeat:no-repeat;margin-bottom:.625rem;padding-left:.875rem}.text{overflow-y:scroll;height:100%}.listText{width:100%;position:relative;float:left;font-size:.875rem;font-weight:600;padding-left:.625rem;padding-right:.625rem;margin-bottom:.125rem;color:var(--primary);display:flex;flex-direction:row;align-items:center;gap:.3125rem}.listText a{color:var(--primary);display:flex;flex-direction:row;align-items:center}.listText a:hover .preview-item-icon{text-decoration:underline}.listText .icon-clear{margin-top:.125rem}.listText .icon-clear:hover{color:var(--background)}::-webkit-scrollbar-thumb{background-color:var(--background)!important}.clickable{color:var(--primary);cursor:pointer}.clickable:hover,.clickable:focus{filter:brightness(90%)}.clickable:active{color:var(--icon);-webkit-transform:translateY(.0625rem);transform:translateY(.0625rem)}.clickable .preview-name:hover,.clickable .preview-name:focus{text-decoration:underline}.clickable .preview-name:active{text-decoration:underline}.preview-name{white-space:pre}.preview-name.preview-placeholder{font-style:italic}.preview-name.no-items-preview{font-weight:400}.preview-item-icon{margin-left:.4375rem;font-family:zac!important}.preview-more-results{margin-top:.3125rem}.preview-more-results .preview-name{font-weight:600;border-top-width:1px;text-transform:uppercase;font-size:.825rem;font-style:italic}.preview-more-results .preview-name:after{content:\"\\e939\";font-family:icomoon!important;font-style:normal}.spinner-container{position:absolute;display:flex;align-items:center;justify-content:center;width:100%;height:100%}.spinner-container .spinner{display:inline-block;width:2rem;height:2rem;border-radius:50%;background:transparent;border-top:.3125rem solid var(--tableText);border-right:.3125rem solid var(--tableText);border-bottom:.3125rem solid transparent;border-left:.3125rem solid transparent;-webkit-animation:loading .5s infinite linear;animation:loading .5s infinite linear;opacity:.7}\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"] }] }); }
15253
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: PreviewListComponent, isStandalone: false, selector: "lib-preview-list", inputs: { label: "label", clickable: "clickable", isLoading: "isLoading", allNames: "allNames", items: "items", allowRemove: "allowRemove", tooltip: "tooltip", showfilter: "showfilter", localFilter: "localFilter" }, outputs: { itemSelected: "itemSelected", itemRemoved: "itemRemoved", filterChanged: "filterChanged" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"listBox row\">\n @if (showfilter) {\n <input (keyup)=\"onKeydownEvent()\" [(ngModel)]=\"filterFor\" placeholder=\"Type to Filter\"/>\n }\n <div class=\"text row\">\n @if (previewItems.length <= 0) {\n <div class=\"listText\">\n <span class=\"preview-name preview-placeholder no-items-preview\">no items to display...</span>\n </div>\n }\n @for (item of previewItems; track item) {\n <div (click)=\"selected(item)\" [ngClass]=\"{ clickable: clickable }\" class=\"listText\">\n @if (allowRemove) {\n <div class=\"icon-clear\" (click)=\"remove(item)\"></div>\n }\n @if (!item.href) {\n <span class=\"preview-name\" matTooltip=\"{{tooltip}}\" matTooltipPosition=\"below\" matTooltipShowDelay=\"1000\">{{ item.name || item }}</span>\n }\n @if (item.href) {\n <a [href]=\"getHrefVal(item.href)\" (click)=\"linkClicked($event, item)\" class=\"{{item.linkClass ? item.linkClass : ''}}\" matTooltip=\"{{tooltip}}\" matTooltipShowDelay=\"1000\" matTooltipPosition=\"below\">\n @if (item.name) {\n <span class=\"preview-name\">{{ item.name }}@if (item.iconClass) {\n <div class=\"preview-item-icon {{item.iconClass}}\" (click)=\"remove(item)\"></div>\n }</span>\n }\n </a>\n }\n </div>\n }\n </div>\n</div>\n@if (isLoading) {\n <div class=\"spinner-container\">\n <span class=\"spinner\"></span>\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;height:auto;position:relative}.counter{background-color:#4c5566;color:var(--white);position:absolute;right:0rem}.listBox{background-color:var(--stroke);padding:.625rem;border-radius:var(--inputBorderRadius);height:auto;min-height:3.75rem}.listBox input{height:2.5rem;border-radius:var(--inputBorderRadius);font-size:.875rem;font-weight:600;background-image:url(/assets/images/Filter.png);background-size:auto 1.3125rem;background-position:top .5rem right .75rem;background-repeat:no-repeat;margin-bottom:.625rem;padding-left:.875rem}.text{overflow-y:auto;height:auto;max-height:12.5rem}.listText{width:100%;position:relative;float:left;font-size:.875rem;font-weight:600;padding-left:.625rem;padding-right:.625rem;margin-bottom:.125rem;color:var(--primary);display:flex;flex-direction:row;align-items:center;gap:.3125rem}.listText a{color:var(--primary);display:flex;flex-direction:row;align-items:center}.listText a:hover .preview-item-icon{text-decoration:underline}.listText .icon-clear{margin-top:.125rem}.listText .icon-clear:hover{color:var(--background)}::-webkit-scrollbar-thumb{background-color:var(--background)!important}.clickable{color:var(--primary);cursor:pointer}.clickable:hover,.clickable:focus{filter:brightness(90%)}.clickable:active{color:var(--icon);-webkit-transform:translateY(.0625rem);transform:translateY(.0625rem)}.clickable .preview-name:hover,.clickable .preview-name:focus{text-decoration:underline}.clickable .preview-name:active{text-decoration:underline}.preview-name{white-space:pre}.preview-name.preview-placeholder{font-style:italic}.preview-name.no-items-preview{font-weight:400}.preview-item-icon{margin-left:.4375rem;font-family:zac!important}.preview-more-results{margin-top:.3125rem}.preview-more-results .preview-name{font-weight:600;border-top-width:1px;text-transform:uppercase;font-size:.825rem;font-style:italic}.preview-more-results .preview-name:after{content:\"\\e939\";font-family:icomoon!important;font-style:normal}.spinner-container{position:absolute;display:flex;align-items:center;justify-content:center;width:100%;height:100%}.spinner-container .spinner{display:inline-block;width:2rem;height:2rem;border-radius:50%;background:transparent;border-top:.3125rem solid var(--tableText);border-right:.3125rem solid var(--tableText);border-bottom:.3125rem solid transparent;border-left:.3125rem solid transparent;-webkit-animation:loading .5s infinite linear;animation:loading .5s infinite linear;opacity:.7}\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"] }] }); }
14862
15254
  }
14863
15255
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PreviewListComponent, decorators: [{
14864
15256
  type: Component,
14865
- args: [{ selector: 'lib-preview-list', standalone: false, template: "<div class=\"listBox row\">\n @if (showfilter) {\n <input (keyup)=\"onKeydownEvent()\" [(ngModel)]=\"filterFor\" placeholder=\"Type to Filter\"/>\n }\n <div class=\"text row\">\n @if (previewItems.length <= 0) {\n <div class=\"listText\">\n <span class=\"preview-name preview-placeholder no-items-preview\">no items to display...</span>\n </div>\n }\n @for (item of previewItems; track item) {\n <div (click)=\"selected(item)\" [ngClass]=\"{ clickable: clickable }\" class=\"listText\">\n @if (allowRemove) {\n <div class=\"icon-clear\" (click)=\"remove(item)\"></div>\n }\n @if (!item.href) {\n <span class=\"preview-name\" matTooltip=\"{{tooltip}}\" matTooltipPosition=\"below\" matTooltipShowDelay=\"1000\">{{ item.name || item }}</span>\n }\n @if (item.href) {\n <a [href]=\"getHrefVal(item.href)\" (click)=\"linkClicked($event, item)\" class=\"{{item.linkClass ? item.linkClass : ''}}\" matTooltip=\"{{tooltip}}\" matTooltipShowDelay=\"1000\" matTooltipPosition=\"below\">\n @if (item.name) {\n <span class=\"preview-name\">{{ item.name }}@if (item.iconClass) {\n <div class=\"preview-item-icon {{item.iconClass}}\" (click)=\"remove(item)\"></div>\n }</span>\n }\n </a>\n }\n </div>\n }\n </div>\n</div>\n@if (isLoading) {\n <div class=\"spinner-container\">\n <span class=\"spinner\"></span>\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;height:100%;position:relative}.counter{background-color:#4c5566;color:var(--white);position:absolute;right:0rem}.listBox{background-color:var(--stroke);padding:.625rem;border-radius:var(--inputBorderRadius);height:100%;min-height:3.75rem}.listBox input{height:2.5rem;border-radius:var(--inputBorderRadius);font-size:.875rem;font-weight:600;background-image:url(/assets/images/Filter.png);background-size:auto 1.3125rem;background-position:top .5rem right .75rem;background-repeat:no-repeat;margin-bottom:.625rem;padding-left:.875rem}.text{overflow-y:scroll;height:100%}.listText{width:100%;position:relative;float:left;font-size:.875rem;font-weight:600;padding-left:.625rem;padding-right:.625rem;margin-bottom:.125rem;color:var(--primary);display:flex;flex-direction:row;align-items:center;gap:.3125rem}.listText a{color:var(--primary);display:flex;flex-direction:row;align-items:center}.listText a:hover .preview-item-icon{text-decoration:underline}.listText .icon-clear{margin-top:.125rem}.listText .icon-clear:hover{color:var(--background)}::-webkit-scrollbar-thumb{background-color:var(--background)!important}.clickable{color:var(--primary);cursor:pointer}.clickable:hover,.clickable:focus{filter:brightness(90%)}.clickable:active{color:var(--icon);-webkit-transform:translateY(.0625rem);transform:translateY(.0625rem)}.clickable .preview-name:hover,.clickable .preview-name:focus{text-decoration:underline}.clickable .preview-name:active{text-decoration:underline}.preview-name{white-space:pre}.preview-name.preview-placeholder{font-style:italic}.preview-name.no-items-preview{font-weight:400}.preview-item-icon{margin-left:.4375rem;font-family:zac!important}.preview-more-results{margin-top:.3125rem}.preview-more-results .preview-name{font-weight:600;border-top-width:1px;text-transform:uppercase;font-size:.825rem;font-style:italic}.preview-more-results .preview-name:after{content:\"\\e939\";font-family:icomoon!important;font-style:normal}.spinner-container{position:absolute;display:flex;align-items:center;justify-content:center;width:100%;height:100%}.spinner-container .spinner{display:inline-block;width:2rem;height:2rem;border-radius:50%;background:transparent;border-top:.3125rem solid var(--tableText);border-right:.3125rem solid var(--tableText);border-bottom:.3125rem solid transparent;border-left:.3125rem solid transparent;-webkit-animation:loading .5s infinite linear;animation:loading .5s infinite linear;opacity:.7}\n"] }]
15257
+ args: [{ selector: 'lib-preview-list', standalone: false, template: "<div class=\"listBox row\">\n @if (showfilter) {\n <input (keyup)=\"onKeydownEvent()\" [(ngModel)]=\"filterFor\" placeholder=\"Type to Filter\"/>\n }\n <div class=\"text row\">\n @if (previewItems.length <= 0) {\n <div class=\"listText\">\n <span class=\"preview-name preview-placeholder no-items-preview\">no items to display...</span>\n </div>\n }\n @for (item of previewItems; track item) {\n <div (click)=\"selected(item)\" [ngClass]=\"{ clickable: clickable }\" class=\"listText\">\n @if (allowRemove) {\n <div class=\"icon-clear\" (click)=\"remove(item)\"></div>\n }\n @if (!item.href) {\n <span class=\"preview-name\" matTooltip=\"{{tooltip}}\" matTooltipPosition=\"below\" matTooltipShowDelay=\"1000\">{{ item.name || item }}</span>\n }\n @if (item.href) {\n <a [href]=\"getHrefVal(item.href)\" (click)=\"linkClicked($event, item)\" class=\"{{item.linkClass ? item.linkClass : ''}}\" matTooltip=\"{{tooltip}}\" matTooltipShowDelay=\"1000\" matTooltipPosition=\"below\">\n @if (item.name) {\n <span class=\"preview-name\">{{ item.name }}@if (item.iconClass) {\n <div class=\"preview-item-icon {{item.iconClass}}\" (click)=\"remove(item)\"></div>\n }</span>\n }\n </a>\n }\n </div>\n }\n </div>\n</div>\n@if (isLoading) {\n <div class=\"spinner-container\">\n <span class=\"spinner\"></span>\n </div>\n}\n", styles: [":host{display:flex;flex-direction:column;height:auto;position:relative}.counter{background-color:#4c5566;color:var(--white);position:absolute;right:0rem}.listBox{background-color:var(--stroke);padding:.625rem;border-radius:var(--inputBorderRadius);height:auto;min-height:3.75rem}.listBox input{height:2.5rem;border-radius:var(--inputBorderRadius);font-size:.875rem;font-weight:600;background-image:url(/assets/images/Filter.png);background-size:auto 1.3125rem;background-position:top .5rem right .75rem;background-repeat:no-repeat;margin-bottom:.625rem;padding-left:.875rem}.text{overflow-y:auto;height:auto;max-height:12.5rem}.listText{width:100%;position:relative;float:left;font-size:.875rem;font-weight:600;padding-left:.625rem;padding-right:.625rem;margin-bottom:.125rem;color:var(--primary);display:flex;flex-direction:row;align-items:center;gap:.3125rem}.listText a{color:var(--primary);display:flex;flex-direction:row;align-items:center}.listText a:hover .preview-item-icon{text-decoration:underline}.listText .icon-clear{margin-top:.125rem}.listText .icon-clear:hover{color:var(--background)}::-webkit-scrollbar-thumb{background-color:var(--background)!important}.clickable{color:var(--primary);cursor:pointer}.clickable:hover,.clickable:focus{filter:brightness(90%)}.clickable:active{color:var(--icon);-webkit-transform:translateY(.0625rem);transform:translateY(.0625rem)}.clickable .preview-name:hover,.clickable .preview-name:focus{text-decoration:underline}.clickable .preview-name:active{text-decoration:underline}.preview-name{white-space:pre}.preview-name.preview-placeholder{font-style:italic}.preview-name.no-items-preview{font-weight:400}.preview-item-icon{margin-left:.4375rem;font-family:zac!important}.preview-more-results{margin-top:.3125rem}.preview-more-results .preview-name{font-weight:600;border-top-width:1px;text-transform:uppercase;font-size:.825rem;font-style:italic}.preview-more-results .preview-name:after{content:\"\\e939\";font-family:icomoon!important;font-style:normal}.spinner-container{position:absolute;display:flex;align-items:center;justify-content:center;width:100%;height:100%}.spinner-container .spinner{display:inline-block;width:2rem;height:2rem;border-radius:50%;background:transparent;border-top:.3125rem solid var(--tableText);border-right:.3125rem solid var(--tableText);border-bottom:.3125rem solid transparent;border-left:.3125rem solid transparent;-webkit-animation:loading .5s infinite linear;animation:loading .5s infinite linear;opacity:.7}\n"] }]
14866
15258
  }], ctorParameters: () => [{ type: i4.Router }], propDecorators: { label: [{
14867
15259
  type: Input
14868
15260
  }], clickable: [{
@@ -15620,7 +16012,7 @@ class IdentityFormComponent extends ProjectableForm {
15620
16012
  });
15621
16013
  }
15622
16014
  getAssociatedServices() {
15623
- this.zitiService.getSubdata('identities', this.formData.id, 'services').then((result) => {
16015
+ this.zitiService.getSubdata('identities', this.formData.id, 'services', this.zitiService.DEFAULT_PAGING).then((result) => {
15624
16016
  this.associatedServices = result.data;
15625
16017
  this.associatedServiceNames = this.associatedServices.map((svc) => {
15626
16018
  return svc.name;
@@ -15628,7 +16020,7 @@ class IdentityFormComponent extends ProjectableForm {
15628
16020
  });
15629
16021
  }
15630
16022
  getAssociatedServicePolicies() {
15631
- this.zitiService.getSubdata('identities', this.formData.id, 'service-policies').then((result) => {
16023
+ this.zitiService.getSubdata('identities', this.formData.id, 'service-policies', this.zitiService.DEFAULT_PAGING).then((result) => {
15632
16024
  this.associatedServicePolicies = result.data;
15633
16025
  this.associatedServicePolicyNames = this.associatedServicePolicies.map((policy) => {
15634
16026
  return policy.name;
@@ -19487,7 +19879,7 @@ class TransitRouterFormService {
19487
19879
  this.growlerService.show(growlerData);
19488
19880
  }
19489
19881
  getAssociatedServices(id) {
19490
- this.zitiService.getSubdata('transit-routers', id, 'services').then((result) => {
19882
+ this.zitiService.getSubdata('transit-routers', id, 'services', this.zitiService.DEFAULT_PAGING).then((result) => {
19491
19883
  this.associatedServices = result.data;
19492
19884
  this.associatedServiceNames = this.associatedServices.map((svc) => {
19493
19885
  return svc.name;
@@ -19495,7 +19887,7 @@ class TransitRouterFormService {
19495
19887
  });
19496
19888
  }
19497
19889
  getAssociatedIdentities(id) {
19498
- this.zitiService.getSubdata('transit-routers', id, 'identities').then((result) => {
19890
+ this.zitiService.getSubdata('transit-routers', id, 'identities', this.zitiService.DEFAULT_PAGING).then((result) => {
19499
19891
  this.associatedIdentities = result.data;
19500
19892
  this.associatedIdentityNames = this.associatedIdentities.map((policy) => {
19501
19893
  return policy.name;
@@ -20989,7 +21381,9 @@ class SimpleServiceComponent extends ProjectableForm {
20989
21381
  this.identityNamedAttributes = namedAttributes;
20990
21382
  this.mergeSelectedIdentityNamesIntoAvailableList();
20991
21383
  }).finally(() => {
20992
- this.identitiesInit.next(true);
21384
+ if (!this.identitiesInit.value) {
21385
+ this.identitiesInit.next(true);
21386
+ }
20993
21387
  });
20994
21388
  }
20995
21389
  checkAllDataInit() {
@@ -23293,7 +23687,7 @@ class EdgeRouterPolicyFormService {
23293
23687
  });
23294
23688
  }
23295
23689
  getAssociatedServicesByRole(id) {
23296
- this.zitiService.getSubdata('edge-router-policies', id, 'edge-routers').then((result) => {
23690
+ this.zitiService.getSubdata('edge-router-policies', id, 'edge-routers', this.zitiService.DEFAULT_PAGING).then((result) => {
23297
23691
  this.associatedEdgeRouters = result.data;
23298
23692
  this.associatedEdgeRouterNames = this.associatedEdgeRouters.map((svc) => {
23299
23693
  return svc.name;
@@ -23301,7 +23695,7 @@ class EdgeRouterPolicyFormService {
23301
23695
  });
23302
23696
  }
23303
23697
  getAssociatedIdentitiesById(id) {
23304
- this.zitiService.getSubdata('edge-router-policies', id, 'identities').then((result) => {
23698
+ this.zitiService.getSubdata('edge-router-policies', id, 'identities', this.zitiService.DEFAULT_PAGING).then((result) => {
23305
23699
  this.associatedIdentities = result.data;
23306
23700
  this.associatedIdentityNames = this.associatedIdentities.map((ident) => {
23307
23701
  return ident.name;
@@ -24963,7 +25357,7 @@ class ConfigurationService {
24963
25357
  });
24964
25358
  }
24965
25359
  getAssociatedServices(configId) {
24966
- return this.dataService.getSubdata('configs', configId, 'services').then((result) => {
25360
+ return this.dataService.getSubdata('configs', configId, 'services', this.dataService.DEFAULT_PAGING).then((result) => {
24967
25361
  const associatedServices = result.data;
24968
25362
  const associatedServiceNames = associatedServices.map((svc) => {
24969
25363
  return svc.name;
@@ -30703,7 +31097,7 @@ class APISessionFormService {
30703
31097
  this.growlerService.show(growlerData);
30704
31098
  }
30705
31099
  getAssociatedServices(id) {
30706
- this.zitiService.getSubdata('transit-routers', id, 'services').then((result) => {
31100
+ this.zitiService.getSubdata('transit-routers', id, 'services', this.zitiService.DEFAULT_PAGING).then((result) => {
30707
31101
  this.associatedServices = result.data;
30708
31102
  this.associatedServiceNames = this.associatedServices.map((svc) => {
30709
31103
  return svc.name;
@@ -30711,7 +31105,7 @@ class APISessionFormService {
30711
31105
  });
30712
31106
  }
30713
31107
  getAssociatedIdentities(id) {
30714
- this.zitiService.getSubdata('transit-routers', id, 'identities').then((result) => {
31108
+ this.zitiService.getSubdata('transit-routers', id, 'identities', this.zitiService.DEFAULT_PAGING).then((result) => {
30715
31109
  this.associatedIdentities = result.data;
30716
31110
  this.associatedIdentityNames = this.associatedIdentities.map((policy) => {
30717
31111
  return policy.name;
@@ -40599,7 +40993,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
40599
40993
  }] }, { type: ZitiOidcService }, { type: GrowlerService }, { type: i0.Injector }] });
40600
40994
 
40601
40995
  const ZAC_VERSION = {
40602
- "version": "4.3.0"
40996
+ "version": "4.3.1"
40603
40997
  };
40604
40998
 
40605
40999
  /*