@trismegisto/spine 3.3.2 → 3.3.4

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 (60) hide show
  1. package/README.md +15 -2
  2. package/fesm2022/trismegisto-spine-audit.mjs +6 -6
  3. package/fesm2022/trismegisto-spine-audit.mjs.map +1 -1
  4. package/fesm2022/{trismegisto-spine-auto-service-auto-service-client.component-BAfeLyTB.mjs → trismegisto-spine-auto-service-auto-service-client.component-kAXaCM5W.mjs} +8 -8
  5. package/fesm2022/{trismegisto-spine-auto-service-auto-service-client.component-BAfeLyTB.mjs.map → trismegisto-spine-auto-service-auto-service-client.component-kAXaCM5W.mjs.map} +1 -1
  6. package/fesm2022/{trismegisto-spine-auto-service-auto-service-groups.component-BYjIQ9ID.mjs → trismegisto-spine-auto-service-auto-service-groups.component-DE2QIGRH.mjs} +251 -85
  7. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-DE2QIGRH.mjs.map +1 -0
  8. package/fesm2022/{trismegisto-spine-auto-service-auto-service-news-page.component-CBemH4hr.mjs → trismegisto-spine-auto-service-auto-service-news-page.component-BW7BJ0oD.mjs} +2 -2
  9. package/fesm2022/{trismegisto-spine-auto-service-auto-service-news-page.component-CBemH4hr.mjs.map → trismegisto-spine-auto-service-auto-service-news-page.component-BW7BJ0oD.mjs.map} +1 -1
  10. package/fesm2022/{trismegisto-spine-auto-service-auto-service-ratings-page.component-C_THGTkw.mjs → trismegisto-spine-auto-service-auto-service-ratings-page.component-CHPU-QLJ.mjs} +2 -2
  11. package/fesm2022/{trismegisto-spine-auto-service-auto-service-ratings-page.component-C_THGTkw.mjs.map → trismegisto-spine-auto-service-auto-service-ratings-page.component-CHPU-QLJ.mjs.map} +1 -1
  12. package/fesm2022/trismegisto-spine-auto-service-auto-service-suggestions-page.component-CkiCTmXD.mjs +3733 -0
  13. package/fesm2022/trismegisto-spine-auto-service-auto-service-suggestions-page.component-CkiCTmXD.mjs.map +1 -0
  14. package/fesm2022/{trismegisto-spine-auto-service-auto-service.component-QsIQ61w2.mjs → trismegisto-spine-auto-service-auto-service.component-D58Ht9PH.mjs} +7 -7
  15. package/fesm2022/{trismegisto-spine-auto-service-auto-service.component-QsIQ61w2.mjs.map → trismegisto-spine-auto-service-auto-service.component-D58Ht9PH.mjs.map} +1 -1
  16. package/fesm2022/{trismegisto-spine-auto-service-groups.technical-_jQrg1Qh.mjs → trismegisto-spine-auto-service-groups.technical-CB1D3ANC.mjs} +12 -3
  17. package/fesm2022/trismegisto-spine-auto-service-groups.technical-CB1D3ANC.mjs.map +1 -0
  18. package/fesm2022/{trismegisto-spine-auto-service-groups.usage-OvWjNeeT.mjs → trismegisto-spine-auto-service-groups.usage-De2MhlE7.mjs} +7 -2
  19. package/fesm2022/{trismegisto-spine-auto-service-groups.usage-OvWjNeeT.mjs.map → trismegisto-spine-auto-service-groups.usage-De2MhlE7.mjs.map} +1 -1
  20. package/fesm2022/{trismegisto-spine-auto-service-home.component-rQ3dIGIE.mjs → trismegisto-spine-auto-service-home.component-9WvXOH3l.mjs} +7 -4
  21. package/fesm2022/trismegisto-spine-auto-service-home.component-9WvXOH3l.mjs.map +1 -0
  22. package/fesm2022/{trismegisto-spine-auto-service-suggestions.technical-BtUK6sKD.mjs → trismegisto-spine-auto-service-suggestions.technical-CGBBzAR8.mjs} +14 -1
  23. package/fesm2022/{trismegisto-spine-auto-service-suggestions.technical-BtUK6sKD.mjs.map → trismegisto-spine-auto-service-suggestions.technical-CGBBzAR8.mjs.map} +1 -1
  24. package/fesm2022/{trismegisto-spine-auto-service-suggestions.usage-BwO9Hjsx.mjs → trismegisto-spine-auto-service-suggestions.usage-DnAJS-Z0.mjs} +26 -3
  25. package/fesm2022/trismegisto-spine-auto-service-suggestions.usage-DnAJS-Z0.mjs.map +1 -0
  26. package/fesm2022/trismegisto-spine-auto-service.mjs +25 -12
  27. package/fesm2022/trismegisto-spine-auto-service.mjs.map +1 -1
  28. package/fesm2022/trismegisto-spine-graphql.mjs +3 -1
  29. package/fesm2022/trismegisto-spine-graphql.mjs.map +1 -1
  30. package/fesm2022/trismegisto-spine-layout-suggestions-page.component-CEKMqera.mjs +532 -0
  31. package/fesm2022/trismegisto-spine-layout-suggestions-page.component-CEKMqera.mjs.map +1 -0
  32. package/fesm2022/trismegisto-spine-layout-trismegisto-spine-layout-DiAfxIGP.mjs +8764 -0
  33. package/fesm2022/trismegisto-spine-layout-trismegisto-spine-layout-DiAfxIGP.mjs.map +1 -0
  34. package/fesm2022/trismegisto-spine-layout.mjs +1 -7662
  35. package/fesm2022/trismegisto-spine-layout.mjs.map +1 -1
  36. package/fesm2022/trismegisto-spine-notification.mjs +1 -1
  37. package/fesm2022/trismegisto-spine-notification.mjs.map +1 -1
  38. package/fesm2022/trismegisto-spine-routing.mjs +16 -3
  39. package/fesm2022/trismegisto-spine-routing.mjs.map +1 -1
  40. package/fesm2022/trismegisto-spine-shell.mjs +4 -2
  41. package/fesm2022/trismegisto-spine-shell.mjs.map +1 -1
  42. package/fesm2022/trismegisto-spine-side-panel.mjs +1 -1
  43. package/fesm2022/trismegisto-spine-side-panel.mjs.map +1 -1
  44. package/fesm2022/trismegisto-spine.mjs +813 -38
  45. package/fesm2022/trismegisto-spine.mjs.map +1 -1
  46. package/package.json +6 -6
  47. package/types/trismegisto-spine-auto-service.d.ts +20 -4
  48. package/types/trismegisto-spine-auto-service.d.ts.map +1 -1
  49. package/types/trismegisto-spine-layout.d.ts +1200 -74
  50. package/types/trismegisto-spine-layout.d.ts.map +1 -1
  51. package/types/trismegisto-spine-routing.d.ts.map +1 -1
  52. package/types/trismegisto-spine-shell.d.ts.map +1 -1
  53. package/types/trismegisto-spine.d.ts +350 -14
  54. package/types/trismegisto-spine.d.ts.map +1 -1
  55. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-BYjIQ9ID.mjs.map +0 -1
  56. package/fesm2022/trismegisto-spine-auto-service-auto-service-suggestions-page.component-DIeunpQ9.mjs +0 -2123
  57. package/fesm2022/trismegisto-spine-auto-service-auto-service-suggestions-page.component-DIeunpQ9.mjs.map +0 -1
  58. package/fesm2022/trismegisto-spine-auto-service-groups.technical-_jQrg1Qh.mjs.map +0 -1
  59. package/fesm2022/trismegisto-spine-auto-service-home.component-rQ3dIGIE.mjs.map +0 -1
  60. package/fesm2022/trismegisto-spine-auto-service-suggestions.usage-BwO9Hjsx.mjs.map +0 -1
package/README.md CHANGED
@@ -197,7 +197,7 @@ antes de "Sobre".
197
197
  | Sessões | `sessoes` | Token de acesso, Tokens de aplicação, Conectar um agente de IA, Sessões e dispositivos | Sempre (cada linha com a própria condição, abaixo) |
198
198
  | Grupos | `grupos` | Lista dos grupos da aplicação de que a pessoa é membro; cada um abre a tela de membros | Com `SPINE_ADMIN_API` e algum grupo (ver abaixo) |
199
199
  | Notificações | `notificacoes` | Tipos de aviso do app, aviso do navegador | Com `SpineConfig.features.notices` |
200
- | Aparência | `aparencia` | Tema, cor, densidade, fonte, menu lateral | Sempre |
200
+ | Aparência | `aparencia` | Tema, cor, densidade, fonte, menu lateral, dividir abas | Sempre |
201
201
  | Região | `regiao` | Idioma, fuso horário | Sempre |
202
202
  | Atalhos | `atalhos` | Lista de atalhos | Sempre |
203
203
  | Sobre | `sobre` | Aplicação, versão, build, ambiente, links, dependências, informações de suporte | Sempre |
@@ -209,7 +209,11 @@ não aparece.
209
209
 
210
210
  Seção **Grupos** (`SpineMyGroupsService`, argos-admin `/me/groups` com o token da sessão): a lista dos grupos da
211
211
  aplicação logada de que a pessoa é membro, cada um com a **função** dela (não confundir com o crachá, que é o tipo do
212
- grupo). Escolher um grupo abre a tela de membros: **Voltar aos grupos** no topo, depois **Incluir membro** e a lista.
212
+ grupo). Escolher um grupo abre a tela de membros: **Voltar aos grupos** no topo, depois **Incluir membro**, a busca e a lista.
213
+ O servidor pagina (`SpineMyGroupsService.members(groupId, { page, limit, search })`, um `Observable`) e a tela pede
214
+ páginas de 200, uma depois da outra, até ter o grupo inteiro: a lista aparece com a primeira e mostra "Carregando
215
+ membros… 400 de 1.250" até terminar. A busca por nome, login ou e-mail é local e instantânea. Falha numa página mostra
216
+ **Tentar de novo**, que continua de onde parou; sair da tela cancela a página pendente.
213
217
  A lista é relida a cada abertura do painel; sem grupo nenhum (ou com um argos-admin que ainda não tem a rota) a seção
214
218
  some. O que cada função faz, com a regra decidida no servidor:
215
219
 
@@ -354,6 +358,15 @@ component: async () => (await import('./admin-audit-page.component')).AdminAudit
354
358
  - **Detecção de mudanças:** todos os componentes do spine são `OnPush`. O componente que o app abre no
355
359
  `ModalService`/`SidePanelService` (como as telas no layout) precisa atualizar a view por signal, `async` ou
356
360
  `markForCheck()` quando o estado muda depois de `await`/`subscribe`.
361
+ - **Abas e divisão de tela:** a área de abas do shell (`hm-workspace`) só se divide em painéis (lado a lado ou
362
+ empilhados, pela bússola do arrasto, pelo botão de painéis, pelo menu da aba ou por atalho) com a preferência **Dividir abas** ligada (Aparência;
363
+ `SpinePreferencesService.splitTabs`, desligada por padrão, guardada no navegador e, com
364
+ `features.accountPreferences`, na conta). Desligada — ao carregar ou ao desligar —, um arranjo com vários painéis é
365
+ fundido num painel só, sem perder aba (a ativa é a do painel que estava focado). No `openTab(url, { beside: true })`
366
+ do `SpineWorkspaceNavService`, `beside` abre num painel novo ao lado só com a preferência ligada; desligada, a aba
367
+ abre no painel focado, como sem `beside`. Ligada, a grade vai até 2 × 2 (os quatro cantos, `SPINE_WORKSPACE_LIMITS`):
368
+ o que passaria disso fica desabilitado na bússola do arrasto, no botão de painéis do fim da fileira, no menu da aba e
369
+ nos atalhos, e `beside` no limite de colunas abre no painel que já está ao lado.
357
370
  - `ModalService`/`SidePanelService` abrem um componente que implementa `ModalContent` (`getResult()`,
358
371
  `isValid?()`). Para diálogos fora do shell, o `@trismegisto/angular` tem `HermesDialogService`.
359
372
 
@@ -481,7 +481,7 @@ class SpineAuditTopCardComponent {
481
481
  </div>
482
482
  }
483
483
  </section>
484
- `, isInline: true, styles: [":host{display:block;min-inline-size:0}.card{border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.card-block{padding:var(--hm-space-sm, 8px)}.muted{color:var(--hm-muted)}.top{display:flex;flex-direction:column;block-size:100%;box-sizing:border-box}.top__head{display:flex;align-items:center;justify-content:space-between;gap:var(--hm-space-sm, 8px);margin:0 0 var(--hm-space-sm, 8px);min-block-size:24px}.top__title{margin:0;font-size:14px;font-weight:600;color:var(--hm-text)}.top__error{margin:var(--hm-space-xs, 4px) 0 0;font-size:12px}.top__pager{display:flex;align-items:center;justify-content:flex-end;gap:var(--hm-space-xs, 4px);margin-block-start:auto;padding-block-start:var(--hm-space-sm, 8px)}.top__range{font-size:12px;color:var(--hm-muted);font-variant-numeric:tabular-nums}.top__buttons{display:inline-flex;gap:2px}.rank{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:2px;transition:opacity .12s ease}.rank--busy{opacity:.55}.rank__item{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:2px var(--hm-space-sm, 8px);inline-size:100%;padding:6px 8px;border:0;border-radius:var(--hm-radius-control);background:transparent;color:var(--hm-text);font:inherit;font-size:13px;text-align:start;cursor:pointer}.rank__item:hover:not(:disabled){background:color-mix(in srgb,var(--hm-action) 8%,transparent)}.rank__item:focus-visible{outline:2px solid var(--hm-action);outline-offset:1px}.rank__item:disabled{cursor:default}.rank__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rank__value{font-variant-numeric:tabular-nums;white-space:nowrap}.rank__fail{color:var(--hm-danger)}.rank__denied{color:var(--hm-warning, #c98a00)}.rank__track{grid-column:1/-1;display:block;block-size:4px;border-radius:2px;background:color-mix(in srgb,var(--hm-text) 8%,transparent);overflow:hidden}.rank__bar{display:flex;justify-content:flex-end;inline-size:100%;block-size:100%;background:var(--hm-success)}.rank__seg{display:block;block-size:100%}.rank__seg--fail{background:var(--hm-danger)}.rank__seg--denied{background:var(--hm-warning)}@media(prefers-reduced-motion:reduce){.rank{transition:none}}:host-context([data-motion=reduce]) .rank{transition:none}\n"], dependencies: [{ 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: SkeletonComponent, selector: "hm-skeleton", inputs: ["shape", "count", "label"] }, { kind: "component", type: SpinnerComponent, selector: "hm-spinner", inputs: ["size", "inverse", "label", "labelVisible", "decorative", "done", "doneLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
484
+ `, isInline: true, styles: [":host{display:block;min-inline-size:0}.card{border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.card-block{padding:var(--hm-space-sm, 8px)}.muted{color:var(--hm-muted)}.top{display:flex;flex-direction:column;block-size:100%;box-sizing:border-box}.top__head{display:flex;align-items:center;justify-content:space-between;gap:var(--hm-space-sm, 8px);margin:0 0 var(--hm-space-sm, 8px);min-block-size:24px}.top__title{margin:0;font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold);color:var(--hm-text)}.top__error{margin:var(--hm-space-xs, 4px) 0 0;font-size:var(--hm-font-size-xs)}.top__pager{display:flex;align-items:center;justify-content:flex-end;gap:var(--hm-space-xs, 4px);margin-block-start:auto;padding-block-start:var(--hm-space-sm, 8px)}.top__range{font-size:var(--hm-font-size-xs);color:var(--hm-muted);font-variant-numeric:tabular-nums}.top__buttons{display:inline-flex;gap:2px}.rank{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:2px;transition:opacity .12s ease}.rank--busy{opacity:.55}.rank__item{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:2px var(--hm-space-sm, 8px);inline-size:100%;padding:6px 8px;border:0;border-radius:var(--hm-radius-control);background:transparent;color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-label);text-align:start;cursor:pointer}.rank__item:hover:not(:disabled){background:color-mix(in srgb,var(--hm-action) 8%,transparent)}.rank__item:focus-visible{outline:2px solid var(--hm-action);outline-offset:1px}.rank__item:disabled{cursor:default}.rank__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rank__value{font-variant-numeric:tabular-nums;white-space:nowrap}.rank__fail{color:var(--hm-danger)}.rank__denied{color:var(--hm-warning, #c98a00)}.rank__track{grid-column:1/-1;display:block;block-size:4px;border-radius:2px;background:color-mix(in srgb,var(--hm-text) 8%,transparent);overflow:hidden}.rank__bar{display:flex;justify-content:flex-end;inline-size:100%;block-size:100%;background:var(--hm-success)}.rank__seg{display:block;block-size:100%}.rank__seg--fail{background:var(--hm-danger)}.rank__seg--denied{background:var(--hm-warning)}@media(prefers-reduced-motion:reduce){.rank{transition:none}}:host-context([data-motion=reduce]) .rank{transition:none}\n"], dependencies: [{ 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: SkeletonComponent, selector: "hm-skeleton", inputs: ["shape", "count", "label"] }, { kind: "component", type: SpinnerComponent, selector: "hm-spinner", inputs: ["size", "inverse", "label", "labelVisible", "decorative", "done", "doneLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
485
485
  }
486
486
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAuditTopCardComponent, decorators: [{
487
487
  type: Component,
@@ -541,7 +541,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
541
541
  </div>
542
542
  }
543
543
  </section>
544
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;min-inline-size:0}.card{border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.card-block{padding:var(--hm-space-sm, 8px)}.muted{color:var(--hm-muted)}.top{display:flex;flex-direction:column;block-size:100%;box-sizing:border-box}.top__head{display:flex;align-items:center;justify-content:space-between;gap:var(--hm-space-sm, 8px);margin:0 0 var(--hm-space-sm, 8px);min-block-size:24px}.top__title{margin:0;font-size:14px;font-weight:600;color:var(--hm-text)}.top__error{margin:var(--hm-space-xs, 4px) 0 0;font-size:12px}.top__pager{display:flex;align-items:center;justify-content:flex-end;gap:var(--hm-space-xs, 4px);margin-block-start:auto;padding-block-start:var(--hm-space-sm, 8px)}.top__range{font-size:12px;color:var(--hm-muted);font-variant-numeric:tabular-nums}.top__buttons{display:inline-flex;gap:2px}.rank{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:2px;transition:opacity .12s ease}.rank--busy{opacity:.55}.rank__item{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:2px var(--hm-space-sm, 8px);inline-size:100%;padding:6px 8px;border:0;border-radius:var(--hm-radius-control);background:transparent;color:var(--hm-text);font:inherit;font-size:13px;text-align:start;cursor:pointer}.rank__item:hover:not(:disabled){background:color-mix(in srgb,var(--hm-action) 8%,transparent)}.rank__item:focus-visible{outline:2px solid var(--hm-action);outline-offset:1px}.rank__item:disabled{cursor:default}.rank__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rank__value{font-variant-numeric:tabular-nums;white-space:nowrap}.rank__fail{color:var(--hm-danger)}.rank__denied{color:var(--hm-warning, #c98a00)}.rank__track{grid-column:1/-1;display:block;block-size:4px;border-radius:2px;background:color-mix(in srgb,var(--hm-text) 8%,transparent);overflow:hidden}.rank__bar{display:flex;justify-content:flex-end;inline-size:100%;block-size:100%;background:var(--hm-success)}.rank__seg{display:block;block-size:100%}.rank__seg--fail{background:var(--hm-danger)}.rank__seg--denied{background:var(--hm-warning)}@media(prefers-reduced-motion:reduce){.rank{transition:none}}:host-context([data-motion=reduce]) .rank{transition:none}\n"] }]
544
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;min-inline-size:0}.card{border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.card-block{padding:var(--hm-space-sm, 8px)}.muted{color:var(--hm-muted)}.top{display:flex;flex-direction:column;block-size:100%;box-sizing:border-box}.top__head{display:flex;align-items:center;justify-content:space-between;gap:var(--hm-space-sm, 8px);margin:0 0 var(--hm-space-sm, 8px);min-block-size:24px}.top__title{margin:0;font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold);color:var(--hm-text)}.top__error{margin:var(--hm-space-xs, 4px) 0 0;font-size:var(--hm-font-size-xs)}.top__pager{display:flex;align-items:center;justify-content:flex-end;gap:var(--hm-space-xs, 4px);margin-block-start:auto;padding-block-start:var(--hm-space-sm, 8px)}.top__range{font-size:var(--hm-font-size-xs);color:var(--hm-muted);font-variant-numeric:tabular-nums}.top__buttons{display:inline-flex;gap:2px}.rank{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:2px;transition:opacity .12s ease}.rank--busy{opacity:.55}.rank__item{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:2px var(--hm-space-sm, 8px);inline-size:100%;padding:6px 8px;border:0;border-radius:var(--hm-radius-control);background:transparent;color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-label);text-align:start;cursor:pointer}.rank__item:hover:not(:disabled){background:color-mix(in srgb,var(--hm-action) 8%,transparent)}.rank__item:focus-visible{outline:2px solid var(--hm-action);outline-offset:1px}.rank__item:disabled{cursor:default}.rank__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rank__value{font-variant-numeric:tabular-nums;white-space:nowrap}.rank__fail{color:var(--hm-danger)}.rank__denied{color:var(--hm-warning, #c98a00)}.rank__track{grid-column:1/-1;display:block;block-size:4px;border-radius:2px;background:color-mix(in srgb,var(--hm-text) 8%,transparent);overflow:hidden}.rank__bar{display:flex;justify-content:flex-end;inline-size:100%;block-size:100%;background:var(--hm-success)}.rank__seg{display:block;block-size:100%}.rank__seg--fail{background:var(--hm-danger)}.rank__seg--denied{background:var(--hm-warning)}@media(prefers-reduced-motion:reduce){.rank{transition:none}}:host-context([data-motion=reduce]) .rank{transition:none}\n"] }]
545
545
  }], propDecorators: { heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: true }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: true }] }], total: [{ type: i0.Input, args: [{ isSignal: true, alias: "total", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], failed: [{ type: i0.Input, args: [{ isSignal: true, alias: "failed", required: false }] }], picked: [{ type: i0.Output, args: ["picked"] }], offsetChange: [{ type: i0.Output, args: ["offsetChange"] }] } });
546
546
 
547
547
  /** Eventos por página da tabela, de saída (o servidor aceita até 200 — `AUDIT_MAX_PAGE_SIZE`). */
@@ -1333,7 +1333,7 @@ class SpineAuditComponent {
1333
1333
  return outcome === 'success' ? 'success' : outcome === 'failure' ? 'danger' : outcome === 'denied' ? 'warning' : 'neutral';
1334
1334
  }
1335
1335
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAuditComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1336
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: SpineAuditComponent, isStandalone: true, selector: "spine-audit", inputs: { scope: { classPropertyName: "scope", publicName: "scope", isSignal: true, isRequired: true, transformFunction: null }, clientId: { classPropertyName: "clientId", publicName: "clientId", isSignal: true, isRequired: false, transformFunction: null }, adminApi: { classPropertyName: "adminApi", publicName: "adminApi", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"audit\" [attr.aria-busy]=\"loading() || pageLoading()\">\n <!-- Per\u00EDodo (escolha r\u00E1pida; as datas da faixa de filtros vencem) + atualizar. -->\n <div class=\"audit__toolbar\">\n <!-- Unidade: por a\u00E7\u00E3o (uma linha por trace, padr\u00E3o) ou por evento (cada registro). -->\n <hm-segmented-control\n [label]=\"text.unitLabel\"\n [options]=\"unitOptions\"\n [value]=\"unit()\"\n (valueChange)=\"setUnit($event)\"\n size=\"small\"\n emphasis=\"action\"\n />\n <hm-segmented-control\n [label]=\"text.period\"\n [options]=\"periodOptions\"\n [value]=\"period()\"\n (valueChange)=\"setPeriod($event)\"\n size=\"small\"\n emphasis=\"action\"\n />\n @if (customDates()) {\n <span class=\"muted audit__period-note\">{{ text.periodFromFilters }}</span>\n }\n <span class=\"audit__toolbar-end\">\n @if (lastUpdated(); as at) {\n <span class=\"muted audit__updated\" aria-live=\"polite\">{{ fmt(text.updatedAt, { time: clock(at) }) }}</span>\n }\n <hm-switch [label]=\"text.autoRefresh\" [value]=\"autoRefresh()\" (valueChange)=\"autoRefresh.set($event)\" />\n <hm-button emphasis=\"medium\" size=\"small\" icon=\"refresh\" [loading]=\"loading()\" (clicked)=\"reload()\">{{ text.refresh }}</hm-button>\n </span>\n </div>\n\n <hm-filter-bar\n class=\"audit__filters\"\n [label]=\"text.filters\"\n [filters]=\"filterFields()\"\n [value]=\"filterValue()\"\n (valueChange)=\"setFilters($event)\"\n [savedFilters]=\"true\"\n [storageKey]=\"storageKey()\"\n />\n\n @if (errorKind()) {\n <hm-state-screen\n class=\"audit__state\"\n [situation]=\"errorSituation()\"\n [heading]=\"text.errorTitle\"\n [description]=\"errorText()\"\n [action]=\"errorKind() === 'generic' ? text.retry : (hasFilters() ? text.clearFilters : '')\"\n actionIcon=\"refresh\"\n (actionTriggered)=\"errorKind() === 'generic' ? reload() : clearFilters()\"\n />\n } @else if (empty()) {\n <hm-state-screen\n class=\"audit__state\"\n situation=\"empty\"\n [heading]=\"unitText().emptyTitle\"\n [description]=\"text.emptyText\"\n [action]=\"hasFilters() ? text.clearFilters : ''\"\n (actionTriggered)=\"clearFilters()\"\n />\n } @else {\n <!-- KPIs: falhas/negados s\u00E3o filtros (altern\u00E2ncia). -->\n <section class=\"audit__kpis\" [attr.aria-label]=\"text.kpis\">\n <hm-stat-card\n icon=\"list\"\n [label]=\"unitText().kpiTotal\"\n [value]=\"number(stats()?.total ?? 0)\"\n [description]=\"unitText().kpiTotalDescription\"\n [loading]=\"loading() && !stats()\"\n />\n <hm-stat-card\n icon=\"error-standard\"\n [label]=\"text.kpiFailures\"\n [value]=\"number(stats()?.by_outcome?.failure ?? 0)\"\n [intent]=\"(stats()?.by_outcome?.failure ?? 0) > 0 ? 'danger' : 'neutral'\"\n [description]=\"fmt(text.kpiFilterHint, { share: share(stats()?.by_outcome?.failure ?? 0) })\"\n [loading]=\"loading() && !stats()\"\n selectable\n [selected]=\"selectedOutcome() === 'failure'\"\n (selectedChange)=\"toggleOutcome('failure', $event)\"\n />\n <hm-stat-card\n icon=\"ban\"\n [label]=\"text.kpiDenied\"\n [value]=\"number(stats()?.by_outcome?.denied ?? 0)\"\n [intent]=\"(stats()?.by_outcome?.denied ?? 0) > 0 ? 'warning' : 'neutral'\"\n [description]=\"fmt(text.kpiFilterHint, { share: share(stats()?.by_outcome?.denied ?? 0) })\"\n [loading]=\"loading() && !stats()\"\n selectable\n [selected]=\"selectedOutcome() === 'denied'\"\n (selectedChange)=\"toggleOutcome('denied', $event)\"\n />\n </section>\n\n <!-- Linha do tempo: barras empilhadas em CSS (sucesso embaixo, falha, negado). Cada barra tem\n o resumo em texto (title + aria-label); o gr\u00E1fico n\u00E3o \u00E9 a \u00FAnica forma de ler o n\u00FAmero. -->\n <section class=\"card card-block audit__timeline\" aria-labelledby=\"audit-timeline-title\">\n <h3 class=\"audit__section-title\" id=\"audit-timeline-title\">{{ text.timeline }}</h3>\n @if (hasTimeline()) {\n <ol class=\"timeline\" [attr.aria-label]=\"fmt(unitText().timelineSummary, { from: timelineEdges()?.from ?? '', to: timelineEdges()?.to ?? '' })\">\n @for (bar of timelineBars(); track bar.key) {\n <li class=\"timeline__bar\" [attr.title]=\"bar.summary\" [attr.aria-label]=\"bar.summary\">\n <span class=\"timeline__seg timeline__seg--denied\" [style.height.%]=\"bar.denied\"></span>\n <span class=\"timeline__seg timeline__seg--fail\" [style.height.%]=\"bar.fail\"></span>\n <span class=\"timeline__seg timeline__seg--ok\" [style.height.%]=\"bar.ok\"></span>\n </li>\n }\n </ol>\n <div class=\"timeline__axis\" aria-hidden=\"true\">\n <span>{{ timelineEdges()?.from }}</span>\n <span>{{ timelineEdges()?.to }}</span>\n </div>\n <ul class=\"timeline__legend\">\n <li><span class=\"swatch swatch--ok\"></span>{{ text.outcomeSuccess }}</li>\n <li><span class=\"swatch swatch--fail\"></span>{{ text.outcomeFailure }}</li>\n <li><span class=\"swatch swatch--denied\"></span>{{ text.outcomeDenied }}</li>\n </ul>\n } @else if (loading()) {\n <hm-skeleton shape=\"card\" [label]=\"text.loading\" />\n } @else {\n <p class=\"muted\">{{ unitText().timelineEmpty }}</p>\n }\n </section>\n\n <!-- Rankings paginados no servidor (tops/:kind). Clicar num item vira filtro. -->\n <div class=\"audit__tops\" [class.audit__tops--three]=\"multiApp()\">\n @if (multiApp()) {\n <spine-audit-top-card\n class=\"audit__top audit__top--apps\"\n [heading]=\"text.topApps\"\n [rows]=\"topAppRows()\"\n [total]=\"tops.apps().total\"\n [offset]=\"tops.apps().offset\"\n [max]=\"tops.apps().max\"\n [pageSize]=\"topPageSize\"\n [loading]=\"tops.apps().loading\"\n [failed]=\"tops.apps().failed\"\n (offsetChange)=\"loadTop('apps', $event)\"\n (picked)=\"pickTop('apps', $event)\"\n />\n }\n <spine-audit-top-card\n class=\"audit__top audit__top--operations\"\n [heading]=\"text.topOperations\"\n [rows]=\"topOperationRows()\"\n [total]=\"tops.operations().total\"\n [offset]=\"tops.operations().offset\"\n [max]=\"tops.operations().max\"\n [pageSize]=\"topPageSize\"\n [loading]=\"tops.operations().loading\"\n [failed]=\"tops.operations().failed\"\n (offsetChange)=\"loadTop('operations', $event)\"\n (picked)=\"pickTop('operations', $event)\"\n />\n <spine-audit-top-card\n class=\"audit__top audit__top--users\"\n [heading]=\"text.topUsers\"\n [rows]=\"topUserRows()\"\n [total]=\"tops.users().total\"\n [offset]=\"tops.users().offset\"\n [max]=\"tops.users().max\"\n [pageSize]=\"topPageSize\"\n [loading]=\"tops.users().loading\"\n [failed]=\"tops.users().failed\"\n (offsetChange)=\"loadTop('users', $event)\"\n (picked)=\"pickTop('users', $event)\"\n />\n </div>\n\n <!-- Tabela: pagina\u00E7\u00E3o no servidor. A grade n\u00E3o corta nada ([paginated]=false): mostra a p\u00E1gina que veio\n (limit = itens por p\u00E1gina) e o rodap\u00E9 usa o total do resumo (mesma consulta). Esqueleto s\u00F3 na 1\u00AA\n carga; nas seguintes as linhas ficam e o indicador aparece por cima (sem piscar). -->\n <div class=\"audit__grid\" [class.audit__grid--busy]=\"gridBusy()\" [attr.aria-busy]=\"gridBusy()\">\n @if (gridBusy()) {\n <hm-progress class=\"audit__grid-progress\" indeterminate [label]=\"text.updating\" labelHidden />\n }\n <hm-data-grid\n [caption]=\"unitText().caption\"\n [columns]=\"columns()\"\n [rows]=\"events()\"\n [rowId]=\"rowId\"\n [loading]=\"gridSkeleton()\"\n [filterable]=\"false\"\n [paginated]=\"false\"\n [totalItems]=\"total()\"\n [pageState]=\"pageState()\"\n (pageStateChange)=\"changePage($event)\"\n [pageSizes]=\"pageSizes\"\n [itemsLabel]=\"unitText().pageItems\"\n [pageSizeLabel]=\"text.pageSize\"\n remoteSort\n rowClickable\n (rowClicked)=\"openRow($event)\"\n >\n <ng-template hmCell=\"operation\" let-item>\n <span class=\"audit__op\" [attr.title]=\"item.operation_label || null\">{{ operationName(item) }}</span>\n </ng-template>\n <ng-template hmCell=\"spans\" let-item>\n <span class=\"audit__spans\" [attr.title]=\"spansHint(item) || null\">\n {{ number(item.span_count ?? 1) }}\n @if (item.orphan) {\n <hm-tag class=\"audit__orphan\" [name]=\"text.orphanTag\" color=\"neutral\" />\n }\n </span>\n </ng-template>\n <ng-template hmCell=\"outcome\" let-item>\n <hm-tag [name]=\"outcomeLabel(item.outcome)\" [color]=\"outcomeColor(item.outcome)\" />\n </ng-template>\n </hm-data-grid>\n </div>\n\n @if (pageFailed()) {\n <hm-alert class=\"audit__page-error\" variant=\"error\" size=\"small\">{{ unitText().pageError }}</hm-alert>\n }\n }\n</div>\n\n<!-- Detalhe do evento + a cadeia (trace). -->\n<hm-drawer [open]=\"detailOpen()\" (openChange)=\"onDetailOpenChange($event)\" [heading]=\"showingAction() ? text.actionDetailHeading : text.detailHeading\">\n @if (selected(); as ev) {\n <div class=\"detail\">\n <div class=\"detail__head\">\n <strong class=\"detail__title\">{{ operationName(ev) }}</strong>\n <hm-tag [name]=\"outcomeLabel(headOutcome())\" [color]=\"outcomeColor(headOutcome())\" />\n </div>\n @if (showingAction() && selectedAction()?.orphan) {\n <hm-alert class=\"detail__orphan\" variant=\"info\" size=\"small\">{{ text.orphanNote }}</hm-alert>\n }\n @if (ev.operation_label && ev.operation_identifier) {\n <p class=\"muted detail__description\">{{ ev.operation_label }}</p>\n }\n\n @if (detailLoading()) {\n <p class=\"muted\" role=\"status\">{{ text.detailLoading }}</p>\n }\n @if (detailFailed()) {\n <hm-alert variant=\"warning\" size=\"small\">{{ text.detailError }}</hm-alert>\n }\n\n <dl class=\"detail__list\">\n @for (field of detailFields(); track field.label) {\n <div class=\"detail__row\">\n <dt>{{ field.label }}</dt>\n <dd>\n @if (field.code) { <code>{{ field.value }}</code> } @else { {{ field.value }} }\n </dd>\n </div>\n }\n </dl>\n\n @if (detailsJson()) {\n <h3 class=\"audit__section-title\">{{ text.fieldDetails }}</h3>\n <pre class=\"detail__json\">{{ detailsJson() }}</pre>\n }\n\n <!-- Por a\u00E7\u00E3o, os spans sobem para logo abaixo do cabe\u00E7alho (order no CSS); por evento, ficam no fim. -->\n @if (ev.trace_id) {\n <div class=\"detail__chain\" [class.detail__chain--first]=\"showingAction()\">\n <div class=\"detail__actions\">\n @if (!trace() && !traceLoading()) {\n <hm-button emphasis=\"medium\" size=\"small\" icon=\"tree-view\" (clicked)=\"showTrace()\">{{ text.viewTrace }}</hm-button>\n }\n <hm-button emphasis=\"minimal\" size=\"small\" icon=\"filter\" (clicked)=\"filterByTrace(ev.trace_id)\">{{ text.filterByTrace }}</hm-button>\n </div>\n @if (traceLoading()) {\n <p class=\"muted\" role=\"status\">{{ text.traceLoading }}</p>\n }\n @if (traceFailed()) {\n <hm-alert variant=\"error\" size=\"small\">{{ text.traceError }}</hm-alert>\n }\n @if (trace(); as tr) {\n <section class=\"trace\" aria-labelledby=\"audit-trace-title\">\n <h3 class=\"audit__section-title\" id=\"audit-trace-title\">{{ showingAction() ? text.spansHeading : text.traceHeading }}</h3>\n @if (tr.hidden_count) {\n <hm-alert class=\"trace__hidden\" variant=\"info\" size=\"small\">{{ plural(text.traceHidden, tr.hidden_count) }}</hm-alert>\n }\n @if (!tr.items.length) {\n <p class=\"muted\">{{ text.traceEmpty }}</p>\n } @else if (spanTimeline(); as tl) {\n <p class=\"muted trace__summary\">{{ fmt(text.spansSummary, { count: number(tl.rows.length), duration: duration(tl.totalMs) }) }}</p>\n <!-- Linha do tempo: cada span \u00E9 uma linha (recuo = profundidade pela cadeia parent_span_id) com a\n barra posicionada no tempo, proporcional \u00E0 dura\u00E7\u00E3o. O texto da linha (e o aria-label) traz\n tudo o que a barra mostra. -->\n <ol class=\"spans\">\n @for (row of tl.rows; track row.event.event_id) {\n <li class=\"spans__item\" [class.spans__item--current]=\"row.event.event_id === ev.event_id\">\n <button type=\"button\" class=\"spans__link\"\n [attr.aria-current]=\"row.event.event_id === ev.event_id ? 'true' : null\"\n [attr.aria-label]=\"spanAria(row)\"\n (click)=\"openEvent(row.event, true)\">\n <span class=\"spans__head\" [style.padding-inline-start.rem]=\"row.depth * 1\">\n <span class=\"spans__app\">{{ row.event.app_name ?? '' }}</span>\n <code class=\"spans__op\">{{ operationName(row.event) }}</code>\n </span>\n <hm-tag class=\"spans__tag\" [name]=\"outcomeLabel(row.event.outcome)\" [color]=\"outcomeColor(row.event.outcome)\" />\n <span class=\"spans__dur\">{{ duration(row.event.duration_ms) }}</span>\n <span class=\"spans__track\" aria-hidden=\"true\">\n <span class=\"spans__bar spans__bar--{{ row.event.outcome }}\" [style.left.%]=\"row.left\" [style.width.%]=\"row.width\"></span>\n </span>\n </button>\n </li>\n }\n </ol>\n }\n </section>\n }\n </div>\n }\n </div>\n }\n</hm-drawer>\n", styles: [".card{border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.card-block{padding:var(--hm-space-sm, 8px)}.muted{color:var(--hm-muted)}:host{display:block}.audit{display:flex;flex-direction:column;gap:var(--hm-space-md, 16px)}.audit__toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:var(--hm-space-sm, 8px)}.audit__period-note{font-size:13px}.audit__toolbar-end{display:inline-flex;align-items:center;flex-wrap:wrap;gap:var(--hm-space-sm, 8px) var(--hm-space-md, 16px);margin-inline-start:auto}.audit__updated{font-size:12px;font-variant-numeric:tabular-nums}.audit__state{min-block-size:320px}.audit__section-title{margin:0 0 var(--hm-space-sm, 8px);font-size:14px;font-weight:600;color:var(--hm-text)}.audit__kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:var(--hm-space-sm, 8px);align-items:stretch}.timeline{display:flex;align-items:flex-end;gap:2px;block-size:120px;margin:0;padding:0;list-style:none;border-block-end:1px solid var(--hm-border)}.timeline__bar{flex:1 1 0;min-inline-size:2px;block-size:100%;display:flex;flex-direction:column;justify-content:flex-end;border-radius:2px 2px 0 0;overflow:hidden}.timeline__bar:hover{background:color-mix(in srgb,var(--hm-text) 6%,transparent)}.timeline__seg{display:block;inline-size:100%}.timeline__seg--ok,.swatch--ok{background:color-mix(in srgb,var(--hm-action) 55%,transparent)}.timeline__seg--fail,.swatch--fail{background:var(--hm-danger)}.timeline__seg--denied,.swatch--denied{background:var(--hm-warning, #c98a00)}.timeline__axis{display:flex;justify-content:space-between;margin-block-start:4px;font-size:12px;color:var(--hm-muted)}.timeline__legend{display:flex;flex-wrap:wrap;gap:var(--hm-space-sm, 8px) var(--hm-space-md, 16px);margin:var(--hm-space-sm, 8px) 0 0;padding:0;list-style:none;font-size:12px;color:var(--hm-muted)}.timeline__legend li{display:inline-flex;align-items:center;gap:6px}.swatch{display:inline-block;inline-size:10px;block-size:10px;border-radius:2px}.audit__tops{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:var(--hm-space-sm, 8px);align-items:stretch}.audit__grid{position:relative;display:block}.audit__grid-progress{position:absolute;inset-inline:0;inset-block-start:0;z-index:2;pointer-events:none}.audit__grid--busy hm-data-grid{opacity:.6;transition:opacity .12s ease}.audit__op{font-family:var(--hm-font-mono, ui-monospace, monospace);font-size:12px}.audit__page-error{display:block}@media(prefers-reduced-motion:reduce){.audit__grid--busy hm-data-grid{transition:none}}:host-context([data-motion=reduce]) .audit__grid--busy hm-data-grid{transition:none}.detail{display:flex;flex-direction:column;gap:var(--hm-space-sm, 8px)}.detail__head{display:flex;align-items:center;gap:var(--hm-space-sm, 8px);flex-wrap:wrap}.detail__title{font-size:16px}.detail__list{display:grid;grid-template-columns:minmax(8rem,max-content) minmax(0,1fr);gap:6px var(--hm-space-md, 16px);margin:0}.detail__row{display:contents}.detail__row dt{color:var(--hm-muted);font-size:13px}.detail__row dd{margin:0;font-size:13px;overflow-wrap:anywhere}.detail__json{margin:0;padding:var(--hm-space-sm, 8px);max-block-size:280px;overflow:auto;border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);background:color-mix(in srgb,var(--hm-text) 4%,transparent);font-size:12px}.detail__description{margin:0;font-size:13px}.detail__actions{display:flex;flex-wrap:wrap;gap:var(--hm-space-sm, 8px)}.trace{margin-block-start:var(--hm-space-sm, 8px)}.trace__hidden{margin-block-end:var(--hm-space-sm, 8px)}.detail__head,.detail__description,.detail__orphan{order:-3}.detail__chain{display:flex;flex-direction:column;gap:var(--hm-space-sm, 8px)}.detail__chain--first{order:-2}.trace__summary{margin:0 0 var(--hm-space-xs, 4px);font-size:13px}.spans{margin:0;padding:0;list-style:none;border-block-start:1px solid var(--hm-border)}.spans__item{border-block-end:1px solid var(--hm-border)}.spans__item--current .spans__link{background:color-mix(in srgb,var(--hm-action) 10%,transparent);box-shadow:inset 2px 0 0 var(--hm-action)}.spans__link{display:grid;grid-template-columns:minmax(0,1fr) auto auto;grid-template-areas:\"head tag dur\" \"track track track\";align-items:center;gap:4px var(--hm-space-sm, 8px);inline-size:100%;padding:6px 8px;border:0;background:transparent;color:var(--hm-text);font:inherit;font-size:13px;text-align:start;cursor:pointer}.spans__link:hover{background:color-mix(in srgb,var(--hm-action) 6%,transparent)}.spans__link:focus-visible{outline:2px solid var(--hm-action);outline-offset:-2px}.spans__head{grid-area:head;display:flex;flex-direction:column;min-inline-size:0}.spans__app{color:var(--hm-muted);font-size:12px}.spans__op{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--hm-font-mono, ui-monospace, monospace);font-size:12px}.spans__tag{grid-area:tag}.spans__dur{grid-area:dur;min-inline-size:4.5rem;text-align:end;font-variant-numeric:tabular-nums;color:var(--hm-muted)}.spans__track{grid-area:track;position:relative;block-size:6px;border-radius:3px;background:color-mix(in srgb,var(--hm-text) 6%,transparent)}.spans__bar{position:absolute;inset-block:0;border-radius:3px;background:color-mix(in srgb,var(--hm-action) 70%,transparent)}.spans__bar--failure{background:var(--hm-danger)}.spans__bar--denied{background:var(--hm-warning, #c98a00)}.audit__spans{display:inline-flex;align-items:center;gap:6px;font-variant-numeric:tabular-nums}\n"], dependencies: [{ kind: "component", type: AlertComponent, selector: "hm-alert", inputs: ["variant", "appearance", "heading", "closable", "size", "showIcon", "link", "linkLabel", "linkTarget", "dismissKey", "duration"], outputs: ["closed"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "directive", type: DataGridCellDirective, selector: "ng-template[hmCell]", inputs: ["hmCell"] }, { 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: "component", type: DrawerComponent, selector: "hm-drawer", inputs: ["open", "heading", "size", "position", "pinned", "closeLabel", "closable", "beforeClose"], outputs: ["openChange", "closed"] }, { kind: "component", type: FilterBarComponent, selector: "hm-filter-bar", inputs: ["filters", "value", "label", "savedFilters", "storageKey", "storage", "applyDefault"], outputs: ["valueChange", "savedChange", "savedApplied"] }, { kind: "component", type: ProgressComponent, selector: "hm-progress", inputs: ["value", "max", "label", "labelHidden", "showValue", "indeterminate", "intent", "size", "hint"] }, { kind: "component", type: SegmentedControlComponent, selector: "hm-segmented-control", inputs: ["options", "value", "label", "size", "emphasis"], outputs: ["valueChange"] }, { kind: "component", type: SkeletonComponent, selector: "hm-skeleton", inputs: ["shape", "count", "label"] }, { kind: "component", type: SpineAuditTopCardComponent, selector: "spine-audit-top-card", inputs: ["heading", "rows", "total", "offset", "pageSize", "max", "loading", "failed"], outputs: ["picked", "offsetChange"] }, { kind: "component", type: StatCardComponent, selector: "hm-stat-card", inputs: ["icon", "label", "value", "intent", "fill", "description", "details", "trend", "hint", "size", "selectable", "selected", "loading"], outputs: ["selectedChange"] }, { kind: "component", type: StateScreenComponent, selector: "hm-state-screen", inputs: ["heading", "description", "situation", "action", "actionIcon"], outputs: ["actionTriggered"] }, { kind: "component", type: SwitchComponent, selector: "hm-switch", inputs: ["value", "label", "hint", "labelPosition", "loading"], outputs: ["valueChange"] }, { kind: "component", type: TagComponent, selector: "hm-tag", inputs: ["name", "color", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1336
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: SpineAuditComponent, isStandalone: true, selector: "spine-audit", inputs: { scope: { classPropertyName: "scope", publicName: "scope", isSignal: true, isRequired: true, transformFunction: null }, clientId: { classPropertyName: "clientId", publicName: "clientId", isSignal: true, isRequired: false, transformFunction: null }, adminApi: { classPropertyName: "adminApi", publicName: "adminApi", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"audit\" [attr.aria-busy]=\"loading() || pageLoading()\">\n <!-- Per\u00EDodo (escolha r\u00E1pida; as datas da faixa de filtros vencem) + atualizar. -->\n <div class=\"audit__toolbar\">\n <!-- Unidade: por a\u00E7\u00E3o (uma linha por trace, padr\u00E3o) ou por evento (cada registro). -->\n <hm-segmented-control\n [label]=\"text.unitLabel\"\n [options]=\"unitOptions\"\n [value]=\"unit()\"\n (valueChange)=\"setUnit($event)\"\n size=\"small\"\n emphasis=\"action\"\n />\n <hm-segmented-control\n [label]=\"text.period\"\n [options]=\"periodOptions\"\n [value]=\"period()\"\n (valueChange)=\"setPeriod($event)\"\n size=\"small\"\n emphasis=\"action\"\n />\n @if (customDates()) {\n <span class=\"muted audit__period-note\">{{ text.periodFromFilters }}</span>\n }\n <span class=\"audit__toolbar-end\">\n @if (lastUpdated(); as at) {\n <span class=\"muted audit__updated\" aria-live=\"polite\">{{ fmt(text.updatedAt, { time: clock(at) }) }}</span>\n }\n <hm-switch [label]=\"text.autoRefresh\" [value]=\"autoRefresh()\" (valueChange)=\"autoRefresh.set($event)\" />\n <hm-button emphasis=\"medium\" size=\"small\" icon=\"refresh\" [loading]=\"loading()\" (clicked)=\"reload()\">{{ text.refresh }}</hm-button>\n </span>\n </div>\n\n <hm-filter-bar\n class=\"audit__filters\"\n [label]=\"text.filters\"\n [filters]=\"filterFields()\"\n [value]=\"filterValue()\"\n (valueChange)=\"setFilters($event)\"\n [savedFilters]=\"true\"\n [storageKey]=\"storageKey()\"\n />\n\n @if (errorKind()) {\n <hm-state-screen\n class=\"audit__state\"\n [situation]=\"errorSituation()\"\n [heading]=\"text.errorTitle\"\n [description]=\"errorText()\"\n [action]=\"errorKind() === 'generic' ? text.retry : (hasFilters() ? text.clearFilters : '')\"\n actionIcon=\"refresh\"\n (actionTriggered)=\"errorKind() === 'generic' ? reload() : clearFilters()\"\n />\n } @else if (empty()) {\n <hm-state-screen\n class=\"audit__state\"\n situation=\"empty\"\n [heading]=\"unitText().emptyTitle\"\n [description]=\"text.emptyText\"\n [action]=\"hasFilters() ? text.clearFilters : ''\"\n (actionTriggered)=\"clearFilters()\"\n />\n } @else {\n <!-- KPIs: falhas/negados s\u00E3o filtros (altern\u00E2ncia). -->\n <section class=\"audit__kpis\" [attr.aria-label]=\"text.kpis\">\n <hm-stat-card\n icon=\"list\"\n [label]=\"unitText().kpiTotal\"\n [value]=\"number(stats()?.total ?? 0)\"\n [description]=\"unitText().kpiTotalDescription\"\n [loading]=\"loading() && !stats()\"\n />\n <hm-stat-card\n icon=\"error-standard\"\n [label]=\"text.kpiFailures\"\n [value]=\"number(stats()?.by_outcome?.failure ?? 0)\"\n [intent]=\"(stats()?.by_outcome?.failure ?? 0) > 0 ? 'danger' : 'neutral'\"\n [description]=\"fmt(text.kpiFilterHint, { share: share(stats()?.by_outcome?.failure ?? 0) })\"\n [loading]=\"loading() && !stats()\"\n selectable\n [selected]=\"selectedOutcome() === 'failure'\"\n (selectedChange)=\"toggleOutcome('failure', $event)\"\n />\n <hm-stat-card\n icon=\"ban\"\n [label]=\"text.kpiDenied\"\n [value]=\"number(stats()?.by_outcome?.denied ?? 0)\"\n [intent]=\"(stats()?.by_outcome?.denied ?? 0) > 0 ? 'warning' : 'neutral'\"\n [description]=\"fmt(text.kpiFilterHint, { share: share(stats()?.by_outcome?.denied ?? 0) })\"\n [loading]=\"loading() && !stats()\"\n selectable\n [selected]=\"selectedOutcome() === 'denied'\"\n (selectedChange)=\"toggleOutcome('denied', $event)\"\n />\n </section>\n\n <!-- Linha do tempo: barras empilhadas em CSS (sucesso embaixo, falha, negado). Cada barra tem\n o resumo em texto (title + aria-label); o gr\u00E1fico n\u00E3o \u00E9 a \u00FAnica forma de ler o n\u00FAmero. -->\n <section class=\"card card-block audit__timeline\" aria-labelledby=\"audit-timeline-title\">\n <h3 class=\"audit__section-title\" id=\"audit-timeline-title\">{{ text.timeline }}</h3>\n @if (hasTimeline()) {\n <ol class=\"timeline\" [attr.aria-label]=\"fmt(unitText().timelineSummary, { from: timelineEdges()?.from ?? '', to: timelineEdges()?.to ?? '' })\">\n @for (bar of timelineBars(); track bar.key) {\n <li class=\"timeline__bar\" [attr.title]=\"bar.summary\" [attr.aria-label]=\"bar.summary\">\n <span class=\"timeline__seg timeline__seg--denied\" [style.height.%]=\"bar.denied\"></span>\n <span class=\"timeline__seg timeline__seg--fail\" [style.height.%]=\"bar.fail\"></span>\n <span class=\"timeline__seg timeline__seg--ok\" [style.height.%]=\"bar.ok\"></span>\n </li>\n }\n </ol>\n <div class=\"timeline__axis\" aria-hidden=\"true\">\n <span>{{ timelineEdges()?.from }}</span>\n <span>{{ timelineEdges()?.to }}</span>\n </div>\n <ul class=\"timeline__legend\">\n <li><span class=\"swatch swatch--ok\"></span>{{ text.outcomeSuccess }}</li>\n <li><span class=\"swatch swatch--fail\"></span>{{ text.outcomeFailure }}</li>\n <li><span class=\"swatch swatch--denied\"></span>{{ text.outcomeDenied }}</li>\n </ul>\n } @else if (loading()) {\n <hm-skeleton shape=\"card\" [label]=\"text.loading\" />\n } @else {\n <p class=\"muted\">{{ unitText().timelineEmpty }}</p>\n }\n </section>\n\n <!-- Rankings paginados no servidor (tops/:kind). Clicar num item vira filtro. -->\n <div class=\"audit__tops\" [class.audit__tops--three]=\"multiApp()\">\n @if (multiApp()) {\n <spine-audit-top-card\n class=\"audit__top audit__top--apps\"\n [heading]=\"text.topApps\"\n [rows]=\"topAppRows()\"\n [total]=\"tops.apps().total\"\n [offset]=\"tops.apps().offset\"\n [max]=\"tops.apps().max\"\n [pageSize]=\"topPageSize\"\n [loading]=\"tops.apps().loading\"\n [failed]=\"tops.apps().failed\"\n (offsetChange)=\"loadTop('apps', $event)\"\n (picked)=\"pickTop('apps', $event)\"\n />\n }\n <spine-audit-top-card\n class=\"audit__top audit__top--operations\"\n [heading]=\"text.topOperations\"\n [rows]=\"topOperationRows()\"\n [total]=\"tops.operations().total\"\n [offset]=\"tops.operations().offset\"\n [max]=\"tops.operations().max\"\n [pageSize]=\"topPageSize\"\n [loading]=\"tops.operations().loading\"\n [failed]=\"tops.operations().failed\"\n (offsetChange)=\"loadTop('operations', $event)\"\n (picked)=\"pickTop('operations', $event)\"\n />\n <spine-audit-top-card\n class=\"audit__top audit__top--users\"\n [heading]=\"text.topUsers\"\n [rows]=\"topUserRows()\"\n [total]=\"tops.users().total\"\n [offset]=\"tops.users().offset\"\n [max]=\"tops.users().max\"\n [pageSize]=\"topPageSize\"\n [loading]=\"tops.users().loading\"\n [failed]=\"tops.users().failed\"\n (offsetChange)=\"loadTop('users', $event)\"\n (picked)=\"pickTop('users', $event)\"\n />\n </div>\n\n <!-- Tabela: pagina\u00E7\u00E3o no servidor. A grade n\u00E3o corta nada ([paginated]=false): mostra a p\u00E1gina que veio\n (limit = itens por p\u00E1gina) e o rodap\u00E9 usa o total do resumo (mesma consulta). Esqueleto s\u00F3 na 1\u00AA\n carga; nas seguintes as linhas ficam e o indicador aparece por cima (sem piscar). -->\n <div class=\"audit__grid\" [class.audit__grid--busy]=\"gridBusy()\" [attr.aria-busy]=\"gridBusy()\">\n @if (gridBusy()) {\n <hm-progress class=\"audit__grid-progress\" indeterminate [label]=\"text.updating\" labelHidden />\n }\n <hm-data-grid\n [caption]=\"unitText().caption\"\n [columns]=\"columns()\"\n [rows]=\"events()\"\n [rowId]=\"rowId\"\n [loading]=\"gridSkeleton()\"\n [filterable]=\"false\"\n [paginated]=\"false\"\n [totalItems]=\"total()\"\n [pageState]=\"pageState()\"\n (pageStateChange)=\"changePage($event)\"\n [pageSizes]=\"pageSizes\"\n [itemsLabel]=\"unitText().pageItems\"\n [pageSizeLabel]=\"text.pageSize\"\n remoteSort\n rowClickable\n (rowClicked)=\"openRow($event)\"\n >\n <ng-template hmCell=\"operation\" let-item>\n <span class=\"audit__op\" [attr.title]=\"item.operation_label || null\">{{ operationName(item) }}</span>\n </ng-template>\n <ng-template hmCell=\"spans\" let-item>\n <span class=\"audit__spans\" [attr.title]=\"spansHint(item) || null\">\n {{ number(item.span_count ?? 1) }}\n @if (item.orphan) {\n <hm-tag class=\"audit__orphan\" [name]=\"text.orphanTag\" color=\"neutral\" />\n }\n </span>\n </ng-template>\n <ng-template hmCell=\"outcome\" let-item>\n <hm-tag [name]=\"outcomeLabel(item.outcome)\" [color]=\"outcomeColor(item.outcome)\" />\n </ng-template>\n </hm-data-grid>\n </div>\n\n @if (pageFailed()) {\n <hm-alert class=\"audit__page-error\" variant=\"error\" size=\"small\">{{ unitText().pageError }}</hm-alert>\n }\n }\n</div>\n\n<!-- Detalhe do evento + a cadeia (trace). -->\n<hm-drawer [open]=\"detailOpen()\" (openChange)=\"onDetailOpenChange($event)\" [heading]=\"showingAction() ? text.actionDetailHeading : text.detailHeading\">\n @if (selected(); as ev) {\n <div class=\"detail\">\n <div class=\"detail__head\">\n <strong class=\"detail__title\">{{ operationName(ev) }}</strong>\n <hm-tag [name]=\"outcomeLabel(headOutcome())\" [color]=\"outcomeColor(headOutcome())\" />\n </div>\n @if (showingAction() && selectedAction()?.orphan) {\n <hm-alert class=\"detail__orphan\" variant=\"info\" size=\"small\">{{ text.orphanNote }}</hm-alert>\n }\n @if (ev.operation_label && ev.operation_identifier) {\n <p class=\"muted detail__description\">{{ ev.operation_label }}</p>\n }\n\n @if (detailLoading()) {\n <p class=\"muted\" role=\"status\">{{ text.detailLoading }}</p>\n }\n @if (detailFailed()) {\n <hm-alert variant=\"warning\" size=\"small\">{{ text.detailError }}</hm-alert>\n }\n\n <dl class=\"detail__list\">\n @for (field of detailFields(); track field.label) {\n <div class=\"detail__row\">\n <dt>{{ field.label }}</dt>\n <dd>\n @if (field.code) { <code>{{ field.value }}</code> } @else { {{ field.value }} }\n </dd>\n </div>\n }\n </dl>\n\n @if (detailsJson()) {\n <h3 class=\"audit__section-title\">{{ text.fieldDetails }}</h3>\n <pre class=\"detail__json\">{{ detailsJson() }}</pre>\n }\n\n <!-- Por a\u00E7\u00E3o, os spans sobem para logo abaixo do cabe\u00E7alho (order no CSS); por evento, ficam no fim. -->\n @if (ev.trace_id) {\n <div class=\"detail__chain\" [class.detail__chain--first]=\"showingAction()\">\n <div class=\"detail__actions\">\n @if (!trace() && !traceLoading()) {\n <hm-button emphasis=\"medium\" size=\"small\" icon=\"tree-view\" (clicked)=\"showTrace()\">{{ text.viewTrace }}</hm-button>\n }\n <hm-button emphasis=\"minimal\" size=\"small\" icon=\"filter\" (clicked)=\"filterByTrace(ev.trace_id)\">{{ text.filterByTrace }}</hm-button>\n </div>\n @if (traceLoading()) {\n <p class=\"muted\" role=\"status\">{{ text.traceLoading }}</p>\n }\n @if (traceFailed()) {\n <hm-alert variant=\"error\" size=\"small\">{{ text.traceError }}</hm-alert>\n }\n @if (trace(); as tr) {\n <section class=\"trace\" aria-labelledby=\"audit-trace-title\">\n <h3 class=\"audit__section-title\" id=\"audit-trace-title\">{{ showingAction() ? text.spansHeading : text.traceHeading }}</h3>\n @if (tr.hidden_count) {\n <hm-alert class=\"trace__hidden\" variant=\"info\" size=\"small\">{{ plural(text.traceHidden, tr.hidden_count) }}</hm-alert>\n }\n @if (!tr.items.length) {\n <p class=\"muted\">{{ text.traceEmpty }}</p>\n } @else if (spanTimeline(); as tl) {\n <p class=\"muted trace__summary\">{{ fmt(text.spansSummary, { count: number(tl.rows.length), duration: duration(tl.totalMs) }) }}</p>\n <!-- Linha do tempo: cada span \u00E9 uma linha (recuo = profundidade pela cadeia parent_span_id) com a\n barra posicionada no tempo, proporcional \u00E0 dura\u00E7\u00E3o. O texto da linha (e o aria-label) traz\n tudo o que a barra mostra. -->\n <ol class=\"spans\">\n @for (row of tl.rows; track row.event.event_id) {\n <li class=\"spans__item\" [class.spans__item--current]=\"row.event.event_id === ev.event_id\">\n <button type=\"button\" class=\"spans__link\"\n [attr.aria-current]=\"row.event.event_id === ev.event_id ? 'true' : null\"\n [attr.aria-label]=\"spanAria(row)\"\n (click)=\"openEvent(row.event, true)\">\n <span class=\"spans__head\" [style.padding-inline-start.rem]=\"row.depth * 1\">\n <span class=\"spans__app\">{{ row.event.app_name ?? '' }}</span>\n <code class=\"spans__op\">{{ operationName(row.event) }}</code>\n </span>\n <hm-tag class=\"spans__tag\" [name]=\"outcomeLabel(row.event.outcome)\" [color]=\"outcomeColor(row.event.outcome)\" />\n <span class=\"spans__dur\">{{ duration(row.event.duration_ms) }}</span>\n <span class=\"spans__track\" aria-hidden=\"true\">\n <span class=\"spans__bar spans__bar--{{ row.event.outcome }}\" [style.left.%]=\"row.left\" [style.width.%]=\"row.width\"></span>\n </span>\n </button>\n </li>\n }\n </ol>\n }\n </section>\n }\n </div>\n }\n </div>\n }\n</hm-drawer>\n", styles: [".card{border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.card-block{padding:var(--hm-space-sm, 8px)}.muted{color:var(--hm-muted)}:host{display:block}.audit{display:flex;flex-direction:column;gap:var(--hm-space-md, 16px)}.audit__toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:var(--hm-space-sm, 8px)}.audit__period-note{font-size:var(--hm-font-size-label)}.audit__toolbar-end{display:inline-flex;align-items:center;flex-wrap:wrap;gap:var(--hm-space-sm, 8px) var(--hm-space-md, 16px);margin-inline-start:auto}.audit__updated{font-size:var(--hm-font-size-xs);font-variant-numeric:tabular-nums}.audit__state{min-block-size:320px}.audit__section-title{margin:0 0 var(--hm-space-sm, 8px);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold);color:var(--hm-text)}.audit__kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:var(--hm-space-sm, 8px);align-items:stretch}.timeline{display:flex;align-items:flex-end;gap:2px;block-size:120px;margin:0;padding:0;list-style:none;border-block-end:1px solid var(--hm-border)}.timeline__bar{flex:1 1 0;min-inline-size:2px;block-size:100%;display:flex;flex-direction:column;justify-content:flex-end;border-radius:2px 2px 0 0;overflow:hidden}.timeline__bar:hover{background:color-mix(in srgb,var(--hm-text) 6%,transparent)}.timeline__seg{display:block;inline-size:100%}.timeline__seg--ok,.swatch--ok{background:color-mix(in srgb,var(--hm-action) 55%,transparent)}.timeline__seg--fail,.swatch--fail{background:var(--hm-danger)}.timeline__seg--denied,.swatch--denied{background:var(--hm-warning, #c98a00)}.timeline__axis{display:flex;justify-content:space-between;margin-block-start:4px;font-size:var(--hm-font-size-xs);color:var(--hm-muted)}.timeline__legend{display:flex;flex-wrap:wrap;gap:var(--hm-space-sm, 8px) var(--hm-space-md, 16px);margin:var(--hm-space-sm, 8px) 0 0;padding:0;list-style:none;font-size:var(--hm-font-size-xs);color:var(--hm-muted)}.timeline__legend li{display:inline-flex;align-items:center;gap:6px}.swatch{display:inline-block;inline-size:10px;block-size:10px;border-radius:2px}.audit__tops{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:var(--hm-space-sm, 8px);align-items:stretch}.audit__grid{position:relative;display:block}.audit__grid-progress{position:absolute;inset-inline:0;inset-block-start:0;z-index:2;pointer-events:none}.audit__grid--busy hm-data-grid{opacity:.6;transition:opacity .12s ease}.audit__op{font-family:var(--hm-font-mono, ui-monospace, monospace);font-size:var(--hm-font-size-xs)}.audit__page-error{display:block}@media(prefers-reduced-motion:reduce){.audit__grid--busy hm-data-grid{transition:none}}:host-context([data-motion=reduce]) .audit__grid--busy hm-data-grid{transition:none}.detail{display:flex;flex-direction:column;gap:var(--hm-space-sm, 8px)}.detail__head{display:flex;align-items:center;gap:var(--hm-space-sm, 8px);flex-wrap:wrap}.detail__title{font-size:var(--hm-font-size-md)}.detail__list{display:grid;grid-template-columns:minmax(8rem,max-content) minmax(0,1fr);gap:6px var(--hm-space-md, 16px);margin:0}.detail__row{display:contents}.detail__row dt{color:var(--hm-muted);font-size:var(--hm-font-size-label)}.detail__row dd{margin:0;font-size:var(--hm-font-size-label);overflow-wrap:anywhere}.detail__json{margin:0;padding:var(--hm-space-sm, 8px);max-block-size:280px;overflow:auto;border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);background:color-mix(in srgb,var(--hm-text) 4%,transparent);font-size:var(--hm-font-size-xs)}.detail__description{margin:0;font-size:var(--hm-font-size-label)}.detail__actions{display:flex;flex-wrap:wrap;gap:var(--hm-space-sm, 8px)}.trace{margin-block-start:var(--hm-space-sm, 8px)}.trace__hidden{margin-block-end:var(--hm-space-sm, 8px)}.detail__head,.detail__description,.detail__orphan{order:-3}.detail__chain{display:flex;flex-direction:column;gap:var(--hm-space-sm, 8px)}.detail__chain--first{order:-2}.trace__summary{margin:0 0 var(--hm-space-xs, 4px);font-size:var(--hm-font-size-label)}.spans{margin:0;padding:0;list-style:none;border-block-start:1px solid var(--hm-border)}.spans__item{border-block-end:1px solid var(--hm-border)}.spans__item--current .spans__link{background:color-mix(in srgb,var(--hm-action) 10%,transparent);box-shadow:inset 2px 0 0 var(--hm-action)}.spans__link{display:grid;grid-template-columns:minmax(0,1fr) auto auto;grid-template-areas:\"head tag dur\" \"track track track\";align-items:center;gap:4px var(--hm-space-sm, 8px);inline-size:100%;padding:6px 8px;border:0;background:transparent;color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-label);text-align:start;cursor:pointer}.spans__link:hover{background:color-mix(in srgb,var(--hm-action) 6%,transparent)}.spans__link:focus-visible{outline:2px solid var(--hm-action);outline-offset:-2px}.spans__head{grid-area:head;display:flex;flex-direction:column;min-inline-size:0}.spans__app{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.spans__op{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--hm-font-mono, ui-monospace, monospace);font-size:var(--hm-font-size-xs)}.spans__tag{grid-area:tag}.spans__dur{grid-area:dur;min-inline-size:4.5rem;text-align:end;font-variant-numeric:tabular-nums;color:var(--hm-muted)}.spans__track{grid-area:track;position:relative;block-size:6px;border-radius:3px;background:color-mix(in srgb,var(--hm-text) 6%,transparent)}.spans__bar{position:absolute;inset-block:0;border-radius:3px;background:color-mix(in srgb,var(--hm-action) 70%,transparent)}.spans__bar--failure{background:var(--hm-danger)}.spans__bar--denied{background:var(--hm-warning, #c98a00)}.audit__spans{display:inline-flex;align-items:center;gap:6px;font-variant-numeric:tabular-nums}\n"], dependencies: [{ kind: "component", type: AlertComponent, selector: "hm-alert", inputs: ["variant", "appearance", "heading", "closable", "size", "showIcon", "link", "linkLabel", "linkTarget", "dismissKey", "duration"], outputs: ["closed"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "directive", type: DataGridCellDirective, selector: "ng-template[hmCell]", inputs: ["hmCell"] }, { 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", "striped", "columnGroups", "editMode", "editBatch", "texts", "treeChildren", "treeHasChildren", "treeExpanded", "searchTerm"], outputs: ["pageStateChange", "sortChange", "selectionChange", "pinnedColumnsChange", "columnStateChange", "actionTriggered", "detailToggled", "rowClicked", "clearFiltersRequested", "filteredChange", "searchTermChanged", "groupByChange", "filterQueryChange", "columnOrderChange", "editSaved", "editStarted", "editCanceled", "treeExpandedChange", "treeToggled", "searchTermChange", "detailPaneToggled"] }, { kind: "component", type: DrawerComponent, selector: "hm-drawer", inputs: ["open", "heading", "size", "position", "pinned", "closeLabel", "closable", "closeOnBackdrop", "beforeClose"], outputs: ["openChange", "closed"] }, { kind: "component", type: FilterBarComponent, selector: "hm-filter-bar", inputs: ["filters", "value", "label", "savedFilters", "storageKey", "storage", "applyDefault"], outputs: ["valueChange", "savedChange", "savedApplied"] }, { kind: "component", type: ProgressComponent, selector: "hm-progress", inputs: ["value", "max", "label", "labelHidden", "showValue", "indeterminate", "intent", "size", "hint", "shape"] }, { kind: "component", type: SegmentedControlComponent, selector: "hm-segmented-control", inputs: ["options", "value", "label", "size", "emphasis"], outputs: ["valueChange"] }, { kind: "component", type: SkeletonComponent, selector: "hm-skeleton", inputs: ["shape", "count", "label"] }, { kind: "component", type: SpineAuditTopCardComponent, selector: "spine-audit-top-card", inputs: ["heading", "rows", "total", "offset", "pageSize", "max", "loading", "failed"], outputs: ["picked", "offsetChange"] }, { kind: "component", type: StatCardComponent, selector: "hm-stat-card", inputs: ["icon", "label", "value", "intent", "fill", "description", "details", "trend", "hint", "size", "selectable", "selected", "loading"], outputs: ["selectedChange"] }, { kind: "component", type: StateScreenComponent, selector: "hm-state-screen", inputs: ["heading", "description", "situation", "action", "actionIcon"], outputs: ["actionTriggered"] }, { kind: "component", type: SwitchComponent, selector: "hm-switch", inputs: ["value", "label", "hint", "labelPosition", "loading"], outputs: ["valueChange"] }, { kind: "component", type: TagComponent, selector: "hm-tag", inputs: ["name", "color", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1337
1337
  }
1338
1338
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineAuditComponent, decorators: [{
1339
1339
  type: Component,
@@ -1341,7 +1341,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
1341
1341
  AlertComponent, ButtonComponent, DataGridCellDirective, DataGridComponent, DrawerComponent, FilterBarComponent,
1342
1342
  ProgressComponent, SegmentedControlComponent, SkeletonComponent, SpineAuditTopCardComponent, StatCardComponent, StateScreenComponent,
1343
1343
  SwitchComponent, TagComponent,
1344
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"audit\" [attr.aria-busy]=\"loading() || pageLoading()\">\n <!-- Per\u00EDodo (escolha r\u00E1pida; as datas da faixa de filtros vencem) + atualizar. -->\n <div class=\"audit__toolbar\">\n <!-- Unidade: por a\u00E7\u00E3o (uma linha por trace, padr\u00E3o) ou por evento (cada registro). -->\n <hm-segmented-control\n [label]=\"text.unitLabel\"\n [options]=\"unitOptions\"\n [value]=\"unit()\"\n (valueChange)=\"setUnit($event)\"\n size=\"small\"\n emphasis=\"action\"\n />\n <hm-segmented-control\n [label]=\"text.period\"\n [options]=\"periodOptions\"\n [value]=\"period()\"\n (valueChange)=\"setPeriod($event)\"\n size=\"small\"\n emphasis=\"action\"\n />\n @if (customDates()) {\n <span class=\"muted audit__period-note\">{{ text.periodFromFilters }}</span>\n }\n <span class=\"audit__toolbar-end\">\n @if (lastUpdated(); as at) {\n <span class=\"muted audit__updated\" aria-live=\"polite\">{{ fmt(text.updatedAt, { time: clock(at) }) }}</span>\n }\n <hm-switch [label]=\"text.autoRefresh\" [value]=\"autoRefresh()\" (valueChange)=\"autoRefresh.set($event)\" />\n <hm-button emphasis=\"medium\" size=\"small\" icon=\"refresh\" [loading]=\"loading()\" (clicked)=\"reload()\">{{ text.refresh }}</hm-button>\n </span>\n </div>\n\n <hm-filter-bar\n class=\"audit__filters\"\n [label]=\"text.filters\"\n [filters]=\"filterFields()\"\n [value]=\"filterValue()\"\n (valueChange)=\"setFilters($event)\"\n [savedFilters]=\"true\"\n [storageKey]=\"storageKey()\"\n />\n\n @if (errorKind()) {\n <hm-state-screen\n class=\"audit__state\"\n [situation]=\"errorSituation()\"\n [heading]=\"text.errorTitle\"\n [description]=\"errorText()\"\n [action]=\"errorKind() === 'generic' ? text.retry : (hasFilters() ? text.clearFilters : '')\"\n actionIcon=\"refresh\"\n (actionTriggered)=\"errorKind() === 'generic' ? reload() : clearFilters()\"\n />\n } @else if (empty()) {\n <hm-state-screen\n class=\"audit__state\"\n situation=\"empty\"\n [heading]=\"unitText().emptyTitle\"\n [description]=\"text.emptyText\"\n [action]=\"hasFilters() ? text.clearFilters : ''\"\n (actionTriggered)=\"clearFilters()\"\n />\n } @else {\n <!-- KPIs: falhas/negados s\u00E3o filtros (altern\u00E2ncia). -->\n <section class=\"audit__kpis\" [attr.aria-label]=\"text.kpis\">\n <hm-stat-card\n icon=\"list\"\n [label]=\"unitText().kpiTotal\"\n [value]=\"number(stats()?.total ?? 0)\"\n [description]=\"unitText().kpiTotalDescription\"\n [loading]=\"loading() && !stats()\"\n />\n <hm-stat-card\n icon=\"error-standard\"\n [label]=\"text.kpiFailures\"\n [value]=\"number(stats()?.by_outcome?.failure ?? 0)\"\n [intent]=\"(stats()?.by_outcome?.failure ?? 0) > 0 ? 'danger' : 'neutral'\"\n [description]=\"fmt(text.kpiFilterHint, { share: share(stats()?.by_outcome?.failure ?? 0) })\"\n [loading]=\"loading() && !stats()\"\n selectable\n [selected]=\"selectedOutcome() === 'failure'\"\n (selectedChange)=\"toggleOutcome('failure', $event)\"\n />\n <hm-stat-card\n icon=\"ban\"\n [label]=\"text.kpiDenied\"\n [value]=\"number(stats()?.by_outcome?.denied ?? 0)\"\n [intent]=\"(stats()?.by_outcome?.denied ?? 0) > 0 ? 'warning' : 'neutral'\"\n [description]=\"fmt(text.kpiFilterHint, { share: share(stats()?.by_outcome?.denied ?? 0) })\"\n [loading]=\"loading() && !stats()\"\n selectable\n [selected]=\"selectedOutcome() === 'denied'\"\n (selectedChange)=\"toggleOutcome('denied', $event)\"\n />\n </section>\n\n <!-- Linha do tempo: barras empilhadas em CSS (sucesso embaixo, falha, negado). Cada barra tem\n o resumo em texto (title + aria-label); o gr\u00E1fico n\u00E3o \u00E9 a \u00FAnica forma de ler o n\u00FAmero. -->\n <section class=\"card card-block audit__timeline\" aria-labelledby=\"audit-timeline-title\">\n <h3 class=\"audit__section-title\" id=\"audit-timeline-title\">{{ text.timeline }}</h3>\n @if (hasTimeline()) {\n <ol class=\"timeline\" [attr.aria-label]=\"fmt(unitText().timelineSummary, { from: timelineEdges()?.from ?? '', to: timelineEdges()?.to ?? '' })\">\n @for (bar of timelineBars(); track bar.key) {\n <li class=\"timeline__bar\" [attr.title]=\"bar.summary\" [attr.aria-label]=\"bar.summary\">\n <span class=\"timeline__seg timeline__seg--denied\" [style.height.%]=\"bar.denied\"></span>\n <span class=\"timeline__seg timeline__seg--fail\" [style.height.%]=\"bar.fail\"></span>\n <span class=\"timeline__seg timeline__seg--ok\" [style.height.%]=\"bar.ok\"></span>\n </li>\n }\n </ol>\n <div class=\"timeline__axis\" aria-hidden=\"true\">\n <span>{{ timelineEdges()?.from }}</span>\n <span>{{ timelineEdges()?.to }}</span>\n </div>\n <ul class=\"timeline__legend\">\n <li><span class=\"swatch swatch--ok\"></span>{{ text.outcomeSuccess }}</li>\n <li><span class=\"swatch swatch--fail\"></span>{{ text.outcomeFailure }}</li>\n <li><span class=\"swatch swatch--denied\"></span>{{ text.outcomeDenied }}</li>\n </ul>\n } @else if (loading()) {\n <hm-skeleton shape=\"card\" [label]=\"text.loading\" />\n } @else {\n <p class=\"muted\">{{ unitText().timelineEmpty }}</p>\n }\n </section>\n\n <!-- Rankings paginados no servidor (tops/:kind). Clicar num item vira filtro. -->\n <div class=\"audit__tops\" [class.audit__tops--three]=\"multiApp()\">\n @if (multiApp()) {\n <spine-audit-top-card\n class=\"audit__top audit__top--apps\"\n [heading]=\"text.topApps\"\n [rows]=\"topAppRows()\"\n [total]=\"tops.apps().total\"\n [offset]=\"tops.apps().offset\"\n [max]=\"tops.apps().max\"\n [pageSize]=\"topPageSize\"\n [loading]=\"tops.apps().loading\"\n [failed]=\"tops.apps().failed\"\n (offsetChange)=\"loadTop('apps', $event)\"\n (picked)=\"pickTop('apps', $event)\"\n />\n }\n <spine-audit-top-card\n class=\"audit__top audit__top--operations\"\n [heading]=\"text.topOperations\"\n [rows]=\"topOperationRows()\"\n [total]=\"tops.operations().total\"\n [offset]=\"tops.operations().offset\"\n [max]=\"tops.operations().max\"\n [pageSize]=\"topPageSize\"\n [loading]=\"tops.operations().loading\"\n [failed]=\"tops.operations().failed\"\n (offsetChange)=\"loadTop('operations', $event)\"\n (picked)=\"pickTop('operations', $event)\"\n />\n <spine-audit-top-card\n class=\"audit__top audit__top--users\"\n [heading]=\"text.topUsers\"\n [rows]=\"topUserRows()\"\n [total]=\"tops.users().total\"\n [offset]=\"tops.users().offset\"\n [max]=\"tops.users().max\"\n [pageSize]=\"topPageSize\"\n [loading]=\"tops.users().loading\"\n [failed]=\"tops.users().failed\"\n (offsetChange)=\"loadTop('users', $event)\"\n (picked)=\"pickTop('users', $event)\"\n />\n </div>\n\n <!-- Tabela: pagina\u00E7\u00E3o no servidor. A grade n\u00E3o corta nada ([paginated]=false): mostra a p\u00E1gina que veio\n (limit = itens por p\u00E1gina) e o rodap\u00E9 usa o total do resumo (mesma consulta). Esqueleto s\u00F3 na 1\u00AA\n carga; nas seguintes as linhas ficam e o indicador aparece por cima (sem piscar). -->\n <div class=\"audit__grid\" [class.audit__grid--busy]=\"gridBusy()\" [attr.aria-busy]=\"gridBusy()\">\n @if (gridBusy()) {\n <hm-progress class=\"audit__grid-progress\" indeterminate [label]=\"text.updating\" labelHidden />\n }\n <hm-data-grid\n [caption]=\"unitText().caption\"\n [columns]=\"columns()\"\n [rows]=\"events()\"\n [rowId]=\"rowId\"\n [loading]=\"gridSkeleton()\"\n [filterable]=\"false\"\n [paginated]=\"false\"\n [totalItems]=\"total()\"\n [pageState]=\"pageState()\"\n (pageStateChange)=\"changePage($event)\"\n [pageSizes]=\"pageSizes\"\n [itemsLabel]=\"unitText().pageItems\"\n [pageSizeLabel]=\"text.pageSize\"\n remoteSort\n rowClickable\n (rowClicked)=\"openRow($event)\"\n >\n <ng-template hmCell=\"operation\" let-item>\n <span class=\"audit__op\" [attr.title]=\"item.operation_label || null\">{{ operationName(item) }}</span>\n </ng-template>\n <ng-template hmCell=\"spans\" let-item>\n <span class=\"audit__spans\" [attr.title]=\"spansHint(item) || null\">\n {{ number(item.span_count ?? 1) }}\n @if (item.orphan) {\n <hm-tag class=\"audit__orphan\" [name]=\"text.orphanTag\" color=\"neutral\" />\n }\n </span>\n </ng-template>\n <ng-template hmCell=\"outcome\" let-item>\n <hm-tag [name]=\"outcomeLabel(item.outcome)\" [color]=\"outcomeColor(item.outcome)\" />\n </ng-template>\n </hm-data-grid>\n </div>\n\n @if (pageFailed()) {\n <hm-alert class=\"audit__page-error\" variant=\"error\" size=\"small\">{{ unitText().pageError }}</hm-alert>\n }\n }\n</div>\n\n<!-- Detalhe do evento + a cadeia (trace). -->\n<hm-drawer [open]=\"detailOpen()\" (openChange)=\"onDetailOpenChange($event)\" [heading]=\"showingAction() ? text.actionDetailHeading : text.detailHeading\">\n @if (selected(); as ev) {\n <div class=\"detail\">\n <div class=\"detail__head\">\n <strong class=\"detail__title\">{{ operationName(ev) }}</strong>\n <hm-tag [name]=\"outcomeLabel(headOutcome())\" [color]=\"outcomeColor(headOutcome())\" />\n </div>\n @if (showingAction() && selectedAction()?.orphan) {\n <hm-alert class=\"detail__orphan\" variant=\"info\" size=\"small\">{{ text.orphanNote }}</hm-alert>\n }\n @if (ev.operation_label && ev.operation_identifier) {\n <p class=\"muted detail__description\">{{ ev.operation_label }}</p>\n }\n\n @if (detailLoading()) {\n <p class=\"muted\" role=\"status\">{{ text.detailLoading }}</p>\n }\n @if (detailFailed()) {\n <hm-alert variant=\"warning\" size=\"small\">{{ text.detailError }}</hm-alert>\n }\n\n <dl class=\"detail__list\">\n @for (field of detailFields(); track field.label) {\n <div class=\"detail__row\">\n <dt>{{ field.label }}</dt>\n <dd>\n @if (field.code) { <code>{{ field.value }}</code> } @else { {{ field.value }} }\n </dd>\n </div>\n }\n </dl>\n\n @if (detailsJson()) {\n <h3 class=\"audit__section-title\">{{ text.fieldDetails }}</h3>\n <pre class=\"detail__json\">{{ detailsJson() }}</pre>\n }\n\n <!-- Por a\u00E7\u00E3o, os spans sobem para logo abaixo do cabe\u00E7alho (order no CSS); por evento, ficam no fim. -->\n @if (ev.trace_id) {\n <div class=\"detail__chain\" [class.detail__chain--first]=\"showingAction()\">\n <div class=\"detail__actions\">\n @if (!trace() && !traceLoading()) {\n <hm-button emphasis=\"medium\" size=\"small\" icon=\"tree-view\" (clicked)=\"showTrace()\">{{ text.viewTrace }}</hm-button>\n }\n <hm-button emphasis=\"minimal\" size=\"small\" icon=\"filter\" (clicked)=\"filterByTrace(ev.trace_id)\">{{ text.filterByTrace }}</hm-button>\n </div>\n @if (traceLoading()) {\n <p class=\"muted\" role=\"status\">{{ text.traceLoading }}</p>\n }\n @if (traceFailed()) {\n <hm-alert variant=\"error\" size=\"small\">{{ text.traceError }}</hm-alert>\n }\n @if (trace(); as tr) {\n <section class=\"trace\" aria-labelledby=\"audit-trace-title\">\n <h3 class=\"audit__section-title\" id=\"audit-trace-title\">{{ showingAction() ? text.spansHeading : text.traceHeading }}</h3>\n @if (tr.hidden_count) {\n <hm-alert class=\"trace__hidden\" variant=\"info\" size=\"small\">{{ plural(text.traceHidden, tr.hidden_count) }}</hm-alert>\n }\n @if (!tr.items.length) {\n <p class=\"muted\">{{ text.traceEmpty }}</p>\n } @else if (spanTimeline(); as tl) {\n <p class=\"muted trace__summary\">{{ fmt(text.spansSummary, { count: number(tl.rows.length), duration: duration(tl.totalMs) }) }}</p>\n <!-- Linha do tempo: cada span \u00E9 uma linha (recuo = profundidade pela cadeia parent_span_id) com a\n barra posicionada no tempo, proporcional \u00E0 dura\u00E7\u00E3o. O texto da linha (e o aria-label) traz\n tudo o que a barra mostra. -->\n <ol class=\"spans\">\n @for (row of tl.rows; track row.event.event_id) {\n <li class=\"spans__item\" [class.spans__item--current]=\"row.event.event_id === ev.event_id\">\n <button type=\"button\" class=\"spans__link\"\n [attr.aria-current]=\"row.event.event_id === ev.event_id ? 'true' : null\"\n [attr.aria-label]=\"spanAria(row)\"\n (click)=\"openEvent(row.event, true)\">\n <span class=\"spans__head\" [style.padding-inline-start.rem]=\"row.depth * 1\">\n <span class=\"spans__app\">{{ row.event.app_name ?? '' }}</span>\n <code class=\"spans__op\">{{ operationName(row.event) }}</code>\n </span>\n <hm-tag class=\"spans__tag\" [name]=\"outcomeLabel(row.event.outcome)\" [color]=\"outcomeColor(row.event.outcome)\" />\n <span class=\"spans__dur\">{{ duration(row.event.duration_ms) }}</span>\n <span class=\"spans__track\" aria-hidden=\"true\">\n <span class=\"spans__bar spans__bar--{{ row.event.outcome }}\" [style.left.%]=\"row.left\" [style.width.%]=\"row.width\"></span>\n </span>\n </button>\n </li>\n }\n </ol>\n }\n </section>\n }\n </div>\n }\n </div>\n }\n</hm-drawer>\n", styles: [".card{border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.card-block{padding:var(--hm-space-sm, 8px)}.muted{color:var(--hm-muted)}:host{display:block}.audit{display:flex;flex-direction:column;gap:var(--hm-space-md, 16px)}.audit__toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:var(--hm-space-sm, 8px)}.audit__period-note{font-size:13px}.audit__toolbar-end{display:inline-flex;align-items:center;flex-wrap:wrap;gap:var(--hm-space-sm, 8px) var(--hm-space-md, 16px);margin-inline-start:auto}.audit__updated{font-size:12px;font-variant-numeric:tabular-nums}.audit__state{min-block-size:320px}.audit__section-title{margin:0 0 var(--hm-space-sm, 8px);font-size:14px;font-weight:600;color:var(--hm-text)}.audit__kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:var(--hm-space-sm, 8px);align-items:stretch}.timeline{display:flex;align-items:flex-end;gap:2px;block-size:120px;margin:0;padding:0;list-style:none;border-block-end:1px solid var(--hm-border)}.timeline__bar{flex:1 1 0;min-inline-size:2px;block-size:100%;display:flex;flex-direction:column;justify-content:flex-end;border-radius:2px 2px 0 0;overflow:hidden}.timeline__bar:hover{background:color-mix(in srgb,var(--hm-text) 6%,transparent)}.timeline__seg{display:block;inline-size:100%}.timeline__seg--ok,.swatch--ok{background:color-mix(in srgb,var(--hm-action) 55%,transparent)}.timeline__seg--fail,.swatch--fail{background:var(--hm-danger)}.timeline__seg--denied,.swatch--denied{background:var(--hm-warning, #c98a00)}.timeline__axis{display:flex;justify-content:space-between;margin-block-start:4px;font-size:12px;color:var(--hm-muted)}.timeline__legend{display:flex;flex-wrap:wrap;gap:var(--hm-space-sm, 8px) var(--hm-space-md, 16px);margin:var(--hm-space-sm, 8px) 0 0;padding:0;list-style:none;font-size:12px;color:var(--hm-muted)}.timeline__legend li{display:inline-flex;align-items:center;gap:6px}.swatch{display:inline-block;inline-size:10px;block-size:10px;border-radius:2px}.audit__tops{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:var(--hm-space-sm, 8px);align-items:stretch}.audit__grid{position:relative;display:block}.audit__grid-progress{position:absolute;inset-inline:0;inset-block-start:0;z-index:2;pointer-events:none}.audit__grid--busy hm-data-grid{opacity:.6;transition:opacity .12s ease}.audit__op{font-family:var(--hm-font-mono, ui-monospace, monospace);font-size:12px}.audit__page-error{display:block}@media(prefers-reduced-motion:reduce){.audit__grid--busy hm-data-grid{transition:none}}:host-context([data-motion=reduce]) .audit__grid--busy hm-data-grid{transition:none}.detail{display:flex;flex-direction:column;gap:var(--hm-space-sm, 8px)}.detail__head{display:flex;align-items:center;gap:var(--hm-space-sm, 8px);flex-wrap:wrap}.detail__title{font-size:16px}.detail__list{display:grid;grid-template-columns:minmax(8rem,max-content) minmax(0,1fr);gap:6px var(--hm-space-md, 16px);margin:0}.detail__row{display:contents}.detail__row dt{color:var(--hm-muted);font-size:13px}.detail__row dd{margin:0;font-size:13px;overflow-wrap:anywhere}.detail__json{margin:0;padding:var(--hm-space-sm, 8px);max-block-size:280px;overflow:auto;border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);background:color-mix(in srgb,var(--hm-text) 4%,transparent);font-size:12px}.detail__description{margin:0;font-size:13px}.detail__actions{display:flex;flex-wrap:wrap;gap:var(--hm-space-sm, 8px)}.trace{margin-block-start:var(--hm-space-sm, 8px)}.trace__hidden{margin-block-end:var(--hm-space-sm, 8px)}.detail__head,.detail__description,.detail__orphan{order:-3}.detail__chain{display:flex;flex-direction:column;gap:var(--hm-space-sm, 8px)}.detail__chain--first{order:-2}.trace__summary{margin:0 0 var(--hm-space-xs, 4px);font-size:13px}.spans{margin:0;padding:0;list-style:none;border-block-start:1px solid var(--hm-border)}.spans__item{border-block-end:1px solid var(--hm-border)}.spans__item--current .spans__link{background:color-mix(in srgb,var(--hm-action) 10%,transparent);box-shadow:inset 2px 0 0 var(--hm-action)}.spans__link{display:grid;grid-template-columns:minmax(0,1fr) auto auto;grid-template-areas:\"head tag dur\" \"track track track\";align-items:center;gap:4px var(--hm-space-sm, 8px);inline-size:100%;padding:6px 8px;border:0;background:transparent;color:var(--hm-text);font:inherit;font-size:13px;text-align:start;cursor:pointer}.spans__link:hover{background:color-mix(in srgb,var(--hm-action) 6%,transparent)}.spans__link:focus-visible{outline:2px solid var(--hm-action);outline-offset:-2px}.spans__head{grid-area:head;display:flex;flex-direction:column;min-inline-size:0}.spans__app{color:var(--hm-muted);font-size:12px}.spans__op{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--hm-font-mono, ui-monospace, monospace);font-size:12px}.spans__tag{grid-area:tag}.spans__dur{grid-area:dur;min-inline-size:4.5rem;text-align:end;font-variant-numeric:tabular-nums;color:var(--hm-muted)}.spans__track{grid-area:track;position:relative;block-size:6px;border-radius:3px;background:color-mix(in srgb,var(--hm-text) 6%,transparent)}.spans__bar{position:absolute;inset-block:0;border-radius:3px;background:color-mix(in srgb,var(--hm-action) 70%,transparent)}.spans__bar--failure{background:var(--hm-danger)}.spans__bar--denied{background:var(--hm-warning, #c98a00)}.audit__spans{display:inline-flex;align-items:center;gap:6px;font-variant-numeric:tabular-nums}\n"] }]
1344
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"audit\" [attr.aria-busy]=\"loading() || pageLoading()\">\n <!-- Per\u00EDodo (escolha r\u00E1pida; as datas da faixa de filtros vencem) + atualizar. -->\n <div class=\"audit__toolbar\">\n <!-- Unidade: por a\u00E7\u00E3o (uma linha por trace, padr\u00E3o) ou por evento (cada registro). -->\n <hm-segmented-control\n [label]=\"text.unitLabel\"\n [options]=\"unitOptions\"\n [value]=\"unit()\"\n (valueChange)=\"setUnit($event)\"\n size=\"small\"\n emphasis=\"action\"\n />\n <hm-segmented-control\n [label]=\"text.period\"\n [options]=\"periodOptions\"\n [value]=\"period()\"\n (valueChange)=\"setPeriod($event)\"\n size=\"small\"\n emphasis=\"action\"\n />\n @if (customDates()) {\n <span class=\"muted audit__period-note\">{{ text.periodFromFilters }}</span>\n }\n <span class=\"audit__toolbar-end\">\n @if (lastUpdated(); as at) {\n <span class=\"muted audit__updated\" aria-live=\"polite\">{{ fmt(text.updatedAt, { time: clock(at) }) }}</span>\n }\n <hm-switch [label]=\"text.autoRefresh\" [value]=\"autoRefresh()\" (valueChange)=\"autoRefresh.set($event)\" />\n <hm-button emphasis=\"medium\" size=\"small\" icon=\"refresh\" [loading]=\"loading()\" (clicked)=\"reload()\">{{ text.refresh }}</hm-button>\n </span>\n </div>\n\n <hm-filter-bar\n class=\"audit__filters\"\n [label]=\"text.filters\"\n [filters]=\"filterFields()\"\n [value]=\"filterValue()\"\n (valueChange)=\"setFilters($event)\"\n [savedFilters]=\"true\"\n [storageKey]=\"storageKey()\"\n />\n\n @if (errorKind()) {\n <hm-state-screen\n class=\"audit__state\"\n [situation]=\"errorSituation()\"\n [heading]=\"text.errorTitle\"\n [description]=\"errorText()\"\n [action]=\"errorKind() === 'generic' ? text.retry : (hasFilters() ? text.clearFilters : '')\"\n actionIcon=\"refresh\"\n (actionTriggered)=\"errorKind() === 'generic' ? reload() : clearFilters()\"\n />\n } @else if (empty()) {\n <hm-state-screen\n class=\"audit__state\"\n situation=\"empty\"\n [heading]=\"unitText().emptyTitle\"\n [description]=\"text.emptyText\"\n [action]=\"hasFilters() ? text.clearFilters : ''\"\n (actionTriggered)=\"clearFilters()\"\n />\n } @else {\n <!-- KPIs: falhas/negados s\u00E3o filtros (altern\u00E2ncia). -->\n <section class=\"audit__kpis\" [attr.aria-label]=\"text.kpis\">\n <hm-stat-card\n icon=\"list\"\n [label]=\"unitText().kpiTotal\"\n [value]=\"number(stats()?.total ?? 0)\"\n [description]=\"unitText().kpiTotalDescription\"\n [loading]=\"loading() && !stats()\"\n />\n <hm-stat-card\n icon=\"error-standard\"\n [label]=\"text.kpiFailures\"\n [value]=\"number(stats()?.by_outcome?.failure ?? 0)\"\n [intent]=\"(stats()?.by_outcome?.failure ?? 0) > 0 ? 'danger' : 'neutral'\"\n [description]=\"fmt(text.kpiFilterHint, { share: share(stats()?.by_outcome?.failure ?? 0) })\"\n [loading]=\"loading() && !stats()\"\n selectable\n [selected]=\"selectedOutcome() === 'failure'\"\n (selectedChange)=\"toggleOutcome('failure', $event)\"\n />\n <hm-stat-card\n icon=\"ban\"\n [label]=\"text.kpiDenied\"\n [value]=\"number(stats()?.by_outcome?.denied ?? 0)\"\n [intent]=\"(stats()?.by_outcome?.denied ?? 0) > 0 ? 'warning' : 'neutral'\"\n [description]=\"fmt(text.kpiFilterHint, { share: share(stats()?.by_outcome?.denied ?? 0) })\"\n [loading]=\"loading() && !stats()\"\n selectable\n [selected]=\"selectedOutcome() === 'denied'\"\n (selectedChange)=\"toggleOutcome('denied', $event)\"\n />\n </section>\n\n <!-- Linha do tempo: barras empilhadas em CSS (sucesso embaixo, falha, negado). Cada barra tem\n o resumo em texto (title + aria-label); o gr\u00E1fico n\u00E3o \u00E9 a \u00FAnica forma de ler o n\u00FAmero. -->\n <section class=\"card card-block audit__timeline\" aria-labelledby=\"audit-timeline-title\">\n <h3 class=\"audit__section-title\" id=\"audit-timeline-title\">{{ text.timeline }}</h3>\n @if (hasTimeline()) {\n <ol class=\"timeline\" [attr.aria-label]=\"fmt(unitText().timelineSummary, { from: timelineEdges()?.from ?? '', to: timelineEdges()?.to ?? '' })\">\n @for (bar of timelineBars(); track bar.key) {\n <li class=\"timeline__bar\" [attr.title]=\"bar.summary\" [attr.aria-label]=\"bar.summary\">\n <span class=\"timeline__seg timeline__seg--denied\" [style.height.%]=\"bar.denied\"></span>\n <span class=\"timeline__seg timeline__seg--fail\" [style.height.%]=\"bar.fail\"></span>\n <span class=\"timeline__seg timeline__seg--ok\" [style.height.%]=\"bar.ok\"></span>\n </li>\n }\n </ol>\n <div class=\"timeline__axis\" aria-hidden=\"true\">\n <span>{{ timelineEdges()?.from }}</span>\n <span>{{ timelineEdges()?.to }}</span>\n </div>\n <ul class=\"timeline__legend\">\n <li><span class=\"swatch swatch--ok\"></span>{{ text.outcomeSuccess }}</li>\n <li><span class=\"swatch swatch--fail\"></span>{{ text.outcomeFailure }}</li>\n <li><span class=\"swatch swatch--denied\"></span>{{ text.outcomeDenied }}</li>\n </ul>\n } @else if (loading()) {\n <hm-skeleton shape=\"card\" [label]=\"text.loading\" />\n } @else {\n <p class=\"muted\">{{ unitText().timelineEmpty }}</p>\n }\n </section>\n\n <!-- Rankings paginados no servidor (tops/:kind). Clicar num item vira filtro. -->\n <div class=\"audit__tops\" [class.audit__tops--three]=\"multiApp()\">\n @if (multiApp()) {\n <spine-audit-top-card\n class=\"audit__top audit__top--apps\"\n [heading]=\"text.topApps\"\n [rows]=\"topAppRows()\"\n [total]=\"tops.apps().total\"\n [offset]=\"tops.apps().offset\"\n [max]=\"tops.apps().max\"\n [pageSize]=\"topPageSize\"\n [loading]=\"tops.apps().loading\"\n [failed]=\"tops.apps().failed\"\n (offsetChange)=\"loadTop('apps', $event)\"\n (picked)=\"pickTop('apps', $event)\"\n />\n }\n <spine-audit-top-card\n class=\"audit__top audit__top--operations\"\n [heading]=\"text.topOperations\"\n [rows]=\"topOperationRows()\"\n [total]=\"tops.operations().total\"\n [offset]=\"tops.operations().offset\"\n [max]=\"tops.operations().max\"\n [pageSize]=\"topPageSize\"\n [loading]=\"tops.operations().loading\"\n [failed]=\"tops.operations().failed\"\n (offsetChange)=\"loadTop('operations', $event)\"\n (picked)=\"pickTop('operations', $event)\"\n />\n <spine-audit-top-card\n class=\"audit__top audit__top--users\"\n [heading]=\"text.topUsers\"\n [rows]=\"topUserRows()\"\n [total]=\"tops.users().total\"\n [offset]=\"tops.users().offset\"\n [max]=\"tops.users().max\"\n [pageSize]=\"topPageSize\"\n [loading]=\"tops.users().loading\"\n [failed]=\"tops.users().failed\"\n (offsetChange)=\"loadTop('users', $event)\"\n (picked)=\"pickTop('users', $event)\"\n />\n </div>\n\n <!-- Tabela: pagina\u00E7\u00E3o no servidor. A grade n\u00E3o corta nada ([paginated]=false): mostra a p\u00E1gina que veio\n (limit = itens por p\u00E1gina) e o rodap\u00E9 usa o total do resumo (mesma consulta). Esqueleto s\u00F3 na 1\u00AA\n carga; nas seguintes as linhas ficam e o indicador aparece por cima (sem piscar). -->\n <div class=\"audit__grid\" [class.audit__grid--busy]=\"gridBusy()\" [attr.aria-busy]=\"gridBusy()\">\n @if (gridBusy()) {\n <hm-progress class=\"audit__grid-progress\" indeterminate [label]=\"text.updating\" labelHidden />\n }\n <hm-data-grid\n [caption]=\"unitText().caption\"\n [columns]=\"columns()\"\n [rows]=\"events()\"\n [rowId]=\"rowId\"\n [loading]=\"gridSkeleton()\"\n [filterable]=\"false\"\n [paginated]=\"false\"\n [totalItems]=\"total()\"\n [pageState]=\"pageState()\"\n (pageStateChange)=\"changePage($event)\"\n [pageSizes]=\"pageSizes\"\n [itemsLabel]=\"unitText().pageItems\"\n [pageSizeLabel]=\"text.pageSize\"\n remoteSort\n rowClickable\n (rowClicked)=\"openRow($event)\"\n >\n <ng-template hmCell=\"operation\" let-item>\n <span class=\"audit__op\" [attr.title]=\"item.operation_label || null\">{{ operationName(item) }}</span>\n </ng-template>\n <ng-template hmCell=\"spans\" let-item>\n <span class=\"audit__spans\" [attr.title]=\"spansHint(item) || null\">\n {{ number(item.span_count ?? 1) }}\n @if (item.orphan) {\n <hm-tag class=\"audit__orphan\" [name]=\"text.orphanTag\" color=\"neutral\" />\n }\n </span>\n </ng-template>\n <ng-template hmCell=\"outcome\" let-item>\n <hm-tag [name]=\"outcomeLabel(item.outcome)\" [color]=\"outcomeColor(item.outcome)\" />\n </ng-template>\n </hm-data-grid>\n </div>\n\n @if (pageFailed()) {\n <hm-alert class=\"audit__page-error\" variant=\"error\" size=\"small\">{{ unitText().pageError }}</hm-alert>\n }\n }\n</div>\n\n<!-- Detalhe do evento + a cadeia (trace). -->\n<hm-drawer [open]=\"detailOpen()\" (openChange)=\"onDetailOpenChange($event)\" [heading]=\"showingAction() ? text.actionDetailHeading : text.detailHeading\">\n @if (selected(); as ev) {\n <div class=\"detail\">\n <div class=\"detail__head\">\n <strong class=\"detail__title\">{{ operationName(ev) }}</strong>\n <hm-tag [name]=\"outcomeLabel(headOutcome())\" [color]=\"outcomeColor(headOutcome())\" />\n </div>\n @if (showingAction() && selectedAction()?.orphan) {\n <hm-alert class=\"detail__orphan\" variant=\"info\" size=\"small\">{{ text.orphanNote }}</hm-alert>\n }\n @if (ev.operation_label && ev.operation_identifier) {\n <p class=\"muted detail__description\">{{ ev.operation_label }}</p>\n }\n\n @if (detailLoading()) {\n <p class=\"muted\" role=\"status\">{{ text.detailLoading }}</p>\n }\n @if (detailFailed()) {\n <hm-alert variant=\"warning\" size=\"small\">{{ text.detailError }}</hm-alert>\n }\n\n <dl class=\"detail__list\">\n @for (field of detailFields(); track field.label) {\n <div class=\"detail__row\">\n <dt>{{ field.label }}</dt>\n <dd>\n @if (field.code) { <code>{{ field.value }}</code> } @else { {{ field.value }} }\n </dd>\n </div>\n }\n </dl>\n\n @if (detailsJson()) {\n <h3 class=\"audit__section-title\">{{ text.fieldDetails }}</h3>\n <pre class=\"detail__json\">{{ detailsJson() }}</pre>\n }\n\n <!-- Por a\u00E7\u00E3o, os spans sobem para logo abaixo do cabe\u00E7alho (order no CSS); por evento, ficam no fim. -->\n @if (ev.trace_id) {\n <div class=\"detail__chain\" [class.detail__chain--first]=\"showingAction()\">\n <div class=\"detail__actions\">\n @if (!trace() && !traceLoading()) {\n <hm-button emphasis=\"medium\" size=\"small\" icon=\"tree-view\" (clicked)=\"showTrace()\">{{ text.viewTrace }}</hm-button>\n }\n <hm-button emphasis=\"minimal\" size=\"small\" icon=\"filter\" (clicked)=\"filterByTrace(ev.trace_id)\">{{ text.filterByTrace }}</hm-button>\n </div>\n @if (traceLoading()) {\n <p class=\"muted\" role=\"status\">{{ text.traceLoading }}</p>\n }\n @if (traceFailed()) {\n <hm-alert variant=\"error\" size=\"small\">{{ text.traceError }}</hm-alert>\n }\n @if (trace(); as tr) {\n <section class=\"trace\" aria-labelledby=\"audit-trace-title\">\n <h3 class=\"audit__section-title\" id=\"audit-trace-title\">{{ showingAction() ? text.spansHeading : text.traceHeading }}</h3>\n @if (tr.hidden_count) {\n <hm-alert class=\"trace__hidden\" variant=\"info\" size=\"small\">{{ plural(text.traceHidden, tr.hidden_count) }}</hm-alert>\n }\n @if (!tr.items.length) {\n <p class=\"muted\">{{ text.traceEmpty }}</p>\n } @else if (spanTimeline(); as tl) {\n <p class=\"muted trace__summary\">{{ fmt(text.spansSummary, { count: number(tl.rows.length), duration: duration(tl.totalMs) }) }}</p>\n <!-- Linha do tempo: cada span \u00E9 uma linha (recuo = profundidade pela cadeia parent_span_id) com a\n barra posicionada no tempo, proporcional \u00E0 dura\u00E7\u00E3o. O texto da linha (e o aria-label) traz\n tudo o que a barra mostra. -->\n <ol class=\"spans\">\n @for (row of tl.rows; track row.event.event_id) {\n <li class=\"spans__item\" [class.spans__item--current]=\"row.event.event_id === ev.event_id\">\n <button type=\"button\" class=\"spans__link\"\n [attr.aria-current]=\"row.event.event_id === ev.event_id ? 'true' : null\"\n [attr.aria-label]=\"spanAria(row)\"\n (click)=\"openEvent(row.event, true)\">\n <span class=\"spans__head\" [style.padding-inline-start.rem]=\"row.depth * 1\">\n <span class=\"spans__app\">{{ row.event.app_name ?? '' }}</span>\n <code class=\"spans__op\">{{ operationName(row.event) }}</code>\n </span>\n <hm-tag class=\"spans__tag\" [name]=\"outcomeLabel(row.event.outcome)\" [color]=\"outcomeColor(row.event.outcome)\" />\n <span class=\"spans__dur\">{{ duration(row.event.duration_ms) }}</span>\n <span class=\"spans__track\" aria-hidden=\"true\">\n <span class=\"spans__bar spans__bar--{{ row.event.outcome }}\" [style.left.%]=\"row.left\" [style.width.%]=\"row.width\"></span>\n </span>\n </button>\n </li>\n }\n </ol>\n }\n </section>\n }\n </div>\n }\n </div>\n }\n</hm-drawer>\n", styles: [".card{border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);background:var(--hm-surface-raised)}.card-block{padding:var(--hm-space-sm, 8px)}.muted{color:var(--hm-muted)}:host{display:block}.audit{display:flex;flex-direction:column;gap:var(--hm-space-md, 16px)}.audit__toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:var(--hm-space-sm, 8px)}.audit__period-note{font-size:var(--hm-font-size-label)}.audit__toolbar-end{display:inline-flex;align-items:center;flex-wrap:wrap;gap:var(--hm-space-sm, 8px) var(--hm-space-md, 16px);margin-inline-start:auto}.audit__updated{font-size:var(--hm-font-size-xs);font-variant-numeric:tabular-nums}.audit__state{min-block-size:320px}.audit__section-title{margin:0 0 var(--hm-space-sm, 8px);font-size:var(--hm-font-size-sm);font-weight:var(--hm-font-weight-semibold);color:var(--hm-text)}.audit__kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:var(--hm-space-sm, 8px);align-items:stretch}.timeline{display:flex;align-items:flex-end;gap:2px;block-size:120px;margin:0;padding:0;list-style:none;border-block-end:1px solid var(--hm-border)}.timeline__bar{flex:1 1 0;min-inline-size:2px;block-size:100%;display:flex;flex-direction:column;justify-content:flex-end;border-radius:2px 2px 0 0;overflow:hidden}.timeline__bar:hover{background:color-mix(in srgb,var(--hm-text) 6%,transparent)}.timeline__seg{display:block;inline-size:100%}.timeline__seg--ok,.swatch--ok{background:color-mix(in srgb,var(--hm-action) 55%,transparent)}.timeline__seg--fail,.swatch--fail{background:var(--hm-danger)}.timeline__seg--denied,.swatch--denied{background:var(--hm-warning, #c98a00)}.timeline__axis{display:flex;justify-content:space-between;margin-block-start:4px;font-size:var(--hm-font-size-xs);color:var(--hm-muted)}.timeline__legend{display:flex;flex-wrap:wrap;gap:var(--hm-space-sm, 8px) var(--hm-space-md, 16px);margin:var(--hm-space-sm, 8px) 0 0;padding:0;list-style:none;font-size:var(--hm-font-size-xs);color:var(--hm-muted)}.timeline__legend li{display:inline-flex;align-items:center;gap:6px}.swatch{display:inline-block;inline-size:10px;block-size:10px;border-radius:2px}.audit__tops{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:var(--hm-space-sm, 8px);align-items:stretch}.audit__grid{position:relative;display:block}.audit__grid-progress{position:absolute;inset-inline:0;inset-block-start:0;z-index:2;pointer-events:none}.audit__grid--busy hm-data-grid{opacity:.6;transition:opacity .12s ease}.audit__op{font-family:var(--hm-font-mono, ui-monospace, monospace);font-size:var(--hm-font-size-xs)}.audit__page-error{display:block}@media(prefers-reduced-motion:reduce){.audit__grid--busy hm-data-grid{transition:none}}:host-context([data-motion=reduce]) .audit__grid--busy hm-data-grid{transition:none}.detail{display:flex;flex-direction:column;gap:var(--hm-space-sm, 8px)}.detail__head{display:flex;align-items:center;gap:var(--hm-space-sm, 8px);flex-wrap:wrap}.detail__title{font-size:var(--hm-font-size-md)}.detail__list{display:grid;grid-template-columns:minmax(8rem,max-content) minmax(0,1fr);gap:6px var(--hm-space-md, 16px);margin:0}.detail__row{display:contents}.detail__row dt{color:var(--hm-muted);font-size:var(--hm-font-size-label)}.detail__row dd{margin:0;font-size:var(--hm-font-size-label);overflow-wrap:anywhere}.detail__json{margin:0;padding:var(--hm-space-sm, 8px);max-block-size:280px;overflow:auto;border:1px solid var(--hm-border);border-radius:var(--hm-radius-control);background:color-mix(in srgb,var(--hm-text) 4%,transparent);font-size:var(--hm-font-size-xs)}.detail__description{margin:0;font-size:var(--hm-font-size-label)}.detail__actions{display:flex;flex-wrap:wrap;gap:var(--hm-space-sm, 8px)}.trace{margin-block-start:var(--hm-space-sm, 8px)}.trace__hidden{margin-block-end:var(--hm-space-sm, 8px)}.detail__head,.detail__description,.detail__orphan{order:-3}.detail__chain{display:flex;flex-direction:column;gap:var(--hm-space-sm, 8px)}.detail__chain--first{order:-2}.trace__summary{margin:0 0 var(--hm-space-xs, 4px);font-size:var(--hm-font-size-label)}.spans{margin:0;padding:0;list-style:none;border-block-start:1px solid var(--hm-border)}.spans__item{border-block-end:1px solid var(--hm-border)}.spans__item--current .spans__link{background:color-mix(in srgb,var(--hm-action) 10%,transparent);box-shadow:inset 2px 0 0 var(--hm-action)}.spans__link{display:grid;grid-template-columns:minmax(0,1fr) auto auto;grid-template-areas:\"head tag dur\" \"track track track\";align-items:center;gap:4px var(--hm-space-sm, 8px);inline-size:100%;padding:6px 8px;border:0;background:transparent;color:var(--hm-text);font:inherit;font-size:var(--hm-font-size-label);text-align:start;cursor:pointer}.spans__link:hover{background:color-mix(in srgb,var(--hm-action) 6%,transparent)}.spans__link:focus-visible{outline:2px solid var(--hm-action);outline-offset:-2px}.spans__head{grid-area:head;display:flex;flex-direction:column;min-inline-size:0}.spans__app{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.spans__op{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--hm-font-mono, ui-monospace, monospace);font-size:var(--hm-font-size-xs)}.spans__tag{grid-area:tag}.spans__dur{grid-area:dur;min-inline-size:4.5rem;text-align:end;font-variant-numeric:tabular-nums;color:var(--hm-muted)}.spans__track{grid-area:track;position:relative;block-size:6px;border-radius:3px;background:color-mix(in srgb,var(--hm-text) 6%,transparent)}.spans__bar{position:absolute;inset-block:0;border-radius:3px;background:color-mix(in srgb,var(--hm-action) 70%,transparent)}.spans__bar--failure{background:var(--hm-danger)}.spans__bar--denied{background:var(--hm-warning, #c98a00)}.audit__spans{display:inline-flex;align-items:center;gap:6px;font-variant-numeric:tabular-nums}\n"] }]
1345
1345
  }], ctorParameters: () => [], propDecorators: { scope: [{ type: i0.Input, args: [{ isSignal: true, alias: "scope", required: true }] }], clientId: [{ type: i0.Input, args: [{ isSignal: true, alias: "clientId", required: false }] }], adminApi: [{ type: i0.Input, args: [{ isSignal: true, alias: "adminApi", required: false }] }] } });
1346
1346
 
1347
1347
  /** Tentativas por página, de saída (o servidor aceita até 200). */
@@ -1528,14 +1528,14 @@ class SpineLoginAttemptsComponent {
1528
1528
  return why ? formatMessage(this.text.loadErrorDetail, { detail: why }) : this.text.loadError;
1529
1529
  }
1530
1530
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineLoginAttemptsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1531
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: SpineLoginAttemptsComponent, isStandalone: true, selector: "spine-login-attempts", inputs: { scope: { classPropertyName: "scope", publicName: "scope", isSignal: true, isRequired: true, transformFunction: null }, clientId: { classPropertyName: "clientId", publicName: "clientId", isSignal: true, isRequired: false, transformFunction: null }, realmId: { classPropertyName: "realmId", publicName: "realmId", isSignal: true, isRequired: false, transformFunction: null }, adminApi: { classPropertyName: "adminApi", publicName: "adminApi", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"attempts\">\n <div class=\"attempts__actions\">\n <hm-button emphasis=\"medium\" icon=\"refresh\" [disabled]=\"loading()\" (clicked)=\"load(pageState().page)\">{{ text.refresh }}</hm-button>\n </div>\n\n @if (loadError(); as message) {\n <hm-alert variant=\"error\" [closable]=\"true\" (closed)=\"loadError.set(null)\">{{ message }}</hm-alert>\n }\n\n <hm-card>\n <div class=\"attempts__filters\" [formGroup]=\"filtersForm\">\n <div class=\"attempts__row\">\n <hm-input class=\"attempts__search\" [label]=\"text.user\" [placeholder]=\"text.userPlaceholder\" formControlName=\"user\" (keyup.enter)=\"applyFilters()\" />\n <hm-date-range-picker class=\"attempts__period\" [label]=\"text.period\" formControlName=\"period\" [presets]=\"periodPresets\" />\n <hm-combobox class=\"attempts__field\" [label]=\"text.result\" formControlName=\"result\" [options]=\"resultOptions\" />\n <hm-combobox class=\"attempts__field\" [label]=\"text.method\" formControlName=\"method\" [options]=\"methodOptions\" />\n <div class=\"attempts__buttons\">\n <hm-button emphasis=\"medium\" (clicked)=\"clearFilters()\">{{ text.clear }}</hm-button>\n <hm-button emphasis=\"minimal\" [iconEnd]=\"showAdvancedFilters() ? 'chevron-up' : 'chevron-down'\" (clicked)=\"showAdvancedFilters.update(v => !v)\">\n {{ text.advancedFilters }}\n </hm-button>\n </div>\n </div>\n @if (showAdvancedFilters()) {\n <div class=\"attempts__row\">\n <hm-input class=\"attempts__field\" [label]=\"text.ip\" formControlName=\"ip\" [placeholder]=\"text.ipPlaceholder\" [hint]=\"text.ipHint\" />\n <hm-combobox class=\"attempts__field attempts__field--wide\" [label]=\"text.reason\" formControlName=\"reason\" [options]=\"reasonOptions\" />\n </div>\n }\n </div>\n </hm-card>\n\n <hm-card>\n <hm-data-grid\n [caption]=\"text.caption\"\n [columns]=\"columns()\"\n [rows]=\"attempts()\"\n [rowId]=\"rowId\"\n [rowActions]=\"rowActions\"\n [loading]=\"loading()\"\n [filterable]=\"false\"\n [paginated]=\"false\"\n [totalItems]=\"total()\"\n [pageState]=\"pageState()\"\n (pageStateChange)=\"onPageStateChange($event)\"\n [pageSizes]=\"pageSizes\"\n [itemsLabel]=\"text.pageItems\"\n [pageSizeLabel]=\"text.pageSize\"\n (actionTriggered)=\"onRowAction($event)\"\n >\n <!-- Data/hora no fuso do navegador (a API manda ISO em UTC). -->\n <ng-template hmCell=\"created_at\" let-a>{{ formatDate(a.created_at) }}</ng-template>\n <ng-template hmCell=\"result\" let-a>\n <hm-tag [name]=\"resultLabel(a.result)\" [color]=\"resultColor(a.result)\" />\n </ng-template>\n <ng-template hmCell=\"user\" let-a>\n @if (a.user_id) {\n <span class=\"attempts__person\">\n <hm-account-avatar size=\"small\" [name]=\"a.user_name || a.user_username || ''\" [imageUrl]=\"a.user_picture\" />\n <span class=\"attempts__user\">\n <span class=\"attempts__user-name\">{{ a.user_name || a.user_username }}</span>\n <span class=\"attempts__user-detail\">{{ a.user_username }}@if (a.user_email) { \u00B7 {{ a.user_email }} }</span>\n </span>\n </span>\n } @else if (a.username) {\n <span class=\"attempts__user\">\n <span class=\"attempts__user-name\">{{ a.username }}</span>\n <span class=\"attempts__user-detail\">{{ text.noMatchingUser }}</span>\n </span>\n } @else {\n <span class=\"attempts__muted\">\u2014</span>\n }\n </ng-template>\n <ng-template hmCell=\"reason\" let-a>\n @if (a.reason) { {{ reasonLabel(a.reason) }} } @else { <span class=\"attempts__muted\">\u2014</span> }\n </ng-template>\n <ng-template hmCell=\"ip\" let-a>\n @if (a.ip) { <code class=\"attempts__ip\">{{ a.ip }}</code> } @else { <span class=\"attempts__muted\">\u2014</span> }\n </ng-template>\n <ng-template hmCell=\"client\" let-a>\n @if (a.client_name) { {{ a.client_name }} } @else { <span class=\"attempts__muted\">\u2014</span> }\n </ng-template>\n <ng-template hmCell=\"user_agent\" let-a>\n @if (a.user_agent) { <span class=\"attempts__ua\" [attr.title]=\"a.user_agent\">{{ a.user_agent }}</span> } @else { <span class=\"attempts__muted\">\u2014</span> }\n </ng-template>\n </hm-data-grid>\n </hm-card>\n</div>\n", styles: [":host{display:block}.attempts{display:flex;flex-direction:column;gap:var(--hm-space-md, 16px)}.attempts__actions{display:flex;justify-content:flex-end}.attempts__filters{display:flex;flex-direction:column;gap:var(--hm-space-md, 16px)}.attempts__row{display:flex;align-items:flex-end;flex-wrap:wrap;gap:var(--hm-space-sm, 12px)}.attempts__row+.attempts__row{padding-top:var(--hm-space-md, 16px);border-top:1px solid var(--hm-divider)}.attempts__search{display:block;flex:1 1 16rem;min-width:0}.attempts__period{display:block;flex:0 0 auto}.attempts__field{display:block;flex:0 1 12rem;min-width:0}.attempts__field--wide{flex-basis:18rem}.attempts__buttons{display:inline-flex;flex-wrap:wrap;gap:var(--hm-space-sm, 8px)}.attempts__person{display:inline-flex;align-items:center;gap:var(--hm-space-sm);min-width:0}.attempts__person hm-account-avatar{flex:none}.attempts__user{display:inline-flex;flex-direction:column;min-width:0;line-height:1.25}.attempts__user-name{font-weight:600}.attempts__user-detail{color:var(--hm-muted);font-size:.8rem}.attempts__ip{font-family:var(--hm-font-mono, monospace);font-size:.85rem}.attempts__ua{display:inline-block;max-width:22rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.attempts__muted{color:var(--hm-muted)}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: AccountAvatarComponent, selector: "hm-account-avatar", inputs: ["name", "imageUrl", "size", "loading"] }, { kind: "component", type: AlertComponent, selector: "hm-alert", inputs: ["variant", "appearance", "heading", "closable", "size", "showIcon", "link", "linkLabel", "linkTarget", "dismissKey", "duration"], outputs: ["closed"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: CardComponent, selector: "hm-card", inputs: ["heading", "icon", "selected", "fill", "media", "mediaAlt", "mediaDecorative", "mediaPosition"] }, { kind: "component", type: ComboboxComponent, selector: "hm-combobox", inputs: ["label", "options", "value", "placeholder", "icon", "hint", "size", "labelHidden", "clearable", "loading", "allowCustomValue"], outputs: ["valueChange"] }, { kind: "directive", type: DataGridCellDirective, selector: "ng-template[hmCell]", inputs: ["hmCell"] }, { 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: "component", type: DateRangePickerComponent, selector: "hm-date-range-picker", inputs: ["value", "label", "labelHidden", "hint", "min", "max", "valueFormat", "presets"], outputs: ["valueChange"] }, { kind: "component", type: InputComponent, selector: "hm-input", inputs: ["value", "label", "type", "placeholder", "icon", "labelHidden", "hint", "prefix", "suffix", "prefixIcon", "mask", "maskValue", "min", "max", "step", "stepButtons", "maxLength", "autocomplete", "inputMode"], outputs: ["valueChange"] }, { kind: "component", type: TagComponent, selector: "hm-tag", inputs: ["name", "color", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1531
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: SpineLoginAttemptsComponent, isStandalone: true, selector: "spine-login-attempts", inputs: { scope: { classPropertyName: "scope", publicName: "scope", isSignal: true, isRequired: true, transformFunction: null }, clientId: { classPropertyName: "clientId", publicName: "clientId", isSignal: true, isRequired: false, transformFunction: null }, realmId: { classPropertyName: "realmId", publicName: "realmId", isSignal: true, isRequired: false, transformFunction: null }, adminApi: { classPropertyName: "adminApi", publicName: "adminApi", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"attempts\">\n <div class=\"attempts__actions\">\n <hm-button emphasis=\"medium\" icon=\"refresh\" [disabled]=\"loading()\" (clicked)=\"load(pageState().page)\">{{ text.refresh }}</hm-button>\n </div>\n\n @if (loadError(); as message) {\n <hm-alert variant=\"error\" [closable]=\"true\" (closed)=\"loadError.set(null)\">{{ message }}</hm-alert>\n }\n\n <hm-card>\n <div class=\"attempts__filters\" [formGroup]=\"filtersForm\">\n <div class=\"attempts__row\">\n <hm-input class=\"attempts__search\" [label]=\"text.user\" [placeholder]=\"text.userPlaceholder\" formControlName=\"user\" (keyup.enter)=\"applyFilters()\" />\n <hm-date-range-picker class=\"attempts__period\" [label]=\"text.period\" formControlName=\"period\" [presets]=\"periodPresets\" />\n <hm-combobox class=\"attempts__field\" [label]=\"text.result\" formControlName=\"result\" [options]=\"resultOptions\" />\n <hm-combobox class=\"attempts__field\" [label]=\"text.method\" formControlName=\"method\" [options]=\"methodOptions\" />\n <div class=\"attempts__buttons\">\n <hm-button emphasis=\"medium\" (clicked)=\"clearFilters()\">{{ text.clear }}</hm-button>\n <hm-button emphasis=\"minimal\" [iconEnd]=\"showAdvancedFilters() ? 'chevron-up' : 'chevron-down'\" (clicked)=\"showAdvancedFilters.update(v => !v)\">\n {{ text.advancedFilters }}\n </hm-button>\n </div>\n </div>\n @if (showAdvancedFilters()) {\n <div class=\"attempts__row\">\n <hm-input class=\"attempts__field\" [label]=\"text.ip\" formControlName=\"ip\" [placeholder]=\"text.ipPlaceholder\" [hint]=\"text.ipHint\" />\n <hm-combobox class=\"attempts__field attempts__field--wide\" [label]=\"text.reason\" formControlName=\"reason\" [options]=\"reasonOptions\" />\n </div>\n }\n </div>\n </hm-card>\n\n <hm-card>\n <hm-data-grid\n [caption]=\"text.caption\"\n [columns]=\"columns()\"\n [rows]=\"attempts()\"\n [rowId]=\"rowId\"\n [rowActions]=\"rowActions\"\n [loading]=\"loading()\"\n [filterable]=\"false\"\n [paginated]=\"false\"\n [totalItems]=\"total()\"\n [pageState]=\"pageState()\"\n (pageStateChange)=\"onPageStateChange($event)\"\n [pageSizes]=\"pageSizes\"\n [itemsLabel]=\"text.pageItems\"\n [pageSizeLabel]=\"text.pageSize\"\n (actionTriggered)=\"onRowAction($event)\"\n >\n <!-- Data/hora no fuso do navegador (a API manda ISO em UTC). -->\n <ng-template hmCell=\"created_at\" let-a>{{ formatDate(a.created_at) }}</ng-template>\n <ng-template hmCell=\"result\" let-a>\n <hm-tag [name]=\"resultLabel(a.result)\" [color]=\"resultColor(a.result)\" />\n </ng-template>\n <ng-template hmCell=\"user\" let-a>\n @if (a.user_id) {\n <span class=\"attempts__person\">\n <hm-account-avatar size=\"small\" [name]=\"a.user_name || a.user_username || ''\" [imageUrl]=\"a.user_picture\" />\n <span class=\"attempts__user\">\n <span class=\"attempts__user-name\">{{ a.user_name || a.user_username }}</span>\n <span class=\"attempts__user-detail\">{{ a.user_username }}@if (a.user_email) { \u00B7 {{ a.user_email }} }</span>\n </span>\n </span>\n } @else if (a.username) {\n <span class=\"attempts__user\">\n <span class=\"attempts__user-name\">{{ a.username }}</span>\n <span class=\"attempts__user-detail\">{{ text.noMatchingUser }}</span>\n </span>\n } @else {\n <span class=\"attempts__muted\">\u2014</span>\n }\n </ng-template>\n <ng-template hmCell=\"reason\" let-a>\n @if (a.reason) { {{ reasonLabel(a.reason) }} } @else { <span class=\"attempts__muted\">\u2014</span> }\n </ng-template>\n <ng-template hmCell=\"ip\" let-a>\n @if (a.ip) { <code class=\"attempts__ip\">{{ a.ip }}</code> } @else { <span class=\"attempts__muted\">\u2014</span> }\n </ng-template>\n <ng-template hmCell=\"client\" let-a>\n @if (a.client_name) { {{ a.client_name }} } @else { <span class=\"attempts__muted\">\u2014</span> }\n </ng-template>\n <ng-template hmCell=\"user_agent\" let-a>\n @if (a.user_agent) { <span class=\"attempts__ua\" [attr.title]=\"a.user_agent\">{{ a.user_agent }}</span> } @else { <span class=\"attempts__muted\">\u2014</span> }\n </ng-template>\n </hm-data-grid>\n </hm-card>\n</div>\n", styles: [":host{display:block}.attempts{display:flex;flex-direction:column;gap:var(--hm-space-md, 16px)}.attempts__actions{display:flex;justify-content:flex-end}.attempts__filters{display:flex;flex-direction:column;gap:var(--hm-space-md, 16px)}.attempts__row{display:flex;align-items:flex-end;flex-wrap:wrap;gap:var(--hm-space-sm, 12px)}.attempts__row+.attempts__row{padding-top:var(--hm-space-md, 16px);border-top:1px solid var(--hm-divider)}.attempts__search{display:block;flex:1 1 16rem;min-width:0}.attempts__period{display:block;flex:0 0 auto}.attempts__field{display:block;flex:0 1 12rem;min-width:0}.attempts__field--wide{flex-basis:18rem}.attempts__buttons{display:inline-flex;flex-wrap:wrap;gap:var(--hm-space-sm, 8px)}.attempts__person{display:inline-flex;align-items:center;gap:var(--hm-space-sm);min-width:0}.attempts__person hm-account-avatar{flex:none}.attempts__user{display:inline-flex;flex-direction:column;min-width:0;line-height:1.25}.attempts__user-name{font-weight:var(--hm-font-weight-semibold)}.attempts__user-detail{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.attempts__ip{font-family:var(--hm-font-mono, monospace);font-size:var(--hm-font-size-label)}.attempts__ua{display:inline-block;max-width:22rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.attempts__muted{color:var(--hm-muted)}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: AccountAvatarComponent, selector: "hm-account-avatar", inputs: ["name", "imageUrl", "size", "loading", "presence", "texts"] }, { kind: "component", type: AlertComponent, selector: "hm-alert", inputs: ["variant", "appearance", "heading", "closable", "size", "showIcon", "link", "linkLabel", "linkTarget", "dismissKey", "duration"], outputs: ["closed"] }, { kind: "component", type: ButtonComponent, selector: "hm-button", inputs: ["emphasis", "intent", "size", "type", "icon", "iconDirection", "iconEnd", "disabled", "loading", "loadingState", "block", "link", "newTab", "caret", "inMenu", "toggle", "pressed"], outputs: ["loadingStateChange", "pressedChange", "clicked"] }, { kind: "component", type: CardComponent, selector: "hm-card", inputs: ["heading", "icon", "selected", "fill", "media", "mediaAlt", "mediaDecorative", "mediaPosition"] }, { kind: "component", type: ComboboxComponent, selector: "hm-combobox", inputs: ["label", "options", "value", "placeholder", "icon", "hint", "size", "labelHidden", "clearable", "loading", "allowCustomValue"], outputs: ["valueChange"] }, { kind: "directive", type: DataGridCellDirective, selector: "ng-template[hmCell]", inputs: ["hmCell"] }, { 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", "striped", "columnGroups", "editMode", "editBatch", "texts", "treeChildren", "treeHasChildren", "treeExpanded", "searchTerm"], outputs: ["pageStateChange", "sortChange", "selectionChange", "pinnedColumnsChange", "columnStateChange", "actionTriggered", "detailToggled", "rowClicked", "clearFiltersRequested", "filteredChange", "searchTermChanged", "groupByChange", "filterQueryChange", "columnOrderChange", "editSaved", "editStarted", "editCanceled", "treeExpandedChange", "treeToggled", "searchTermChange", "detailPaneToggled"] }, { kind: "component", type: DateRangePickerComponent, selector: "hm-date-range-picker", inputs: ["value", "label", "labelHidden", "hint", "min", "max", "valueFormat", "presets"], outputs: ["valueChange"] }, { kind: "component", type: InputComponent, selector: "hm-input", inputs: ["value", "label", "type", "placeholder", "icon", "labelHidden", "hint", "prefix", "suffix", "prefixIcon", "mask", "maskValue", "min", "max", "step", "stepButtons", "maxLength", "autocomplete", "inputMode"], outputs: ["valueChange"] }, { kind: "component", type: TagComponent, selector: "hm-tag", inputs: ["name", "color", "compact"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1532
1532
  }
1533
1533
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SpineLoginAttemptsComponent, decorators: [{
1534
1534
  type: Component,
1535
1535
  args: [{ selector: 'spine-login-attempts', imports: [
1536
1536
  ReactiveFormsModule, AccountAvatarComponent, AlertComponent, ButtonComponent, CardComponent, ComboboxComponent, DataGridCellDirective, DataGridComponent,
1537
1537
  DateRangePickerComponent, InputComponent, TagComponent,
1538
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"attempts\">\n <div class=\"attempts__actions\">\n <hm-button emphasis=\"medium\" icon=\"refresh\" [disabled]=\"loading()\" (clicked)=\"load(pageState().page)\">{{ text.refresh }}</hm-button>\n </div>\n\n @if (loadError(); as message) {\n <hm-alert variant=\"error\" [closable]=\"true\" (closed)=\"loadError.set(null)\">{{ message }}</hm-alert>\n }\n\n <hm-card>\n <div class=\"attempts__filters\" [formGroup]=\"filtersForm\">\n <div class=\"attempts__row\">\n <hm-input class=\"attempts__search\" [label]=\"text.user\" [placeholder]=\"text.userPlaceholder\" formControlName=\"user\" (keyup.enter)=\"applyFilters()\" />\n <hm-date-range-picker class=\"attempts__period\" [label]=\"text.period\" formControlName=\"period\" [presets]=\"periodPresets\" />\n <hm-combobox class=\"attempts__field\" [label]=\"text.result\" formControlName=\"result\" [options]=\"resultOptions\" />\n <hm-combobox class=\"attempts__field\" [label]=\"text.method\" formControlName=\"method\" [options]=\"methodOptions\" />\n <div class=\"attempts__buttons\">\n <hm-button emphasis=\"medium\" (clicked)=\"clearFilters()\">{{ text.clear }}</hm-button>\n <hm-button emphasis=\"minimal\" [iconEnd]=\"showAdvancedFilters() ? 'chevron-up' : 'chevron-down'\" (clicked)=\"showAdvancedFilters.update(v => !v)\">\n {{ text.advancedFilters }}\n </hm-button>\n </div>\n </div>\n @if (showAdvancedFilters()) {\n <div class=\"attempts__row\">\n <hm-input class=\"attempts__field\" [label]=\"text.ip\" formControlName=\"ip\" [placeholder]=\"text.ipPlaceholder\" [hint]=\"text.ipHint\" />\n <hm-combobox class=\"attempts__field attempts__field--wide\" [label]=\"text.reason\" formControlName=\"reason\" [options]=\"reasonOptions\" />\n </div>\n }\n </div>\n </hm-card>\n\n <hm-card>\n <hm-data-grid\n [caption]=\"text.caption\"\n [columns]=\"columns()\"\n [rows]=\"attempts()\"\n [rowId]=\"rowId\"\n [rowActions]=\"rowActions\"\n [loading]=\"loading()\"\n [filterable]=\"false\"\n [paginated]=\"false\"\n [totalItems]=\"total()\"\n [pageState]=\"pageState()\"\n (pageStateChange)=\"onPageStateChange($event)\"\n [pageSizes]=\"pageSizes\"\n [itemsLabel]=\"text.pageItems\"\n [pageSizeLabel]=\"text.pageSize\"\n (actionTriggered)=\"onRowAction($event)\"\n >\n <!-- Data/hora no fuso do navegador (a API manda ISO em UTC). -->\n <ng-template hmCell=\"created_at\" let-a>{{ formatDate(a.created_at) }}</ng-template>\n <ng-template hmCell=\"result\" let-a>\n <hm-tag [name]=\"resultLabel(a.result)\" [color]=\"resultColor(a.result)\" />\n </ng-template>\n <ng-template hmCell=\"user\" let-a>\n @if (a.user_id) {\n <span class=\"attempts__person\">\n <hm-account-avatar size=\"small\" [name]=\"a.user_name || a.user_username || ''\" [imageUrl]=\"a.user_picture\" />\n <span class=\"attempts__user\">\n <span class=\"attempts__user-name\">{{ a.user_name || a.user_username }}</span>\n <span class=\"attempts__user-detail\">{{ a.user_username }}@if (a.user_email) { \u00B7 {{ a.user_email }} }</span>\n </span>\n </span>\n } @else if (a.username) {\n <span class=\"attempts__user\">\n <span class=\"attempts__user-name\">{{ a.username }}</span>\n <span class=\"attempts__user-detail\">{{ text.noMatchingUser }}</span>\n </span>\n } @else {\n <span class=\"attempts__muted\">\u2014</span>\n }\n </ng-template>\n <ng-template hmCell=\"reason\" let-a>\n @if (a.reason) { {{ reasonLabel(a.reason) }} } @else { <span class=\"attempts__muted\">\u2014</span> }\n </ng-template>\n <ng-template hmCell=\"ip\" let-a>\n @if (a.ip) { <code class=\"attempts__ip\">{{ a.ip }}</code> } @else { <span class=\"attempts__muted\">\u2014</span> }\n </ng-template>\n <ng-template hmCell=\"client\" let-a>\n @if (a.client_name) { {{ a.client_name }} } @else { <span class=\"attempts__muted\">\u2014</span> }\n </ng-template>\n <ng-template hmCell=\"user_agent\" let-a>\n @if (a.user_agent) { <span class=\"attempts__ua\" [attr.title]=\"a.user_agent\">{{ a.user_agent }}</span> } @else { <span class=\"attempts__muted\">\u2014</span> }\n </ng-template>\n </hm-data-grid>\n </hm-card>\n</div>\n", styles: [":host{display:block}.attempts{display:flex;flex-direction:column;gap:var(--hm-space-md, 16px)}.attempts__actions{display:flex;justify-content:flex-end}.attempts__filters{display:flex;flex-direction:column;gap:var(--hm-space-md, 16px)}.attempts__row{display:flex;align-items:flex-end;flex-wrap:wrap;gap:var(--hm-space-sm, 12px)}.attempts__row+.attempts__row{padding-top:var(--hm-space-md, 16px);border-top:1px solid var(--hm-divider)}.attempts__search{display:block;flex:1 1 16rem;min-width:0}.attempts__period{display:block;flex:0 0 auto}.attempts__field{display:block;flex:0 1 12rem;min-width:0}.attempts__field--wide{flex-basis:18rem}.attempts__buttons{display:inline-flex;flex-wrap:wrap;gap:var(--hm-space-sm, 8px)}.attempts__person{display:inline-flex;align-items:center;gap:var(--hm-space-sm);min-width:0}.attempts__person hm-account-avatar{flex:none}.attempts__user{display:inline-flex;flex-direction:column;min-width:0;line-height:1.25}.attempts__user-name{font-weight:600}.attempts__user-detail{color:var(--hm-muted);font-size:.8rem}.attempts__ip{font-family:var(--hm-font-mono, monospace);font-size:.85rem}.attempts__ua{display:inline-block;max-width:22rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.attempts__muted{color:var(--hm-muted)}\n"] }]
1538
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"attempts\">\n <div class=\"attempts__actions\">\n <hm-button emphasis=\"medium\" icon=\"refresh\" [disabled]=\"loading()\" (clicked)=\"load(pageState().page)\">{{ text.refresh }}</hm-button>\n </div>\n\n @if (loadError(); as message) {\n <hm-alert variant=\"error\" [closable]=\"true\" (closed)=\"loadError.set(null)\">{{ message }}</hm-alert>\n }\n\n <hm-card>\n <div class=\"attempts__filters\" [formGroup]=\"filtersForm\">\n <div class=\"attempts__row\">\n <hm-input class=\"attempts__search\" [label]=\"text.user\" [placeholder]=\"text.userPlaceholder\" formControlName=\"user\" (keyup.enter)=\"applyFilters()\" />\n <hm-date-range-picker class=\"attempts__period\" [label]=\"text.period\" formControlName=\"period\" [presets]=\"periodPresets\" />\n <hm-combobox class=\"attempts__field\" [label]=\"text.result\" formControlName=\"result\" [options]=\"resultOptions\" />\n <hm-combobox class=\"attempts__field\" [label]=\"text.method\" formControlName=\"method\" [options]=\"methodOptions\" />\n <div class=\"attempts__buttons\">\n <hm-button emphasis=\"medium\" (clicked)=\"clearFilters()\">{{ text.clear }}</hm-button>\n <hm-button emphasis=\"minimal\" [iconEnd]=\"showAdvancedFilters() ? 'chevron-up' : 'chevron-down'\" (clicked)=\"showAdvancedFilters.update(v => !v)\">\n {{ text.advancedFilters }}\n </hm-button>\n </div>\n </div>\n @if (showAdvancedFilters()) {\n <div class=\"attempts__row\">\n <hm-input class=\"attempts__field\" [label]=\"text.ip\" formControlName=\"ip\" [placeholder]=\"text.ipPlaceholder\" [hint]=\"text.ipHint\" />\n <hm-combobox class=\"attempts__field attempts__field--wide\" [label]=\"text.reason\" formControlName=\"reason\" [options]=\"reasonOptions\" />\n </div>\n }\n </div>\n </hm-card>\n\n <hm-card>\n <hm-data-grid\n [caption]=\"text.caption\"\n [columns]=\"columns()\"\n [rows]=\"attempts()\"\n [rowId]=\"rowId\"\n [rowActions]=\"rowActions\"\n [loading]=\"loading()\"\n [filterable]=\"false\"\n [paginated]=\"false\"\n [totalItems]=\"total()\"\n [pageState]=\"pageState()\"\n (pageStateChange)=\"onPageStateChange($event)\"\n [pageSizes]=\"pageSizes\"\n [itemsLabel]=\"text.pageItems\"\n [pageSizeLabel]=\"text.pageSize\"\n (actionTriggered)=\"onRowAction($event)\"\n >\n <!-- Data/hora no fuso do navegador (a API manda ISO em UTC). -->\n <ng-template hmCell=\"created_at\" let-a>{{ formatDate(a.created_at) }}</ng-template>\n <ng-template hmCell=\"result\" let-a>\n <hm-tag [name]=\"resultLabel(a.result)\" [color]=\"resultColor(a.result)\" />\n </ng-template>\n <ng-template hmCell=\"user\" let-a>\n @if (a.user_id) {\n <span class=\"attempts__person\">\n <hm-account-avatar size=\"small\" [name]=\"a.user_name || a.user_username || ''\" [imageUrl]=\"a.user_picture\" />\n <span class=\"attempts__user\">\n <span class=\"attempts__user-name\">{{ a.user_name || a.user_username }}</span>\n <span class=\"attempts__user-detail\">{{ a.user_username }}@if (a.user_email) { \u00B7 {{ a.user_email }} }</span>\n </span>\n </span>\n } @else if (a.username) {\n <span class=\"attempts__user\">\n <span class=\"attempts__user-name\">{{ a.username }}</span>\n <span class=\"attempts__user-detail\">{{ text.noMatchingUser }}</span>\n </span>\n } @else {\n <span class=\"attempts__muted\">\u2014</span>\n }\n </ng-template>\n <ng-template hmCell=\"reason\" let-a>\n @if (a.reason) { {{ reasonLabel(a.reason) }} } @else { <span class=\"attempts__muted\">\u2014</span> }\n </ng-template>\n <ng-template hmCell=\"ip\" let-a>\n @if (a.ip) { <code class=\"attempts__ip\">{{ a.ip }}</code> } @else { <span class=\"attempts__muted\">\u2014</span> }\n </ng-template>\n <ng-template hmCell=\"client\" let-a>\n @if (a.client_name) { {{ a.client_name }} } @else { <span class=\"attempts__muted\">\u2014</span> }\n </ng-template>\n <ng-template hmCell=\"user_agent\" let-a>\n @if (a.user_agent) { <span class=\"attempts__ua\" [attr.title]=\"a.user_agent\">{{ a.user_agent }}</span> } @else { <span class=\"attempts__muted\">\u2014</span> }\n </ng-template>\n </hm-data-grid>\n </hm-card>\n</div>\n", styles: [":host{display:block}.attempts{display:flex;flex-direction:column;gap:var(--hm-space-md, 16px)}.attempts__actions{display:flex;justify-content:flex-end}.attempts__filters{display:flex;flex-direction:column;gap:var(--hm-space-md, 16px)}.attempts__row{display:flex;align-items:flex-end;flex-wrap:wrap;gap:var(--hm-space-sm, 12px)}.attempts__row+.attempts__row{padding-top:var(--hm-space-md, 16px);border-top:1px solid var(--hm-divider)}.attempts__search{display:block;flex:1 1 16rem;min-width:0}.attempts__period{display:block;flex:0 0 auto}.attempts__field{display:block;flex:0 1 12rem;min-width:0}.attempts__field--wide{flex-basis:18rem}.attempts__buttons{display:inline-flex;flex-wrap:wrap;gap:var(--hm-space-sm, 8px)}.attempts__person{display:inline-flex;align-items:center;gap:var(--hm-space-sm);min-width:0}.attempts__person hm-account-avatar{flex:none}.attempts__user{display:inline-flex;flex-direction:column;min-width:0;line-height:1.25}.attempts__user-name{font-weight:var(--hm-font-weight-semibold)}.attempts__user-detail{color:var(--hm-muted);font-size:var(--hm-font-size-xs)}.attempts__ip{font-family:var(--hm-font-mono, monospace);font-size:var(--hm-font-size-label)}.attempts__ua{display:inline-block;max-width:22rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}.attempts__muted{color:var(--hm-muted)}\n"] }]
1539
1539
  }], ctorParameters: () => [], propDecorators: { scope: [{ type: i0.Input, args: [{ isSignal: true, alias: "scope", required: true }] }], clientId: [{ type: i0.Input, args: [{ isSignal: true, alias: "clientId", required: false }] }], realmId: [{ type: i0.Input, args: [{ isSignal: true, alias: "realmId", required: false }] }], adminApi: [{ type: i0.Input, args: [{ isSignal: true, alias: "adminApi", required: false }] }] } });
1540
1540
 
1541
1541
  /*