@trismegisto/spine 2.1.2 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +74 -17
  2. package/fesm2022/trismegisto-spine-audit.mjs +264 -10
  3. package/fesm2022/trismegisto-spine-audit.mjs.map +1 -1
  4. package/fesm2022/trismegisto-spine-auto-service-apps.technical-dKM4fG42.mjs +89 -0
  5. package/fesm2022/trismegisto-spine-auto-service-apps.technical-dKM4fG42.mjs.map +1 -0
  6. package/fesm2022/trismegisto-spine-auto-service-apps.usage-VMrCG5o3.mjs +91 -0
  7. package/fesm2022/trismegisto-spine-auto-service-apps.usage-VMrCG5o3.mjs.map +1 -0
  8. package/fesm2022/{trismegisto-spine-auto-service-audit.technical-C901aIIQ.mjs → trismegisto-spine-auto-service-audit.technical-B75d4mh2.mjs} +6 -5
  9. package/fesm2022/trismegisto-spine-auto-service-audit.technical-B75d4mh2.mjs.map +1 -0
  10. package/fesm2022/{trismegisto-spine-auto-service-audit.usage-GIJ2RwuH.mjs → trismegisto-spine-auto-service-audit.usage-IdXMruv5.mjs} +9 -8
  11. package/fesm2022/trismegisto-spine-auto-service-audit.usage-IdXMruv5.mjs.map +1 -0
  12. package/fesm2022/trismegisto-spine-auto-service-auto-service-audit.component-BNtNWfiu.mjs +37 -0
  13. package/fesm2022/{trismegisto-spine-auto-service-auto-service-audit.component-CCiz-q8o.mjs.map → trismegisto-spine-auto-service-auto-service-audit.component-BNtNWfiu.mjs.map} +1 -1
  14. package/fesm2022/{trismegisto-spine-auto-service-auto-service-client-picker.component-Z0wCANF6.mjs → trismegisto-spine-auto-service-auto-service-client-picker.component-DdtDk_xl.mjs} +3 -3
  15. package/fesm2022/{trismegisto-spine-auto-service-auto-service-client-picker.component-Z0wCANF6.mjs.map → trismegisto-spine-auto-service-auto-service-client-picker.component-DdtDk_xl.mjs.map} +1 -1
  16. package/fesm2022/{trismegisto-spine-auto-service-auto-service-client.component-C7SPavrX.mjs → trismegisto-spine-auto-service-auto-service-client.component-DXl1y6Sx.mjs} +123 -27
  17. package/fesm2022/trismegisto-spine-auto-service-auto-service-client.component-DXl1y6Sx.mjs.map +1 -0
  18. package/fesm2022/trismegisto-spine-auto-service-auto-service-delete-B1tXgMvi.mjs +88 -0
  19. package/fesm2022/trismegisto-spine-auto-service-auto-service-delete-B1tXgMvi.mjs.map +1 -0
  20. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-C4iQ2GPX.mjs +599 -0
  21. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-C4iQ2GPX.mjs.map +1 -0
  22. package/fesm2022/trismegisto-spine-auto-service-auto-service-login-attempts.component-B87vibNB.mjs +37 -0
  23. package/fesm2022/trismegisto-spine-auto-service-auto-service-login-attempts.component-B87vibNB.mjs.map +1 -0
  24. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-DV88Uger.mjs +941 -0
  25. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-DV88Uger.mjs.map +1 -0
  26. package/fesm2022/{trismegisto-spine-auto-service-client.technical-BHWhpyWv.mjs → trismegisto-spine-auto-service-client.technical-DP5G8i3o.mjs} +24 -3
  27. package/fesm2022/trismegisto-spine-auto-service-client.technical-DP5G8i3o.mjs.map +1 -0
  28. package/fesm2022/{trismegisto-spine-auto-service-client.usage-CM2WB2e2.mjs → trismegisto-spine-auto-service-client.usage-CcJANb20.mjs} +15 -6
  29. package/fesm2022/trismegisto-spine-auto-service-client.usage-CcJANb20.mjs.map +1 -0
  30. package/fesm2022/trismegisto-spine-auto-service-groups.technical-D05838rU.mjs +69 -0
  31. package/fesm2022/trismegisto-spine-auto-service-groups.technical-D05838rU.mjs.map +1 -0
  32. package/fesm2022/trismegisto-spine-auto-service-groups.usage-ChGMgYU3.mjs +80 -0
  33. package/fesm2022/trismegisto-spine-auto-service-groups.usage-ChGMgYU3.mjs.map +1 -0
  34. package/fesm2022/{trismegisto-spine-auto-service-home.component-vl3BPFnk.mjs → trismegisto-spine-auto-service-home.component-5_aji1hf.mjs} +5 -4
  35. package/fesm2022/trismegisto-spine-auto-service-home.component-5_aji1hf.mjs.map +1 -0
  36. package/fesm2022/trismegisto-spine-auto-service-login-attempts.technical-CasKIIws.mjs +37 -0
  37. package/fesm2022/trismegisto-spine-auto-service-login-attempts.technical-CasKIIws.mjs.map +1 -0
  38. package/fesm2022/trismegisto-spine-auto-service-login-attempts.usage-BMgLNu7W.mjs +60 -0
  39. package/fesm2022/trismegisto-spine-auto-service-login-attempts.usage-BMgLNu7W.mjs.map +1 -0
  40. package/fesm2022/trismegisto-spine-auto-service.mjs +49 -29
  41. package/fesm2022/trismegisto-spine-auto-service.mjs.map +1 -1
  42. package/fesm2022/trismegisto-spine-layout.mjs +1698 -156
  43. package/fesm2022/trismegisto-spine-layout.mjs.map +1 -1
  44. package/fesm2022/trismegisto-spine-shell.mjs +17 -1
  45. package/fesm2022/trismegisto-spine-shell.mjs.map +1 -1
  46. package/fesm2022/trismegisto-spine.mjs +1175 -85
  47. package/fesm2022/trismegisto-spine.mjs.map +1 -1
  48. package/package.json +6 -6
  49. package/types/trismegisto-spine-audit.d.ts +180 -5
  50. package/types/trismegisto-spine-audit.d.ts.map +1 -1
  51. package/types/trismegisto-spine-auto-service.d.ts +100 -7
  52. package/types/trismegisto-spine-auto-service.d.ts.map +1 -1
  53. package/types/trismegisto-spine-layout.d.ts +833 -13
  54. package/types/trismegisto-spine-layout.d.ts.map +1 -1
  55. package/types/trismegisto-spine-shell.d.ts +8 -0
  56. package/types/trismegisto-spine-shell.d.ts.map +1 -1
  57. package/types/trismegisto-spine.d.ts +506 -32
  58. package/types/trismegisto-spine.d.ts.map +1 -1
  59. package/fesm2022/trismegisto-spine-auto-service-apps.technical-ClMKB9cK.mjs +0 -48
  60. package/fesm2022/trismegisto-spine-auto-service-apps.technical-ClMKB9cK.mjs.map +0 -1
  61. package/fesm2022/trismegisto-spine-auto-service-apps.usage-Cf5SG-oQ.mjs +0 -55
  62. package/fesm2022/trismegisto-spine-auto-service-apps.usage-Cf5SG-oQ.mjs.map +0 -1
  63. package/fesm2022/trismegisto-spine-auto-service-audit.technical-C901aIIQ.mjs.map +0 -1
  64. package/fesm2022/trismegisto-spine-auto-service-audit.usage-GIJ2RwuH.mjs.map +0 -1
  65. package/fesm2022/trismegisto-spine-auto-service-auto-service-audit.component-CCiz-q8o.mjs +0 -37
  66. package/fesm2022/trismegisto-spine-auto-service-auto-service-client.component-C7SPavrX.mjs.map +0 -1
  67. package/fesm2022/trismegisto-spine-auto-service-auto-service-delete-6Bk46MOf.mjs +0 -39
  68. package/fesm2022/trismegisto-spine-auto-service-auto-service-delete-6Bk46MOf.mjs.map +0 -1
  69. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-CqIVe2LU.mjs +0 -357
  70. package/fesm2022/trismegisto-spine-auto-service-auto-service-groups.component-CqIVe2LU.mjs.map +0 -1
  71. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-BLN49F4a.mjs +0 -502
  72. package/fesm2022/trismegisto-spine-auto-service-auto-service.component-BLN49F4a.mjs.map +0 -1
  73. package/fesm2022/trismegisto-spine-auto-service-client.technical-BHWhpyWv.mjs.map +0 -1
  74. package/fesm2022/trismegisto-spine-auto-service-client.usage-CM2WB2e2.mjs.map +0 -1
  75. package/fesm2022/trismegisto-spine-auto-service-groups.technical-jQlwaQPn.mjs +0 -39
  76. package/fesm2022/trismegisto-spine-auto-service-groups.technical-jQlwaQPn.mjs.map +0 -1
  77. package/fesm2022/trismegisto-spine-auto-service-groups.usage-Bh7ig2zZ.mjs +0 -54
  78. package/fesm2022/trismegisto-spine-auto-service-groups.usage-Bh7ig2zZ.mjs.map +0 -1
  79. package/fesm2022/trismegisto-spine-auto-service-home.component-vl3BPFnk.mjs.map +0 -1
@@ -1,9 +1,9 @@
1
- import { AlertComponent, ButtonComponent, DrawerComponent, IconButtonComponent, IconComponent, MenuButtonComponent, MenuComponent, ModalComponent, SidebarComponent, SidebarGroupComponent, SidebarLinkComponent, SidebarNodeComponent, ToastStackComponent, HermesThemeService, HERMES_THEME_CONFIG, HERMES_SIDEBAR_CONFIG, HERMES_WORKSPACE_PERSISTENCE_CONFIG, provideHermesIcons, HermesShortcutService, HermesI18nService, liveCatalog } from '@trismegisto/angular';
1
+ import { AlertComponent, ButtonComponent, DrawerComponent, IconButtonComponent, IconComponent, MenuButtonComponent, MenuComponent, ModalComponent, SidebarComponent, SidebarGroupComponent, SidebarLinkComponent, SidebarNodeComponent, ToastStackComponent, HermesThemeService, HERMES_THEME_CONFIG, HERMES_SIDEBAR_CONFIG, HERMES_WORKSPACE_PERSISTENCE_CONFIG, provideHermesIcons, HermesShortcutService, HermesI18nService, HermesZoomService, HermesMotionService, liveCatalog } from '@trismegisto/angular';
2
2
  import { isPlatformBrowser, DOCUMENT } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
4
  import { InjectionToken, inject, effect, PLATFORM_ID, Inject, Optional, Injectable, makeEnvironmentProviders, provideAppInitializer, NgZone, signal, computed, untracked, DestroyRef } from '@angular/core';
5
5
  import { HERMES_ICONS } from '@trismegisto/icons/catalog';
6
- import { extendThemeConfig, DEFAULT_THEME_CONFIG, mergeCatalog } from '@trismegisto/primitives';
6
+ import { extendThemeConfig, DEFAULT_THEME_CONFIG, normalizeTextScale, normalizeMotionPreference, mergeCatalog } from '@trismegisto/primitives';
7
7
  import { AuthorizationService } from '@orfeu/angular/core';
8
8
 
9
9
  /** `router_id` da tela (nó 'page'), aceitando o argos antigo (que só mandava `route_id`, mesmo valor). */
@@ -371,15 +371,30 @@ const APP_PACKAGE_INFO = new InjectionToken('APP_PACKAGE_INFO');
371
371
  const TIME_ZONE_SUFFIX = 'time_zone';
372
372
  const LANGUAGE_SUFFIX = 'language';
373
373
  const CHARACTER_KEYS_SUFFIX = 'shortcut_character_keys';
374
+ const TEXT_SCALE_SUFFIX = 'text_scale';
375
+ const MOTION_SUFFIX = 'motion';
376
+ const HOUR_CYCLE_SUFFIX = 'hour_cycle';
377
+ const WEEK_START_SUFFIX = 'week_start';
378
+ const IDLE_SUFFIX = 'idle_minutes';
379
+ const START_PAGE_SUFFIX = 'start_page';
380
+ const NOTICES_SUFFIX = 'notices';
381
+ const BROWSER_NOTICES_SUFFIX = 'browser_notices';
382
+ /** Minutos de inatividade oferecidos em "Encerrar por inatividade" (0 = nunca). */
383
+ const SPINE_IDLE_OPTIONS = [0, 5, 15, 30, 60];
374
384
  const CHANNEL_BASE = 'spine_preferences';
375
385
  /**
376
386
  * Preferências do usuário que não são de tema (tema/família/densidade continuam no
377
- * `HermesThemeService`; modo do menu lateral no `HermesSidebarService`): fuso horário, idioma e o
378
- * interruptor dos atalhos de tecla única (WCAG 2.1.4) do `HermesShortcutService`.
387
+ * `HermesThemeService`; modo do menu lateral no `HermesSidebarService`): fuso horário, idioma, relógio de
388
+ * 12/24 h e primeiro dia da semana (`HermesI18nService.setRegionalFormat`), tamanho do texto
389
+ * (`HermesZoomService.setTextScale`), movimento (`HermesMotionService.setPreference`) e o interruptor dos
390
+ * atalhos de tecla única (WCAG 2.1.4) do `HermesShortcutService`.
379
391
  *
380
- * Por enquanto vive no navegador (localStorage, sincronizado entre abas). A sincronização com o
381
- * servidor (argos-admin `/me/preferences`) entra depois sem mudar esta API: quem lê `timeZone()`
382
- * não precisa saber de onde o valor veio.
392
+ * Também guarda as preferências dos recursos opcionais (`SpineFeatures`): minutos de inatividade, tela inicial,
393
+ * tipos de aviso ligados e o aviso do navegador.
394
+ *
395
+ * Vive no navegador (localStorage, sincronizado entre abas). Com `SpineFeatures.accountPreferences`, o
396
+ * `SpineAccountPreferencesService` (`@trismegisto/spine/layout`) espelha estes valores na conta do Argos e os aplica
397
+ * pelos mesmos `set*` no login: quem lê `timeZone()` não precisa saber de onde o valor veio.
383
398
  */
384
399
  class SpinePreferencesService {
385
400
  constructor() {
@@ -389,8 +404,14 @@ class SpinePreferencesService {
389
404
  this.timeZoneKey = `${this.ns ? `hermes_${this.ns}` : 'hermes'}_${TIME_ZONE_SUFFIX}`;
390
405
  this.languageKey = `${this.ns ? `hermes_${this.ns}` : 'hermes'}_${LANGUAGE_SUFFIX}`;
391
406
  this.characterKeysKey = `${this.ns ? `hermes_${this.ns}` : 'hermes'}_${CHARACTER_KEYS_SUFFIX}`;
407
+ this.textScaleKey = `${this.ns ? `hermes_${this.ns}` : 'hermes'}_${TEXT_SCALE_SUFFIX}`;
408
+ this.motionKey = `${this.ns ? `hermes_${this.ns}` : 'hermes'}_${MOTION_SUFFIX}`;
409
+ this.hourCycleKey = `${this.ns ? `hermes_${this.ns}` : 'hermes'}_${HOUR_CYCLE_SUFFIX}`;
410
+ this.weekStartKey = `${this.ns ? `hermes_${this.ns}` : 'hermes'}_${WEEK_START_SUFFIX}`;
392
411
  this.shortcuts = inject(HermesShortcutService);
393
412
  this.i18n = inject(HermesI18nService);
413
+ this.zoom = inject(HermesZoomService);
414
+ this.motionService = inject(HermesMotionService);
394
415
  /** Idioma com que o app subiu (`provideHermesI18n`) — vale quando a pessoa não escolheu nenhum. */
395
416
  this.appLanguage = this.i18n.locale();
396
417
  this.channel = this.browser && typeof BroadcastChannel !== 'undefined'
@@ -409,6 +430,27 @@ class SpinePreferencesService {
409
430
  * sem escolha salva, vale o padrão do `HermesShortcutService` (`provideHermesShortcuts`).
410
431
  */
411
432
  this.characterKeys = signal(this.readCharacterKeys(), ...(ngDevMode ? [{ debugName: "characterKeys" }] : /* istanbul ignore next */ []));
433
+ /** Tamanho do texto (1 = 100%; os oferecidos em `HERMES_TEXT_SCALES`). */
434
+ this.textScale = signal(normalizeTextScale(this.read(this.textScaleKey)), ...(ngDevMode ? [{ debugName: "textScale" }] : /* istanbul ignore next */ []));
435
+ /** Movimento: `system` segue o `prefers-reduced-motion`; `reduce` sempre reduz; `full` nunca. */
436
+ this.motion = signal(normalizeMotionPreference(this.read(this.motionKey)), ...(ngDevMode ? [{ debugName: "motion" }] : /* istanbul ignore next */ []));
437
+ /** Relógio: `h23` (24 horas), `h12` (12 horas) ou vazio (o do idioma). */
438
+ this.hourCycle = signal(toHourCycle(this.read(this.hourCycleKey)), ...(ngDevMode ? [{ debugName: "hourCycle" }] : /* istanbul ignore next */ []));
439
+ /** Primeiro dia da semana (0 = domingo, 1 = segunda) ou `null` (o do idioma). */
440
+ this.weekStart = signal(toWeekStart(this.read(this.weekStartKey)), ...(ngDevMode ? [{ debugName: "weekStart" }] : /* istanbul ignore next */ []));
441
+ this.idleKey = `${this.ns ? `hermes_${this.ns}` : 'hermes'}_${IDLE_SUFFIX}`;
442
+ this.startPageKey = `${this.ns ? `hermes_${this.ns}` : 'hermes'}_${START_PAGE_SUFFIX}`;
443
+ this.noticesKey = `${this.ns ? `hermes_${this.ns}` : 'hermes'}_${NOTICES_SUFFIX}`;
444
+ this.browserNoticesKey = `${this.ns ? `hermes_${this.ns}` : 'hermes'}_${BROWSER_NOTICES_SUFFIX}`;
445
+ /** Encerrar a sessão depois de tantos minutos sem uso (0 = nunca). Os limites do app (`SpineFeatures.idleTimeout`)
446
+ * são aplicados por quem usa o valor (`SpineIdleService`), não aqui. */
447
+ this.idleMinutes = signal(toIdleMinutes(this.read(this.idleKey)), ...(ngDevMode ? [{ debugName: "idleMinutes" }] : /* istanbul ignore next */ []));
448
+ /** Tela que abre depois do login. */
449
+ this.startPage = signal(toStartPage(this.read(this.startPageKey)), ...(ngDevMode ? [{ debugName: "startPage" }] : /* istanbul ignore next */ []));
450
+ /** Tipos de aviso que a pessoa ligou/desligou (`id` → ligado); tipo ausente segue o `defaultEnabled` dele. */
451
+ this.noticeTypes = signal(toNoticeTypes(this.read(this.noticesKey)), ...(ngDevMode ? [{ debugName: "noticeTypes" }] : /* istanbul ignore next */ []));
452
+ /** Aviso do navegador (Notification API) quando a aba está em segundo plano. */
453
+ this.browserNotices = signal(this.read(this.browserNoticesKey) === 'on', ...(ngDevMode ? [{ debugName: "browserNotices" }] : /* istanbul ignore next */ []));
412
454
  /** Idiomas oferecidos na configuração, cada um com o nome no próprio idioma (padrão de mercado). */
413
455
  this.languages = [
414
456
  // check-strings: off — cada idioma escrito no próprio idioma (padrão de mercado), igual em todo catálogo.
@@ -425,11 +467,33 @@ class SpinePreferencesService {
425
467
  this.applyLanguage(e.data.value ?? '');
426
468
  if (e.data?.type === 'CHARACTER_KEYS_CHANGED')
427
469
  this.applyCharacterKeys(e.data.value !== false);
470
+ if (e.data?.type === 'TEXT_SCALE_CHANGED')
471
+ this.applyTextScale(normalizeTextScale(e.data.value));
472
+ if (e.data?.type === 'MOTION_CHANGED')
473
+ this.applyMotion(normalizeMotionPreference(e.data.value));
474
+ if (e.data?.type === 'HOUR_CYCLE_CHANGED')
475
+ this.applyRegionalFormat(toHourCycle(e.data.value), this.weekStart());
476
+ if (e.data?.type === 'WEEK_START_CHANGED')
477
+ this.applyRegionalFormat(this.hourCycle(), toWeekStart(e.data.value));
478
+ if (e.data?.type === 'IDLE_CHANGED')
479
+ this.idleMinutes.set(toIdleMinutes(e.data.value));
480
+ if (e.data?.type === 'START_PAGE_CHANGED')
481
+ this.startPage.set(toStartPage(e.data.value));
482
+ if (e.data?.type === 'NOTICES_CHANGED')
483
+ this.noticeTypes.set(toNoticeTypes(JSON.stringify(e.data.value ?? {})));
484
+ if (e.data?.type === 'BROWSER_NOTICES_CHANGED')
485
+ this.browserNotices.set(e.data.value === true);
428
486
  });
429
487
  }
430
488
  // Escolha salva vale desde a primeira tela (o layout injeta este serviço no boot).
431
489
  if (this.language())
432
490
  this.i18n.setLocale(this.language());
491
+ if (this.textScale() !== 1)
492
+ this.zoom.setTextScale(this.textScale());
493
+ if (this.motion() !== 'system')
494
+ this.motionService.setPreference(this.motion());
495
+ if (this.hourCycle() || this.weekStart() !== null)
496
+ this.applyRegionalFormat(this.hourCycle(), this.weekStart());
433
497
  if (this.characterKeys() !== this.shortcuts.characterKeys())
434
498
  this.shortcuts.setCharacterKeys(this.characterKeys());
435
499
  // O interruptor do hm-shortcut-list fala direto com o HermesShortcutService: a mudança que vier
@@ -468,6 +532,78 @@ class SpinePreferencesService {
468
532
  this.language.set(value);
469
533
  this.i18n.setLocale(value || this.appLanguage);
470
534
  }
535
+ /** Tamanho do texto na hora (1 = 100%), guarda e avisa as outras abas. */
536
+ setTextScale(value) {
537
+ const scale = normalizeTextScale(value);
538
+ this.applyTextScale(scale);
539
+ this.write(this.textScaleKey, scale === 1 ? '' : String(scale));
540
+ this.channel?.postMessage({ type: 'TEXT_SCALE_CHANGED', value: scale });
541
+ }
542
+ applyTextScale(scale) {
543
+ this.textScale.set(scale);
544
+ this.zoom.setTextScale(scale);
545
+ }
546
+ /** Movimento na hora (`system`/`reduce`/`full`), guarda e avisa as outras abas. */
547
+ setMotion(value) {
548
+ const motion = normalizeMotionPreference(value);
549
+ this.applyMotion(motion);
550
+ this.write(this.motionKey, motion === 'system' ? '' : motion);
551
+ this.channel?.postMessage({ type: 'MOTION_CHANGED', value: motion });
552
+ }
553
+ applyMotion(motion) {
554
+ this.motion.set(motion);
555
+ this.motionService.setPreference(motion);
556
+ }
557
+ /** Relógio na hora (`h23`/`h12`; vazio = o do idioma), guarda e avisa as outras abas. */
558
+ setHourCycle(value) {
559
+ const cycle = toHourCycle(value);
560
+ this.applyRegionalFormat(cycle, this.weekStart());
561
+ this.write(this.hourCycleKey, cycle);
562
+ this.channel?.postMessage({ type: 'HOUR_CYCLE_CHANGED', value: cycle });
563
+ }
564
+ /** Primeiro dia da semana na hora (0 = domingo, 1 = segunda; `null` = o do idioma), guarda e avisa. */
565
+ setWeekStart(value) {
566
+ const day = toWeekStart(value);
567
+ this.applyRegionalFormat(this.hourCycle(), day);
568
+ this.write(this.weekStartKey, day === null ? '' : String(day));
569
+ this.channel?.postMessage({ type: 'WEEK_START_CHANGED', value: day });
570
+ }
571
+ applyRegionalFormat(hourCycle, weekStart) {
572
+ this.hourCycle.set(hourCycle);
573
+ this.weekStart.set(weekStart);
574
+ this.i18n.setRegionalFormat({ hourCycle: hourCycle || null, firstDayOfWeek: weekStart });
575
+ }
576
+ /** Minutos sem uso até encerrar a sessão (0 = nunca), guarda e avisa as outras abas. */
577
+ setIdleMinutes(value) {
578
+ const minutes = toIdleMinutes(value);
579
+ this.idleMinutes.set(minutes);
580
+ this.write(this.idleKey, minutes ? String(minutes) : '');
581
+ this.channel?.postMessage({ type: 'IDLE_CHANGED', value: minutes });
582
+ }
583
+ /** Tela inicial depois do login, guarda e avisa as outras abas. */
584
+ setStartPage(value) {
585
+ const page = toStartPage(value);
586
+ this.startPage.set(page);
587
+ this.write(this.startPageKey, page === 'home' ? '' : page);
588
+ this.channel?.postMessage({ type: 'START_PAGE_CHANGED', value: page });
589
+ }
590
+ /** Liga/desliga um tipo de aviso, guarda e avisa as outras abas. */
591
+ setNoticeType(id, enabled) {
592
+ this.setNoticeTypes({ ...this.noticeTypes(), [id]: enabled });
593
+ }
594
+ /** Troca o mapa inteiro de tipos de aviso (restaurar, sincronização com a conta). */
595
+ setNoticeTypes(value) {
596
+ const types = toNoticeTypes(JSON.stringify(value ?? {}));
597
+ this.noticeTypes.set(types);
598
+ this.write(this.noticesKey, Object.keys(types).length ? JSON.stringify(types) : '');
599
+ this.channel?.postMessage({ type: 'NOTICES_CHANGED', value: types });
600
+ }
601
+ /** Liga/desliga o aviso do navegador, guarda e avisa as outras abas (a permissão é pedida por quem chama). */
602
+ setBrowserNotices(enabled) {
603
+ this.browserNotices.set(enabled);
604
+ this.write(this.browserNoticesKey, enabled ? 'on' : '');
605
+ this.channel?.postMessage({ type: 'BROWSER_NOTICES_CHANGED', value: enabled });
606
+ }
471
607
  setTimeZone(value) {
472
608
  this.timeZone.set(value);
473
609
  this.write(this.timeZoneKey, value);
@@ -511,6 +647,45 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
511
647
  type: Injectable,
512
648
  args: [{ providedIn: 'root' }]
513
649
  }], ctorParameters: () => [] });
650
+ function toIdleMinutes(value) {
651
+ const minutes = Number(value);
652
+ return Number.isInteger(minutes) && minutes > 0 && minutes <= 24 * 60 ? minutes : 0;
653
+ }
654
+ function toStartPage(value) {
655
+ if (value === 'last')
656
+ return 'last';
657
+ if (typeof value === 'string' && value.startsWith('fav:') && value.length > 4 && value.length <= 500)
658
+ return value;
659
+ return 'home';
660
+ }
661
+ /** Texto JSON salvo → mapa `id` → booleano (o resto é descartado). */
662
+ function toNoticeTypes(raw) {
663
+ let parsed;
664
+ try {
665
+ parsed = raw ? JSON.parse(raw) : {};
666
+ }
667
+ catch {
668
+ return {};
669
+ }
670
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
671
+ return {};
672
+ const out = {};
673
+ for (const [id, enabled] of Object.entries(parsed)) {
674
+ if (typeof enabled === 'boolean' && /^[\w.:-]{1,64}$/.test(id))
675
+ out[id] = enabled;
676
+ }
677
+ return out;
678
+ }
679
+ function toHourCycle(value) {
680
+ return value === 'h12' || value === 'h23' ? value : '';
681
+ }
682
+ /** `0`…`6` (número ou texto salvo) → dia da semana; o resto, `null`. */
683
+ function toWeekStart(value) {
684
+ if (value === null || value === undefined || value === '')
685
+ return null;
686
+ const day = Number(value);
687
+ return Number.isInteger(day) && day >= 0 && day <= 6 ? day : null;
688
+ }
514
689
 
515
690
  /**
516
691
  * Base da API do argos-admin-server (ex.: `https://exemplo.com.br/argos/admin/api`) —
@@ -526,9 +701,13 @@ const SPINE_USER_STORAGE_KEYS = {
526
701
  favorites: 'nav_favorites',
527
702
  recents: 'nav_recent',
528
703
  recentSearches: 'header_recent_searches',
704
+ /** Última tela visitada, guardada só com a tela inicial "Última tela visitada" (`SpineStartPageService`). */
705
+ lastPage: 'start_last_page',
529
706
  };
530
707
  /** Históricos (o que a pessoa visitou/buscou): apagados no logout. Favoritos ficam (preferência do usuário). */
531
708
  const HISTORY_KEYS = [SPINE_USER_STORAGE_KEYS.recents, SPINE_USER_STORAGE_KEYS.recentSearches];
709
+ // A última tela (`lastPage`) fica no logout de propósito: é ela que a tela inicial "Última tela visitada" abre no
710
+ // próximo login. Só é gravada quando a pessoa escolheu essa opção.
532
711
  /**
533
712
  * Chaves de `localStorage` do shell por usuário: `<clientId>_<lista>_<sub>`, com o `sub` do token da sessão.
534
713
  * Num computador compartilhado, uma pessoa não vê os recentes/buscas/favoritos de quem entrou antes.
@@ -597,6 +776,34 @@ class SpineUserStorage {
597
776
  }
598
777
  this.notify();
599
778
  }
779
+ /**
780
+ * Apaga uma lista do usuário atual (Configurações › Dados do navegador): a chave dele, e também a antiga sem
781
+ * usuário. Quem guarda a lista em memória relê (`onChange`).
782
+ */
783
+ clear(list) {
784
+ if (!this.browser)
785
+ return;
786
+ try {
787
+ localStorage.removeItem(this.key(list));
788
+ localStorage.removeItem(`${this.ns}_${list}`);
789
+ }
790
+ catch {
791
+ /* storage indisponível */
792
+ }
793
+ this.notify();
794
+ }
795
+ /** Quantos itens a lista do usuário atual tem guardados (0 sem nada ou com valor ilegível). */
796
+ count(list) {
797
+ if (!this.browser)
798
+ return 0;
799
+ try {
800
+ const parsed = JSON.parse(localStorage.getItem(this.key(list)) ?? '[]');
801
+ return Array.isArray(parsed) ? parsed.length : 0;
802
+ }
803
+ catch {
804
+ return 0;
805
+ }
806
+ }
600
807
  /** Avisa quando a sessão muda ou o histórico é limpo (quem guarda a lista em memória relê). */
601
808
  onChange(listener) {
602
809
  this.listeners.add(listener);
@@ -689,6 +896,18 @@ class SpineQuickAccessService {
689
896
  const next = current.some((f) => f.id === item.id) ? current.filter((f) => f.id !== item.id) : [...current, item];
690
897
  this.commit(this.favoriteList, this.favoritesKey, next);
691
898
  }
899
+ /** Troca a lista de favoritos inteira (sincronização com a conta). Itens inválidos ou repetidos são descartados. */
900
+ setFavorites(items) {
901
+ const seen = new Set();
902
+ const next = items.flatMap((entry) => {
903
+ const item = normalize(entry);
904
+ if (!item || seen.has(item.id))
905
+ return [];
906
+ seen.add(item.id);
907
+ return [item];
908
+ });
909
+ this.commit(this.favoriteList, this.favoritesKey, next);
910
+ }
692
911
  recordVisit(item) {
693
912
  const current = this.recentList();
694
913
  const top = current[0];
@@ -934,7 +1153,7 @@ const SPINE_MESSAGES_PT_BR = {
934
1153
  appearance: 'Aparência',
935
1154
  appearanceKeywords: 'visual cores tema layout',
936
1155
  theme: 'Tema',
937
- themeDescription: 'Claro, escuro ou acompanhando o sistema operacional.',
1156
+ themeDescription: 'Claro, escuro ou o do sistema operacional.',
938
1157
  themeKeywords: 'modo escuro dark light noturno',
939
1158
  themeLight: 'Claro',
940
1159
  themeDark: 'Escuro',
@@ -943,7 +1162,7 @@ const SPINE_MESSAGES_PT_BR = {
943
1162
  accentDescription: 'Família de cores do cabeçalho, dos botões e dos destaques.',
944
1163
  accentKeywords: 'família paleta cor accent',
945
1164
  density: 'Densidade',
946
- densityDescription: 'Compacta aproxima linhas e controles para caber mais informação na tela.',
1165
+ densityDescription: 'Compacta mostra mais informação na tela.',
947
1166
  densityKeywords: 'espaçamento tamanho compacto',
948
1167
  comfortable: 'Confortável',
949
1168
  compact: 'Compacta',
@@ -951,64 +1170,121 @@ const SPINE_MESSAGES_PT_BR = {
951
1170
  fontDescription: 'Família de letra de todo o aplicativo.',
952
1171
  fontKeywords: 'tipografia letra texto roboto rubik figtree geist',
953
1172
  sidebar: 'Menu lateral',
954
- sidebarDescription: 'Automático abre ao aproximar o ponteiro; fixo fica sempre aberto; compacto mostra só os ícones.',
1173
+ sidebarDescription: 'Como o menu lateral abre e fecha.',
955
1174
  sidebarKeywords: 'sidebar navegação barra lateral',
1175
+ sidebarHelp: 'Automático: abre ao aproximar o ponteiro. Fixo: fica sempre aberto. Compacto: mostra só os ícones.',
956
1176
  sidebarAuto: 'Automático',
957
1177
  sidebarFixed: 'Fixo',
958
1178
  sidebarCompact: 'Compacto',
959
1179
  region: 'Região',
960
- regionKeywords: 'idioma língua localização horário data',
1180
+ regionKeywords: 'idioma língua localização horário data hora semana',
961
1181
  language: 'Idioma',
962
- languageDescription: 'Idioma dos textos do sistema. Vale na hora, sem recarregar.',
1182
+ languageDescription: 'Vale na hora, sem recarregar.',
963
1183
  languageKeywords: 'língua language idioma tradução inglês espanhol português',
964
1184
  languageAuto: 'Padrão do sistema ({language})',
965
1185
  timeZone: 'Fuso horário',
966
1186
  timeZoneKeywords: 'timezone hora utc gmt',
967
- timeZoneHint: 'Usado para exibir datas e horários. Agora: {now}',
1187
+ timeZoneHint: 'Para datas e horários. Agora: {now}',
968
1188
  timeZoneAuto: 'Automático ({zone})',
1189
+ hourCycle: 'Formato de hora',
1190
+ hourCycleHint: 'Agora: {now}',
1191
+ hourCycleKeywords: 'relógio 24h 12h am pm horário',
1192
+ hourCycleAuto: 'Automático',
1193
+ hourCycle24: '24 horas',
1194
+ hourCycle12: '12 horas',
1195
+ weekStart: 'Primeiro dia da semana',
1196
+ weekStartDescription: 'Para calendários e seletores de data.',
1197
+ weekStartKeywords: 'semana calendário domingo segunda',
1198
+ weekStartAuto: 'Automático',
969
1199
  shortcuts: 'Atalhos',
970
1200
  shortcutsDescription: 'Pressione ? em qualquer tela para ver esta lista.',
971
- shortcutsKeywords: 'teclado teclas tour ajuda',
1201
+ shortcutsKeywords: 'teclado teclas',
972
1202
  tour: 'Tour guiado',
973
- tourDescription: 'Mostra de novo, passo a passo, onde fica cada parte da tela.',
1203
+ tourDescription: 'Mostra de novo onde fica cada parte da tela.',
974
1204
  tourKeywords: 'onboarding ajuda apresentação',
975
1205
  replayTour: 'Refazer tour',
1206
+ accessibility: 'Acessibilidade',
1207
+ accessibilityKeywords: 'acessibilidade leitura visão',
1208
+ textSize: 'Tamanho do texto',
1209
+ textSizeDescription: 'Aumenta o texto e os controles de todas as telas.',
1210
+ textSizeKeywords: 'zoom fonte letra maior ampliar',
1211
+ motion: 'Reduzir animações',
1212
+ motionDescription: 'Sistema segue o computador ou o celular.',
1213
+ motionKeywords: 'animação movimento transição reduced motion enjoo tontura',
1214
+ motionSystem: 'Sistema',
1215
+ motionReduce: 'Sempre',
1216
+ motionFull: 'Nunca',
1217
+ help: 'Ajuda',
1218
+ helpKeywords: 'ajuda novidades tour apresentação',
1219
+ news: 'Novidades',
1220
+ newsDescription: 'O que mudou nas últimas versões.',
1221
+ newsKeywords: 'novidades versão lançamento changelog',
1222
+ showNews: 'Ver novidades',
1223
+ browserData: 'Dados do navegador',
1224
+ browserDataDescription: 'Guardados só neste navegador, para a sua conta.',
1225
+ browserDataKeywords: 'privacidade limpar apagar histórico dados',
1226
+ recentPages: 'Páginas recentes',
1227
+ recentPagesKeywords: 'histórico visitadas recentes',
1228
+ recentSearches: 'Buscas recentes',
1229
+ recentSearchesKeywords: 'busca pesquisa histórico',
1230
+ favorites: 'Favoritos',
1231
+ favoritesKeywords: 'favoritos estrela',
1232
+ pagesCount: { zero: 'Nenhuma página guardada', one: '{count} página guardada', other: '{count} páginas guardadas' },
1233
+ searchesCount: { zero: 'Nenhuma busca guardada', one: '{count} busca guardada', other: '{count} buscas guardadas' },
1234
+ clear: 'Limpar',
1235
+ confirmClear: 'Confirmar',
1236
+ cancel: 'Cancelar',
1237
+ recentPagesCleared: 'Páginas recentes apagadas.',
1238
+ recentSearchesCleared: 'Buscas recentes apagadas.',
1239
+ favoritesCleared: 'Favoritos apagados.',
1240
+ restorePreferences: 'Preferências',
1241
+ restorePreferencesDescription: 'Aparência, acessibilidade e formatos voltam ao padrão.',
1242
+ restorePreferencesHelp: 'Voltam ao padrão: tema, cor principal, densidade, fonte, menu lateral, tamanho do texto, animações, formato de hora e primeiro dia da semana. Idioma, fuso horário e atalhos de tecla única ficam como estão.',
1243
+ restorePreferencesKeywords: 'restaurar padrão redefinir reset preferências',
1244
+ restore: 'Restaurar padrão',
1245
+ confirmRestore: 'Confirmar',
1246
+ preferencesRestored: 'Preferências restauradas.',
976
1247
  account: 'Conta',
977
- accountKeywords: 'perfil usuário sessão login',
1248
+ accountKeywords: 'perfil usuário login nível sair',
978
1249
  user: 'Usuário',
979
1250
  accessLevel: 'Nível de acesso',
980
1251
  accessLevelKeywords: 'crachá badge perfil permissão grupo',
981
1252
  session: 'Sessão',
982
- sessionDescription: 'A sessão é renovada sozinha enquanto a aplicação estiver aberta.',
1253
+ sessionDescription: 'Renovada sozinha enquanto a aplicação estiver aberta.',
983
1254
  sessionKeywords: 'expiração token login',
984
1255
  sessionUnknown: 'Não informado',
985
1256
  sessionExpired: 'Expirada às {time}',
986
1257
  sessionExpiresIn: 'Expira em {minutes} min (às {time})',
987
1258
  sessionExpiresAt: 'Expira às {time}',
988
1259
  accessToken: 'Token de acesso',
989
- accessTokenDescription: 'Para testar uma API em seu nome. Dá acesso à sua conta até expirar: nunca compartilhe.',
1260
+ accessTokenDescription: 'Para testar uma API em seu nome. Não compartilhe.',
990
1261
  accessTokenKeywords: 'jwt bearer api desenvolvedor',
991
1262
  copy: 'Copiar',
1263
+ moreAbout: 'Mais sobre {label}',
992
1264
  appTokens: 'Tokens de aplicação',
993
- appTokensDescription: 'Para usar esta aplicação fora da interface em seu nome — um MCP, um script. O token vale só para esta aplicação, com as permissões do nível de acesso escolhido, e expira em no máximo 1 ano. Envie no cabeçalho x-app-token.',
1265
+ appTokensDescription: 'Para usar a aplicação fora da interface: um MCP, um script.',
994
1266
  appTokensKeywords: 'token app mcp api chave pat x-app-token automação script integração',
1267
+ appTokensHelp: 'O token vale só para esta aplicação, com as permissões do nível de acesso escolhido, e expira em no máximo 1 ano. Envie no cabeçalho x-app-token.',
995
1268
  logout: 'Sair',
996
1269
  logoutDescription: 'Encerra a sessão neste navegador.',
997
1270
  logoutKeywords: 'logout desconectar',
998
- security: 'Conta e segurança',
999
- securityDescription: 'Verificação em duas etapas, códigos de recuperação e sessões da sua conta no Argos, o login das aplicações.',
1000
- securityKeywords: 'segurança argos senha autenticação mfa 2fa dispositivos',
1271
+ mfa: 'MFA',
1272
+ mfaDescription: 'Verificação em duas etapas da sua conta no Argos.',
1273
+ mfaKeywords: 'segurança argos senha autenticação mfa 2fa dois fatores duas etapas verificação',
1001
1274
  signedInAccount: 'Conta no Argos',
1002
1275
  signedInRealm: 'Realm {realm}',
1003
1276
  twoFactor: 'Verificação em duas etapas',
1004
- twoFactorDescription: 'Adicione ou remova métodos (aplicativo autenticador ou e-mail) e troque o aparelho do autenticador.',
1277
+ twoFactorDescription: 'Um código além da senha para entrar.',
1005
1278
  twoFactorKeywords: 'mfa 2fa totp autenticador authenticator código e-mail método aparelho celular',
1006
1279
  recoveryCodes: 'Códigos de recuperação',
1007
- recoveryCodesDescription: 'Servem para entrar se você perder o aparelho. Gerar novos códigos invalida os anteriores.',
1280
+ recoveryCodesDescription: 'Para entrar se você perder o aparelho.',
1008
1281
  recoveryCodesKeywords: 'backup recuperação perdi celular aparelho mfa 2fa',
1009
1282
  sessions: 'Sessões e dispositivos',
1010
- sessionsDescription: 'Veja onde a sua conta está conectada (navegador, sistema, IP, último uso) e encerre uma sessão ou todas as outras.',
1283
+ sessionsDescription: 'Onde a sua conta está conectada.',
1011
1284
  sessionsKeywords: 'dispositivos navegador ip encerrar desconectar sair aparelhos conectados',
1285
+ sessionsSection: 'Sessões',
1286
+ sessionsSectionDescription: 'Esta sessão, os tokens de aplicação e os dispositivos conectados.',
1287
+ sessionsSectionKeywords: 'sessão sessões token tokens dispositivos login segurança',
1012
1288
  about: 'Sobre',
1013
1289
  aboutKeywords: 'versão build ambiente dependências suporte',
1014
1290
  application: 'Aplicação',
@@ -1026,7 +1302,7 @@ const SPINE_MESSAGES_PT_BR = {
1026
1302
  dependenciesDescription: 'Versões declaradas no package.json desta aplicação.',
1027
1303
  dependenciesKeywords: 'angular hermes rxjs apollo graphql',
1028
1304
  supportInfo: 'Informações para suporte',
1029
- supportInfoDescription: 'Copia versão, ambiente, navegador, página e preferências — cole no chamado.',
1305
+ supportInfoDescription: 'Versão, ambiente, navegador e preferências, para colar no chamado.',
1030
1306
  supportInfoKeywords: 'ajuda chamado diagnóstico',
1031
1307
  noSessionToken: 'Nenhum token de sessão disponível.',
1032
1308
  tokenCopied: 'Token copiado. Não compartilhe: ele dá acesso à sua conta até expirar.',
@@ -1108,7 +1384,7 @@ const SPINE_MESSAGES_PT_BR = {
1108
1384
  removeHint: 'Você deixa de poder usar este método para entrar.',
1109
1385
  removeLastHint: 'Sem nenhum método, os códigos de recuperação também deixam de valer.',
1110
1386
  reauthTitle: 'Confirme sua identidade',
1111
- reauthHint: 'Para mudar a verificação em duas etapas ou gerar códigos, prove de novo quem é você. Vale por 5 minutos.',
1387
+ reauthHint: 'Por segurança, confirme antes de continuar. Vale por 5 minutos.',
1112
1388
  reauthHow: 'Como confirmar',
1113
1389
  reauthPassword: 'Senha',
1114
1390
  reauthRecoveryCode: 'Código de recuperação',
@@ -1132,13 +1408,13 @@ const SPINE_MESSAGES_PT_BR = {
1132
1408
  sendCode: 'Enviar código',
1133
1409
  emailCodeTitle: 'Confirme o e-mail',
1134
1410
  emailCodeHint: 'Digite o código de 6 dígitos enviado para {email}.',
1135
- codesRemaining: { zero: 'Nenhum código disponível.', one: '1 código disponível.', other: '{count} códigos disponíveis.' },
1136
- codesHint: 'Cada um vale uma vez para entrar se você perder o seu método de verificação.',
1411
+ codesRemaining: { zero: 'Nenhum código disponível', one: '1 código disponível', other: '{count} códigos disponíveis' },
1412
+ codesHint: 'Cada um vale uma vez. Gerar novos invalida os anteriores.',
1137
1413
  codesNeedMethod: 'Cadastre um método de verificação para gerar códigos de recuperação.',
1138
1414
  generateCodes: 'Gerar códigos',
1139
1415
  regenerateCodes: 'Gerar novos códigos',
1140
1416
  codesTitle: 'Seus códigos de recuperação',
1141
- codesShownOnce: 'Guarde estes códigos num lugar seguro. Cada um vale uma única vez para entrar se você perder o acesso ao seu método de verificação. Eles não serão mostrados de novo.',
1417
+ codesShownOnce: 'Guarde num lugar seguro: eles não serão mostrados de novo. Cada um vale uma vez.',
1142
1418
  copyCodes: 'Copiar',
1143
1419
  downloadCodes: 'Baixar .txt',
1144
1420
  codesCopied: 'Códigos copiados.',
@@ -1263,10 +1539,10 @@ const SPINE_MESSAGES_PT_BR = {
1263
1539
  tabRoutes: 'Rotas',
1264
1540
  tabRouters: 'Roteadores',
1265
1541
  tabAccess: 'Acesso',
1266
- noClients: 'Você ainda não administra nenhum client por aqui. É preciso (a) ter acesso concedido a um client e (b) estar logado com o crachá <strong>Administrador</strong> ativo. Troque de crachá no menu do seu perfil, se disponível, ou peça acesso a um administrador central.',
1542
+ noClients: 'Você não administra este app. É preciso (a) estar no grupo de administração do projeto dele e (b) estar logado com o crachá <strong>Administrador</strong> ativo. Troque de crachá no menu do seu perfil, se disponível, ou peça acesso a quem administra o projeto.',
1267
1543
  pickClient: 'Selecione o client que deseja administrar',
1268
1544
  groupsTitle: 'Grupos',
1269
- groupsIntro: 'Gerencie os membros dos grupos deste client — sem depender do painel central.',
1545
+ groupsIntro: 'Crie, edite e exclua os grupos deste client e gerencie os membros — sem depender do painel central.',
1270
1546
  groupsFixed: 'Os 5 grupos abaixo são fixos deste client e não podem ser renomeados, criados ou excluídos — consulte um para gerenciar seus membros.',
1271
1547
  pickGroup: 'Selecione um grupo',
1272
1548
  noDescription: 'Sem descrição cadastrada.',
@@ -1352,6 +1628,9 @@ const SPINE_MESSAGES_PT_BR = {
1352
1628
  colPage: 'Página',
1353
1629
  colBuiltPath: 'Caminho montado',
1354
1630
  colVisibility: 'Visibilidade',
1631
+ colAllowedBadges: 'Crachás permitidos',
1632
+ anyBadge: 'Qualquer crachá',
1633
+ allowedBadgesHint: 'Vazio = qualquer crachá. Com um ou mais escolhidos, só os grupos com um desses crachás podem receber acesso a esta tela na aba Acesso.',
1355
1634
  routesTitle: 'Rotas',
1356
1635
  newRoute: 'Nova rota',
1357
1636
  routesCaption: 'Rotas do client',
@@ -1370,6 +1649,19 @@ const SPINE_MESSAGES_PT_BR = {
1370
1649
  pathPlaceholder: 'ex.: dashboard',
1371
1650
  pathHint: 'Um trecho só do endereço: letras minúsculas e hífen. O "/" entre níveis é o roteador que monta.',
1372
1651
  pathInvalid: 'Use só letras minúsculas (a–z) e hífen, sem "/", espaço, número ou acento, e sem hífen no início ou no fim.',
1652
+ rotateSecret: 'Gerar novo segredo',
1653
+ confirmRotateSecretHeading: 'Gerar um novo segredo?',
1654
+ confirmRotateSecret: 'O segredo atual deixa de funcionar na hora; atualize o APP_SECRET do serviço que usa esta aplicação. Tokens já emitidos valem até expirar (até 1 hora).',
1655
+ rotatedSecretHeading: 'Novo segredo de {name}',
1656
+ rotatedSecretWarning: 'Copie agora: o segredo não será mostrado de novo.',
1657
+ rotatedSecretNote: 'O servidor guarda só um hash do segredo. Ao fechar esta janela não há como consultá-lo; se ele se perder, gere outro.',
1658
+ rotatedSecretLabel: 'Segredo da aplicação {name}',
1659
+ copySecret: 'Copiar',
1660
+ secretCopied: 'Segredo copiado.',
1661
+ rotatedSecretDone: 'Concluir',
1662
+ rotateSecretError: 'Não foi possível gerar o novo segredo. Tente de novo.',
1663
+ rotateSecretNotFound: 'Aplicação não encontrada neste client.',
1664
+ rotateSecretForbidden: 'Você não administra este client.',
1373
1665
  confirmDeleteAppHeading: 'Excluir aplicação?',
1374
1666
  confirmDeleteApp: 'A aplicação {name} será excluída e a credencial dela para de funcionar. Esta ação não pode ser desfeita.',
1375
1667
  confirmDeleteRouteHeading: 'Excluir rota?',
@@ -1404,6 +1696,95 @@ const SPINE_MESSAGES_PT_BR = {
1404
1696
  reorderLoadError: 'Não foi possível carregar o menu. Tente de novo.',
1405
1697
  reorderSaveError: 'Não foi possível salvar a ordem. Nada foi perdido: tente de novo.',
1406
1698
  reorderSave: 'Salvar ordem',
1699
+ newGroup: 'Novo grupo',
1700
+ editGroup: 'Editar grupo',
1701
+ groupsCaption: 'Grupos do projeto',
1702
+ noGroups: 'Nenhum grupo cadastrado ainda.',
1703
+ accessLevel: 'Nível de acesso',
1704
+ pickAccessLevel: 'Selecione o nível',
1705
+ noLevel: 'Sem nível',
1706
+ noGroupsOfLevel: 'Nenhum grupo neste nível.',
1707
+ colStatus: 'Situação',
1708
+ groupName: 'Nome',
1709
+ groupType: 'Tipo',
1710
+ groupTypePlaceholder: 'Selecione o tipo',
1711
+ groupDescription: 'Descrição (opcional)',
1712
+ openGroup: 'Abrir o grupo {name}',
1713
+ adminGroupTag: 'Administração',
1714
+ adminGroupNote: 'Grupo de administração do projeto: quem está nele administra este app no auto-serviço. É o único do projeto, não pode ser excluído, renomeado, desativado nem mudar de tipo, e não pode ficar sem membros.',
1715
+ adminGroupEditNote: 'Grupo de administração do projeto: só a descrição pode ser alterada.',
1716
+ adminGroupLastMemberWarning: 'O grupo de administração não pode ficar sem membros: inclua outra pessoa ou desfaça uma remoção.',
1717
+ confirmDeleteGroupHeading: 'Excluir grupo?',
1718
+ confirmDeleteGroup: 'O grupo {name} será excluído junto com os membros, os acessos a telas e as permissões em aplicações. Esta ação não pode ser desfeita.',
1719
+ groupSaveError: 'Não foi possível salvar o grupo. Tente de novo.',
1720
+ membersSaveError: 'Não foi possível salvar todas as alterações dos membros. Confira a lista e tente de novo.',
1721
+ appsCaption: 'Aplicações do projeto',
1722
+ noApps: 'Nenhuma aplicação cadastrada ainda.',
1723
+ colGrants: 'Grants',
1724
+ openApp: 'Abrir a aplicação {name}',
1725
+ tabTokens: 'Tokens',
1726
+ tokensCaption: 'Tokens da aplicação',
1727
+ noTokens: 'Nenhum token criado para esta aplicação ainda.',
1728
+ newToken: 'Novo token',
1729
+ colTokenPrefix: 'Início do token',
1730
+ colExpiresAt: 'Expira em',
1731
+ colLastUsed: 'Último uso',
1732
+ tokenExpired: 'Expirado',
1733
+ tokenRevoked: 'Revogado',
1734
+ neverUsed: 'Nunca',
1735
+ revokeToken: 'Revogar',
1736
+ confirmRevokeTokenHeading: 'Revogar token?',
1737
+ confirmRevokeToken: 'O token {name} deixa de funcionar na hora. A revogação não pode ser desfeita.',
1738
+ tokenRevokedDone: 'Token {name} revogado.',
1739
+ tokenRevokeError: 'Não foi possível revogar o token. Tente de novo.',
1740
+ tokensLoadError: 'Não foi possível carregar os tokens da aplicação.',
1741
+ tokenName: 'Nome',
1742
+ tokenDescription: 'Descrição (opcional)',
1743
+ tokenExpiresAt: 'Expira em',
1744
+ tokenExpiresHint: 'De amanhã até 365 dias a partir de hoje.',
1745
+ tokenExpiresInvalid: 'Escolha uma data entre amanhã e 365 dias a partir de hoje.',
1746
+ tokenOperations: 'Operações liberadas ao token',
1747
+ tokenOperationsNote: 'Nada vem marcado: o token não tem login, então marque as operações protected e private que ele pode chamar. As public não precisam de token.',
1748
+ freeForAll: 'liberada para todos',
1749
+ freeForAuthenticated: 'liberada para autenticados',
1750
+ freeNoPermission: 'não depende de permissão',
1751
+ publicNoToken: 'pública: não precisa de token',
1752
+ createToken: 'Criar token',
1753
+ tokenSaveError: 'Não foi possível criar o token. Tente de novo.',
1754
+ tokenCreatedHeading: 'Token {name} criado',
1755
+ tokenCreatedWarning: 'Copie agora: o token não será mostrado de novo.',
1756
+ tokenCreatedNote: 'Envie no cabeçalho x-api-token. O servidor guarda só um hash do token; se ele se perder, revogue e crie outro.',
1757
+ tokenValueLabel: 'Token {name}',
1758
+ copyToken: 'Copiar',
1759
+ tokenCopied: 'Token copiado.',
1760
+ hierarchyHint: 'De cima para baixo: Navegação só com Header, Menu só com Navegação, Item só com Menu e Submenu só com Item. Limpar um nível limpa os de baixo.',
1761
+ routerSaveError: 'Não foi possível salvar o roteador. Tente de novo.',
1762
+ permissionsSaveError: 'Não foi possível salvar todas as alterações das operações. Reabra o painel e confira.',
1763
+ tokenErrors: {
1764
+ invalidExpiresAt: 'Data de validade inválida. Escolha outra data.',
1765
+ invalidValidity: 'A validade precisa ficar entre amanhã e 365 dias a partir de hoje.',
1766
+ invalidOperationIds: 'Alguma operação escolhida não existe mais nesta aplicação. Reabra o formulário e escolha de novo.',
1767
+ operationNotProtected: 'Só operações protected podem ser liberadas a um grupo; as public e private já valem para ele.',
1768
+ operationPublic: 'Operação pública não precisa de token e não pode ser liberada a ele. Desmarque-a e tente de novo.',
1769
+ appNotFound: 'Aplicação não encontrada neste client. Ela pode ter sido excluída.',
1770
+ tokenNotFound: 'Token não encontrado nesta aplicação. Ele pode já ter sido revogado.',
1771
+ },
1772
+ routerErrors: {
1773
+ hierarchyGap: 'A hierarquia tem um nível vazio no meio: cada nível precisa do de cima (Header → Navegação → Menu → Item → Submenu). Preencha o nível que falta ou limpe os de baixo.',
1774
+ invalidAllowedTypes: 'Algum crachá escolhido não existe mais. Revise os crachás permitidos.',
1775
+ },
1776
+ groupErrors: {
1777
+ invalidType: 'Esse tipo de grupo não existe mais. Escolha outro tipo.',
1778
+ adminTypeReserved: 'O tipo Administrador é só do grupo de administração do projeto.',
1779
+ emptyUpdate: 'Nada para salvar: nenhum campo foi alterado.',
1780
+ groupProjectImmutable: 'O grupo não muda de projeto.',
1781
+ groupNameTaken: 'Já existe um grupo com esse nome neste projeto.',
1782
+ adminGroupImmutable: 'O grupo de administração não muda de nome, de tipo nem é desativado.',
1783
+ adminGroupProtected: 'O grupo de administração do projeto não pode ser excluído.',
1784
+ adminGroupLastMember: 'O grupo de administração não pode ficar sem membros.',
1785
+ adminGroupAccessProtected: 'O acesso do grupo de administração a este app não pode ser removido.',
1786
+ groupNotFound: 'Grupo não encontrado neste projeto. Ele pode ter sido excluído.',
1787
+ },
1407
1788
  },
1408
1789
  autoServiceAudit: {
1409
1790
  title: 'Auditoria',
@@ -1573,6 +1954,132 @@ const SPINE_MESSAGES_PT_BR = {
1573
1954
  spansSummary: '{count} spans em {duration}',
1574
1955
  spanRow: '{app}, {operation}: {outcome}, começa em +{offset}, dura {duration}',
1575
1956
  },
1957
+ loginAttempts: {
1958
+ refresh: 'Atualizar',
1959
+ user: 'Usuário',
1960
+ userPlaceholder: 'Usuário, e-mail ou identificador digitado',
1961
+ period: 'Período',
1962
+ result: 'Resultado',
1963
+ method: 'Método',
1964
+ all: 'Todos',
1965
+ clear: 'Limpar',
1966
+ advancedFilters: 'Filtros avançados',
1967
+ ip: 'IP ou rede',
1968
+ ipPlaceholder: '10.0.0.5 ou 10.0.0.0/8',
1969
+ ipHint: 'IP exato ou rede em notação CIDR.',
1970
+ reason: 'Motivo',
1971
+ caption: 'Tentativas de login',
1972
+ pageItems: 'tentativas',
1973
+ pageSize: 'Tentativas por página',
1974
+ colDate: 'Data/hora',
1975
+ colResult: 'Resultado',
1976
+ colMethod: 'Método',
1977
+ colUser: 'Usuário',
1978
+ colReason: 'Motivo',
1979
+ colIp: 'IP',
1980
+ colRealm: 'Realm',
1981
+ colClient: 'Client',
1982
+ colUserAgent: 'Navegador',
1983
+ filterByIp: 'Filtrar por este IP',
1984
+ filterByUser: 'Filtrar por este usuário',
1985
+ noMatchingUser: 'sem usuário correspondente',
1986
+ methods: { local: 'Senha', mfa: 'MFA', github: 'GitHub', google: 'Google', keycloak: 'Keycloak', entraid: 'Entra ID' },
1987
+ results: { success: 'Sucesso', failure: 'Falha', cancelled: 'Cancelada' },
1988
+ reasons: {
1989
+ bad_password: 'Senha incorreta',
1990
+ unknown_user: 'Usuário inexistente ou inativo',
1991
+ locked: 'Conta bloqueada por tentativas',
1992
+ mfa_failed: 'Código de verificação incorreto',
1993
+ mfa_expired: 'Código de verificação vencido',
1994
+ mfa_not_allowed: 'Método de verificação não permitido',
1995
+ mfa_invalid_method: 'Método de verificação desconhecido',
1996
+ recovery_failed: 'Código de recuperação incorreto ou já usado',
1997
+ recovery_refused: 'Código de recuperação recusado',
1998
+ not_linked: 'Conta externa não vinculada',
1999
+ wrong_realm: 'Usuário de outro realm',
2000
+ },
2001
+ forbiddenAdmin: 'Sua conta não pode consultar as tentativas de login. Peça a um administrador a operação admin:attempts:list.',
2002
+ forbiddenClient: 'Só o grupo de administração do projeto, com o crachá Administrador ativo, vê as tentativas de login desta aplicação.',
2003
+ loadErrorDetail: 'Não foi possível carregar as tentativas: {detail}',
2004
+ loadError: 'Não foi possível carregar as tentativas de login.',
2005
+ },
2006
+ accountFeatures: {
2007
+ aiAgent: 'Conectar um agente de IA',
2008
+ aiAgentDescription: 'Gera o token e a configuração MCP para colar no Claude Code.',
2009
+ aiAgentKeywords: 'ia agente claude mcp assistente llm automação',
2010
+ aiAgentHelp: 'O agente usa um token de aplicação pessoal (x-app-token) com as permissões do nível de acesso escolhido. Revogue em "Tokens de aplicação" quando não precisar mais.',
2011
+ aiAgentStart: 'Conectar agente',
2012
+ aiAgentServers: 'Servidores MCP',
2013
+ aiAgentAccess: 'Acesso do agente',
2014
+ aiAgentReadOnly: 'Só leitura',
2015
+ aiAgentReadWrite: 'Leitura e escrita',
2016
+ aiAgentWriteHint: 'O agente poderá alterar dados em seu nome. Prefira só leitura quando bastar.',
2017
+ aiAgentReadOnlyServer: '{name} (só leitura)',
2018
+ aiAgentTokenName: 'Agente de IA ({date})',
2019
+ aiAgentGenerate: 'Gerar configuração',
2020
+ aiAgentSelectServer: 'Escolha pelo menos um servidor.',
2021
+ aiAgentResult: 'Configuração pronta',
2022
+ aiAgentResultHint: 'Cole em .mcp.json, na raiz do projeto (Claude Code). O token aparece só agora: copie antes de concluir. Vale até {date}.',
2023
+ aiAgentCopy: 'Copiar configuração',
2024
+ aiAgentCopied: 'Configuração copiada. Não compartilhe: ela contém o seu token.',
2025
+ aiAgentDone: 'Concluir',
2026
+ aiAgentCreateError: 'Não foi possível gerar o token do agente.',
2027
+ aiAgentUnavailable: 'Indisponível neste aplicativo: ele não está ligado ao argos-admin.',
2028
+ activity: 'Atividade da conta',
2029
+ activityDescription: 'Entradas e tentativas que falharam nos últimos 90 dias.',
2030
+ activityKeywords: 'login entrada acesso histórico tentativa falha ip navegador segurança',
2031
+ activityFilter: 'Mostrar',
2032
+ activityAll: 'Todas',
2033
+ activityFailures: 'Só falhas',
2034
+ activityDetail: '{date} · IP {ip}',
2035
+ activityApp: 'Aplicativo: {app}',
2036
+ activityEmpty: 'Nada nos últimos 90 dias.',
2037
+ activityMore: 'Carregar mais',
2038
+ activityShowing: '{shown} de {total}',
2039
+ activityLoadError: 'Não foi possível carregar a atividade da conta.',
2040
+ unknownIp: 'desconhecido',
2041
+ unknownApp: 'sem nome',
2042
+ idle: 'Encerrar por inatividade',
2043
+ idleDescription: 'Sai sozinho depois de um tempo sem uso, com um aviso antes.',
2044
+ idleKeywords: 'inatividade ocioso tempo sair automático logout segurança bloqueio',
2045
+ idleNever: 'Nunca',
2046
+ idleOption: '{minutes} min',
2047
+ idleLimitHint: 'Esta aplicação encerra a sessão em no máximo {minutes} min sem uso.',
2048
+ idleWarningHeading: 'A sessão vai ser encerrada',
2049
+ idleWarningText: { one: 'Nenhuma atividade há algum tempo. A sessão termina em {count} segundo.', other: 'Nenhuma atividade há algum tempo. A sessão termina em {count} segundos.' },
2050
+ idleStay: 'Continuar conectado',
2051
+ idleLogout: 'Sair agora',
2052
+ startPage: 'Tela inicial',
2053
+ startPageDescription: 'Onde a aplicação abre depois do login.',
2054
+ startPageKeywords: 'início home abertura login favorito última tela',
2055
+ startHome: 'Início',
2056
+ startLast: 'Última tela visitada',
2057
+ startFavorite: 'Favorito: {name}',
2058
+ startPageHint: 'Marque telas como favoritas para abrir numa delas.',
2059
+ defaultBadge: 'Crachá padrão',
2060
+ defaultBadgeDescription: 'Nível de acesso com que você entra nesta aplicação, em qualquer navegador.',
2061
+ defaultBadgeKeywords: 'crachá badge nível acesso perfil padrão login',
2062
+ defaultBadgeAuto: 'Automático (o de menor nível)',
2063
+ defaultBadgeSaved: 'Crachá padrão salvo. Vale a partir do próximo login.',
2064
+ defaultBadgeError: 'Não foi possível salvar o crachá padrão.',
2065
+ defaultBadgeLoadError: 'Não foi possível carregar os seus crachás.',
2066
+ notices: 'Notificações',
2067
+ noticesDescription: 'Os avisos que esta aplicação mostra.',
2068
+ noticesKeywords: 'notificação aviso alerta navegador push',
2069
+ browserNotices: 'Avisos do navegador',
2070
+ browserNoticesDescription: 'Aviso do sistema quando a aba está em segundo plano.',
2071
+ browserNoticesDenied: 'O navegador bloqueou as notificações deste site. Libere nas configurações do navegador.',
2072
+ browserNoticesUnsupported: 'Este navegador não mostra notificações.',
2073
+ noticeOpen: 'Abrir',
2074
+ sync: 'Preferências na conta',
2075
+ syncDescription: 'Aparência, região, favoritos e as demais preferências acompanham você em qualquer navegador.',
2076
+ syncKeywords: 'sincronizar nuvem conta navegador preferências backup',
2077
+ syncSynced: 'Sincronizado às {time}',
2078
+ syncSaving: 'Salvando…',
2079
+ syncLocal: 'Só neste navegador: a conta não está disponível.',
2080
+ syncError: 'Não foi possível sincronizar. Tente de novo.',
2081
+ syncNow: 'Sincronizar agora',
2082
+ },
1576
2083
  };
1577
2084
 
1578
2085
  const SPINE_MESSAGES_EN = {
@@ -1697,7 +2204,7 @@ const SPINE_MESSAGES_EN = {
1697
2204
  appearance: 'Appearance',
1698
2205
  appearanceKeywords: 'visual colors theme layout',
1699
2206
  theme: 'Theme',
1700
- themeDescription: 'Light, dark or following the operating system.',
2207
+ themeDescription: 'Light, dark or the operating system one.',
1701
2208
  themeKeywords: 'dark mode light night',
1702
2209
  themeLight: 'Light',
1703
2210
  themeDark: 'Dark',
@@ -1706,7 +2213,7 @@ const SPINE_MESSAGES_EN = {
1706
2213
  accentDescription: 'Color family for the header, buttons and highlights.',
1707
2214
  accentKeywords: 'family palette color accent',
1708
2215
  density: 'Density',
1709
- densityDescription: 'Compact brings rows and controls closer to fit more information on screen.',
2216
+ densityDescription: 'Compact shows more information on screen.',
1710
2217
  densityKeywords: 'spacing size compact',
1711
2218
  comfortable: 'Comfortable',
1712
2219
  compact: 'Compact',
@@ -1714,64 +2221,121 @@ const SPINE_MESSAGES_EN = {
1714
2221
  fontDescription: 'Typeface for the whole application.',
1715
2222
  fontKeywords: 'typography typeface text roboto rubik figtree geist',
1716
2223
  sidebar: 'Side menu',
1717
- sidebarDescription: 'Automatic opens when the pointer approaches; fixed stays open; compact shows only icons.',
2224
+ sidebarDescription: 'How the side menu opens and closes.',
1718
2225
  sidebarKeywords: 'sidebar navigation side bar',
2226
+ sidebarHelp: 'Automatic: opens when the pointer approaches. Fixed: always open. Compact: shows only the icons.',
1719
2227
  sidebarAuto: 'Automatic',
1720
2228
  sidebarFixed: 'Fixed',
1721
2229
  sidebarCompact: 'Compact',
1722
2230
  region: 'Region',
1723
- regionKeywords: 'language locale time date',
2231
+ regionKeywords: 'language locale time date hour week',
1724
2232
  language: 'Language',
1725
- languageDescription: 'Language of the system texts. Applies immediately, no reload.',
2233
+ languageDescription: 'Applies immediately, no reload.',
1726
2234
  languageKeywords: 'language locale translation english spanish portuguese',
1727
2235
  languageAuto: 'System default ({language})',
1728
2236
  timeZone: 'Time zone',
1729
2237
  timeZoneKeywords: 'timezone hour utc gmt',
1730
- timeZoneHint: 'Used to display dates and times. Now: {now}',
2238
+ timeZoneHint: 'For dates and times. Now: {now}',
1731
2239
  timeZoneAuto: 'Automatic ({zone})',
2240
+ hourCycle: 'Time format',
2241
+ hourCycleHint: 'Now: {now}',
2242
+ hourCycleKeywords: 'clock 24h 12h am pm hour',
2243
+ hourCycleAuto: 'Automatic',
2244
+ hourCycle24: '24-hour',
2245
+ hourCycle12: '12-hour',
2246
+ weekStart: 'First day of the week',
2247
+ weekStartDescription: 'For calendars and date pickers.',
2248
+ weekStartKeywords: 'week calendar sunday monday',
2249
+ weekStartAuto: 'Automatic',
1732
2250
  shortcuts: 'Shortcuts',
1733
2251
  shortcutsDescription: 'Press ? on any screen to see this list.',
1734
- shortcutsKeywords: 'keyboard keys tour help',
2252
+ shortcutsKeywords: 'keyboard keys',
1735
2253
  tour: 'Guided tour',
1736
- tourDescription: 'Shows again, step by step, where each part of the screen is.',
2254
+ tourDescription: 'Shows again where each part of the screen is.',
1737
2255
  tourKeywords: 'onboarding help introduction',
1738
2256
  replayTour: 'Replay tour',
2257
+ accessibility: 'Accessibility',
2258
+ accessibilityKeywords: 'accessibility reading vision',
2259
+ textSize: 'Text size',
2260
+ textSizeDescription: 'Makes text and controls larger on every screen.',
2261
+ textSizeKeywords: 'zoom font letter bigger enlarge',
2262
+ motion: 'Reduce motion',
2263
+ motionDescription: 'System follows your computer or phone.',
2264
+ motionKeywords: 'animation motion transition reduced dizziness',
2265
+ motionSystem: 'System',
2266
+ motionReduce: 'Always',
2267
+ motionFull: 'Never',
2268
+ help: 'Help',
2269
+ helpKeywords: "help what's new tour introduction",
2270
+ news: "What's new",
2271
+ newsDescription: 'What changed in the latest versions.',
2272
+ newsKeywords: 'news version release changelog',
2273
+ showNews: "See what's new",
2274
+ browserData: 'Browser data',
2275
+ browserDataDescription: 'Stored only in this browser, for your account.',
2276
+ browserDataKeywords: 'privacy clear delete history data',
2277
+ recentPages: 'Recent pages',
2278
+ recentPagesKeywords: 'history visited recent',
2279
+ recentSearches: 'Recent searches',
2280
+ recentSearchesKeywords: 'search query history',
2281
+ favorites: 'Favorites',
2282
+ favoritesKeywords: 'favorites star bookmarks',
2283
+ pagesCount: { zero: 'No pages stored', one: '{count} page stored', other: '{count} pages stored' },
2284
+ searchesCount: { zero: 'No searches stored', one: '{count} search stored', other: '{count} searches stored' },
2285
+ clear: 'Clear',
2286
+ confirmClear: 'Confirm',
2287
+ cancel: 'Cancel',
2288
+ recentPagesCleared: 'Recent pages cleared.',
2289
+ recentSearchesCleared: 'Recent searches cleared.',
2290
+ favoritesCleared: 'Favorites cleared.',
2291
+ restorePreferences: 'Preferences',
2292
+ restorePreferencesDescription: 'Appearance, accessibility and formats go back to the defaults.',
2293
+ restorePreferencesHelp: 'Back to the defaults: theme, main color, density, font, side menu, text size, animations, time format and first day of the week. Language, time zone and single-key shortcuts stay as they are.',
2294
+ restorePreferencesKeywords: 'restore default reset preferences',
2295
+ restore: 'Restore defaults',
2296
+ confirmRestore: 'Confirm',
2297
+ preferencesRestored: 'Preferences restored.',
1739
2298
  account: 'Account',
1740
- accountKeywords: 'profile user session login',
2299
+ accountKeywords: 'profile user login level sign out',
1741
2300
  user: 'User',
1742
2301
  accessLevel: 'Access level',
1743
2302
  accessLevelKeywords: 'badge profile permission group',
1744
2303
  session: 'Session',
1745
- sessionDescription: 'The session renews itself while the application is open.',
2304
+ sessionDescription: 'Renews itself while the application is open.',
1746
2305
  sessionKeywords: 'expiration token login',
1747
2306
  sessionUnknown: 'Not provided',
1748
2307
  sessionExpired: 'Expired at {time}',
1749
2308
  sessionExpiresIn: 'Expires in {minutes} min (at {time})',
1750
2309
  sessionExpiresAt: 'Expires at {time}',
1751
2310
  accessToken: 'Access token',
1752
- accessTokenDescription: 'To test an API on your behalf. It gives access to your account until it expires: never share it.',
2311
+ accessTokenDescription: "To test an API on your behalf. Don't share it.",
1753
2312
  accessTokenKeywords: 'jwt bearer api developer',
1754
2313
  copy: 'Copy',
2314
+ moreAbout: 'More about {label}',
1755
2315
  appTokens: 'Application tokens',
1756
- appTokensDescription: 'To use this application outside the interface on your behalf — an MCP, a script. The token is valid only for this application, with the permissions of the chosen access level, and expires in at most 1 year. Send it in the x-app-token header.',
2316
+ appTokensDescription: 'To use the application outside the interface: an MCP, a script.',
1757
2317
  appTokensKeywords: 'token app mcp api key pat x-app-token automation script integration',
2318
+ appTokensHelp: 'The token is valid only for this application, with the permissions of the chosen access level, and expires in at most 1 year. Send it in the x-app-token header.',
1758
2319
  logout: 'Sign out',
1759
2320
  logoutDescription: 'Ends the session in this browser.',
1760
2321
  logoutKeywords: 'logout disconnect',
1761
- security: 'Account and security',
1762
- securityDescription: 'Two-step verification, recovery codes and sessions of your Argos account, the sign-in for the applications.',
1763
- securityKeywords: 'security argos password authentication mfa 2fa devices',
2322
+ mfa: 'MFA',
2323
+ mfaDescription: 'Two-step verification of your Argos account.',
2324
+ mfaKeywords: 'security argos password authentication mfa 2fa two-factor two-step verification',
1764
2325
  signedInAccount: 'Argos account',
1765
2326
  signedInRealm: 'Realm {realm}',
1766
2327
  twoFactor: 'Two-step verification',
1767
- twoFactorDescription: 'Add or remove methods (authenticator app or email) and switch the authenticator to a new device.',
2328
+ twoFactorDescription: 'A code on top of the password to sign in.',
1768
2329
  twoFactorKeywords: 'mfa 2fa totp authenticator code email method device phone',
1769
2330
  recoveryCodes: 'Recovery codes',
1770
- recoveryCodesDescription: 'Use them to sign in if you lose your device. Generating new codes invalidates the previous ones.',
2331
+ recoveryCodesDescription: 'To sign in if you lose your device.',
1771
2332
  recoveryCodesKeywords: 'backup recovery lost phone device mfa 2fa',
1772
2333
  sessions: 'Sessions and devices',
1773
- sessionsDescription: 'See where your account is signed in (browser, system, IP, last used) and end one session or all the others.',
2334
+ sessionsDescription: 'Where your account is signed in.',
1774
2335
  sessionsKeywords: 'devices browser ip end sign out disconnect connected',
2336
+ sessionsSection: 'Sessions',
2337
+ sessionsSectionDescription: 'This session, the application tokens and the signed-in devices.',
2338
+ sessionsSectionKeywords: 'session sessions token tokens devices login security',
1775
2339
  about: 'About',
1776
2340
  aboutKeywords: 'version build environment dependencies support',
1777
2341
  application: 'Application',
@@ -1789,7 +2353,7 @@ const SPINE_MESSAGES_EN = {
1789
2353
  dependenciesDescription: "Versions declared in this application's package.json.",
1790
2354
  dependenciesKeywords: 'angular hermes rxjs apollo graphql',
1791
2355
  supportInfo: 'Support information',
1792
- supportInfoDescription: 'Copies version, environment, browser, page and preferences — paste it into the ticket.',
2356
+ supportInfoDescription: 'Version, environment, browser and preferences, to paste into the ticket.',
1793
2357
  supportInfoKeywords: 'help ticket diagnostics',
1794
2358
  noSessionToken: 'No session token available.',
1795
2359
  tokenCopied: "Token copied. Don't share it: it gives access to your account until it expires.",
@@ -1871,7 +2435,7 @@ const SPINE_MESSAGES_EN = {
1871
2435
  removeHint: "You won't be able to use this method to sign in anymore.",
1872
2436
  removeLastHint: 'With no method left, the recovery codes stop working too.',
1873
2437
  reauthTitle: 'Confirm your identity',
1874
- reauthHint: 'To change two-step verification or generate codes, prove who you are again. It lasts 5 minutes.',
2438
+ reauthHint: 'For security, confirm before continuing. It lasts 5 minutes.',
1875
2439
  reauthHow: 'How to confirm',
1876
2440
  reauthPassword: 'Password',
1877
2441
  reauthRecoveryCode: 'Recovery code',
@@ -1895,13 +2459,13 @@ const SPINE_MESSAGES_EN = {
1895
2459
  sendCode: 'Send code',
1896
2460
  emailCodeTitle: 'Confirm the email',
1897
2461
  emailCodeHint: 'Enter the 6-digit code sent to {email}.',
1898
- codesRemaining: { zero: 'No codes available.', one: '1 code available.', other: '{count} codes available.' },
1899
- codesHint: 'Each one signs you in once if you lose your verification method.',
2462
+ codesRemaining: { zero: 'No codes available', one: '1 code available', other: '{count} codes available' },
2463
+ codesHint: 'Each one works once. Generating new ones invalidates the previous ones.',
1900
2464
  codesNeedMethod: 'Set up a verification method to generate recovery codes.',
1901
2465
  generateCodes: 'Generate codes',
1902
2466
  regenerateCodes: 'Generate new codes',
1903
2467
  codesTitle: 'Your recovery codes',
1904
- codesShownOnce: "Keep these codes somewhere safe. Each one signs you in once if you lose access to your verification method. They won't be shown again.",
2468
+ codesShownOnce: "Keep them somewhere safe: they won't be shown again. Each one works once.",
1905
2469
  copyCodes: 'Copy',
1906
2470
  downloadCodes: 'Download .txt',
1907
2471
  codesCopied: 'Codes copied.',
@@ -2026,10 +2590,10 @@ const SPINE_MESSAGES_EN = {
2026
2590
  tabRoutes: 'Routes',
2027
2591
  tabRouters: 'Routers',
2028
2592
  tabAccess: 'Access',
2029
- noClients: "You don't manage any client here yet. You need to (a) have been granted access to a client and (b) be signed in with the <strong>Administrator</strong> badge active. Switch badges in your profile menu, if available, or ask a central administrator for access.",
2593
+ noClients: "You don't manage this app. You need to (a) be in its project's administration group and (b) be signed in with the <strong>Administrator</strong> badge active. Switch badges in your profile menu, if available, or ask whoever manages the project for access.",
2030
2594
  pickClient: 'Select the client you want to manage',
2031
2595
  groupsTitle: 'Groups',
2032
- groupsIntro: "Manage the members of this client's groups — without relying on the central panel.",
2596
+ groupsIntro: "Create, edit and delete this client's groups and manage their members — without relying on the central panel.",
2033
2597
  groupsFixed: "The 5 groups below are fixed for this client and can't be renamed, created or deleted — look one up to manage its members.",
2034
2598
  pickGroup: 'Select a group',
2035
2599
  noDescription: 'No description provided.',
@@ -2115,6 +2679,9 @@ const SPINE_MESSAGES_EN = {
2115
2679
  colPage: 'Page',
2116
2680
  colBuiltPath: 'Built path',
2117
2681
  colVisibility: 'Visibility',
2682
+ colAllowedBadges: 'Allowed badges',
2683
+ anyBadge: 'Any badge',
2684
+ allowedBadgesHint: 'Empty = any badge. With one or more chosen, only groups with one of those badges can be given access to this screen in the Access tab.',
2118
2685
  routesTitle: 'Routes',
2119
2686
  newRoute: 'New route',
2120
2687
  routesCaption: 'Client routes',
@@ -2133,6 +2700,19 @@ const SPINE_MESSAGES_EN = {
2133
2700
  pathPlaceholder: 'e.g.: dashboard',
2134
2701
  pathHint: 'A single piece of the address: lowercase letters and hyphens. The router adds the "/" between levels.',
2135
2702
  pathInvalid: 'Use only lowercase letters (a–z) and hyphens — no "/", spaces, digits or accents, and no hyphen at the start or end.',
2703
+ rotateSecret: 'Generate new secret',
2704
+ confirmRotateSecretHeading: 'Generate a new secret?',
2705
+ confirmRotateSecret: 'The current secret stops working right away; update the APP_SECRET of the service that uses this application. Tokens already issued stay valid until they expire (up to 1 hour).',
2706
+ rotatedSecretHeading: 'New secret for {name}',
2707
+ rotatedSecretWarning: "Copy it now: the secret won't be shown again.",
2708
+ rotatedSecretNote: 'The server keeps only a hash of the secret. Once this window is closed there is no way to look it up; if it gets lost, generate another one.',
2709
+ rotatedSecretLabel: 'Secret of application {name}',
2710
+ copySecret: 'Copy',
2711
+ secretCopied: 'Secret copied.',
2712
+ rotatedSecretDone: 'Done',
2713
+ rotateSecretError: "Couldn't generate the new secret. Try again.",
2714
+ rotateSecretNotFound: 'Application not found in this client.',
2715
+ rotateSecretForbidden: "You don't manage this client.",
2136
2716
  confirmDeleteAppHeading: 'Delete application?',
2137
2717
  confirmDeleteApp: 'The application {name} will be deleted and its credential will stop working. This cannot be undone.',
2138
2718
  confirmDeleteRouteHeading: 'Delete route?',
@@ -2167,6 +2747,95 @@ const SPINE_MESSAGES_EN = {
2167
2747
  reorderLoadError: 'Could not load the menu. Try again.',
2168
2748
  reorderSaveError: 'Could not save the order. Nothing was lost: try again.',
2169
2749
  reorderSave: 'Save order',
2750
+ newGroup: 'New group',
2751
+ editGroup: 'Edit group',
2752
+ groupsCaption: 'Project groups',
2753
+ noGroups: 'No groups yet.',
2754
+ accessLevel: 'Access level',
2755
+ pickAccessLevel: 'Select a level',
2756
+ noLevel: 'No level',
2757
+ noGroupsOfLevel: 'No groups at this level.',
2758
+ colStatus: 'Status',
2759
+ groupName: 'Name',
2760
+ groupType: 'Type',
2761
+ groupTypePlaceholder: 'Select the type',
2762
+ groupDescription: 'Description (optional)',
2763
+ openGroup: 'Open the {name} group',
2764
+ adminGroupTag: 'Administration',
2765
+ adminGroupNote: "The project's administration group: its members manage this app in self-service. It is the only one in the project and can't be deleted, renamed, disabled or change type, nor be left without members.",
2766
+ adminGroupEditNote: "The project's administration group: only the description can change.",
2767
+ adminGroupLastMemberWarning: "The administration group can't be left without members: add someone else or undo a removal.",
2768
+ confirmDeleteGroupHeading: 'Delete group?',
2769
+ confirmDeleteGroup: 'The {name} group will be deleted along with its members, screen access and application permissions. This cannot be undone.',
2770
+ groupSaveError: 'Could not save the group. Try again.',
2771
+ membersSaveError: 'Could not save all member changes. Check the list and try again.',
2772
+ appsCaption: 'Project applications',
2773
+ noApps: 'No applications registered yet.',
2774
+ colGrants: 'Grants',
2775
+ openApp: 'Open the application {name}',
2776
+ tabTokens: 'Tokens',
2777
+ tokensCaption: 'Application tokens',
2778
+ noTokens: 'No tokens created for this application yet.',
2779
+ newToken: 'New token',
2780
+ colTokenPrefix: 'Token prefix',
2781
+ colExpiresAt: 'Expires',
2782
+ colLastUsed: 'Last used',
2783
+ tokenExpired: 'Expired',
2784
+ tokenRevoked: 'Revoked',
2785
+ neverUsed: 'Never',
2786
+ revokeToken: 'Revoke',
2787
+ confirmRevokeTokenHeading: 'Revoke token?',
2788
+ confirmRevokeToken: 'The token {name} stops working right away. Revoking cannot be undone.',
2789
+ tokenRevokedDone: 'Token {name} revoked.',
2790
+ tokenRevokeError: 'Could not revoke the token. Try again.',
2791
+ tokensLoadError: 'Could not load the application tokens.',
2792
+ tokenName: 'Name',
2793
+ tokenDescription: 'Description (optional)',
2794
+ tokenExpiresAt: 'Expires on',
2795
+ tokenExpiresHint: 'From tomorrow up to 365 days from today.',
2796
+ tokenExpiresInvalid: 'Pick a date between tomorrow and 365 days from today.',
2797
+ tokenOperations: 'Operations granted to the token',
2798
+ tokenOperationsNote: 'Nothing comes checked: a token has no login, so check the protected and private operations it may call. Public ones need no token.',
2799
+ freeForAll: 'open to everyone',
2800
+ freeForAuthenticated: 'open to authenticated callers',
2801
+ freeNoPermission: 'does not depend on permission',
2802
+ publicNoToken: 'public: no token needed',
2803
+ createToken: 'Create token',
2804
+ tokenSaveError: 'Could not create the token. Try again.',
2805
+ tokenCreatedHeading: 'Token {name} created',
2806
+ tokenCreatedWarning: 'Copy it now: the token will not be shown again.',
2807
+ tokenCreatedNote: 'Send it in the x-api-token header. The server keeps only a hash of the token; if it is lost, revoke it and create another.',
2808
+ tokenValueLabel: 'Token {name}',
2809
+ copyToken: 'Copy',
2810
+ tokenCopied: 'Token copied.',
2811
+ hierarchyHint: 'Top-down: Navigation only with a Header, Menu only with a Navigation, Item only with a Menu and Submenu only with an Item. Clearing a level clears the ones below.',
2812
+ routerSaveError: 'Could not save the router. Try again.',
2813
+ permissionsSaveError: 'Could not save all operation changes. Reopen the panel and check.',
2814
+ tokenErrors: {
2815
+ invalidExpiresAt: 'Invalid expiration date. Pick another date.',
2816
+ invalidValidity: 'The expiration must be between tomorrow and 365 days from today.',
2817
+ invalidOperationIds: 'Some selected operation no longer exists in this application. Reopen the form and choose again.',
2818
+ operationNotProtected: 'Only protected operations can be granted to a group; public and private ones already apply to it.',
2819
+ operationPublic: 'A public operation needs no token and cannot be granted to one. Uncheck it and try again.',
2820
+ appNotFound: 'Application not found in this client. It may have been deleted.',
2821
+ tokenNotFound: 'Token not found in this application. It may have been revoked already.',
2822
+ },
2823
+ routerErrors: {
2824
+ hierarchyGap: 'The hierarchy has an empty level in the middle: each level needs the one above it (Header → Navigation → Menu → Item → Submenu). Fill in the missing level or clear the ones below.',
2825
+ invalidAllowedTypes: 'A chosen badge no longer exists. Review the allowed badges.',
2826
+ },
2827
+ groupErrors: {
2828
+ invalidType: 'That group type no longer exists. Choose another type.',
2829
+ adminTypeReserved: "The Administrator type belongs only to the project's administration group.",
2830
+ emptyUpdate: 'Nothing to save: no field was changed.',
2831
+ groupProjectImmutable: "A group can't move to another project.",
2832
+ groupNameTaken: 'A group with this name already exists in this project.',
2833
+ adminGroupImmutable: "The administration group can't be renamed, change type or be disabled.",
2834
+ adminGroupProtected: "The project's administration group can't be deleted.",
2835
+ adminGroupLastMember: "The administration group can't be left without members.",
2836
+ adminGroupAccessProtected: "The administration group's access to this app can't be removed.",
2837
+ groupNotFound: 'Group not found in this project. It may have been deleted.',
2838
+ },
2170
2839
  },
2171
2840
  autoServiceAudit: {
2172
2841
  title: 'Audit',
@@ -2336,6 +3005,132 @@ const SPINE_MESSAGES_EN = {
2336
3005
  spansSummary: '{count} spans in {duration}',
2337
3006
  spanRow: '{app}, {operation}: {outcome}, starts at +{offset}, takes {duration}',
2338
3007
  },
3008
+ loginAttempts: {
3009
+ refresh: 'Refresh',
3010
+ user: 'User',
3011
+ userPlaceholder: 'User, e-mail or typed identifier',
3012
+ period: 'Period',
3013
+ result: 'Result',
3014
+ method: 'Method',
3015
+ all: 'All',
3016
+ clear: 'Clear',
3017
+ advancedFilters: 'Advanced filters',
3018
+ ip: 'IP or network',
3019
+ ipPlaceholder: '10.0.0.5 or 10.0.0.0/8',
3020
+ ipHint: 'Exact IP or a network in CIDR notation.',
3021
+ reason: 'Reason',
3022
+ caption: 'Login attempts',
3023
+ pageItems: 'attempts',
3024
+ pageSize: 'Attempts per page',
3025
+ colDate: 'Date/time',
3026
+ colResult: 'Result',
3027
+ colMethod: 'Method',
3028
+ colUser: 'User',
3029
+ colReason: 'Reason',
3030
+ colIp: 'IP',
3031
+ colRealm: 'Realm',
3032
+ colClient: 'Client',
3033
+ colUserAgent: 'Browser',
3034
+ filterByIp: 'Filter by this IP',
3035
+ filterByUser: 'Filter by this user',
3036
+ noMatchingUser: 'no matching user',
3037
+ methods: { local: 'Password', mfa: 'MFA', github: 'GitHub', google: 'Google', keycloak: 'Keycloak', entraid: 'Entra ID' },
3038
+ results: { success: 'Success', failure: 'Failure', cancelled: 'Cancelled' },
3039
+ reasons: {
3040
+ bad_password: 'Wrong password',
3041
+ unknown_user: 'Unknown or inactive user',
3042
+ locked: 'Account locked after failed attempts',
3043
+ mfa_failed: 'Wrong verification code',
3044
+ mfa_expired: 'Expired verification code',
3045
+ mfa_not_allowed: 'Verification method not allowed',
3046
+ mfa_invalid_method: 'Unknown verification method',
3047
+ recovery_failed: 'Recovery code wrong or already used',
3048
+ recovery_refused: 'Recovery code refused',
3049
+ not_linked: 'External account not linked',
3050
+ wrong_realm: 'User from another realm',
3051
+ },
3052
+ forbiddenAdmin: 'Your account cannot read login attempts. Ask an administrator for the admin:attempts:list operation.',
3053
+ forbiddenClient: 'Only the project administration group, with the Administrator badge active, sees the login attempts of this application.',
3054
+ loadErrorDetail: 'Could not load the attempts: {detail}',
3055
+ loadError: 'Could not load the login attempts.',
3056
+ },
3057
+ accountFeatures: {
3058
+ aiAgent: 'Connect an AI agent',
3059
+ aiAgentDescription: 'Generates the token and the MCP configuration to paste into Claude Code.',
3060
+ aiAgentKeywords: 'ai agent claude mcp assistant llm automation',
3061
+ aiAgentHelp: 'The agent uses a personal application token (x-app-token) with the permissions of the chosen access level. Revoke it under "Application tokens" when you no longer need it.',
3062
+ aiAgentStart: 'Connect agent',
3063
+ aiAgentServers: 'MCP servers',
3064
+ aiAgentAccess: 'Agent access',
3065
+ aiAgentReadOnly: 'Read only',
3066
+ aiAgentReadWrite: 'Read and write',
3067
+ aiAgentWriteHint: 'The agent will be able to change data on your behalf. Prefer read only when it is enough.',
3068
+ aiAgentReadOnlyServer: '{name} (read only)',
3069
+ aiAgentTokenName: 'AI agent ({date})',
3070
+ aiAgentGenerate: 'Generate configuration',
3071
+ aiAgentSelectServer: 'Choose at least one server.',
3072
+ aiAgentResult: 'Configuration ready',
3073
+ aiAgentResultHint: 'Paste into .mcp.json at the project root (Claude Code). The token is shown only now: copy it before finishing. Valid until {date}.',
3074
+ aiAgentCopy: 'Copy configuration',
3075
+ aiAgentCopied: 'Configuration copied. Do not share it: it contains your token.',
3076
+ aiAgentDone: 'Finish',
3077
+ aiAgentCreateError: 'Could not generate the agent token.',
3078
+ aiAgentUnavailable: 'Unavailable in this application: it is not connected to argos-admin.',
3079
+ activity: 'Account activity',
3080
+ activityDescription: 'Sign-ins and failed attempts in the last 90 days.',
3081
+ activityKeywords: 'login sign-in access history attempt failure ip browser security',
3082
+ activityFilter: 'Show',
3083
+ activityAll: 'All',
3084
+ activityFailures: 'Failures only',
3085
+ activityDetail: '{date} · IP {ip}',
3086
+ activityApp: 'Application: {app}',
3087
+ activityEmpty: 'Nothing in the last 90 days.',
3088
+ activityMore: 'Load more',
3089
+ activityShowing: '{shown} of {total}',
3090
+ activityLoadError: 'Could not load the account activity.',
3091
+ unknownIp: 'unknown',
3092
+ unknownApp: 'unnamed',
3093
+ idle: 'Sign out when idle',
3094
+ idleDescription: 'Signs out by itself after a while without use, with a warning first.',
3095
+ idleKeywords: 'idle inactivity timeout sign out automatic logout security lock',
3096
+ idleNever: 'Never',
3097
+ idleOption: '{minutes} min',
3098
+ idleLimitHint: 'This application signs out after at most {minutes} min without use.',
3099
+ idleWarningHeading: 'Your session is about to end',
3100
+ idleWarningText: { one: 'No activity for a while. The session ends in {count} second.', other: 'No activity for a while. The session ends in {count} seconds.' },
3101
+ idleStay: 'Stay signed in',
3102
+ idleLogout: 'Sign out now',
3103
+ startPage: 'Start page',
3104
+ startPageDescription: 'Where the application opens after signing in.',
3105
+ startPageKeywords: 'home opening login favorite last page',
3106
+ startHome: 'Home',
3107
+ startLast: 'Last visited page',
3108
+ startFavorite: 'Favorite: {name}',
3109
+ startPageHint: 'Mark pages as favorites to open on one of them.',
3110
+ defaultBadge: 'Default badge',
3111
+ defaultBadgeDescription: 'Access level you sign in with in this application, in any browser.',
3112
+ defaultBadgeKeywords: 'badge access level profile default login',
3113
+ defaultBadgeAuto: 'Automatic (the lowest level)',
3114
+ defaultBadgeSaved: 'Default badge saved. It applies from the next sign-in.',
3115
+ defaultBadgeError: 'Could not save the default badge.',
3116
+ defaultBadgeLoadError: 'Could not load your badges.',
3117
+ notices: 'Notifications',
3118
+ noticesDescription: 'The notices this application shows.',
3119
+ noticesKeywords: 'notification notice alert browser push',
3120
+ browserNotices: 'Browser notifications',
3121
+ browserNoticesDescription: 'System notification when the tab is in the background.',
3122
+ browserNoticesDenied: 'The browser blocked notifications for this site. Allow them in the browser settings.',
3123
+ browserNoticesUnsupported: 'This browser does not show notifications.',
3124
+ noticeOpen: 'Open',
3125
+ sync: 'Preferences in your account',
3126
+ syncDescription: 'Appearance, region, favorites and the other preferences follow you in any browser.',
3127
+ syncKeywords: 'sync cloud account browser preferences backup',
3128
+ syncSynced: 'Synced at {time}',
3129
+ syncSaving: 'Saving…',
3130
+ syncLocal: 'Only in this browser: the account is not available.',
3131
+ syncError: 'Could not sync. Try again.',
3132
+ syncNow: 'Sync now',
3133
+ },
2339
3134
  };
2340
3135
 
2341
3136
  const SPINE_MESSAGES_ES = {
@@ -2460,7 +3255,7 @@ const SPINE_MESSAGES_ES = {
2460
3255
  appearance: 'Apariencia',
2461
3256
  appearanceKeywords: 'visual colores tema diseño',
2462
3257
  theme: 'Tema',
2463
- themeDescription: 'Claro, oscuro o siguiendo el sistema operativo.',
3258
+ themeDescription: 'Claro, oscuro o el del sistema operativo.',
2464
3259
  themeKeywords: 'modo oscuro dark light nocturno',
2465
3260
  themeLight: 'Claro',
2466
3261
  themeDark: 'Oscuro',
@@ -2469,7 +3264,7 @@ const SPINE_MESSAGES_ES = {
2469
3264
  accentDescription: 'Familia de colores del encabezado, los botones y los destacados.',
2470
3265
  accentKeywords: 'familia paleta color acento',
2471
3266
  density: 'Densidad',
2472
- densityDescription: 'Compacta acerca filas y controles para que quepa más información en la pantalla.',
3267
+ densityDescription: 'Compacta muestra más información en pantalla.',
2473
3268
  densityKeywords: 'espaciado tamaño compacto',
2474
3269
  comfortable: 'Cómoda',
2475
3270
  compact: 'Compacta',
@@ -2477,64 +3272,121 @@ const SPINE_MESSAGES_ES = {
2477
3272
  fontDescription: 'Tipografía de toda la aplicación.',
2478
3273
  fontKeywords: 'tipografía letra texto roboto rubik figtree geist',
2479
3274
  sidebar: 'Menú lateral',
2480
- sidebarDescription: 'Automático se abre al acercar el puntero; fijo queda siempre abierto; compacto muestra solo los íconos.',
3275
+ sidebarDescription: 'Cómo se abre y se cierra el menú lateral.',
2481
3276
  sidebarKeywords: 'sidebar navegación barra lateral',
3277
+ sidebarHelp: 'Automático: se abre al acercar el puntero. Fijo: siempre abierto. Compacto: muestra solo los íconos.',
2482
3278
  sidebarAuto: 'Automático',
2483
3279
  sidebarFixed: 'Fijo',
2484
3280
  sidebarCompact: 'Compacto',
2485
3281
  region: 'Región',
2486
- regionKeywords: 'idioma localización horario fecha',
3282
+ regionKeywords: 'idioma localización horario fecha hora semana',
2487
3283
  language: 'Idioma',
2488
- languageDescription: 'Idioma de los textos del sistema. Se aplica al instante, sin recargar.',
3284
+ languageDescription: 'Se aplica al instante, sin recargar.',
2489
3285
  languageKeywords: 'idioma lengua traducción inglés español portugués',
2490
3286
  languageAuto: 'Predeterminado del sistema ({language})',
2491
3287
  timeZone: 'Zona horaria',
2492
3288
  timeZoneKeywords: 'timezone hora utc gmt',
2493
- timeZoneHint: 'Se usa para mostrar fechas y horas. Ahora: {now}',
3289
+ timeZoneHint: 'Para fechas y horas. Ahora: {now}',
2494
3290
  timeZoneAuto: 'Automático ({zone})',
3291
+ hourCycle: 'Formato de hora',
3292
+ hourCycleHint: 'Ahora: {now}',
3293
+ hourCycleKeywords: 'reloj 24h 12h am pm horario',
3294
+ hourCycleAuto: 'Automático',
3295
+ hourCycle24: '24 horas',
3296
+ hourCycle12: '12 horas',
3297
+ weekStart: 'Primer día de la semana',
3298
+ weekStartDescription: 'Para calendarios y selectores de fecha.',
3299
+ weekStartKeywords: 'semana calendario domingo lunes',
3300
+ weekStartAuto: 'Automático',
2495
3301
  shortcuts: 'Atajos',
2496
3302
  shortcutsDescription: 'Pulsa ? en cualquier pantalla para ver esta lista.',
2497
- shortcutsKeywords: 'teclado teclas recorrido ayuda',
3303
+ shortcutsKeywords: 'teclado teclas',
2498
3304
  tour: 'Recorrido guiado',
2499
- tourDescription: 'Muestra de nuevo, paso a paso, dónde está cada parte de la pantalla.',
3305
+ tourDescription: 'Muestra de nuevo dónde está cada parte de la pantalla.',
2500
3306
  tourKeywords: 'onboarding ayuda presentación',
2501
3307
  replayTour: 'Repetir recorrido',
3308
+ accessibility: 'Accesibilidad',
3309
+ accessibilityKeywords: 'accesibilidad lectura visión',
3310
+ textSize: 'Tamaño del texto',
3311
+ textSizeDescription: 'Agranda el texto y los controles de todas las pantallas.',
3312
+ textSizeKeywords: 'zoom fuente letra más grande ampliar',
3313
+ motion: 'Reducir animaciones',
3314
+ motionDescription: 'Sistema sigue la computadora o el celular.',
3315
+ motionKeywords: 'animación movimiento transición reducido mareo',
3316
+ motionSystem: 'Sistema',
3317
+ motionReduce: 'Siempre',
3318
+ motionFull: 'Nunca',
3319
+ help: 'Ayuda',
3320
+ helpKeywords: 'ayuda novedades recorrido presentación',
3321
+ news: 'Novedades',
3322
+ newsDescription: 'Lo que cambió en las últimas versiones.',
3323
+ newsKeywords: 'novedades versión lanzamiento changelog',
3324
+ showNews: 'Ver novedades',
3325
+ browserData: 'Datos del navegador',
3326
+ browserDataDescription: 'Guardados solo en este navegador, para tu cuenta.',
3327
+ browserDataKeywords: 'privacidad limpiar borrar historial datos',
3328
+ recentPages: 'Páginas recientes',
3329
+ recentPagesKeywords: 'historial visitadas recientes',
3330
+ recentSearches: 'Búsquedas recientes',
3331
+ recentSearchesKeywords: 'búsqueda historial',
3332
+ favorites: 'Favoritos',
3333
+ favoritesKeywords: 'favoritos estrella',
3334
+ pagesCount: { zero: 'Ninguna página guardada', one: '{count} página guardada', other: '{count} páginas guardadas' },
3335
+ searchesCount: { zero: 'Ninguna búsqueda guardada', one: '{count} búsqueda guardada', other: '{count} búsquedas guardadas' },
3336
+ clear: 'Limpiar',
3337
+ confirmClear: 'Confirmar',
3338
+ cancel: 'Cancelar',
3339
+ recentPagesCleared: 'Páginas recientes borradas.',
3340
+ recentSearchesCleared: 'Búsquedas recientes borradas.',
3341
+ favoritesCleared: 'Favoritos borrados.',
3342
+ restorePreferences: 'Preferencias',
3343
+ restorePreferencesDescription: 'Apariencia, accesibilidad y formatos vuelven a los valores predeterminados.',
3344
+ restorePreferencesHelp: 'Vuelven a lo predeterminado: tema, color principal, densidad, fuente, menú lateral, tamaño del texto, animaciones, formato de hora y primer día de la semana. El idioma, la zona horaria y los atajos de una sola tecla se mantienen.',
3345
+ restorePreferencesKeywords: 'restaurar predeterminado restablecer reset preferencias',
3346
+ restore: 'Restaurar',
3347
+ confirmRestore: 'Confirmar',
3348
+ preferencesRestored: 'Preferencias restauradas.',
2502
3349
  account: 'Cuenta',
2503
- accountKeywords: 'perfil usuario sesión login',
3350
+ accountKeywords: 'perfil usuario login nivel salir',
2504
3351
  user: 'Usuario',
2505
3352
  accessLevel: 'Nivel de acceso',
2506
3353
  accessLevelKeywords: 'credencial badge perfil permiso grupo',
2507
3354
  session: 'Sesión',
2508
- sessionDescription: 'La sesión se renueva sola mientras la aplicación esté abierta.',
3355
+ sessionDescription: 'Se renueva sola mientras la aplicación esté abierta.',
2509
3356
  sessionKeywords: 'expiración token login',
2510
3357
  sessionUnknown: 'No informado',
2511
3358
  sessionExpired: 'Expiró a las {time}',
2512
3359
  sessionExpiresIn: 'Expira en {minutes} min (a las {time})',
2513
3360
  sessionExpiresAt: 'Expira a las {time}',
2514
3361
  accessToken: 'Token de acceso',
2515
- accessTokenDescription: 'Para probar una API en tu nombre. Da acceso a tu cuenta hasta que expire: nunca lo compartas.',
3362
+ accessTokenDescription: 'Para probar una API en tu nombre. No lo compartas.',
2516
3363
  accessTokenKeywords: 'jwt bearer api desarrollador',
2517
3364
  copy: 'Copiar',
3365
+ moreAbout: 'Más sobre {label}',
2518
3366
  appTokens: 'Tokens de aplicación',
2519
- appTokensDescription: 'Para usar esta aplicación fuera de la interfaz en tu nombre — un MCP, un script. El token vale solo para esta aplicación, con los permisos del nivel de acceso elegido, y expira en un máximo de 1 año. Envíalo en el encabezado x-app-token.',
3367
+ appTokensDescription: 'Para usar la aplicación fuera de la interfaz: un MCP, un script.',
2520
3368
  appTokensKeywords: 'token app mcp api clave pat x-app-token automatización script integración',
3369
+ appTokensHelp: 'El token vale solo para esta aplicación, con los permisos del nivel de acceso elegido, y vence en como máximo 1 año. Envíalo en el encabezado x-app-token.',
2521
3370
  logout: 'Salir',
2522
3371
  logoutDescription: 'Cierra la sesión en este navegador.',
2523
3372
  logoutKeywords: 'logout desconectar',
2524
- security: 'Cuenta y seguridad',
2525
- securityDescription: 'Verificación en dos pasos, códigos de recuperación y sesiones de tu cuenta de Argos, el inicio de sesión de las aplicaciones.',
2526
- securityKeywords: 'seguridad argos contraseña autenticación mfa 2fa dispositivos',
3373
+ mfa: 'MFA',
3374
+ mfaDescription: 'Verificación en dos pasos de tu cuenta en Argos.',
3375
+ mfaKeywords: 'seguridad argos contraseña autenticación mfa 2fa dos factores dos pasos verificación',
2527
3376
  signedInAccount: 'Cuenta de Argos',
2528
3377
  signedInRealm: 'Realm {realm}',
2529
3378
  twoFactor: 'Verificación en dos pasos',
2530
- twoFactorDescription: 'Agrega o quita métodos (aplicación de autenticación o correo electrónico) y cambia el dispositivo del autenticador.',
3379
+ twoFactorDescription: 'Un código además de la contraseña para entrar.',
2531
3380
  twoFactorKeywords: 'mfa 2fa totp autenticador authenticator código correo método dispositivo celular',
2532
3381
  recoveryCodes: 'Códigos de recuperación',
2533
- recoveryCodesDescription: 'Sirven para iniciar sesión si pierdes el dispositivo. Generar códigos nuevos invalida los anteriores.',
3382
+ recoveryCodesDescription: 'Para entrar si pierdes el dispositivo.',
2534
3383
  recoveryCodesKeywords: 'respaldo recuperación perdí celular dispositivo mfa 2fa',
2535
3384
  sessions: 'Sesiones y dispositivos',
2536
- sessionsDescription: 'Ve dónde está conectada tu cuenta (navegador, sistema, IP, último uso) y cierra una sesión o todas las demás.',
3385
+ sessionsDescription: 'Dónde está conectada tu cuenta.',
2537
3386
  sessionsKeywords: 'dispositivos navegador ip cerrar desconectar salir conectados',
3387
+ sessionsSection: 'Sesiones',
3388
+ sessionsSectionDescription: 'Esta sesión, los tokens de aplicación y los dispositivos conectados.',
3389
+ sessionsSectionKeywords: 'sesión sesiones token tokens dispositivos login seguridad',
2538
3390
  about: 'Acerca de',
2539
3391
  aboutKeywords: 'versión build entorno dependencias soporte',
2540
3392
  application: 'Aplicación',
@@ -2552,7 +3404,7 @@ const SPINE_MESSAGES_ES = {
2552
3404
  dependenciesDescription: 'Versiones declaradas en el package.json de esta aplicación.',
2553
3405
  dependenciesKeywords: 'angular hermes rxjs apollo graphql',
2554
3406
  supportInfo: 'Información para soporte',
2555
- supportInfoDescription: 'Copia versión, entorno, navegador, página y preferencias — pégala en el ticket.',
3407
+ supportInfoDescription: 'Versión, entorno, navegador y preferencias, para pegar en el ticket.',
2556
3408
  supportInfoKeywords: 'ayuda ticket diagnóstico',
2557
3409
  noSessionToken: 'No hay ningún token de sesión disponible.',
2558
3410
  tokenCopied: 'Token copiado. No lo compartas: da acceso a tu cuenta hasta que expire.',
@@ -2634,7 +3486,7 @@ const SPINE_MESSAGES_ES = {
2634
3486
  removeHint: 'Ya no podrás usar este método para iniciar sesión.',
2635
3487
  removeLastHint: 'Sin ningún método, los códigos de recuperación también dejan de valer.',
2636
3488
  reauthTitle: 'Confirma tu identidad',
2637
- reauthHint: 'Para cambiar la verificación en dos pasos o generar códigos, demuestra de nuevo quién eres. Vale por 5 minutos.',
3489
+ reauthHint: 'Por seguridad, confirma antes de continuar. Vale por 5 minutos.',
2638
3490
  reauthHow: 'Cómo confirmar',
2639
3491
  reauthPassword: 'Contraseña',
2640
3492
  reauthRecoveryCode: 'Código de recuperación',
@@ -2658,13 +3510,13 @@ const SPINE_MESSAGES_ES = {
2658
3510
  sendCode: 'Enviar código',
2659
3511
  emailCodeTitle: 'Confirma el correo electrónico',
2660
3512
  emailCodeHint: 'Escribe el código de 6 dígitos enviado a {email}.',
2661
- codesRemaining: { zero: 'Ningún código disponible.', one: '1 código disponible.', other: '{count} códigos disponibles.' },
2662
- codesHint: 'Cada uno vale una vez para iniciar sesión si pierdes tu método de verificación.',
3513
+ codesRemaining: { zero: 'Ningún código disponible', one: '1 código disponible', other: '{count} códigos disponibles' },
3514
+ codesHint: 'Cada uno vale una vez. Generar nuevos invalida los anteriores.',
2663
3515
  codesNeedMethod: 'Registra un método de verificación para generar códigos de recuperación.',
2664
3516
  generateCodes: 'Generar códigos',
2665
3517
  regenerateCodes: 'Generar códigos nuevos',
2666
3518
  codesTitle: 'Tus códigos de recuperación',
2667
- codesShownOnce: 'Guarda estos códigos en un lugar seguro. Cada uno vale una sola vez para iniciar sesión si pierdes el acceso a tu método de verificación. No se volverán a mostrar.',
3519
+ codesShownOnce: 'Guárdalos en un lugar seguro: no se volverán a mostrar. Cada uno vale una vez.',
2668
3520
  copyCodes: 'Copiar',
2669
3521
  downloadCodes: 'Descargar .txt',
2670
3522
  codesCopied: 'Códigos copiados.',
@@ -2789,10 +3641,10 @@ const SPINE_MESSAGES_ES = {
2789
3641
  tabRoutes: 'Rutas',
2790
3642
  tabRouters: 'Enrutadores',
2791
3643
  tabAccess: 'Acceso',
2792
- noClients: 'Todavía no administras ningún client aquí. Necesitas (a) tener acceso concedido a un client y (b) haber iniciado sesión con la credencial <strong>Administrador</strong> activa. Cambia de credencial en el menú de tu perfil, si está disponible, o pide acceso a un administrador central.',
3644
+ noClients: 'No administras esta app. Necesitas (a) estar en el grupo de administración de su proyecto y (b) haber iniciado sesión con la credencial <strong>Administrador</strong> activa. Cambia de credencial en el menú de tu perfil, si está disponible, o pide acceso a quien administra el proyecto.',
2793
3645
  pickClient: 'Selecciona el client que quieres administrar',
2794
3646
  groupsTitle: 'Grupos',
2795
- groupsIntro: 'Gestiona los miembros de los grupos de este client — sin depender del panel central.',
3647
+ groupsIntro: 'Crea, edita y elimina los grupos de este client y gestiona sus miembros — sin depender del panel central.',
2796
3648
  groupsFixed: 'Los 5 grupos de abajo son fijos de este client y no se pueden renombrar, crear ni eliminar — consulta uno para gestionar sus miembros.',
2797
3649
  pickGroup: 'Selecciona un grupo',
2798
3650
  noDescription: 'Sin descripción registrada.',
@@ -2878,6 +3730,9 @@ const SPINE_MESSAGES_ES = {
2878
3730
  colPage: 'Página',
2879
3731
  colBuiltPath: 'Ruta armada',
2880
3732
  colVisibility: 'Visibilidad',
3733
+ colAllowedBadges: 'Credenciales permitidas',
3734
+ anyBadge: 'Cualquier credencial',
3735
+ allowedBadgesHint: 'Vacío = cualquier credencial. Con una o más elegidas, solo los grupos con una de esas credenciales pueden recibir acceso a esta pantalla en la pestaña Acceso.',
2881
3736
  routesTitle: 'Rutas',
2882
3737
  newRoute: 'Nueva ruta',
2883
3738
  routesCaption: 'Rutas del client',
@@ -2896,6 +3751,19 @@ const SPINE_MESSAGES_ES = {
2896
3751
  pathPlaceholder: 'ej.: dashboard',
2897
3752
  pathHint: 'Un solo tramo de la dirección: letras minúsculas y guion. El "/" entre niveles lo arma el enrutador.',
2898
3753
  pathInvalid: 'Usa solo letras minúsculas (a–z) y guion — sin "/", espacios, números ni acentos, y sin guion al inicio o al final.',
3754
+ rotateSecret: 'Generar nuevo secreto',
3755
+ confirmRotateSecretHeading: '¿Generar un nuevo secreto?',
3756
+ confirmRotateSecret: 'El secreto actual deja de funcionar de inmediato; actualice el APP_SECRET del servicio que usa esta aplicación. Los tokens ya emitidos valen hasta que expiren (hasta 1 hora).',
3757
+ rotatedSecretHeading: 'Nuevo secreto de {name}',
3758
+ rotatedSecretWarning: 'Cópielo ahora: el secreto no se volverá a mostrar.',
3759
+ rotatedSecretNote: 'El servidor guarda solo un hash del secreto. Al cerrar esta ventana no hay forma de consultarlo; si se pierde, genere otro.',
3760
+ rotatedSecretLabel: 'Secreto de la aplicación {name}',
3761
+ copySecret: 'Copiar',
3762
+ secretCopied: 'Secreto copiado.',
3763
+ rotatedSecretDone: 'Listo',
3764
+ rotateSecretError: 'No se pudo generar el nuevo secreto. Inténtelo de nuevo.',
3765
+ rotateSecretNotFound: 'Aplicación no encontrada en este client.',
3766
+ rotateSecretForbidden: 'Usted no administra este client.',
2899
3767
  confirmDeleteAppHeading: '¿Eliminar aplicación?',
2900
3768
  confirmDeleteApp: 'La aplicación {name} se eliminará y su credencial dejará de funcionar. Esta acción no se puede deshacer.',
2901
3769
  confirmDeleteRouteHeading: '¿Eliminar ruta?',
@@ -2930,6 +3798,95 @@ const SPINE_MESSAGES_ES = {
2930
3798
  reorderLoadError: 'No se pudo cargar el menú. Inténtelo de nuevo.',
2931
3799
  reorderSaveError: 'No se pudo guardar el orden. No se perdió nada: inténtelo de nuevo.',
2932
3800
  reorderSave: 'Guardar orden',
3801
+ newGroup: 'Nuevo grupo',
3802
+ editGroup: 'Editar grupo',
3803
+ groupsCaption: 'Grupos del proyecto',
3804
+ noGroups: 'Aún no hay grupos registrados.',
3805
+ accessLevel: 'Nivel de acceso',
3806
+ pickAccessLevel: 'Selecciona un nivel',
3807
+ noLevel: 'Sin nivel',
3808
+ noGroupsOfLevel: 'Ningún grupo en este nivel.',
3809
+ colStatus: 'Estado',
3810
+ groupName: 'Nombre',
3811
+ groupType: 'Tipo',
3812
+ groupTypePlaceholder: 'Selecciona el tipo',
3813
+ groupDescription: 'Descripción (opcional)',
3814
+ openGroup: 'Abrir el grupo {name}',
3815
+ adminGroupTag: 'Administración',
3816
+ adminGroupNote: 'Grupo de administración del proyecto: sus miembros administran esta app en el autoservicio. Es el único del proyecto, no se puede eliminar, renombrar, desactivar ni cambiar de tipo, y no puede quedarse sin miembros.',
3817
+ adminGroupEditNote: 'Grupo de administración del proyecto: solo se puede cambiar la descripción.',
3818
+ adminGroupLastMemberWarning: 'El grupo de administración no puede quedarse sin miembros: agrega a otra persona o deshaz una eliminación.',
3819
+ confirmDeleteGroupHeading: '¿Eliminar grupo?',
3820
+ confirmDeleteGroup: 'El grupo {name} se eliminará junto con sus miembros, los accesos a pantallas y los permisos en aplicaciones. Esta acción no se puede deshacer.',
3821
+ groupSaveError: 'No se pudo guardar el grupo. Inténtalo de nuevo.',
3822
+ membersSaveError: 'No se pudieron guardar todos los cambios de los miembros. Revisa la lista e inténtalo de nuevo.',
3823
+ appsCaption: 'Aplicaciones del proyecto',
3824
+ noApps: 'Ninguna aplicación registrada todavía.',
3825
+ colGrants: 'Grants',
3826
+ openApp: 'Abrir la aplicación {name}',
3827
+ tabTokens: 'Tokens',
3828
+ tokensCaption: 'Tokens de la aplicación',
3829
+ noTokens: 'Ningún token creado para esta aplicación todavía.',
3830
+ newToken: 'Nuevo token',
3831
+ colTokenPrefix: 'Inicio del token',
3832
+ colExpiresAt: 'Vence',
3833
+ colLastUsed: 'Último uso',
3834
+ tokenExpired: 'Vencido',
3835
+ tokenRevoked: 'Revocado',
3836
+ neverUsed: 'Nunca',
3837
+ revokeToken: 'Revocar',
3838
+ confirmRevokeTokenHeading: '¿Revocar token?',
3839
+ confirmRevokeToken: 'El token {name} deja de funcionar de inmediato. La revocación no se puede deshacer.',
3840
+ tokenRevokedDone: 'Token {name} revocado.',
3841
+ tokenRevokeError: 'No se pudo revocar el token. Inténtalo de nuevo.',
3842
+ tokensLoadError: 'No se pudieron cargar los tokens de la aplicación.',
3843
+ tokenName: 'Nombre',
3844
+ tokenDescription: 'Descripción (opcional)',
3845
+ tokenExpiresAt: 'Vence el',
3846
+ tokenExpiresHint: 'Desde mañana hasta 365 días a partir de hoy.',
3847
+ tokenExpiresInvalid: 'Elige una fecha entre mañana y 365 días a partir de hoy.',
3848
+ tokenOperations: 'Operaciones concedidas al token',
3849
+ tokenOperationsNote: 'Nada viene marcado: el token no tiene login, así que marca las operaciones protected y private que puede llamar. Las public no necesitan token.',
3850
+ freeForAll: 'abierta a todos',
3851
+ freeForAuthenticated: 'abierta a autenticados',
3852
+ freeNoPermission: 'no depende de permiso',
3853
+ publicNoToken: 'pública: no necesita token',
3854
+ createToken: 'Crear token',
3855
+ tokenSaveError: 'No se pudo crear el token. Inténtalo de nuevo.',
3856
+ tokenCreatedHeading: 'Token {name} creado',
3857
+ tokenCreatedWarning: 'Cópialo ahora: el token no se mostrará de nuevo.',
3858
+ tokenCreatedNote: 'Envíalo en el encabezado x-api-token. El servidor guarda solo un hash del token; si se pierde, revócalo y crea otro.',
3859
+ tokenValueLabel: 'Token {name}',
3860
+ copyToken: 'Copiar',
3861
+ tokenCopied: 'Token copiado.',
3862
+ hierarchyHint: 'De arriba hacia abajo: Navegación solo con Header, Menú solo con Navegación, Ítem solo con Menú y Submenú solo con Ítem. Borrar un nivel borra los de abajo.',
3863
+ routerSaveError: 'No se pudo guardar el enrutador. Inténtalo de nuevo.',
3864
+ permissionsSaveError: 'No se pudieron guardar todos los cambios de las operaciones. Vuelve a abrir el panel y revisa.',
3865
+ tokenErrors: {
3866
+ invalidExpiresAt: 'Fecha de vencimiento no válida. Elige otra fecha.',
3867
+ invalidValidity: 'El vencimiento debe estar entre mañana y 365 días a partir de hoy.',
3868
+ invalidOperationIds: 'Alguna operación elegida ya no existe en esta aplicación. Vuelve a abrir el formulario y elige de nuevo.',
3869
+ operationNotProtected: 'Solo las operaciones protected se pueden conceder a un grupo; las public y private ya valen para él.',
3870
+ operationPublic: 'Una operación pública no necesita token y no se le puede conceder. Desmárcala e inténtalo de nuevo.',
3871
+ appNotFound: 'Aplicación no encontrada en este client. Puede haber sido eliminada.',
3872
+ tokenNotFound: 'Token no encontrado en esta aplicación. Puede que ya haya sido revocado.',
3873
+ },
3874
+ routerErrors: {
3875
+ hierarchyGap: 'La jerarquía tiene un nivel vacío en el medio: cada nivel necesita el de arriba (Header → Navegación → Menú → Ítem → Submenú). Completa el nivel que falta o borra los de abajo.',
3876
+ invalidAllowedTypes: 'Alguna credencial elegida ya no existe. Revisa las credenciales permitidas.',
3877
+ },
3878
+ groupErrors: {
3879
+ invalidType: 'Ese tipo de grupo ya no existe. Elige otro tipo.',
3880
+ adminTypeReserved: 'El tipo Administrador es solo del grupo de administración del proyecto.',
3881
+ emptyUpdate: 'Nada que guardar: no se cambió ningún campo.',
3882
+ groupProjectImmutable: 'El grupo no cambia de proyecto.',
3883
+ groupNameTaken: 'Ya existe un grupo con ese nombre en este proyecto.',
3884
+ adminGroupImmutable: 'El grupo de administración no cambia de nombre ni de tipo, ni se desactiva.',
3885
+ adminGroupProtected: 'El grupo de administración del proyecto no se puede eliminar.',
3886
+ adminGroupLastMember: 'El grupo de administración no puede quedarse sin miembros.',
3887
+ adminGroupAccessProtected: 'El acceso del grupo de administración a esta app no se puede quitar.',
3888
+ groupNotFound: 'Grupo no encontrado en este proyecto. Puede que se haya eliminado.',
3889
+ },
2933
3890
  },
2934
3891
  autoServiceAudit: {
2935
3892
  title: 'Auditoría',
@@ -3099,6 +4056,132 @@ const SPINE_MESSAGES_ES = {
3099
4056
  spansSummary: '{count} spans en {duration}',
3100
4057
  spanRow: '{app}, {operation}: {outcome}, empieza en +{offset}, dura {duration}',
3101
4058
  },
4059
+ loginAttempts: {
4060
+ refresh: 'Actualizar',
4061
+ user: 'Usuario',
4062
+ userPlaceholder: 'Usuario, correo o identificador escrito',
4063
+ period: 'Período',
4064
+ result: 'Resultado',
4065
+ method: 'Método',
4066
+ all: 'Todos',
4067
+ clear: 'Limpiar',
4068
+ advancedFilters: 'Filtros avanzados',
4069
+ ip: 'IP o red',
4070
+ ipPlaceholder: '10.0.0.5 o 10.0.0.0/8',
4071
+ ipHint: 'IP exacta o red en notación CIDR.',
4072
+ reason: 'Motivo',
4073
+ caption: 'Intentos de inicio de sesión',
4074
+ pageItems: 'intentos',
4075
+ pageSize: 'Intentos por página',
4076
+ colDate: 'Fecha/hora',
4077
+ colResult: 'Resultado',
4078
+ colMethod: 'Método',
4079
+ colUser: 'Usuario',
4080
+ colReason: 'Motivo',
4081
+ colIp: 'IP',
4082
+ colRealm: 'Realm',
4083
+ colClient: 'Client',
4084
+ colUserAgent: 'Navegador',
4085
+ filterByIp: 'Filtrar por esta IP',
4086
+ filterByUser: 'Filtrar por este usuario',
4087
+ noMatchingUser: 'sin usuario correspondiente',
4088
+ methods: { local: 'Contraseña', mfa: 'MFA', github: 'GitHub', google: 'Google', keycloak: 'Keycloak', entraid: 'Entra ID' },
4089
+ results: { success: 'Éxito', failure: 'Fallo', cancelled: 'Cancelado' },
4090
+ reasons: {
4091
+ bad_password: 'Contraseña incorrecta',
4092
+ unknown_user: 'Usuario inexistente o inactivo',
4093
+ locked: 'Cuenta bloqueada por intentos',
4094
+ mfa_failed: 'Código de verificación incorrecto',
4095
+ mfa_expired: 'Código de verificación vencido',
4096
+ mfa_not_allowed: 'Método de verificación no permitido',
4097
+ mfa_invalid_method: 'Método de verificación desconocido',
4098
+ recovery_failed: 'Código de recuperación incorrecto o ya usado',
4099
+ recovery_refused: 'Código de recuperación rechazado',
4100
+ not_linked: 'Cuenta externa no vinculada',
4101
+ wrong_realm: 'Usuario de otro realm',
4102
+ },
4103
+ forbiddenAdmin: 'Tu cuenta no puede consultar los intentos de inicio de sesión. Pide a un administrador la operación admin:attempts:list.',
4104
+ forbiddenClient: 'Solo el grupo de administración del proyecto, con la insignia Administrador activa, ve los intentos de inicio de sesión de esta aplicación.',
4105
+ loadErrorDetail: 'No se pudieron cargar los intentos: {detail}',
4106
+ loadError: 'No se pudieron cargar los intentos de inicio de sesión.',
4107
+ },
4108
+ accountFeatures: {
4109
+ aiAgent: 'Conectar un agente de IA',
4110
+ aiAgentDescription: 'Genera el token y la configuración MCP para pegar en Claude Code.',
4111
+ aiAgentKeywords: 'ia agente claude mcp asistente llm automatización',
4112
+ aiAgentHelp: 'El agente usa un token de aplicación personal (x-app-token) con los permisos del nivel de acceso elegido. Revóquelo en "Tokens de aplicación" cuando ya no lo necesite.',
4113
+ aiAgentStart: 'Conectar agente',
4114
+ aiAgentServers: 'Servidores MCP',
4115
+ aiAgentAccess: 'Acceso del agente',
4116
+ aiAgentReadOnly: 'Solo lectura',
4117
+ aiAgentReadWrite: 'Lectura y escritura',
4118
+ aiAgentWriteHint: 'El agente podrá cambiar datos en su nombre. Prefiera solo lectura cuando sea suficiente.',
4119
+ aiAgentReadOnlyServer: '{name} (solo lectura)',
4120
+ aiAgentTokenName: 'Agente de IA ({date})',
4121
+ aiAgentGenerate: 'Generar configuración',
4122
+ aiAgentSelectServer: 'Elija al menos un servidor.',
4123
+ aiAgentResult: 'Configuración lista',
4124
+ aiAgentResultHint: 'Péguelo en .mcp.json, en la raíz del proyecto (Claude Code). El token aparece solo ahora: cópielo antes de terminar. Válido hasta {date}.',
4125
+ aiAgentCopy: 'Copiar configuración',
4126
+ aiAgentCopied: 'Configuración copiada. No la comparta: contiene su token.',
4127
+ aiAgentDone: 'Terminar',
4128
+ aiAgentCreateError: 'No fue posible generar el token del agente.',
4129
+ aiAgentUnavailable: 'No disponible en esta aplicación: no está conectada a argos-admin.',
4130
+ activity: 'Actividad de la cuenta',
4131
+ activityDescription: 'Inicios de sesión e intentos fallidos en los últimos 90 días.',
4132
+ activityKeywords: 'login acceso historial intento falla ip navegador seguridad',
4133
+ activityFilter: 'Mostrar',
4134
+ activityAll: 'Todas',
4135
+ activityFailures: 'Solo fallas',
4136
+ activityDetail: '{date} · IP {ip}',
4137
+ activityApp: 'Aplicación: {app}',
4138
+ activityEmpty: 'Nada en los últimos 90 días.',
4139
+ activityMore: 'Cargar más',
4140
+ activityShowing: '{shown} de {total}',
4141
+ activityLoadError: 'No fue posible cargar la actividad de la cuenta.',
4142
+ unknownIp: 'desconocido',
4143
+ unknownApp: 'sin nombre',
4144
+ idle: 'Cerrar por inactividad',
4145
+ idleDescription: 'Sale solo después de un tiempo sin uso, con un aviso antes.',
4146
+ idleKeywords: 'inactividad tiempo salir automático logout seguridad bloqueo',
4147
+ idleNever: 'Nunca',
4148
+ idleOption: '{minutes} min',
4149
+ idleLimitHint: 'Esta aplicación cierra la sesión en un máximo de {minutes} min sin uso.',
4150
+ idleWarningHeading: 'La sesión se va a cerrar',
4151
+ idleWarningText: { one: 'Sin actividad desde hace un tiempo. La sesión termina en {count} segundo.', other: 'Sin actividad desde hace un tiempo. La sesión termina en {count} segundos.' },
4152
+ idleStay: 'Seguir conectado',
4153
+ idleLogout: 'Salir ahora',
4154
+ startPage: 'Pantalla inicial',
4155
+ startPageDescription: 'Dónde se abre la aplicación después del inicio de sesión.',
4156
+ startPageKeywords: 'inicio apertura login favorito última pantalla',
4157
+ startHome: 'Inicio',
4158
+ startLast: 'Última pantalla visitada',
4159
+ startFavorite: 'Favorito: {name}',
4160
+ startPageHint: 'Marque pantallas como favoritas para abrir en una de ellas.',
4161
+ defaultBadge: 'Credencial predeterminada',
4162
+ defaultBadgeDescription: 'Nivel de acceso con el que entra en esta aplicación, en cualquier navegador.',
4163
+ defaultBadgeKeywords: 'credencial badge nivel acceso perfil predeterminado login',
4164
+ defaultBadgeAuto: 'Automático (el de menor nivel)',
4165
+ defaultBadgeSaved: 'Credencial predeterminada guardada. Vale desde el próximo inicio de sesión.',
4166
+ defaultBadgeError: 'No fue posible guardar la credencial predeterminada.',
4167
+ defaultBadgeLoadError: 'No fue posible cargar sus credenciales.',
4168
+ notices: 'Notificaciones',
4169
+ noticesDescription: 'Los avisos que muestra esta aplicación.',
4170
+ noticesKeywords: 'notificación aviso alerta navegador push',
4171
+ browserNotices: 'Avisos del navegador',
4172
+ browserNoticesDescription: 'Aviso del sistema cuando la pestaña está en segundo plano.',
4173
+ browserNoticesDenied: 'El navegador bloqueó las notificaciones de este sitio. Permítalas en la configuración del navegador.',
4174
+ browserNoticesUnsupported: 'Este navegador no muestra notificaciones.',
4175
+ noticeOpen: 'Abrir',
4176
+ sync: 'Preferencias en la cuenta',
4177
+ syncDescription: 'Apariencia, región, favoritos y las demás preferencias lo acompañan en cualquier navegador.',
4178
+ syncKeywords: 'sincronizar nube cuenta navegador preferencias respaldo',
4179
+ syncSynced: 'Sincronizado a las {time}',
4180
+ syncSaving: 'Guardando…',
4181
+ syncLocal: 'Solo en este navegador: la cuenta no está disponible.',
4182
+ syncError: 'No fue posible sincronizar. Inténtelo de nuevo.',
4183
+ syncNow: 'Sincronizar ahora',
4184
+ },
3102
4185
  };
3103
4186
 
3104
4187
  const SPINE_I18N_CONFIG = new InjectionToken('SPINE_I18N_CONFIG', {
@@ -3125,6 +4208,10 @@ function currentSpineMessages() {
3125
4208
  * Textos do spine no idioma ativo da lib (`HermesI18nService.locale`): uma troca de idioma com
3126
4209
  * `setLocale` atualiza a lib e o spine juntos, sem recarregar. Leitura igual à do `HERMES_MESSAGES`
3127
4210
  * (`inject(SPINE_MESSAGES).header.settings`), viva também em template.
4211
+ *
4212
+ * `meta.locale` é o de formatação da lib (`HermesI18nService.formatLocale`) quando a língua é a mesma: a
4213
+ * variante regional (`en-GB`) e o relógio/primeiro dia da semana escolhidos em Região valem também nas
4214
+ * datas e horas que o spine formata.
3128
4215
  */
3129
4216
  const SPINE_MESSAGES = new InjectionToken('SPINE_MESSAGES', {
3130
4217
  providedIn: 'root',
@@ -3133,8 +4220,11 @@ const SPINE_MESSAGES = new InjectionToken('SPINE_MESSAGES', {
3133
4220
  const config = inject(SPINE_I18N_CONFIG);
3134
4221
  const catalogs = { ...BUILT_IN, ...config.catalogs };
3135
4222
  const active = computed(() => {
3136
- const base = i18n.pick(catalogs, SPINE_MESSAGES_PT_BR);
3137
- return config.override ? mergeCatalog(config.override, base) : base;
4223
+ const picked = i18n.pick(catalogs, SPINE_MESSAGES_PT_BR);
4224
+ const base = config.override ? mergeCatalog(config.override, picked) : picked;
4225
+ const locale = i18n.formatLocale();
4226
+ const sameLanguage = locale.split('-')[0].toLowerCase() === base.meta.locale.split('-')[0].toLowerCase();
4227
+ return sameLanguage && locale !== base.meta.locale ? { ...base, meta: { ...base.meta, locale } } : base;
3138
4228
  }, ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
3139
4229
  current = liveCatalog(() => active());
3140
4230
  return current;
@@ -3149,5 +4239,5 @@ function provideSpineMessages(config) {
3149
4239
  * Generated bundle index. Do not edit.
3150
4240
  */
3151
4241
 
3152
- export { APP_PACKAGE_INFO, DensityService, HERMES_DS_IMPORTS, SPINE_ADMIN_API, SPINE_CONFIG, SPINE_DEFAULT_LOGO, SPINE_I18N_CONFIG, SPINE_MESSAGES, SPINE_MESSAGES_EN, SPINE_MESSAGES_ES, SPINE_MESSAGES_PT_BR, SPINE_NEWS, SPINE_USER_STORAGE_KEYS, SpinePreferencesService, SpineQuickAccessService, SpineUserStorage, ThemePresetService, ThemeService, applyRuntimeEnvironment, applySpineFavicon, currentSpineMessages, mergeRouteTree, provideSpineMessages, provideSpineNews, provideUi, resolveSpineLogo, routerIdOf, spineRoutesOf };
4242
+ export { APP_PACKAGE_INFO, DensityService, HERMES_DS_IMPORTS, SPINE_ADMIN_API, SPINE_CONFIG, SPINE_DEFAULT_LOGO, SPINE_I18N_CONFIG, SPINE_IDLE_OPTIONS, SPINE_MESSAGES, SPINE_MESSAGES_EN, SPINE_MESSAGES_ES, SPINE_MESSAGES_PT_BR, SPINE_NEWS, SPINE_USER_STORAGE_KEYS, SpinePreferencesService, SpineQuickAccessService, SpineUserStorage, ThemePresetService, ThemeService, applyRuntimeEnvironment, applySpineFavicon, currentSpineMessages, mergeRouteTree, provideSpineMessages, provideSpineNews, provideUi, resolveSpineLogo, routerIdOf, spineRoutesOf };
3153
4243
  //# sourceMappingURL=trismegisto-spine.mjs.map