@trismegisto/spine 2.1.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +105 -19
  2. package/fesm2022/trismegisto-spine-audit.mjs +264 -10
  3. package/fesm2022/trismegisto-spine-audit.mjs.map +1 -1
  4. package/fesm2022/trismegisto-spine-auto-service-apps.technical-BAKdkVdz.mjs +84 -0
  5. package/fesm2022/trismegisto-spine-auto-service-apps.technical-BAKdkVdz.mjs.map +1 -0
  6. package/fesm2022/trismegisto-spine-auto-service-apps.usage-CAXMP5r8.mjs +87 -0
  7. package/fesm2022/trismegisto-spine-auto-service-apps.usage-CAXMP5r8.mjs.map +1 -0
  8. package/fesm2022/{trismegisto-spine-auto-service-audit.technical-C901aIIQ.mjs → trismegisto-spine-auto-service-audit.technical-B75d4mh2.mjs} +6 -5
  9. package/fesm2022/trismegisto-spine-auto-service-audit.technical-B75d4mh2.mjs.map +1 -0
  10. package/fesm2022/{trismegisto-spine-auto-service-audit.usage-GIJ2RwuH.mjs → trismegisto-spine-auto-service-audit.usage-IdXMruv5.mjs} +9 -8
  11. package/fesm2022/trismegisto-spine-auto-service-audit.usage-IdXMruv5.mjs.map +1 -0
  12. package/fesm2022/{trismegisto-spine-auto-service-auto-service-audit.component-CCiz-q8o.mjs → trismegisto-spine-auto-service-auto-service-audit.component-BIL8tH1I.mjs} +4 -4
  13. package/fesm2022/{trismegisto-spine-auto-service-auto-service-audit.component-CCiz-q8o.mjs.map → trismegisto-spine-auto-service-auto-service-audit.component-BIL8tH1I.mjs.map} +1 -1
  14. package/fesm2022/{trismegisto-spine-auto-service-auto-service-client-picker.component-Z0wCANF6.mjs → trismegisto-spine-auto-service-auto-service-client-picker.component-CBt_0Uli.mjs} +3 -3
  15. package/fesm2022/{trismegisto-spine-auto-service-auto-service-client-picker.component-Z0wCANF6.mjs.map → trismegisto-spine-auto-service-auto-service-client-picker.component-CBt_0Uli.mjs.map} +1 -1
  16. package/fesm2022/{trismegisto-spine-auto-service-auto-service-client.component-C7SPavrX.mjs → trismegisto-spine-auto-service-auto-service-client.component-DAxhZC-a.mjs} +123 -27
  17. package/fesm2022/trismegisto-spine-auto-service-auto-service-client.component-DAxhZC-a.mjs.map +1 -0
  18. package/fesm2022/trismegisto-spine-auto-service-auto-service-delete-CoJYTtBU.mjs +87 -0
  19. package/fesm2022/trismegisto-spine-auto-service-auto-service-delete-CoJYTtBU.mjs.map +1 -0
  20. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-UVMGF7-B.mjs +544 -0
  21. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-UVMGF7-B.mjs.map +1 -0
  22. package/fesm2022/trismegisto-spine-auto-service-auto-service-login-attempts.component-Boi-kG0G.mjs +37 -0
  23. package/fesm2022/trismegisto-spine-auto-service-auto-service-login-attempts.component-Boi-kG0G.mjs.map +1 -0
  24. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-DmLafUkX.mjs +943 -0
  25. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-DmLafUkX.mjs.map +1 -0
  26. package/fesm2022/{trismegisto-spine-auto-service-client.technical-BHWhpyWv.mjs → trismegisto-spine-auto-service-client.technical-DP5G8i3o.mjs} +24 -3
  27. package/fesm2022/trismegisto-spine-auto-service-client.technical-DP5G8i3o.mjs.map +1 -0
  28. package/fesm2022/{trismegisto-spine-auto-service-client.usage-CM2WB2e2.mjs → trismegisto-spine-auto-service-client.usage-CcJANb20.mjs} +15 -6
  29. package/fesm2022/trismegisto-spine-auto-service-client.usage-CcJANb20.mjs.map +1 -0
  30. package/fesm2022/trismegisto-spine-auto-service-groups.technical-BlktCM70.mjs +65 -0
  31. package/fesm2022/trismegisto-spine-auto-service-groups.technical-BlktCM70.mjs.map +1 -0
  32. package/fesm2022/trismegisto-spine-auto-service-groups.usage-Cz5sCKgn.mjs +81 -0
  33. package/fesm2022/trismegisto-spine-auto-service-groups.usage-Cz5sCKgn.mjs.map +1 -0
  34. package/fesm2022/{trismegisto-spine-auto-service-home.component-vl3BPFnk.mjs → trismegisto-spine-auto-service-home.component-5_aji1hf.mjs} +5 -4
  35. package/fesm2022/trismegisto-spine-auto-service-home.component-5_aji1hf.mjs.map +1 -0
  36. package/fesm2022/trismegisto-spine-auto-service-login-attempts.technical-CasKIIws.mjs +37 -0
  37. package/fesm2022/trismegisto-spine-auto-service-login-attempts.technical-CasKIIws.mjs.map +1 -0
  38. package/fesm2022/trismegisto-spine-auto-service-login-attempts.usage-BMgLNu7W.mjs +60 -0
  39. package/fesm2022/trismegisto-spine-auto-service-login-attempts.usage-BMgLNu7W.mjs.map +1 -0
  40. package/fesm2022/trismegisto-spine-auto-service.mjs +49 -29
  41. package/fesm2022/trismegisto-spine-auto-service.mjs.map +1 -1
  42. package/fesm2022/trismegisto-spine-layout.mjs +1850 -217
  43. package/fesm2022/trismegisto-spine-layout.mjs.map +1 -1
  44. package/fesm2022/trismegisto-spine-shell.mjs +28 -8
  45. package/fesm2022/trismegisto-spine-shell.mjs.map +1 -1
  46. package/fesm2022/trismegisto-spine.mjs +1163 -87
  47. package/fesm2022/trismegisto-spine.mjs.map +1 -1
  48. package/package.json +6 -6
  49. package/types/trismegisto-spine-audit.d.ts +180 -5
  50. package/types/trismegisto-spine-audit.d.ts.map +1 -1
  51. package/types/trismegisto-spine-auto-service.d.ts +100 -7
  52. package/types/trismegisto-spine-auto-service.d.ts.map +1 -1
  53. package/types/trismegisto-spine-layout.d.ts +885 -22
  54. package/types/trismegisto-spine-layout.d.ts.map +1 -1
  55. package/types/trismegisto-spine-shell.d.ts +11 -2
  56. package/types/trismegisto-spine-shell.d.ts.map +1 -1
  57. package/types/trismegisto-spine.d.ts +496 -34
  58. package/types/trismegisto-spine.d.ts.map +1 -1
  59. package/fesm2022/trismegisto-spine-auto-service-apps.technical-ClMKB9cK.mjs +0 -48
  60. package/fesm2022/trismegisto-spine-auto-service-apps.technical-ClMKB9cK.mjs.map +0 -1
  61. package/fesm2022/trismegisto-spine-auto-service-apps.usage-Cf5SG-oQ.mjs +0 -55
  62. package/fesm2022/trismegisto-spine-auto-service-apps.usage-Cf5SG-oQ.mjs.map +0 -1
  63. package/fesm2022/trismegisto-spine-auto-service-audit.technical-C901aIIQ.mjs.map +0 -1
  64. package/fesm2022/trismegisto-spine-auto-service-audit.usage-GIJ2RwuH.mjs.map +0 -1
  65. package/fesm2022/trismegisto-spine-auto-service-auto-service-client.component-C7SPavrX.mjs.map +0 -1
  66. package/fesm2022/trismegisto-spine-auto-service-auto-service-delete-6Bk46MOf.mjs +0 -39
  67. package/fesm2022/trismegisto-spine-auto-service-auto-service-delete-6Bk46MOf.mjs.map +0 -1
  68. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-CqIVe2LU.mjs +0 -357
  69. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-CqIVe2LU.mjs.map +0 -1
  70. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-BLN49F4a.mjs +0 -502
  71. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-BLN49F4a.mjs.map +0 -1
  72. package/fesm2022/trismegisto-spine-auto-service-client.technical-BHWhpyWv.mjs.map +0 -1
  73. package/fesm2022/trismegisto-spine-auto-service-client.usage-CM2WB2e2.mjs.map +0 -1
  74. package/fesm2022/trismegisto-spine-auto-service-groups.technical-jQlwaQPn.mjs +0 -39
  75. package/fesm2022/trismegisto-spine-auto-service-groups.technical-jQlwaQPn.mjs.map +0 -1
  76. package/fesm2022/trismegisto-spine-auto-service-groups.usage-Bh7ig2zZ.mjs +0 -54
  77. package/fesm2022/trismegisto-spine-auto-service-groups.usage-Bh7ig2zZ.mjs.map +0 -1
  78. package/fesm2022/trismegisto-spine-auto-service-home.component-vl3BPFnk.mjs.map +0 -1
@@ -9,7 +9,7 @@ import { pluralMessage, formatMessage } from '@trismegisto/primitives';
9
9
  import { spineRoutesOf, SpineQuickAccessService, SPINE_MESSAGES } from '@trismegisto/spine';
10
10
  import { SpineDocsService, SpineSettingsService } from '@trismegisto/spine/layout';
11
11
  import { map } from 'rxjs';
12
- import { AUTO_SERVICE_GROUPS_TEMPLATE_ROUTER_ID, AUTO_SERVICE_APPS_TEMPLATE_ROUTER_ID, AUTO_SERVICE_CLIENT_TEMPLATE_ROUTER_ID, AUTO_SERVICE_AUDIT_TEMPLATE_ROUTER_ID, AUTO_SERVICE_HOME_TEMPLATE_ROUTER_ID } from './trismegisto-spine-auto-service.mjs';
12
+ import { AUTO_SERVICE_GROUPS_TEMPLATE_ROUTER_ID, AUTO_SERVICE_APPS_TEMPLATE_ROUTER_ID, AUTO_SERVICE_CLIENT_TEMPLATE_ROUTER_ID, AUTO_SERVICE_AUDIT_TEMPLATE_ROUTER_ID, AUTO_SERVICE_LOGIN_ATTEMPTS_TEMPLATE_ROUTER_ID, AUTO_SERVICE_HOME_TEMPLATE_ROUTER_ID } from './trismegisto-spine-auto-service.mjs';
13
13
 
14
14
  // Above this many tiles a grid stops being scannable at a glance.
15
15
  const MAX_TILES = 12;
@@ -18,6 +18,7 @@ const ADMIN_TEMPLATES = new Set([
18
18
  AUTO_SERVICE_APPS_TEMPLATE_ROUTER_ID,
19
19
  AUTO_SERVICE_CLIENT_TEMPLATE_ROUTER_ID,
20
20
  AUTO_SERVICE_AUDIT_TEMPLATE_ROUTER_ID,
21
+ AUTO_SERVICE_LOGIN_ATTEMPTS_TEMPLATE_ROUTER_ID,
21
22
  ].map((id) => id.toLowerCase()));
22
23
  const HOME_TEMPLATE = AUTO_SERVICE_HOME_TEMPLATE_ROUTER_ID.toLowerCase();
23
24
  function childrenOf(node) {
@@ -227,12 +228,12 @@ class HomeComponent {
227
228
  this.docs.openIndex();
228
229
  }
229
230
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: HomeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
230
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: HomeComponent, isStandalone: true, selector: "home", ngImport: i0, template: "@if (noAccess()) {\n <hm-state-page kind=\"no-rights\" [heading]=\"t.noAccessTitle\" [description]=\"t.noAccessText\" action=\"\" />\n} @else {\n <div class=\"hub\" [class.hub--columns]=\"sideBySide()\">\n @if (workTiles().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-work'\">\n <h2 class=\"block__title\" [id]=\"uid + '-work'\">{{ descended() ? t.yourScreens : t.modules }}</h2>\n <ng-container *ngTemplateOutlet=\"tileGrid; context: { $implicit: workTiles(), admin: false }\" />\n </section>\n }\n\n @if (adminTiles().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-admin'\">\n <h2 class=\"block__title\" [id]=\"uid + '-admin'\">{{ t.privileged }}</h2>\n <ng-container *ngTemplateOutlet=\"tileGrid; context: { $implicit: adminTiles(), admin: true }\" />\n </section>\n }\n\n @if (recentRows().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-recent'\">\n <h2 class=\"block__title\" [id]=\"uid + '-recent'\">{{ t.recent }}</h2>\n <hm-data-grid\n [caption]=\"t.recentCaption\"\n [columns]=\"recentColumns\"\n [rows]=\"recentRows()\"\n [rowId]=\"rowId\"\n [filterable]=\"false\"\n [paginated]=\"false\"\n >\n <ng-template hmCell=\"displayName\" let-value>\n @let item = row(value);\n <button type=\"button\" class=\"link\" (click)=\"go(item.redirect)\">\n <hm-icon [shape]=\"item.icon || 'grid-layout'\" size=\"sm\" />\n <span>{{ item.displayName }}</span>\n </button>\n </ng-template>\n <ng-template hmCell=\"favorite\" let-value>\n @let item = row(value);\n <hm-icon-button\n emphasis=\"subtle\"\n icon=\"heart\"\n [solid]=\"isFavorite(item.id)\"\n [class.heart--on]=\"isFavorite(item.id)\"\n [label]=\"favoriteLabel(item)\"\n (clicked)=\"toggleFavorite(item)\"\n />\n </ng-template>\n </hm-data-grid>\n </section>\n }\n\n @if (favorites().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-fav'\">\n <h2 class=\"block__title\" [id]=\"uid + '-fav'\">{{ t.favorites }}</h2>\n <ul class=\"chips\">\n @for (fav of favorites(); track fav.id) {\n <li>\n <button type=\"button\" class=\"chip\" [title]=\"whereOf(fav)\" (click)=\"go(fav.redirect)\">\n <hm-icon [shape]=\"fav.icon || 'grid-layout'\" size=\"sm\" />\n <span>{{ fav.displayName }}</span>\n </button>\n </li>\n }\n </ul>\n </section>\n }\n\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-nav'\">\n <h2 class=\"block__title\" [id]=\"uid + '-nav'\">{{ t.navigate }}</h2>\n <ul class=\"chips\">\n @for (area of areas(); track area.routed_id) {\n <li>\n <button type=\"button\" class=\"chip\" (click)=\"go(area.route_redirect)\">\n <hm-icon [shape]=\"area.route_icon || 'home'\" size=\"sm\" />\n <span>{{ area.route_display_name }}</span>\n </button>\n </li>\n }\n <li>\n <button type=\"button\" class=\"chip\" (click)=\"openSettings()\">\n <hm-icon shape=\"cog\" size=\"sm\" />\n <span>{{ t.settings }}</span>\n </button>\n </li>\n </ul>\n </section>\n\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-tools'\">\n <h2 class=\"block__title\" [id]=\"uid + '-tools'\">{{ t.tools }}</h2>\n <ul class=\"chips\">\n <li>\n <button type=\"button\" class=\"chip\" (click)=\"openDocs()\">\n <hm-icon shape=\"doc-list\" size=\"sm\" />\n <span>{{ t.systemDocs }}</span>\n </button>\n </li>\n </ul>\n </section>\n </div>\n}\n\n<ng-template #tileGrid let-tiles let-admin=\"admin\">\n <ul class=\"tiles\" [class.tiles--admin]=\"admin\">\n @for (tile of tiles; track tile.id; let i = $index) {\n <li class=\"tiles__cell\" [style.--tile-order]=\"i\">\n <button type=\"button\" class=\"tile\" (click)=\"go(tile.redirect)\">\n <hm-icon class=\"tile__icon\" [shape]=\"tile.icon ?? 'grid-layout'\" size=\"lg\" />\n <span class=\"tile__text\">\n <span class=\"tile__label\">{{ tile.label }}</span>\n @if (caption(tile); as meta) {\n <span class=\"tile__meta\">{{ meta }}</span>\n }\n </span>\n </button>\n </li>\n }\n </ul>\n</ng-template>\n", styles: [":host{display:flex;flex:1 1 auto;flex-direction:column;min-block-size:100%;min-inline-size:0}hm-state-page{flex:1 1 auto}.hub{display:flex;flex-direction:column;gap:var(--hm-space-xl);padding:var(--hm-space-lg);min-inline-size:0}.hub--columns{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,20rem),1fr));align-items:start}.block{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-inline-size:0}.block__title{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}ul{margin:0;padding:0;list-style:none}.link,.chip,.tile{display:inline-flex;align-items:center;gap:var(--hm-space-xs);border:1px solid transparent;border-radius:var(--hm-radius-control);background:transparent;color:var(--hm-text);font:inherit;text-align:start;cursor:pointer}.link:hover,.chip:hover,.tile:hover{background:var(--hm-hover)}.link:focus-visible,.chip:focus-visible,.tile:focus-visible{outline:var(--hm-border-thin) solid var(--hm-accent);outline-offset:2px}@media(forced-colors:active){.link,.chip,.tile{border-color:ButtonText}.link:focus-visible,.chip:focus-visible,.tile:focus-visible{outline-color:Highlight}}.tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,13rem),1fr));gap:var(--hm-space-sm)}.tiles__cell{display:flex}.tile{flex:1 1 auto;gap:var(--hm-space-md);padding:var(--hm-space-md);border-color:var(--hm-divider);background:var(--hm-surface-raised)}.tile__icon{flex:none;color:var(--hm-accent)}.tiles--admin .tile__icon{color:var(--hm-warning)}.tile__text{display:flex;flex-direction:column;gap:var(--hm-space-nano);min-inline-size:0}.tile__label{font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-medium);overflow-wrap:anywhere}.tile__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}@media(prefers-reduced-motion:no-preference){.tiles__cell{animation:tile-in var(--hm-duration-standard) var(--hm-ease-decelerate) both;animation-delay:calc(min(var(--tile-order, 0),10)*35ms)}}@keyframes tile-in{0%{opacity:0;transform:translateY(var(--hm-space-xs))}}.chips{display:flex;flex-wrap:wrap;gap:var(--hm-space-xs)}.chip{padding:var(--hm-space-xs) var(--hm-space-sm);border-color:var(--hm-divider);background:var(--hm-surface)}.link{padding:var(--hm-space-nano) var(--hm-space-xs);margin-inline-start:calc(-1 * var(--hm-space-xs));color:var(--hm-accent)}.heart--on{color:var(--hm-danger)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: DataGridComponent, selector: "hm-data-grid", inputs: ["columns", "rows", "rowId", "caption", "rowActions", "loading", "error", "emptyMessage", "noResultsMessage", "filterable", "paginated", "pageState", "pagination", "totalItems", "sort", "selectable", "selectionMode", "selection", "rowSelectable", "rowSelectableReason", "rowDetailDisabled", "rowDetailHidden", "virtual", "rowHeight", "pinnedColumns", "columnState", "pageSizes", "fillHeight", "maxHeight", "bordered", "rowState", "rowClickable", "remoteSearch", "remoteSort", "itemsLabel", "pageSizeLabel", "exportable", "exportFileName", "groupBy", "aggregates", "aggregateFormatter", "persistKey", "persistStorage", "advancedFilter", "filterQuery", "columnManager", "columnOrder", "searchTerm"], outputs: ["pageStateChange", "sortChange", "selectionChange", "pinnedColumnsChange", "columnStateChange", "actionTriggered", "detailToggled", "rowClicked", "clearFiltersRequested", "filteredChange", "searchTermChanged", "groupByChange", "filterQueryChange", "columnOrderChange", "searchTermChange", "detailPaneToggled"] }, { kind: "directive", type: DataGridCellDirective, selector: "ng-template[hmCell]", inputs: ["hmCell"] }, { kind: "component", type: IconButtonComponent, selector: "hm-icon-button", inputs: ["label", "icon", "solid", "iconDirection", "emphasis", "intent", "size", "disabled", "toggle", "pressed", "keyShortcuts"], outputs: ["pressedChange", "clicked"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: StatePageComponent, selector: "hm-state-page", inputs: ["kind", "heading", "description", "action", "actionIcon"], outputs: ["actionTriggered"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
231
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: HomeComponent, isStandalone: true, selector: "home", ngImport: i0, template: "@if (noAccess()) {\n <hm-state-page kind=\"no-rights\" [heading]=\"t.noAccessTitle\" [description]=\"t.noAccessText\" action=\"\" />\n} @else {\n <div class=\"hub\" [class.hub--columns]=\"sideBySide()\">\n @if (workTiles().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-work'\">\n <h2 class=\"block__title\" [id]=\"uid + '-work'\">{{ descended() ? t.yourScreens : t.modules }}</h2>\n <ng-container *ngTemplateOutlet=\"tileGrid; context: { $implicit: workTiles(), admin: false }\" />\n </section>\n }\n\n @if (adminTiles().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-admin'\">\n <h2 class=\"block__title\" [id]=\"uid + '-admin'\">{{ t.privileged }}</h2>\n <ng-container *ngTemplateOutlet=\"tileGrid; context: { $implicit: adminTiles(), admin: true }\" />\n </section>\n }\n\n @if (recentRows().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-recent'\">\n <h2 class=\"block__title\" [id]=\"uid + '-recent'\">{{ t.recent }}</h2>\n <hm-data-grid\n [caption]=\"t.recentCaption\"\n [columns]=\"recentColumns\"\n [rows]=\"recentRows()\"\n [rowId]=\"rowId\"\n [filterable]=\"false\"\n [paginated]=\"false\"\n >\n <ng-template hmCell=\"displayName\" let-value>\n @let item = row(value);\n <button type=\"button\" class=\"link\" (click)=\"go(item.redirect)\">\n <hm-icon [shape]=\"item.icon || 'grid-layout'\" size=\"sm\" />\n <span>{{ item.displayName }}</span>\n </button>\n </ng-template>\n <ng-template hmCell=\"favorite\" let-value>\n @let item = row(value);\n <hm-icon-button\n emphasis=\"subtle\"\n icon=\"heart\"\n [solid]=\"isFavorite(item.id)\"\n [class.heart--on]=\"isFavorite(item.id)\"\n [label]=\"favoriteLabel(item)\"\n (clicked)=\"toggleFavorite(item)\"\n />\n </ng-template>\n </hm-data-grid>\n </section>\n }\n\n @if (favorites().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-fav'\">\n <h2 class=\"block__title\" [id]=\"uid + '-fav'\">{{ t.favorites }}</h2>\n <ul class=\"chips\">\n @for (fav of favorites(); track fav.id) {\n <li>\n <button type=\"button\" class=\"chip\" [title]=\"whereOf(fav)\" (click)=\"go(fav.redirect)\">\n <hm-icon [shape]=\"fav.icon || 'grid-layout'\" size=\"sm\" />\n <span>{{ fav.displayName }}</span>\n </button>\n </li>\n }\n </ul>\n </section>\n }\n\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-nav'\">\n <h2 class=\"block__title\" [id]=\"uid + '-nav'\">{{ t.navigate }}</h2>\n <ul class=\"chips\">\n @for (area of areas(); track area.routed_id) {\n <li>\n <button type=\"button\" class=\"chip\" (click)=\"go(area.route_redirect)\">\n <hm-icon [shape]=\"area.route_icon || 'home'\" size=\"sm\" />\n <span>{{ area.route_display_name }}</span>\n </button>\n </li>\n }\n <li>\n <button type=\"button\" class=\"chip\" (click)=\"openSettings()\">\n <hm-icon shape=\"cog\" size=\"sm\" />\n <span>{{ t.settings }}</span>\n </button>\n </li>\n </ul>\n </section>\n\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-tools'\">\n <h2 class=\"block__title\" [id]=\"uid + '-tools'\">{{ t.tools }}</h2>\n <ul class=\"chips\">\n <li>\n <button type=\"button\" class=\"chip\" (click)=\"openDocs()\">\n <hm-icon shape=\"doc-list\" size=\"sm\" />\n <span>{{ t.systemDocs }}</span>\n </button>\n </li>\n </ul>\n </section>\n </div>\n}\n\n<ng-template #tileGrid let-tiles let-admin=\"admin\">\n <ul class=\"tiles\" [class.tiles--admin]=\"admin\">\n @for (tile of tiles; track tile.id; let i = $index) {\n <li class=\"tiles__cell\" [style.--tile-order]=\"i\">\n <button type=\"button\" class=\"tile\" (click)=\"go(tile.redirect)\">\n <hm-icon class=\"tile__icon\" [shape]=\"tile.icon ?? 'grid-layout'\" size=\"lg\" />\n <span class=\"tile__text\">\n <span class=\"tile__label\">{{ tile.label }}</span>\n @if (caption(tile); as meta) {\n <span class=\"tile__meta\">{{ meta }}</span>\n }\n </span>\n </button>\n </li>\n }\n </ul>\n</ng-template>\n", styles: [":host{display:flex;flex:1 1 auto;flex-direction:column;min-block-size:100%;min-inline-size:0}hm-state-page{flex:1 1 auto}.hub{display:flex;flex-direction:column;gap:var(--hm-space-xl);padding:var(--hm-space-lg);min-inline-size:0}.hub--columns{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,20rem),1fr));align-items:start}.block{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-inline-size:0}.block__title{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}ul{margin:0;padding:0;list-style:none}.link,.chip,.tile{display:inline-flex;align-items:center;gap:var(--hm-space-xs);border:1px solid transparent;border-radius:var(--hm-radius-control);background:transparent;color:var(--hm-text);font:inherit;text-align:start;cursor:pointer}.link:hover,.chip:hover,.tile:hover{background:var(--hm-hover)}.link:focus-visible,.chip:focus-visible,.tile:focus-visible{outline:var(--hm-border-thin) solid var(--hm-accent);outline-offset:2px}@media(forced-colors:active){.link,.chip,.tile{border-color:ButtonText}.link:focus-visible,.chip:focus-visible,.tile:focus-visible{outline-color:Highlight}}.tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,13rem),1fr));gap:var(--hm-space-sm)}.tiles__cell{display:flex}.tile{flex:1 1 auto;gap:var(--hm-space-md);padding:var(--hm-space-md);border-color:var(--hm-divider);background:var(--hm-surface-raised)}.tile__icon{flex:none;color:var(--hm-accent)}.tiles--admin .tile__icon{color:var(--hm-warning)}.tile__text{display:flex;flex-direction:column;gap:var(--hm-space-nano);min-inline-size:0}.tile__label{font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-medium);overflow-wrap:anywhere}.tile__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}@media(prefers-reduced-motion:no-preference){.tiles__cell{animation:tile-in var(--hm-duration-standard) var(--hm-ease-decelerate) both;animation-delay:calc(min(var(--tile-order, 0),10)*35ms)}}:host-context([data-motion=full]) .tiles__cell{animation:tile-in var(--hm-duration-standard) var(--hm-ease-decelerate) both;animation-delay:calc(min(var(--tile-order, 0),10)*35ms)}:host-context([data-motion=reduce]) .tiles__cell{animation:none}@keyframes tile-in{0%{opacity:0;transform:translateY(var(--hm-space-xs))}}.chips{display:flex;flex-wrap:wrap;gap:var(--hm-space-xs)}.chip{padding:var(--hm-space-xs) var(--hm-space-sm);border-color:var(--hm-divider);background:var(--hm-surface)}.link{padding:var(--hm-space-nano) var(--hm-space-xs);margin-inline-start:calc(-1 * var(--hm-space-xs));color:var(--hm-accent)}.heart--on{color:var(--hm-danger)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: DataGridComponent, selector: "hm-data-grid", inputs: ["columns", "rows", "rowId", "caption", "rowActions", "loading", "error", "emptyMessage", "noResultsMessage", "filterable", "paginated", "pageState", "pagination", "totalItems", "sort", "selectable", "selectionMode", "selection", "rowSelectable", "rowSelectableReason", "rowDetailDisabled", "rowDetailHidden", "virtual", "rowHeight", "pinnedColumns", "columnState", "pageSizes", "fillHeight", "maxHeight", "bordered", "rowState", "rowClickable", "remoteSearch", "remoteSort", "itemsLabel", "pageSizeLabel", "exportable", "exportFileName", "groupBy", "aggregates", "aggregateFormatter", "persistKey", "persistStorage", "advancedFilter", "filterQuery", "columnManager", "columnOrder", "searchTerm"], outputs: ["pageStateChange", "sortChange", "selectionChange", "pinnedColumnsChange", "columnStateChange", "actionTriggered", "detailToggled", "rowClicked", "clearFiltersRequested", "filteredChange", "searchTermChanged", "groupByChange", "filterQueryChange", "columnOrderChange", "searchTermChange", "detailPaneToggled"] }, { kind: "directive", type: DataGridCellDirective, selector: "ng-template[hmCell]", inputs: ["hmCell"] }, { kind: "component", type: IconButtonComponent, selector: "hm-icon-button", inputs: ["label", "icon", "solid", "iconDirection", "emphasis", "intent", "size", "disabled", "toggle", "pressed", "keyShortcuts"], outputs: ["pressedChange", "clicked"] }, { kind: "component", type: IconComponent, selector: "hm-icon", inputs: ["shape", "direction", "size", "solid", "badge", "status", "flip", "inverse", "label"] }, { kind: "component", type: StatePageComponent, selector: "hm-state-page", inputs: ["kind", "heading", "description", "action", "actionIcon"], outputs: ["actionTriggered"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
231
232
  }
232
233
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: HomeComponent, decorators: [{
233
234
  type: Component,
234
- args: [{ selector: 'home', imports: [NgTemplateOutlet, DataGridComponent, DataGridCellDirective, IconButtonComponent, IconComponent, StatePageComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (noAccess()) {\n <hm-state-page kind=\"no-rights\" [heading]=\"t.noAccessTitle\" [description]=\"t.noAccessText\" action=\"\" />\n} @else {\n <div class=\"hub\" [class.hub--columns]=\"sideBySide()\">\n @if (workTiles().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-work'\">\n <h2 class=\"block__title\" [id]=\"uid + '-work'\">{{ descended() ? t.yourScreens : t.modules }}</h2>\n <ng-container *ngTemplateOutlet=\"tileGrid; context: { $implicit: workTiles(), admin: false }\" />\n </section>\n }\n\n @if (adminTiles().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-admin'\">\n <h2 class=\"block__title\" [id]=\"uid + '-admin'\">{{ t.privileged }}</h2>\n <ng-container *ngTemplateOutlet=\"tileGrid; context: { $implicit: adminTiles(), admin: true }\" />\n </section>\n }\n\n @if (recentRows().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-recent'\">\n <h2 class=\"block__title\" [id]=\"uid + '-recent'\">{{ t.recent }}</h2>\n <hm-data-grid\n [caption]=\"t.recentCaption\"\n [columns]=\"recentColumns\"\n [rows]=\"recentRows()\"\n [rowId]=\"rowId\"\n [filterable]=\"false\"\n [paginated]=\"false\"\n >\n <ng-template hmCell=\"displayName\" let-value>\n @let item = row(value);\n <button type=\"button\" class=\"link\" (click)=\"go(item.redirect)\">\n <hm-icon [shape]=\"item.icon || 'grid-layout'\" size=\"sm\" />\n <span>{{ item.displayName }}</span>\n </button>\n </ng-template>\n <ng-template hmCell=\"favorite\" let-value>\n @let item = row(value);\n <hm-icon-button\n emphasis=\"subtle\"\n icon=\"heart\"\n [solid]=\"isFavorite(item.id)\"\n [class.heart--on]=\"isFavorite(item.id)\"\n [label]=\"favoriteLabel(item)\"\n (clicked)=\"toggleFavorite(item)\"\n />\n </ng-template>\n </hm-data-grid>\n </section>\n }\n\n @if (favorites().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-fav'\">\n <h2 class=\"block__title\" [id]=\"uid + '-fav'\">{{ t.favorites }}</h2>\n <ul class=\"chips\">\n @for (fav of favorites(); track fav.id) {\n <li>\n <button type=\"button\" class=\"chip\" [title]=\"whereOf(fav)\" (click)=\"go(fav.redirect)\">\n <hm-icon [shape]=\"fav.icon || 'grid-layout'\" size=\"sm\" />\n <span>{{ fav.displayName }}</span>\n </button>\n </li>\n }\n </ul>\n </section>\n }\n\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-nav'\">\n <h2 class=\"block__title\" [id]=\"uid + '-nav'\">{{ t.navigate }}</h2>\n <ul class=\"chips\">\n @for (area of areas(); track area.routed_id) {\n <li>\n <button type=\"button\" class=\"chip\" (click)=\"go(area.route_redirect)\">\n <hm-icon [shape]=\"area.route_icon || 'home'\" size=\"sm\" />\n <span>{{ area.route_display_name }}</span>\n </button>\n </li>\n }\n <li>\n <button type=\"button\" class=\"chip\" (click)=\"openSettings()\">\n <hm-icon shape=\"cog\" size=\"sm\" />\n <span>{{ t.settings }}</span>\n </button>\n </li>\n </ul>\n </section>\n\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-tools'\">\n <h2 class=\"block__title\" [id]=\"uid + '-tools'\">{{ t.tools }}</h2>\n <ul class=\"chips\">\n <li>\n <button type=\"button\" class=\"chip\" (click)=\"openDocs()\">\n <hm-icon shape=\"doc-list\" size=\"sm\" />\n <span>{{ t.systemDocs }}</span>\n </button>\n </li>\n </ul>\n </section>\n </div>\n}\n\n<ng-template #tileGrid let-tiles let-admin=\"admin\">\n <ul class=\"tiles\" [class.tiles--admin]=\"admin\">\n @for (tile of tiles; track tile.id; let i = $index) {\n <li class=\"tiles__cell\" [style.--tile-order]=\"i\">\n <button type=\"button\" class=\"tile\" (click)=\"go(tile.redirect)\">\n <hm-icon class=\"tile__icon\" [shape]=\"tile.icon ?? 'grid-layout'\" size=\"lg\" />\n <span class=\"tile__text\">\n <span class=\"tile__label\">{{ tile.label }}</span>\n @if (caption(tile); as meta) {\n <span class=\"tile__meta\">{{ meta }}</span>\n }\n </span>\n </button>\n </li>\n }\n </ul>\n</ng-template>\n", styles: [":host{display:flex;flex:1 1 auto;flex-direction:column;min-block-size:100%;min-inline-size:0}hm-state-page{flex:1 1 auto}.hub{display:flex;flex-direction:column;gap:var(--hm-space-xl);padding:var(--hm-space-lg);min-inline-size:0}.hub--columns{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,20rem),1fr));align-items:start}.block{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-inline-size:0}.block__title{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}ul{margin:0;padding:0;list-style:none}.link,.chip,.tile{display:inline-flex;align-items:center;gap:var(--hm-space-xs);border:1px solid transparent;border-radius:var(--hm-radius-control);background:transparent;color:var(--hm-text);font:inherit;text-align:start;cursor:pointer}.link:hover,.chip:hover,.tile:hover{background:var(--hm-hover)}.link:focus-visible,.chip:focus-visible,.tile:focus-visible{outline:var(--hm-border-thin) solid var(--hm-accent);outline-offset:2px}@media(forced-colors:active){.link,.chip,.tile{border-color:ButtonText}.link:focus-visible,.chip:focus-visible,.tile:focus-visible{outline-color:Highlight}}.tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,13rem),1fr));gap:var(--hm-space-sm)}.tiles__cell{display:flex}.tile{flex:1 1 auto;gap:var(--hm-space-md);padding:var(--hm-space-md);border-color:var(--hm-divider);background:var(--hm-surface-raised)}.tile__icon{flex:none;color:var(--hm-accent)}.tiles--admin .tile__icon{color:var(--hm-warning)}.tile__text{display:flex;flex-direction:column;gap:var(--hm-space-nano);min-inline-size:0}.tile__label{font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-medium);overflow-wrap:anywhere}.tile__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}@media(prefers-reduced-motion:no-preference){.tiles__cell{animation:tile-in var(--hm-duration-standard) var(--hm-ease-decelerate) both;animation-delay:calc(min(var(--tile-order, 0),10)*35ms)}}@keyframes tile-in{0%{opacity:0;transform:translateY(var(--hm-space-xs))}}.chips{display:flex;flex-wrap:wrap;gap:var(--hm-space-xs)}.chip{padding:var(--hm-space-xs) var(--hm-space-sm);border-color:var(--hm-divider);background:var(--hm-surface)}.link{padding:var(--hm-space-nano) var(--hm-space-xs);margin-inline-start:calc(-1 * var(--hm-space-xs));color:var(--hm-accent)}.heart--on{color:var(--hm-danger)}\n"] }]
235
+ args: [{ selector: 'home', imports: [NgTemplateOutlet, DataGridComponent, DataGridCellDirective, IconButtonComponent, IconComponent, StatePageComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (noAccess()) {\n <hm-state-page kind=\"no-rights\" [heading]=\"t.noAccessTitle\" [description]=\"t.noAccessText\" action=\"\" />\n} @else {\n <div class=\"hub\" [class.hub--columns]=\"sideBySide()\">\n @if (workTiles().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-work'\">\n <h2 class=\"block__title\" [id]=\"uid + '-work'\">{{ descended() ? t.yourScreens : t.modules }}</h2>\n <ng-container *ngTemplateOutlet=\"tileGrid; context: { $implicit: workTiles(), admin: false }\" />\n </section>\n }\n\n @if (adminTiles().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-admin'\">\n <h2 class=\"block__title\" [id]=\"uid + '-admin'\">{{ t.privileged }}</h2>\n <ng-container *ngTemplateOutlet=\"tileGrid; context: { $implicit: adminTiles(), admin: true }\" />\n </section>\n }\n\n @if (recentRows().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-recent'\">\n <h2 class=\"block__title\" [id]=\"uid + '-recent'\">{{ t.recent }}</h2>\n <hm-data-grid\n [caption]=\"t.recentCaption\"\n [columns]=\"recentColumns\"\n [rows]=\"recentRows()\"\n [rowId]=\"rowId\"\n [filterable]=\"false\"\n [paginated]=\"false\"\n >\n <ng-template hmCell=\"displayName\" let-value>\n @let item = row(value);\n <button type=\"button\" class=\"link\" (click)=\"go(item.redirect)\">\n <hm-icon [shape]=\"item.icon || 'grid-layout'\" size=\"sm\" />\n <span>{{ item.displayName }}</span>\n </button>\n </ng-template>\n <ng-template hmCell=\"favorite\" let-value>\n @let item = row(value);\n <hm-icon-button\n emphasis=\"subtle\"\n icon=\"heart\"\n [solid]=\"isFavorite(item.id)\"\n [class.heart--on]=\"isFavorite(item.id)\"\n [label]=\"favoriteLabel(item)\"\n (clicked)=\"toggleFavorite(item)\"\n />\n </ng-template>\n </hm-data-grid>\n </section>\n }\n\n @if (favorites().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-fav'\">\n <h2 class=\"block__title\" [id]=\"uid + '-fav'\">{{ t.favorites }}</h2>\n <ul class=\"chips\">\n @for (fav of favorites(); track fav.id) {\n <li>\n <button type=\"button\" class=\"chip\" [title]=\"whereOf(fav)\" (click)=\"go(fav.redirect)\">\n <hm-icon [shape]=\"fav.icon || 'grid-layout'\" size=\"sm\" />\n <span>{{ fav.displayName }}</span>\n </button>\n </li>\n }\n </ul>\n </section>\n }\n\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-nav'\">\n <h2 class=\"block__title\" [id]=\"uid + '-nav'\">{{ t.navigate }}</h2>\n <ul class=\"chips\">\n @for (area of areas(); track area.routed_id) {\n <li>\n <button type=\"button\" class=\"chip\" (click)=\"go(area.route_redirect)\">\n <hm-icon [shape]=\"area.route_icon || 'home'\" size=\"sm\" />\n <span>{{ area.route_display_name }}</span>\n </button>\n </li>\n }\n <li>\n <button type=\"button\" class=\"chip\" (click)=\"openSettings()\">\n <hm-icon shape=\"cog\" size=\"sm\" />\n <span>{{ t.settings }}</span>\n </button>\n </li>\n </ul>\n </section>\n\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-tools'\">\n <h2 class=\"block__title\" [id]=\"uid + '-tools'\">{{ t.tools }}</h2>\n <ul class=\"chips\">\n <li>\n <button type=\"button\" class=\"chip\" (click)=\"openDocs()\">\n <hm-icon shape=\"doc-list\" size=\"sm\" />\n <span>{{ t.systemDocs }}</span>\n </button>\n </li>\n </ul>\n </section>\n </div>\n}\n\n<ng-template #tileGrid let-tiles let-admin=\"admin\">\n <ul class=\"tiles\" [class.tiles--admin]=\"admin\">\n @for (tile of tiles; track tile.id; let i = $index) {\n <li class=\"tiles__cell\" [style.--tile-order]=\"i\">\n <button type=\"button\" class=\"tile\" (click)=\"go(tile.redirect)\">\n <hm-icon class=\"tile__icon\" [shape]=\"tile.icon ?? 'grid-layout'\" size=\"lg\" />\n <span class=\"tile__text\">\n <span class=\"tile__label\">{{ tile.label }}</span>\n @if (caption(tile); as meta) {\n <span class=\"tile__meta\">{{ meta }}</span>\n }\n </span>\n </button>\n </li>\n }\n </ul>\n</ng-template>\n", styles: [":host{display:flex;flex:1 1 auto;flex-direction:column;min-block-size:100%;min-inline-size:0}hm-state-page{flex:1 1 auto}.hub{display:flex;flex-direction:column;gap:var(--hm-space-xl);padding:var(--hm-space-lg);min-inline-size:0}.hub--columns{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,20rem),1fr));align-items:start}.block{display:flex;flex-direction:column;gap:var(--hm-space-sm);min-inline-size:0}.block__title{margin:0;color:var(--hm-muted);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold)}ul{margin:0;padding:0;list-style:none}.link,.chip,.tile{display:inline-flex;align-items:center;gap:var(--hm-space-xs);border:1px solid transparent;border-radius:var(--hm-radius-control);background:transparent;color:var(--hm-text);font:inherit;text-align:start;cursor:pointer}.link:hover,.chip:hover,.tile:hover{background:var(--hm-hover)}.link:focus-visible,.chip:focus-visible,.tile:focus-visible{outline:var(--hm-border-thin) solid var(--hm-accent);outline-offset:2px}@media(forced-colors:active){.link,.chip,.tile{border-color:ButtonText}.link:focus-visible,.chip:focus-visible,.tile:focus-visible{outline-color:Highlight}}.tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,13rem),1fr));gap:var(--hm-space-sm)}.tiles__cell{display:flex}.tile{flex:1 1 auto;gap:var(--hm-space-md);padding:var(--hm-space-md);border-color:var(--hm-divider);background:var(--hm-surface-raised)}.tile__icon{flex:none;color:var(--hm-accent)}.tiles--admin .tile__icon{color:var(--hm-warning)}.tile__text{display:flex;flex-direction:column;gap:var(--hm-space-nano);min-inline-size:0}.tile__label{font-size:var(--hm-font-size-md);font-weight:var(--hm-font-weight-medium);overflow-wrap:anywhere}.tile__meta{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}@media(prefers-reduced-motion:no-preference){.tiles__cell{animation:tile-in var(--hm-duration-standard) var(--hm-ease-decelerate) both;animation-delay:calc(min(var(--tile-order, 0),10)*35ms)}}:host-context([data-motion=full]) .tiles__cell{animation:tile-in var(--hm-duration-standard) var(--hm-ease-decelerate) both;animation-delay:calc(min(var(--tile-order, 0),10)*35ms)}:host-context([data-motion=reduce]) .tiles__cell{animation:none}@keyframes tile-in{0%{opacity:0;transform:translateY(var(--hm-space-xs))}}.chips{display:flex;flex-wrap:wrap;gap:var(--hm-space-xs)}.chip{padding:var(--hm-space-xs) var(--hm-space-sm);border-color:var(--hm-divider);background:var(--hm-surface)}.link{padding:var(--hm-space-nano) var(--hm-space-xs);margin-inline-start:calc(-1 * var(--hm-space-xs));color:var(--hm-accent)}.heart--on{color:var(--hm-danger)}\n"] }]
235
236
  }], ctorParameters: () => [] });
236
237
 
237
238
  export { HomeAutoRedirect, HomeComponent };
238
- //# sourceMappingURL=trismegisto-spine-auto-service-home.component-vl3BPFnk.mjs.map
239
+ //# sourceMappingURL=trismegisto-spine-auto-service-home.component-5_aji1hf.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trismegisto-spine-auto-service-home.component-5_aji1hf.mjs","sources":["../../../projects/ui/auto-service/src/home/home-shortcuts.ts","../../../projects/ui/auto-service/src/home/home.component.ts","../../../projects/ui/auto-service/src/home/home.component.html"],"sourcesContent":["import { Route } from '@trismegisto/spine';\nimport {\n AUTO_SERVICE_APPS_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_AUDIT_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_CLIENT_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_GROUPS_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_HOME_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_LOGIN_ATTEMPTS_TEMPLATE_ROUTER_ID,\n} from '../auto-service.tokens';\n\n/** A page the person can open, with the labels of the menus above it. */\nexport interface HomeDestination {\n readonly id: string;\n readonly label: string;\n readonly icon: string | null;\n readonly redirect: string;\n readonly trail: readonly string[];\n}\n\n/** A large entry tile on the home screen. */\nexport interface HomeShortcut extends HomeDestination {\n readonly screens: number;\n readonly privileged: boolean;\n}\n\n// Above this many tiles a grid stops being scannable at a glance.\nconst MAX_TILES = 12;\n\nconst ADMIN_TEMPLATES = new Set(\n [\n AUTO_SERVICE_GROUPS_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_APPS_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_CLIENT_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_AUDIT_TEMPLATE_ROUTER_ID,\n AUTO_SERVICE_LOGIN_ATTEMPTS_TEMPLATE_ROUTER_ID,\n ].map((id) => id.toLowerCase()),\n);\nconst HOME_TEMPLATE = AUTO_SERVICE_HOME_TEMPLATE_ROUTER_ID.toLowerCase();\n\nfunction childrenOf(node: Route): Route[] {\n return (node.routes ?? []).filter((child): child is Route => child != null);\n}\n\nfunction labelOf(node: Route): string {\n return (node.route_display_name ?? node.route_name ?? '').trim();\n}\n\nfunction identityOf(node: Route): string {\n return node.route_redirect || `${node.route_type ?? 'node'}:${node.routed_id}`;\n}\n\nfunction templateOf(node: Route): string {\n return (node.source_template_router_id ?? '').toLowerCase();\n}\n\nexport function isPage(node: Route): boolean {\n return !!node.route_redirect && (node.route_type === 'page' || childrenOf(node).length === 0);\n}\n\nexport function isHome(node: Route): boolean {\n return templateOf(node) === HOME_TEMPLATE || (isPage(node) && node.route_path === 'home');\n}\n\nexport function modules(tree: readonly Route[]): readonly Route[] {\n return tree.filter((node) => node != null && !isHome(node));\n}\n\nexport function systemAreas(tree: readonly Route[]): readonly Route[] {\n return tree.filter((node) => node != null && isHome(node));\n}\n\nexport function destinations(tree: readonly Route[], trail: readonly string[] = []): readonly HomeDestination[] {\n const found: HomeDestination[] = [];\n const walk = (nodes: readonly Route[], path: readonly string[]) => {\n for (const node of nodes) {\n if (node == null) continue;\n if (isPage(node)) {\n found.push({ id: identityOf(node), label: labelOf(node), icon: node.route_icon ?? null, redirect: node.route_redirect!, trail: path });\n }\n const kids = childrenOf(node);\n if (kids.length) walk(kids, [...path, labelOf(node)]);\n }\n };\n walk(tree, trail);\n return found;\n}\n\nexport function trailFor(tree: readonly Route[], redirect: string): readonly string[] | null {\n return destinations(tree).find((d) => d.redirect === redirect)?.trail ?? null;\n}\n\nfunction isPrivileged(node: Route): boolean {\n return ADMIN_TEMPLATES.has(templateOf(node)) || childrenOf(node).some(isPrivileged);\n}\n\ninterface Candidate {\n readonly node: Route;\n readonly trail: readonly string[];\n readonly privileged: boolean;\n}\n\nfunction navigableChildren(node: Route): Route[] {\n return childrenOf(node).filter((child) => isPage(child) || destinations([child]).length > 0);\n}\n\n/** Replaces each candidate by its navigable children; `null` when nobody has any. */\nfunction descend(level: readonly Candidate[]): Candidate[] | null {\n let changed = false;\n const next = level.flatMap((item) => {\n const kids = navigableChildren(item.node);\n if (!kids.length) return [item];\n changed = true;\n const trail = [...item.trail, labelOf(item.node)];\n return kids.map((node) => ({ node, trail, privileged: item.privileged }));\n });\n return changed ? next : null;\n}\n\nfunction toShortcut({ node, trail, privileged }: Candidate): HomeShortcut {\n const below = destinations([node]);\n return {\n id: identityOf(node),\n label: labelOf(node),\n icon: node.route_icon ?? null,\n redirect: node.route_redirect || below[0]?.redirect || '',\n trail,\n screens: below.length - (isPage(node) ? 1 : 0),\n privileged,\n };\n}\n\n/**\n * Picks the shallowest level of the tree where there is a real choice to make: a single item is\n * skipped over, a pair is opened up while the result still fits the grid, three or more stay put.\n */\nexport function entryShortcuts(tree: readonly Route[]): readonly HomeShortcut[] {\n let level: readonly Candidate[] = modules(tree).map((node) => ({ node, trail: [], privileged: isPrivileged(node) }));\n while (level.length > 0 && level.length < 3) {\n const next = descend(level);\n if (!next) break;\n if (level.length === 2 && next.length > MAX_TILES) break;\n level = next;\n }\n return level.map(toShortcut);\n}\n","import { ChangeDetectionStrategy, Component, Injectable, computed, effect, inject } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { Router } from '@angular/router';\nimport { ViewService } from '@orfeu/angular/core';\nimport {\n DataGridCellDirective,\n DataGridColumn,\n DataGridComponent,\n IconButtonComponent,\n IconComponent,\n StatePageComponent,\n} from '@trismegisto/angular';\nimport { formatMessage, pluralMessage } from '@trismegisto/primitives';\nimport { QuickAccessItem, Route, SPINE_MESSAGES, SpineQuickAccessService, spineRoutesOf } from '@trismegisto/spine';\nimport { SpineDocsService, SpineSettingsService } from '@trismegisto/spine/layout';\nimport { map } from 'rxjs';\nimport { HomeShortcut, destinations, entryShortcuts, modules, systemAreas, trailFor } from './home-shortcuts';\n\n/** Remembers, for the whole app load, that the home screen already jumped to the only screen. */\n@Injectable({ providedIn: 'root' })\nexport class HomeAutoRedirect {\n done = false;\n}\n\ninterface RecentRow extends QuickAccessItem {\n readonly where: string;\n}\n\nconst TRAIL_SEPARATOR = ' › ';\nconst RECENT_ROWS = 4;\n\nlet instances = 0;\n\nfunction cleanTree(view: Parameters<typeof spineRoutesOf>[0]): Route[] {\n return spineRoutesOf(view).filter((node): node is Route => node != null);\n}\n\n@Component({\n selector: 'home',\n imports: [NgTemplateOutlet, DataGridComponent, DataGridCellDirective, IconButtonComponent, IconComponent, StatePageComponent],\n templateUrl: './home.component.html',\n styleUrl: './home.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class HomeComponent {\n private readonly router = inject(Router);\n private readonly quickAccess = inject(SpineQuickAccessService);\n private readonly docs = inject(SpineDocsService);\n private readonly settings = inject(SpineSettingsService);\n private readonly autoRedirect = inject(HomeAutoRedirect);\n private readonly messages = inject(SPINE_MESSAGES);\n protected readonly t = this.messages.home;\n protected readonly uid = `home-${++instances}`;\n\n private readonly view = inject(ViewService);\n private readonly tree = toSignal(this.view.currentView$.pipe(map(cleanTree)), {\n initialValue: cleanTree(this.view.currentView),\n });\n\n private readonly workDestinations = computed(() => destinations(modules(this.tree())));\n protected readonly noAccess = computed(() => this.tree().length > 0 && modules(this.tree()).length === 0);\n protected readonly areas = computed(() => systemAreas(this.tree()));\n\n private readonly shortcuts = computed(() => entryShortcuts(this.tree()));\n protected readonly workTiles = computed(() => this.shortcuts().filter((s) => !s.privileged));\n protected readonly adminTiles = computed(() => this.shortcuts().filter((s) => s.privileged));\n protected readonly descended = computed(() => this.shortcuts().some((s) => s.trail.length > 0));\n protected readonly sideBySide = computed(() => this.shortcuts().length <= 2);\n\n /** Every tile already opens a final screen, so recents/favorites would not save a single click. */\n private readonly everythingInView = computed(() => {\n const list = this.shortcuts();\n return list.length > 0 && list.every((s) => s.screens === 0);\n });\n\n protected readonly favorites = computed(() => (this.everythingInView() ? [] : this.quickAccess.favorites()));\n protected readonly recentRows = computed<readonly RecentRow[]>(() => {\n if (this.everythingInView()) return [];\n return this.quickAccess\n .recents()\n .slice(0, RECENT_ROWS)\n .map((item) => ({ ...item, where: this.whereOf(item) }));\n });\n\n protected readonly recentColumns: readonly DataGridColumn<RecentRow>[] = [\n { key: 'displayName', title: this.t.colScreen, value: (row) => row.displayName },\n { key: 'where', title: this.t.colWhere, value: (row) => row.where },\n {\n key: 'favorite',\n title: this.t.colFavorite,\n value: () => '',\n filterable: false,\n sortable: false,\n searchable: false,\n initialWidth: '5.5rem',\n },\n ];\n protected readonly rowId = (row: RecentRow) => row.id;\n\n constructor() {\n effect(() => {\n const only = this.workDestinations();\n if (only.length !== 1 || this.autoRedirect.done) return;\n this.autoRedirect.done = true;\n void this.router.navigateByUrl(only[0].redirect, { replaceUrl: true });\n });\n }\n\n protected caption(tile: HomeShortcut): string {\n if (tile.screens > 0) return pluralMessage(this.t.screenCount, tile.screens, this.messages.meta.locale);\n return tile.trail.join(TRAIL_SEPARATOR);\n }\n\n protected whereOf(item: QuickAccessItem): string {\n const trail = item.trail ?? trailFor(this.tree(), item.redirect);\n return trail?.join(TRAIL_SEPARATOR) || this.t.noModule;\n }\n\n protected row(value: unknown): RecentRow {\n return value as RecentRow;\n }\n\n protected isFavorite(id: string): boolean {\n return this.quickAccess.isFavorite(id);\n }\n\n protected favoriteLabel(row: RecentRow): string {\n const template = this.isFavorite(row.id) ? this.t.unfavorite : this.t.favorite;\n return formatMessage(template, { name: row.displayName });\n }\n\n protected toggleFavorite(row: RecentRow): void {\n this.quickAccess.toggleFavorite({\n id: row.id,\n icon: row.icon,\n displayName: row.displayName,\n redirect: row.redirect,\n trail: row.trail ?? trailFor(this.tree(), row.redirect) ?? [],\n });\n }\n\n protected go(redirect: string | null | undefined): void {\n if (redirect) void this.router.navigateByUrl(redirect);\n }\n\n protected openSettings(): void {\n this.settings.open();\n }\n\n protected openDocs(): void {\n this.docs.openIndex();\n }\n}\n","@if (noAccess()) {\n <hm-state-page kind=\"no-rights\" [heading]=\"t.noAccessTitle\" [description]=\"t.noAccessText\" action=\"\" />\n} @else {\n <div class=\"hub\" [class.hub--columns]=\"sideBySide()\">\n @if (workTiles().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-work'\">\n <h2 class=\"block__title\" [id]=\"uid + '-work'\">{{ descended() ? t.yourScreens : t.modules }}</h2>\n <ng-container *ngTemplateOutlet=\"tileGrid; context: { $implicit: workTiles(), admin: false }\" />\n </section>\n }\n\n @if (adminTiles().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-admin'\">\n <h2 class=\"block__title\" [id]=\"uid + '-admin'\">{{ t.privileged }}</h2>\n <ng-container *ngTemplateOutlet=\"tileGrid; context: { $implicit: adminTiles(), admin: true }\" />\n </section>\n }\n\n @if (recentRows().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-recent'\">\n <h2 class=\"block__title\" [id]=\"uid + '-recent'\">{{ t.recent }}</h2>\n <hm-data-grid\n [caption]=\"t.recentCaption\"\n [columns]=\"recentColumns\"\n [rows]=\"recentRows()\"\n [rowId]=\"rowId\"\n [filterable]=\"false\"\n [paginated]=\"false\"\n >\n <ng-template hmCell=\"displayName\" let-value>\n @let item = row(value);\n <button type=\"button\" class=\"link\" (click)=\"go(item.redirect)\">\n <hm-icon [shape]=\"item.icon || 'grid-layout'\" size=\"sm\" />\n <span>{{ item.displayName }}</span>\n </button>\n </ng-template>\n <ng-template hmCell=\"favorite\" let-value>\n @let item = row(value);\n <hm-icon-button\n emphasis=\"subtle\"\n icon=\"heart\"\n [solid]=\"isFavorite(item.id)\"\n [class.heart--on]=\"isFavorite(item.id)\"\n [label]=\"favoriteLabel(item)\"\n (clicked)=\"toggleFavorite(item)\"\n />\n </ng-template>\n </hm-data-grid>\n </section>\n }\n\n @if (favorites().length) {\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-fav'\">\n <h2 class=\"block__title\" [id]=\"uid + '-fav'\">{{ t.favorites }}</h2>\n <ul class=\"chips\">\n @for (fav of favorites(); track fav.id) {\n <li>\n <button type=\"button\" class=\"chip\" [title]=\"whereOf(fav)\" (click)=\"go(fav.redirect)\">\n <hm-icon [shape]=\"fav.icon || 'grid-layout'\" size=\"sm\" />\n <span>{{ fav.displayName }}</span>\n </button>\n </li>\n }\n </ul>\n </section>\n }\n\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-nav'\">\n <h2 class=\"block__title\" [id]=\"uid + '-nav'\">{{ t.navigate }}</h2>\n <ul class=\"chips\">\n @for (area of areas(); track area.routed_id) {\n <li>\n <button type=\"button\" class=\"chip\" (click)=\"go(area.route_redirect)\">\n <hm-icon [shape]=\"area.route_icon || 'home'\" size=\"sm\" />\n <span>{{ area.route_display_name }}</span>\n </button>\n </li>\n }\n <li>\n <button type=\"button\" class=\"chip\" (click)=\"openSettings()\">\n <hm-icon shape=\"cog\" size=\"sm\" />\n <span>{{ t.settings }}</span>\n </button>\n </li>\n </ul>\n </section>\n\n <section class=\"block\" [attr.aria-labelledby]=\"uid + '-tools'\">\n <h2 class=\"block__title\" [id]=\"uid + '-tools'\">{{ t.tools }}</h2>\n <ul class=\"chips\">\n <li>\n <button type=\"button\" class=\"chip\" (click)=\"openDocs()\">\n <hm-icon shape=\"doc-list\" size=\"sm\" />\n <span>{{ t.systemDocs }}</span>\n </button>\n </li>\n </ul>\n </section>\n </div>\n}\n\n<ng-template #tileGrid let-tiles let-admin=\"admin\">\n <ul class=\"tiles\" [class.tiles--admin]=\"admin\">\n @for (tile of tiles; track tile.id; let i = $index) {\n <li class=\"tiles__cell\" [style.--tile-order]=\"i\">\n <button type=\"button\" class=\"tile\" (click)=\"go(tile.redirect)\">\n <hm-icon class=\"tile__icon\" [shape]=\"tile.icon ?? 'grid-layout'\" size=\"lg\" />\n <span class=\"tile__text\">\n <span class=\"tile__label\">{{ tile.label }}</span>\n @if (caption(tile); as meta) {\n <span class=\"tile__meta\">{{ meta }}</span>\n }\n </span>\n </button>\n </li>\n }\n </ul>\n</ng-template>\n"],"names":[],"mappings":";;;;;;;;;;;;;AAyBA;AACA,MAAM,SAAS,GAAG,EAAE;AAEpB,MAAM,eAAe,GAAG,IAAI,GAAG,CAC7B;IACE,sCAAsC;IACtC,oCAAoC;IACpC,sCAAsC;IACtC,qCAAqC;IACrC,8CAA8C;AAC/C,CAAA,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,WAAW,EAAE,CAAC,CAChC;AACD,MAAM,aAAa,GAAG,oCAAoC,CAAC,WAAW,EAAE;AAExE,SAAS,UAAU,CAAC,IAAW,EAAA;AAC7B,IAAA,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC,KAAK,KAAqB,KAAK,IAAI,IAAI,CAAC;AAC7E;AAEA,SAAS,OAAO,CAAC,IAAW,EAAA;AAC1B,IAAA,OAAO,CAAC,IAAI,CAAC,kBAAkB,IAAI,IAAI,CAAC,UAAU,IAAI,EAAE,EAAE,IAAI,EAAE;AAClE;AAEA,SAAS,UAAU,CAAC,IAAW,EAAA;AAC7B,IAAA,OAAO,IAAI,CAAC,cAAc,IAAI,GAAG,IAAI,CAAC,UAAU,IAAI,MAAM,CAAA,CAAA,EAAI,IAAI,CAAC,SAAS,EAAE;AAChF;AAEA,SAAS,UAAU,CAAC,IAAW,EAAA;IAC7B,OAAO,CAAC,IAAI,CAAC,yBAAyB,IAAI,EAAE,EAAE,WAAW,EAAE;AAC7D;AAEM,SAAU,MAAM,CAAC,IAAW,EAAA;IAChC,OAAO,CAAC,CAAC,IAAI,CAAC,cAAc,KAAK,IAAI,CAAC,UAAU,KAAK,MAAM,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;AAC/F;AAEM,SAAU,MAAM,CAAC,IAAW,EAAA;AAChC,IAAA,OAAO,UAAU,CAAC,IAAI,CAAC,KAAK,aAAa,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,KAAK,MAAM,CAAC;AAC3F;AAEM,SAAU,OAAO,CAAC,IAAsB,EAAA;AAC5C,IAAA,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7D;AAEM,SAAU,WAAW,CAAC,IAAsB,EAAA;AAChD,IAAA,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC;AAC5D;SAEgB,YAAY,CAAC,IAAsB,EAAE,QAA2B,EAAE,EAAA;IAChF,MAAM,KAAK,GAAsB,EAAE;AACnC,IAAA,MAAM,IAAI,GAAG,CAAC,KAAuB,EAAE,IAAuB,KAAI;AAChE,QAAA,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;YACxB,IAAI,IAAI,IAAI,IAAI;gBAAE;AAClB,YAAA,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE;AAChB,gBAAA,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,cAAe,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;YACxI;AACA,YAAA,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC;YAC7B,IAAI,IAAI,CAAC,MAAM;AAAE,gBAAA,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACvD;AACF,IAAA,CAAC;AACD,IAAA,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC;AACjB,IAAA,OAAO,KAAK;AACd;AAEM,SAAU,QAAQ,CAAC,IAAsB,EAAE,QAAgB,EAAA;IAC/D,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,EAAE,KAAK,IAAI,IAAI;AAC/E;AAEA,SAAS,YAAY,CAAC,IAAW,EAAA;AAC/B,IAAA,OAAO,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC;AACrF;AAQA,SAAS,iBAAiB,CAAC,IAAW,EAAA;AACpC,IAAA,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,CAAC,IAAI,YAAY,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;AAC9F;AAEA;AACA,SAAS,OAAO,CAAC,KAA2B,EAAA;IAC1C,IAAI,OAAO,GAAG,KAAK;IACnB,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,KAAI;QAClC,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC;QACzC,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO,CAAC,IAAI,CAAC;QAC/B,OAAO,GAAG,IAAI;AACd,QAAA,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;AAC3E,IAAA,CAAC,CAAC;IACF,OAAO,OAAO,GAAG,IAAI,GAAG,IAAI;AAC9B;AAEA,SAAS,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAa,EAAA;IACxD,MAAM,KAAK,GAAG,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC;IAClC,OAAO;AACL,QAAA,EAAE,EAAE,UAAU,CAAC,IAAI,CAAC;AACpB,QAAA,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC;AACpB,QAAA,IAAI,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;AAC7B,QAAA,QAAQ,EAAE,IAAI,CAAC,cAAc,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,QAAQ,IAAI,EAAE;QACzD,KAAK;AACL,QAAA,OAAO,EAAE,KAAK,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC9C,UAAU;KACX;AACH;AAEA;;;AAGG;AACG,SAAU,cAAc,CAAC,IAAsB,EAAA;AACnD,IAAA,IAAI,KAAK,GAAyB,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACpH,IAAA,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;AAC3C,QAAA,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC;AAC3B,QAAA,IAAI,CAAC,IAAI;YAAE;QACX,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,SAAS;YAAE;QACnD,KAAK,GAAG,IAAI;IACd;AACA,IAAA,OAAO,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC;AAC9B;;AC7HA;MAEa,gBAAgB,CAAA;AAD7B,IAAA,WAAA,GAAA;QAEE,IAAA,CAAA,IAAI,GAAG,KAAK;AACb,IAAA;+GAFY,gBAAgB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA,CAAA;AAAhB,IAAA,SAAA,IAAA,CAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,gBAAgB,cADH,MAAM,EAAA,CAAA,CAAA;;4FACnB,gBAAgB,EAAA,UAAA,EAAA,CAAA;kBAD5B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;AASlC,MAAM,eAAe,GAAG,KAAK;AAC7B,MAAM,WAAW,GAAG,CAAC;AAErB,IAAI,SAAS,GAAG,CAAC;AAEjB,SAAS,SAAS,CAAC,IAAyC,EAAA;AAC1D,IAAA,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,KAAoB,IAAI,IAAI,IAAI,CAAC;AAC1E;MASa,aAAa,CAAA;AAuDxB,IAAA,WAAA,GAAA;AAtDiB,QAAA,IAAA,CAAA,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;AACvB,QAAA,IAAA,CAAA,WAAW,GAAG,MAAM,CAAC,uBAAuB,CAAC;AAC7C,QAAA,IAAA,CAAA,IAAI,GAAG,MAAM,CAAC,gBAAgB,CAAC;AAC/B,QAAA,IAAA,CAAA,QAAQ,GAAG,MAAM,CAAC,oBAAoB,CAAC;AACvC,QAAA,IAAA,CAAA,YAAY,GAAG,MAAM,CAAC,gBAAgB,CAAC;AACvC,QAAA,IAAA,CAAA,QAAQ,GAAG,MAAM,CAAC,cAAc,CAAC;AAC/B,QAAA,IAAA,CAAA,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI;AACtB,QAAA,IAAA,CAAA,GAAG,GAAG,CAAA,KAAA,EAAQ,EAAE,SAAS,EAAE;AAE7B,QAAA,IAAA,CAAA,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC;AAC1B,QAAA,IAAA,CAAA,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE;YAC5E,YAAY,EAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC;AAC/C,SAAA,CAAC;AAEe,QAAA,IAAA,CAAA,gBAAgB,GAAG,QAAQ,CAAC,MAAM,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,uFAAC;QACnE,IAAA,CAAA,QAAQ,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,UAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CAAC;AACtF,QAAA,IAAA,CAAA,KAAK,GAAG,QAAQ,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,4EAAC;AAElD,QAAA,IAAA,CAAA,SAAS,GAAG,QAAQ,CAAC,MAAM,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,gFAAC;QACrD,IAAA,CAAA,SAAS,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,WAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CAAC;QACzE,IAAA,CAAA,UAAU,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,YAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CAAC;QACzE,IAAA,CAAA,SAAS,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,WAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CAAC;AAC5E,QAAA,IAAA,CAAA,UAAU,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC,MAAM,IAAI,CAAC,iFAAC;;AAG3D,QAAA,IAAA,CAAA,gBAAgB,GAAG,QAAQ,CAAC,MAAK;AAChD,YAAA,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE;YAC7B,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC;AAC9D,QAAA,CAAC,uFAAC;QAEiB,IAAA,CAAA,SAAS,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,gBAAgB,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,WAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CAAC;AACzF,QAAA,IAAA,CAAA,UAAU,GAAG,QAAQ,CAAuB,MAAK;YAClE,IAAI,IAAI,CAAC,gBAAgB,EAAE;AAAE,gBAAA,OAAO,EAAE;YACtC,OAAO,IAAI,CAAC;AACT,iBAAA,OAAO;AACP,iBAAA,KAAK,CAAC,CAAC,EAAE,WAAW;iBACpB,GAAG,CAAC,CAAC,IAAI,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC5D,QAAA,CAAC,iFAAC;AAEiB,QAAA,IAAA,CAAA,aAAa,GAAyC;YACvE,EAAE,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,GAAG,CAAC,WAAW,EAAE;YAChF,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,GAAG,KAAK,GAAG,CAAC,KAAK,EAAE;AACnE,YAAA;AACE,gBAAA,GAAG,EAAE,UAAU;AACf,gBAAA,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,WAAW;AACzB,gBAAA,KAAK,EAAE,MAAM,EAAE;AACf,gBAAA,UAAU,EAAE,KAAK;AACjB,gBAAA,QAAQ,EAAE,KAAK;AACf,gBAAA,UAAU,EAAE,KAAK;AACjB,gBAAA,YAAY,EAAE,QAAQ;AACvB,aAAA;SACF;QACkB,IAAA,CAAA,KAAK,GAAG,CAAC,GAAc,KAAK,GAAG,CAAC,EAAE;QAGnD,MAAM,CAAC,MAAK;AACV,YAAA,MAAM,IAAI,GAAG,IAAI,CAAC,gBAAgB,EAAE;YACpC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI;gBAAE;AACjD,YAAA,IAAI,CAAC,YAAY,CAAC,IAAI,GAAG,IAAI;AAC7B,YAAA,KAAK,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;AACxE,QAAA,CAAC,CAAC;IACJ;AAEU,IAAA,OAAO,CAAC,IAAkB,EAAA;AAClC,QAAA,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC;YAAE,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;QACvG,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC;IACzC;AAEU,IAAA,OAAO,CAAC,IAAqB,EAAA;AACrC,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC;AAChE,QAAA,OAAO,KAAK,EAAE,IAAI,CAAC,eAAe,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ;IACxD;AAEU,IAAA,GAAG,CAAC,KAAc,EAAA;AAC1B,QAAA,OAAO,KAAkB;IAC3B;AAEU,IAAA,UAAU,CAAC,EAAU,EAAA;QAC7B,OAAO,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;IACxC;AAEU,IAAA,aAAa,CAAC,GAAc,EAAA;QACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ;AAC9E,QAAA,OAAO,aAAa,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC;IAC3D;AAEU,IAAA,cAAc,CAAC,GAAc,EAAA;AACrC,QAAA,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC;YAC9B,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,WAAW,EAAE,GAAG,CAAC,WAAW;YAC5B,QAAQ,EAAE,GAAG,CAAC,QAAQ;AACtB,YAAA,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE;AAC9D,SAAA,CAAC;IACJ;AAEU,IAAA,EAAE,CAAC,QAAmC,EAAA;AAC9C,QAAA,IAAI,QAAQ;YAAE,KAAK,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC;IACxD;IAEU,YAAY,GAAA;AACpB,QAAA,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE;IACtB;IAEU,QAAQ,GAAA;AAChB,QAAA,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE;IACvB;+GA3GW,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;AAAb,IAAA,SAAA,IAAA,CAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,aAAa,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,MAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EC7C1B,+mJAsHA,EAAA,MAAA,EAAA,CAAA,4lFAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,ED9EY,gBAAgB,EAAA,QAAA,EAAA,oBAAA,EAAA,MAAA,EAAA,CAAA,yBAAA,EAAA,kBAAA,EAAA,0BAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,iBAAiB,EAAA,QAAA,EAAA,cAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,MAAA,EAAA,OAAA,EAAA,SAAA,EAAA,YAAA,EAAA,SAAA,EAAA,OAAA,EAAA,cAAA,EAAA,kBAAA,EAAA,YAAA,EAAA,WAAA,EAAA,WAAA,EAAA,YAAA,EAAA,YAAA,EAAA,MAAA,EAAA,YAAA,EAAA,eAAA,EAAA,WAAA,EAAA,eAAA,EAAA,qBAAA,EAAA,mBAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,WAAA,EAAA,eAAA,EAAA,aAAA,EAAA,WAAA,EAAA,YAAA,EAAA,WAAA,EAAA,UAAA,EAAA,UAAA,EAAA,cAAA,EAAA,cAAA,EAAA,YAAA,EAAA,YAAA,EAAA,eAAA,EAAA,YAAA,EAAA,gBAAA,EAAA,SAAA,EAAA,YAAA,EAAA,oBAAA,EAAA,YAAA,EAAA,gBAAA,EAAA,gBAAA,EAAA,aAAA,EAAA,eAAA,EAAA,aAAA,EAAA,YAAA,CAAA,EAAA,OAAA,EAAA,CAAA,iBAAA,EAAA,YAAA,EAAA,iBAAA,EAAA,qBAAA,EAAA,mBAAA,EAAA,iBAAA,EAAA,eAAA,EAAA,YAAA,EAAA,uBAAA,EAAA,gBAAA,EAAA,mBAAA,EAAA,eAAA,EAAA,mBAAA,EAAA,mBAAA,EAAA,kBAAA,EAAA,mBAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,qBAAqB,EAAA,QAAA,EAAA,qBAAA,EAAA,MAAA,EAAA,CAAA,QAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,mBAAmB,EAAA,QAAA,EAAA,gBAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,MAAA,EAAA,OAAA,EAAA,eAAA,EAAA,UAAA,EAAA,QAAA,EAAA,MAAA,EAAA,UAAA,EAAA,QAAA,EAAA,SAAA,EAAA,cAAA,CAAA,EAAA,OAAA,EAAA,CAAA,eAAA,EAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAE,aAAa,oJAAE,kBAAkB,EAAA,QAAA,EAAA,eAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,SAAA,EAAA,aAAA,EAAA,QAAA,EAAA,YAAA,CAAA,EAAA,OAAA,EAAA,CAAA,iBAAA,CAAA,EAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA,CAAA;;4FAKjH,aAAa,EAAA,UAAA,EAAA,CAAA;kBAPzB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,MAAM,WACP,CAAC,gBAAgB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,aAAa,EAAE,kBAAkB,CAAC,EAAA,eAAA,EAG5G,uBAAuB,CAAC,MAAM,EAAA,QAAA,EAAA,+mJAAA,EAAA,MAAA,EAAA,CAAA,4lFAAA,CAAA,EAAA;;;"}
@@ -0,0 +1,37 @@
1
+ // Especificação técnica da tela Tentativas de login (menu Auditoria) do auto-serviço (`RouteDocument.loadTechnical`),
2
+ // em Markdown. Módulo próprio, carregado só pelo `import()` de `AUTO_SERVICE_DOCUMENTS`: vira um chunk à parte no FESM
3
+ // e fica fora do bundle inicial. Não importe estaticamente nem reexporte no `public-api.ts`.
4
+ var loginAttempts_technical = `# Tentativas de login — especificação técnica
5
+
6
+ Tela \`AutoServiceLoginAttemptsComponent\`, ligada ao router de template
7
+ \`AUTO_SERVICE_LOGIN_ATTEMPTS_TEMPLATE_ROUTER_ID\` (\`ad100005-…\`, página \`administration-login-attempts\` no menu
8
+ \`administration-audit\`): o seletor de client + \`SpineLoginAttemptsComponent\` (\`@trismegisto/spine/audit\`) com
9
+ \`scope="client"\`. No painel central do Argos a mesma tela usa \`scope="admin"\` (todos os realms, colunas Realm e
10
+ Client).
11
+
12
+ ## Endpoints
13
+
14
+ | Ação | Endpoint |
15
+ | --- | --- |
16
+ | Clients que a pessoa administra | \`GET /auto-service\` |
17
+ | Tentativas (filtros, página e total no servidor) | \`GET /auto-service/:clientId/attempts\` |
18
+
19
+ Query: \`from\`/\`to\` (ISO; o período escolhido vira [início do primeiro dia, início do dia seguinte ao último) no
20
+ fuso do navegador), \`user\`, \`ip\` (IP ou CIDR), \`result\`, \`method\`, \`reason\`, \`page\`, \`limit\` (20, 50 ou
21
+ 100). Resposta: \`{ items, total, page, limit }\`, mais recentes primeiro.
22
+
23
+ ## Regras
24
+
25
+ - **Autorização** (\`clientAdminGuard\`): grupo de administração do projeto do client + crachá Administrador ativo. O
26
+ servidor força \`client_id = :clientId\` (o da query é ignorado) e não aceita filtro de realm; 403 vira a mensagem de
27
+ falta de acesso.
28
+ - Filtro inválido (\`invalid_filter\`) ou janela acima de 366 dias (\`range_too_large\`) mostram o detalhe do servidor.
29
+ - Busca por usuário e IP aplicam 350 ms depois de parar de digitar; os outros filtros, na hora. Só a resposta da
30
+ consulta mais recente é usada.
31
+ - Motivo e método desconhecidos aparecem com o código que veio do banco (\`auth.auth_attempts.reason\`/\`method\`).
32
+ - A tela fica no ponto \`@trismegisto/spine/audit\`, importado só pelo chunk lazy desta página (o \`check-lazy\` do
33
+ build garante).
34
+ `;
35
+
36
+ export { loginAttempts_technical as default };
37
+ //# sourceMappingURL=trismegisto-spine-auto-service-login-attempts.technical-CasKIIws.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trismegisto-spine-auto-service-login-attempts.technical-CasKIIws.mjs","sources":["../../../projects/ui/auto-service/src/docs/login-attempts.technical.ts"],"sourcesContent":["// Especificação técnica da tela Tentativas de login (menu Auditoria) do auto-serviço (`RouteDocument.loadTechnical`),\n// em Markdown. Módulo próprio, carregado só pelo `import()` de `AUTO_SERVICE_DOCUMENTS`: vira um chunk à parte no FESM\n// e fica fora do bundle inicial. Não importe estaticamente nem reexporte no `public-api.ts`.\nexport default `# Tentativas de login — especificação técnica\n\nTela \\`AutoServiceLoginAttemptsComponent\\`, ligada ao router de template\n\\`AUTO_SERVICE_LOGIN_ATTEMPTS_TEMPLATE_ROUTER_ID\\` (\\`ad100005-…\\`, página \\`administration-login-attempts\\` no menu\n\\`administration-audit\\`): o seletor de client + \\`SpineLoginAttemptsComponent\\` (\\`@trismegisto/spine/audit\\`) com\n\\`scope=\"client\"\\`. No painel central do Argos a mesma tela usa \\`scope=\"admin\"\\` (todos os realms, colunas Realm e\nClient).\n\n## Endpoints\n\n| Ação | Endpoint |\n| --- | --- |\n| Clients que a pessoa administra | \\`GET /auto-service\\` |\n| Tentativas (filtros, página e total no servidor) | \\`GET /auto-service/:clientId/attempts\\` |\n\nQuery: \\`from\\`/\\`to\\` (ISO; o período escolhido vira [início do primeiro dia, início do dia seguinte ao último) no\nfuso do navegador), \\`user\\`, \\`ip\\` (IP ou CIDR), \\`result\\`, \\`method\\`, \\`reason\\`, \\`page\\`, \\`limit\\` (20, 50 ou\n100). Resposta: \\`{ items, total, page, limit }\\`, mais recentes primeiro.\n\n## Regras\n\n- **Autorização** (\\`clientAdminGuard\\`): grupo de administração do projeto do client + crachá Administrador ativo. O\n servidor força \\`client_id = :clientId\\` (o da query é ignorado) e não aceita filtro de realm; 403 vira a mensagem de\n falta de acesso.\n- Filtro inválido (\\`invalid_filter\\`) ou janela acima de 366 dias (\\`range_too_large\\`) mostram o detalhe do servidor.\n- Busca por usuário e IP aplicam 350 ms depois de parar de digitar; os outros filtros, na hora. Só a resposta da\n consulta mais recente é usada.\n- Motivo e método desconhecidos aparecem com o código que veio do banco (\\`auth.auth_attempts.reason\\`/\\`method\\`).\n- A tela fica no ponto \\`@trismegisto/spine/audit\\`, importado só pelo chunk lazy desta página (o \\`check-lazy\\` do\n build garante).\n`;\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA,8BAAe,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8Bd;;"}
@@ -0,0 +1,60 @@
1
+ // Manual de uso da tela Tentativas de login (menu Auditoria) do auto-serviço (`RouteDocument.load`), em Markdown.
2
+ // Módulo próprio, carregado só pelo `import()` de `AUTO_SERVICE_DOCUMENTS`: vira um chunk à parte no FESM e fica fora
3
+ // do bundle inicial. Não importe estaticamente nem reexporte no `public-api.ts`.
4
+ var loginAttempts_usage = `# Tentativas de login
5
+
6
+ Cada tentativa de entrar nesta aplicação: quando foi, quem tentou, de qual IP e navegador, por qual método e com que
7
+ resultado. Nas falhas, mostra também o motivo. Fica no menu **Auditoria**, ao lado de **Ações**. Serve para
8
+ investigar acessos suspeitos, contas bloqueadas e erros de login relatados por quem usa a aplicação. A tela é só de
9
+ consulta.
10
+
11
+ ## Antes de começar
12
+
13
+ Como nas outras telas do auto-serviço, o client é o do app em que você está logado. É preciso estar no grupo de
14
+ administração do projeto desse app e com o crachá **Administrador** ativo. Você só vê as tentativas de login feitas
15
+ nesta aplicação, não as de outras aplicações do Argos.
16
+
17
+ ## Como usar
18
+
19
+ 1. **Usuário**: procura no username, no e-mail e no nome, e também no que foi digitado na tela de login, mesmo que
20
+ não exista usuário com esse nome. Aplica ao parar de digitar ou com Enter.
21
+ 2. **Período**: de um dia a outro, ou um dos atalhos (hoje, últimos 7 dias, últimos 30 dias, este mês, mês passado).
22
+ Ao abrir a tela, são os últimos 7 dias. Cada consulta cobre no máximo 366 dias.
23
+ 3. **Resultado** (Sucesso, Falha ou Cancelada) e **Método** (Senha, MFA ou o provedor externo: GitHub, Google,
24
+ Keycloak, Entra ID).
25
+ 4. **Filtros avançados** abre **IP ou rede** (um IP, como \`10.0.0.5\`, ou uma rede, como \`10.0.0.0/8\`) e **Motivo**.
26
+ 5. **Limpar** volta tudo ao padrão; **Atualizar** recarrega na hora.
27
+ 6. A tabela lista as tentativas da mais recente para a mais antiga: **Data/hora** (no fuso do seu navegador),
28
+ **Resultado**, **Método**, **Usuário**, **Motivo**, **IP** e **Navegador** (coluna que pode ser escondida). Escolha
29
+ quantas linhas por página no rodapé.
30
+ 7. Nas ações de cada linha, **Filtrar por este IP** e **Filtrar por este usuário** aplicam o filtro na hora.
31
+
32
+ ## Usuário
33
+
34
+ Quando a tentativa é de um usuário cadastrado, aparecem o nome e, abaixo, o username e o e-mail. Quando o que foi
35
+ digitado não corresponde a ninguém, aparece o texto digitado com a nota **sem usuário correspondente**: pode ser
36
+ erro de digitação ou alguém tentando nomes ao acaso.
37
+
38
+ ## Motivos de falha
39
+
40
+ | Motivo | O que significa |
41
+ | --- | --- |
42
+ | Senha incorreta | O usuário existe, a senha não confere. |
43
+ | Usuário inexistente ou inativo | Ninguém com esse identificador, ou a conta está desativada. |
44
+ | Conta bloqueada por tentativas | Falhas demais seguidas; a conta fica bloqueada por um tempo. |
45
+ | Código de verificação incorreto / vencido | O código do MFA não confere ou passou da validade. |
46
+ | Método de verificação não permitido / desconhecido | O MFA escolhido não vale para essa conta. |
47
+ | Código de recuperação incorreto ou já usado / recusado | Problema com um código de recuperação do MFA. |
48
+ | Conta externa não vinculada | Login por provedor externo sem conta correspondente no Argos. |
49
+ | Usuário de outro realm | A conta existe, mas em outro realm. |
50
+
51
+ ## Quando algo não carrega
52
+
53
+ - **Só o grupo de administração do projeto vê as tentativas:** sua conta não administra este client, ou o crachá
54
+ Administrador não está ativo.
55
+ - **Não foi possível carregar as tentativas:** a mensagem traz o motivo (por exemplo, um IP inválido no filtro);
56
+ corrija o filtro ou use **Atualizar**.
57
+ `;
58
+
59
+ export { loginAttempts_usage as default };
60
+ //# sourceMappingURL=trismegisto-spine-auto-service-login-attempts.usage-BMgLNu7W.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trismegisto-spine-auto-service-login-attempts.usage-BMgLNu7W.mjs","sources":["../../../projects/ui/auto-service/src/docs/login-attempts.usage.ts"],"sourcesContent":["// Manual de uso da tela Tentativas de login (menu Auditoria) do auto-serviço (`RouteDocument.load`), em Markdown.\n// Módulo próprio, carregado só pelo `import()` de `AUTO_SERVICE_DOCUMENTS`: vira um chunk à parte no FESM e fica fora\n// do bundle inicial. Não importe estaticamente nem reexporte no `public-api.ts`.\nexport default `# Tentativas de login\n\nCada tentativa de entrar nesta aplicação: quando foi, quem tentou, de qual IP e navegador, por qual método e com que\nresultado. Nas falhas, mostra também o motivo. Fica no menu **Auditoria**, ao lado de **Ações**. Serve para\ninvestigar acessos suspeitos, contas bloqueadas e erros de login relatados por quem usa a aplicação. A tela é só de\nconsulta.\n\n## Antes de começar\n\nComo nas outras telas do auto-serviço, o client é o do app em que você está logado. É preciso estar no grupo de\nadministração do projeto desse app e com o crachá **Administrador** ativo. Você só vê as tentativas de login feitas\nnesta aplicação, não as de outras aplicações do Argos.\n\n## Como usar\n\n1. **Usuário**: procura no username, no e-mail e no nome, e também no que foi digitado na tela de login, mesmo que\n não exista usuário com esse nome. Aplica ao parar de digitar ou com Enter.\n2. **Período**: de um dia a outro, ou um dos atalhos (hoje, últimos 7 dias, últimos 30 dias, este mês, mês passado).\n Ao abrir a tela, são os últimos 7 dias. Cada consulta cobre no máximo 366 dias.\n3. **Resultado** (Sucesso, Falha ou Cancelada) e **Método** (Senha, MFA ou o provedor externo: GitHub, Google,\n Keycloak, Entra ID).\n4. **Filtros avançados** abre **IP ou rede** (um IP, como \\`10.0.0.5\\`, ou uma rede, como \\`10.0.0.0/8\\`) e **Motivo**.\n5. **Limpar** volta tudo ao padrão; **Atualizar** recarrega na hora.\n6. A tabela lista as tentativas da mais recente para a mais antiga: **Data/hora** (no fuso do seu navegador),\n **Resultado**, **Método**, **Usuário**, **Motivo**, **IP** e **Navegador** (coluna que pode ser escondida). Escolha\n quantas linhas por página no rodapé.\n7. Nas ações de cada linha, **Filtrar por este IP** e **Filtrar por este usuário** aplicam o filtro na hora.\n\n## Usuário\n\nQuando a tentativa é de um usuário cadastrado, aparecem o nome e, abaixo, o username e o e-mail. Quando o que foi\ndigitado não corresponde a ninguém, aparece o texto digitado com a nota **sem usuário correspondente**: pode ser\nerro de digitação ou alguém tentando nomes ao acaso.\n\n## Motivos de falha\n\n| Motivo | O que significa |\n| --- | --- |\n| Senha incorreta | O usuário existe, a senha não confere. |\n| Usuário inexistente ou inativo | Ninguém com esse identificador, ou a conta está desativada. |\n| Conta bloqueada por tentativas | Falhas demais seguidas; a conta fica bloqueada por um tempo. |\n| Código de verificação incorreto / vencido | O código do MFA não confere ou passou da validade. |\n| Método de verificação não permitido / desconhecido | O MFA escolhido não vale para essa conta. |\n| Código de recuperação incorreto ou já usado / recusado | Problema com um código de recuperação do MFA. |\n| Conta externa não vinculada | Login por provedor externo sem conta correspondente no Argos. |\n| Usuário de outro realm | A conta existe, mas em outro realm. |\n\n## Quando algo não carrega\n\n- **Só o grupo de administração do projeto vê as tentativas:** sua conta não administra este client, ou o crachá\n Administrador não está ativo.\n- **Não foi possível carregar as tentativas:** a mensagem traz o motivo (por exemplo, um IP inválido no filtro);\n corrija o filtro ou use **Atualizar**.\n`;\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA,0BAAe,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDd;;"}
@@ -9,41 +9,42 @@ import { urlPath } from '@trismegisto/primitives';
9
9
  import * as i2 from '@orfeu/angular/core';
10
10
 
11
11
  const AUTO_SERVICE_CONFIG = new InjectionToken('AUTO_SERVICE_CONFIG');
12
- // router_id dos 3 roteadores (páginas) do template "auto-service" (web.template_router,
13
- // ver apps/argos/database/data/web/template_router.json) -- valores literais e
12
+ // router_id dos roteadores (páginas) do template "auto-service" (web.template_router,
13
+ // ver apps/argos/database/seed/04-web.template_router.sql) -- valores literais e
14
14
  // estáveis (mesma convenção do resto do seed: UUID fixo, nunca gerado em runtime). É
15
15
  // contra ISSO que o ComponentDistributorService casa via source_template_router_id
16
16
  // (não o router_id materializado de cada client, que varia): qualquer client
17
- // vinculado ao template "auto-service" resolve os 3 automaticamente, sem precisar
18
- // declarar nada no próprio features.routes.ts. As 3 páginas vivem dentro de um menu
19
- // "Administração" no template -- o menu em si nunca precisa de componente (nunca é
20
- // page_id de nenhum router).
17
+ // vinculado ao template "auto-service" resolve todos automaticamente, sem precisar
18
+ // declarar nada no próprio features.routes.ts. As páginas vivem dentro da navegação
19
+ // "Administração" do template (Ações e Tentativas de login num menu "Auditoria" dentro
20
+ // dela) -- navegação e menu nunca precisam de componente (nunca são page_id de router).
21
21
  const AUTO_SERVICE_GROUPS_TEMPLATE_ROUTER_ID = 'ad100001-0000-4000-8000-000000000001';
22
22
  const AUTO_SERVICE_APPS_TEMPLATE_ROUTER_ID = 'ad100002-0000-4000-8000-000000000001';
23
23
  const AUTO_SERVICE_CLIENT_TEMPLATE_ROUTER_ID = 'ad100003-0000-4000-8000-000000000001';
24
- // Auditoria: 4ª página do menu "Administração", ao lado de Grupos/Aplicações. O componente já fica
25
- // ligado a este id; a entrada no menu aparece quando o template "auto-service" do banco ganhar o router
26
- // (mesmos guards da página de Aplicações). Até lá a tela também abre pela aba "Auditoria" da própria
27
- // página de Aplicações -- mesma autorização, por construção.
24
+ // Menu "Auditoria" (rota AD000006, dentro de "Administração", migration 0012 do argos): duas páginas.
25
+ // Ações (a leitura da auditoria, `audit.events`) -- o nome da constante ficou do tempo em que era a página "Auditoria".
28
26
  const AUTO_SERVICE_AUDIT_TEMPLATE_ROUTER_ID = 'ad100004-0000-4000-8000-000000000001';
27
+ // Tentativas de login (`auth.auth_attempts` com o client do app).
28
+ const AUTO_SERVICE_LOGIN_ATTEMPTS_TEMPLATE_ROUTER_ID = 'ad100005-0000-4000-8000-000000000001';
29
29
  // Início ("home", página de topo e pública do mesmo template). Todo client clonado do
30
30
  // template ganha a própria linha em web.router com source_template_router_id apontando pra cá.
31
31
  const AUTO_SERVICE_HOME_TEMPLATE_ROUTER_ID = '6a9a13db-f31b-494e-b51c-79af0ac28b60';
32
32
 
33
33
  /**
34
- * As 5 telas do auto-serviço, carregadas sob demanda. Cada `import()` relativo vira um chunk próprio no
34
+ * As 6 telas do auto-serviço, carregadas sob demanda. Cada `import()` relativo vira um chunk próprio no
35
35
  * FESM publicado (`trismegisto-spine-auto-service-<tela>-<hash>.mjs`), que o bundler do app mantém fora
36
36
  * do carregamento inicial. Não exporte as telas no `public-api.ts` (ver o comentário lá).
37
37
  */
38
38
  const AUTO_SERVICE_SCREENS = {
39
- home: () => import('./trismegisto-spine-auto-service-home.component-vl3BPFnk.mjs').then(m => m.HomeComponent),
40
- groups: () => import('./trismegisto-spine-auto-service-auto-service-groups.component-CqIVe2LU.mjs').then(m => m.AutoServiceGroupsComponent),
41
- apps: () => import('./trismegisto-spine-auto-service-auto-service.component-BLN49F4a.mjs').then(m => m.AutoServiceComponent),
42
- client: () => import('./trismegisto-spine-auto-service-auto-service-client.component-C7SPavrX.mjs').then(m => m.AutoServiceClientComponent),
43
- audit: () => import('./trismegisto-spine-auto-service-auto-service-audit.component-CCiz-q8o.mjs').then(m => m.AutoServiceAuditComponent),
39
+ home: () => import('./trismegisto-spine-auto-service-home.component-5_aji1hf.mjs').then(m => m.HomeComponent),
40
+ groups: () => import('./trismegisto-spine-auto-service-auto-service-groups.component-UVMGF7-B.mjs').then(m => m.AutoServiceGroupsComponent),
41
+ apps: () => import('./trismegisto-spine-auto-service-auto-service.component-DmLafUkX.mjs').then(m => m.AutoServiceComponent),
42
+ client: () => import('./trismegisto-spine-auto-service-auto-service-client.component-DAxhZC-a.mjs').then(m => m.AutoServiceClientComponent),
43
+ audit: () => import('./trismegisto-spine-auto-service-auto-service-audit.component-BIL8tH1I.mjs').then(m => m.AutoServiceAuditComponent),
44
+ loginAttempts: () => import('./trismegisto-spine-auto-service-auto-service-login-attempts.component-Boi-kG0G.mjs').then(m => m.AutoServiceLoginAttemptsComponent),
44
45
  };
45
46
  /**
46
- * Documentação das 5 telas (botão de documentação da barra da página e índice "Documentação do sistema"): o manual
47
+ * Documentação das 6 telas (botão de documentação da barra da página e índice "Documentação do sistema"): o manual
47
48
  * de uso (`load`) e a especificação técnica (`loadTechnical`, só com `SpineConfig.technicalDocs`). O Markdown fica em
48
49
  * módulos `.ts` próprios em `./docs/` (o ng-packagr não carrega `.md` como texto), cada um atrás de um `import()`
49
50
  * relativo: vira um chunk à parte no FESM e só é baixado quando alguém abre o documento. Sem `title`/`group`: o
@@ -56,20 +57,24 @@ const AUTO_SERVICE_DOCUMENTS = {
56
57
  loadTechnical: () => import('./trismegisto-spine-auto-service-home.technical-BrVhcgDO.mjs').then(m => m.default),
57
58
  },
58
59
  groups: {
59
- load: () => import('./trismegisto-spine-auto-service-groups.usage-Bh7ig2zZ.mjs').then(m => m.default),
60
- loadTechnical: () => import('./trismegisto-spine-auto-service-groups.technical-jQlwaQPn.mjs').then(m => m.default),
60
+ load: () => import('./trismegisto-spine-auto-service-groups.usage-Cz5sCKgn.mjs').then(m => m.default),
61
+ loadTechnical: () => import('./trismegisto-spine-auto-service-groups.technical-BlktCM70.mjs').then(m => m.default),
61
62
  },
62
63
  apps: {
63
- load: () => import('./trismegisto-spine-auto-service-apps.usage-Cf5SG-oQ.mjs').then(m => m.default),
64
- loadTechnical: () => import('./trismegisto-spine-auto-service-apps.technical-ClMKB9cK.mjs').then(m => m.default),
64
+ load: () => import('./trismegisto-spine-auto-service-apps.usage-CAXMP5r8.mjs').then(m => m.default),
65
+ loadTechnical: () => import('./trismegisto-spine-auto-service-apps.technical-BAKdkVdz.mjs').then(m => m.default),
65
66
  },
66
67
  client: {
67
- load: () => import('./trismegisto-spine-auto-service-client.usage-CM2WB2e2.mjs').then(m => m.default),
68
- loadTechnical: () => import('./trismegisto-spine-auto-service-client.technical-BHWhpyWv.mjs').then(m => m.default),
68
+ load: () => import('./trismegisto-spine-auto-service-client.usage-CcJANb20.mjs').then(m => m.default),
69
+ loadTechnical: () => import('./trismegisto-spine-auto-service-client.technical-DP5G8i3o.mjs').then(m => m.default),
69
70
  },
70
71
  audit: {
71
- load: () => import('./trismegisto-spine-auto-service-audit.usage-GIJ2RwuH.mjs').then(m => m.default),
72
- loadTechnical: () => import('./trismegisto-spine-auto-service-audit.technical-C901aIIQ.mjs').then(m => m.default),
72
+ load: () => import('./trismegisto-spine-auto-service-audit.usage-IdXMruv5.mjs').then(m => m.default),
73
+ loadTechnical: () => import('./trismegisto-spine-auto-service-audit.technical-B75d4mh2.mjs').then(m => m.default),
74
+ },
75
+ loginAttempts: {
76
+ load: () => import('./trismegisto-spine-auto-service-login-attempts.usage-BMgLNu7W.mjs').then(m => m.default),
77
+ loadTechnical: () => import('./trismegisto-spine-auto-service-login-attempts.technical-CasKIIws.mjs').then(m => m.default),
73
78
  },
74
79
  };
75
80
  // Cada app consumidora chama isso uma vez no bootstrap (mesmo padrão do
@@ -79,7 +84,7 @@ const AUTO_SERVICE_DOCUMENTS = {
79
84
  // route_id -- esse varia por client, ver ComponentDistributorService). O app não
80
85
  // precisa (e não deve) declarar nada em features.routes.ts pra isso funcionar; basta
81
86
  // o client dele estar vinculado ao template "auto-service" (auth.clients.template_id).
82
- // 5 páginas (Início + Grupos/Aplicações/Cliente/Auditoria) -- o ComponentDistributorService
87
+ // 6 páginas (Início + Grupos/Aplicações/Cliente + Ações/Tentativas de login no menu Auditoria) -- o ComponentDistributorService
83
88
  // já suporta N entradas, cada uma casando pelo seu próprio source_template_router_id.
84
89
  function provideAutoService(config) {
85
90
  const componentRoutes = [
@@ -88,6 +93,7 @@ function provideAutoService(config) {
88
93
  { source_template_router_id: AUTO_SERVICE_APPS_TEMPLATE_ROUTER_ID, load: AUTO_SERVICE_SCREENS.apps, document: AUTO_SERVICE_DOCUMENTS.apps },
89
94
  { source_template_router_id: AUTO_SERVICE_CLIENT_TEMPLATE_ROUTER_ID, load: AUTO_SERVICE_SCREENS.client, document: AUTO_SERVICE_DOCUMENTS.client },
90
95
  { source_template_router_id: AUTO_SERVICE_AUDIT_TEMPLATE_ROUTER_ID, load: AUTO_SERVICE_SCREENS.audit, document: AUTO_SERVICE_DOCUMENTS.audit },
96
+ { source_template_router_id: AUTO_SERVICE_LOGIN_ATTEMPTS_TEMPLATE_ROUTER_ID, load: AUTO_SERVICE_SCREENS.loginAttempts, document: AUTO_SERVICE_DOCUMENTS.loginAttempts },
91
97
  ];
92
98
  return makeEnvironmentProviders([
93
99
  { provide: AUTO_SERVICE_CONFIG, useValue: config },
@@ -133,8 +139,13 @@ class AutoServiceService {
133
139
  * em todos os seus routers. Vale também para routers de template (ordem é do client).
134
140
  */
135
141
  reorderRouters(clientId, groups) { return firstValueFrom(this.http.patch(this.url `/${clientId}/routers/reorder`, { groups }, { headers: this.headers })); }
136
- // Groups
142
+ // Groups (do projeto do client, sem o grupo de administração: editar/excluir/membros dele respondem 404)
137
143
  getGroups(clientId) { return firstValueFrom(this.http.get(this.url `/${clientId}/groups`, { headers: this.headers })); }
144
+ /** Tipos (crachás) do realm; `assignable: false` no Administrador, que é só do grupo de administração (painel central). */
145
+ getGroupTypes(clientId) { return firstValueFrom(this.http.get(this.url `/${clientId}/group-types`, { headers: this.headers })); }
146
+ createGroup(clientId, body) { return firstValueFrom(this.http.post(this.url `/${clientId}/groups`, body, { headers: this.headers })); }
147
+ updateGroup(clientId, groupId, body) { return firstValueFrom(this.http.put(this.url `/${clientId}/groups/${groupId}`, body, { headers: this.headers })); }
148
+ deleteGroup(clientId, groupId) { return firstValueFrom(this.http.delete(this.url `/${clientId}/groups/${groupId}`, { headers: this.headers })); }
138
149
  getGroupMembers(clientId, groupId) { return firstValueFrom(this.http.get(this.url `/${clientId}/groups/${groupId}/members`, { headers: this.headers })); }
139
150
  addGroupMember(clientId, groupId, userId, role) { return firstValueFrom(this.http.post(this.url `/${clientId}/groups/${groupId}/members`, { user_id: userId, role }, { headers: this.headers })); }
140
151
  updateGroupMemberRole(clientId, groupId, userId, role) { return firstValueFrom(this.http.put(this.url `/${clientId}/groups/${groupId}/members/${userId}`, { role }, { headers: this.headers })); }
@@ -159,9 +170,18 @@ class AutoServiceService {
159
170
  getApps(clientId) { return firstValueFrom(this.http.get(this.url `/${clientId}/apps`, { headers: this.headers })); }
160
171
  createApp(clientId, body) { return firstValueFrom(this.http.post(this.url `/${clientId}/apps`, body, { headers: this.headers })); }
161
172
  updateApp(clientId, appId, body) { return firstValueFrom(this.http.put(this.url `/${clientId}/apps/${appId}`, body, { headers: this.headers })); }
173
+ /** Gera um segredo novo para a aplicação (o anterior para de valer na hora). A resposta traz o segredo uma única vez. */
174
+ rotateAppSecret(clientId, appId) { return firstValueFrom(this.http.post(this.url `/${clientId}/apps/${appId}/secret`, {}, { headers: this.headers })); }
162
175
  deleteApp(clientId, appId) { return firstValueFrom(this.http.delete(this.url `/${clientId}/apps/${appId}`, { headers: this.headers })); }
163
176
  getAppOperations(clientId, appId) { return firstValueFrom(this.http.get(this.url `/${clientId}/apps/${appId}/operations`, { headers: this.headers })); }
164
- // Consumers (grupo ↔ aplicação)
177
+ // Tokens de API da aplicação (x-api-token)
178
+ getAppTokens(clientId, appId) { return firstValueFrom(this.http.get(this.url `/${clientId}/apps/${appId}/tokens`, { headers: this.headers })); }
179
+ /** Cria o token (e o consumer dele, com as permissões escolhidas). A resposta traz o valor uma única vez. */
180
+ createAppToken(clientId, appId, body) { return firstValueFrom(this.http.post(this.url `/${clientId}/apps/${appId}/tokens`, body, { headers: this.headers })); }
181
+ /** Revoga: o token para de valer na hora e continua na lista como revogado. */
182
+ revokeAppToken(clientId, appId, tokenId) { return firstValueFrom(this.http.delete(this.url `/${clientId}/apps/${appId}/tokens/${tokenId}`, { headers: this.headers })); }
183
+ // Consumers (grupo ↔ aplicação), sem o do grupo de administração: o servidor não o lista e o :consumerId dele é 404
184
+ // (servidor antigo ainda devolve com `group_is_admin: true` e a aba Acessos descarta)
165
185
  getAppConsumers(clientId, appId) { return firstValueFrom(this.http.get(this.url `/${clientId}/apps/${appId}/consumers`, { headers: this.headers })); }
166
186
  createAppConsumer(clientId, appId, groupId) { return firstValueFrom(this.http.post(this.url `/${clientId}/apps/${appId}/consumers`, { group_id: groupId }, { headers: this.headers })); }
167
187
  deleteAppConsumer(clientId, consumerId) { return firstValueFrom(this.http.delete(this.url `/${clientId}/apps/consumers/${consumerId}`, { headers: this.headers })); }
@@ -192,5 +212,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
192
212
  * Generated bundle index. Do not edit.
193
213
  */
194
214
 
195
- export { AUTO_SERVICE_APPS_TEMPLATE_ROUTER_ID, AUTO_SERVICE_AUDIT_TEMPLATE_ROUTER_ID, AUTO_SERVICE_CLIENT_TEMPLATE_ROUTER_ID, AUTO_SERVICE_CONFIG, AUTO_SERVICE_DOCUMENTS, AUTO_SERVICE_GROUPS_TEMPLATE_ROUTER_ID, AUTO_SERVICE_HOME_TEMPLATE_ROUTER_ID, AUTO_SERVICE_SCREENS, AutoServiceService, provideAutoService };
215
+ export { AUTO_SERVICE_APPS_TEMPLATE_ROUTER_ID, AUTO_SERVICE_AUDIT_TEMPLATE_ROUTER_ID, AUTO_SERVICE_CLIENT_TEMPLATE_ROUTER_ID, AUTO_SERVICE_CONFIG, AUTO_SERVICE_DOCUMENTS, AUTO_SERVICE_GROUPS_TEMPLATE_ROUTER_ID, AUTO_SERVICE_HOME_TEMPLATE_ROUTER_ID, AUTO_SERVICE_LOGIN_ATTEMPTS_TEMPLATE_ROUTER_ID, AUTO_SERVICE_SCREENS, AutoServiceService, provideAutoService };
196
216
  //# sourceMappingURL=trismegisto-spine-auto-service.mjs.map