myio-js-library 0.1.487 → 0.1.490

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.
package/dist/index.cjs CHANGED
@@ -711,6 +711,7 @@ __export(index_exports, {
711
711
  MenuShoppingView: () => MenuShoppingView,
712
712
  MenuView: () => MenuView,
713
713
  ModalHeader: () => ModalHeader,
714
+ MyIOAuthContext: () => MyIOAuthContext,
714
715
  MyIOChartModal: () => MyIOChartModal,
715
716
  MyIODraggableCard: () => MyIODraggableCard,
716
717
  MyIOSelectionStore: () => MyIOSelectionStore,
@@ -739,6 +740,7 @@ __export(index_exports, {
739
740
  OperationalGeneralListController: () => OperationalGeneralListController,
740
741
  OperationalGeneralListView: () => OperationalGeneralListView,
741
742
  OperationalHeaderDevicesGridView: () => OperationalHeaderDevicesGridView,
743
+ PERM: () => PERM,
742
744
  POWER_LIMITS_DEVICE_TYPES: () => DEVICE_TYPES,
743
745
  POWER_LIMITS_STATUS_CONFIG: () => STATUS_CONFIG,
744
746
  POWER_LIMITS_TELEMETRY_TYPES: () => TELEMETRY_TYPES2,
@@ -1007,6 +1009,7 @@ __export(index_exports, {
1007
1009
  groupByDay: () => groupByDay,
1008
1010
  handleDeviceType: () => handleDeviceType,
1009
1011
  hasSelectedDays: () => hasSelectedDays,
1012
+ initMyIOAuthContext: () => initMyIOAuthContext,
1010
1013
  initOnOffTimelineTooltips: () => initOnOffTimelineTooltips,
1011
1014
  injectActionButtonStyles: () => injectActionButtonStyles,
1012
1015
  injectAlarmsNotificationsPanelStyles: () => injectAlarmsNotificationsPanelStyles,
@@ -1159,7 +1162,7 @@ module.exports = __toCommonJS(index_exports);
1159
1162
  // package.json
1160
1163
  var package_default = {
1161
1164
  name: "myio-js-library",
1162
- version: "0.1.487",
1165
+ version: "0.1.490",
1163
1166
  description: "A clean, standalone JS SDK for MYIO projects",
1164
1167
  license: "MIT",
1165
1168
  repository: "github:gh-myio/myio-js-library",
@@ -58602,15 +58605,24 @@ var AlarmsTab = class {
58602
58605
  * Falls back to prefetchedAlarms prop when ASO is not available (e.g. standalone showcase).
58603
58606
  */
58604
58607
  readAlarmsFromASO() {
58605
- const aso = window.AlarmServiceOrchestrator;
58608
+ const win = window;
58609
+ const aso = win.AlarmServiceOrchestrator;
58610
+ const showOffline = win.MyIOOrchestrator?.showOfflineAlarms === true;
58611
+ const _OFFLINE_TYPES = ["DEVICE OFFLINE", "DISPOSITIVO OFFLINE"];
58612
+ const _isOffline = (a) => {
58613
+ const t = ((a.title ?? a.alarmType) || "").toUpperCase();
58614
+ return _OFFLINE_TYPES.some((ex) => t.startsWith(ex)) || a.alarmType === "connectivity";
58615
+ };
58616
+ let alarms = [];
58606
58617
  if (aso && this.config.gcdrDeviceId) {
58607
- return aso.getAlarmsForDevice(this.config.gcdrDeviceId);
58608
- }
58609
- const prefetched = this.config.prefetchedAlarms;
58610
- if (prefetched != null) {
58611
- return prefetched.filter((a) => a.deviceId === this.config.gcdrDeviceId);
58618
+ alarms = aso.getAlarmsForDevice(this.config.gcdrDeviceId);
58619
+ } else {
58620
+ const prefetched = this.config.prefetchedAlarms;
58621
+ if (prefetched != null) {
58622
+ alarms = prefetched.filter((a) => a.deviceId === this.config.gcdrDeviceId);
58623
+ }
58612
58624
  }
58613
- return [];
58625
+ return showOffline ? alarms : alarms.filter((a) => !_isOffline(a));
58614
58626
  }
58615
58627
  // ============================================================================
58616
58628
  // Card mapping — separado (Disp. + Tipo) view: one card per individual alarm
@@ -88359,6 +88371,9 @@ var UserListTab = class {
88359
88371
  loading = false;
88360
88372
  users = [];
88361
88373
  highlightedUserId = null;
88374
+ gcdrConfigs = /* @__PURE__ */ new Map();
88375
+ gcdrSyncing = /* @__PURE__ */ new Set();
88376
+ syncTooltipEl = null;
88362
88377
  constructor(config, callbacks) {
88363
88378
  this.config = config;
88364
88379
  this.callbacks = callbacks;
@@ -88384,6 +88399,8 @@ var UserListTab = class {
88384
88399
  <th>Nome</th>
88385
88400
  <th>E-mail</th>
88386
88401
  <th>Perfil</th>
88402
+ <th>Status</th>
88403
+ <th class="um-col-gcdr">GCDR</th>
88387
88404
  <th class="um-col-actions">A\xE7\xF5es</th>
88388
88405
  </tr>
88389
88406
  </thead>
@@ -88449,6 +88466,7 @@ var UserListTab = class {
88449
88466
  this.totalPages = page.totalPages;
88450
88467
  this.renderRows();
88451
88468
  this.renderPagination(page);
88469
+ this.fetchGcdrConfigsBatch();
88452
88470
  } catch (err) {
88453
88471
  console.error("[UserListTab] fetchUsers error", err);
88454
88472
  this.callbacks.showToast("Erro ao carregar usu\xE1rios. Tente novamente.", "error");
@@ -88475,11 +88493,36 @@ var UserListTab = class {
88475
88493
  }
88476
88494
  const name = [user.firstName, user.lastName].filter(Boolean).join(" ") || "\u2014";
88477
88495
  const role = user.authority === "TENANT_ADMIN" ? "Admin" : "Usu\xE1rio";
88496
+ const statusBadge = this.buildStatusBadge(user);
88497
+ const uid2 = user.id.id;
88478
88498
  tr.innerHTML = `
88479
88499
  <td>${this.esc(name)}</td>
88480
88500
  <td>${this.esc(user.email)}</td>
88481
88501
  <td><span class="um-badge um-badge--${user.authority === "TENANT_ADMIN" ? "admin" : "user"}">${role}</span></td>
88502
+ <td>${statusBadge}</td>
88503
+ <td class="um-col-gcdr">
88504
+ <span class="um-sync-icon um-sync-icon--loading" data-sync-uid="${uid2}">
88505
+ <span class="um-spinner" style="width:12px;height:12px;border-width:1.5px;display:block;margin:0 auto;"></span>
88506
+ </span>
88507
+ <button class="um-icon-btn um-force-sync-btn" title="Sincronizar com GCDR" style="margin-left:2px;">
88508
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
88509
+ stroke-linecap="round" stroke-linejoin="round">
88510
+ <polyline points="23 4 23 10 17 10"/>
88511
+ <polyline points="1 20 1 14 7 14"/>
88512
+ <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
88513
+ </svg>
88514
+ </button>
88515
+ </td>
88482
88516
  <td class="um-col-actions">
88517
+ <button class="um-icon-btn um-assign-btn" title="Ver Fun\xE7\xF5es / Pap\xE9is" style="margin-right:4px;">
88518
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
88519
+ stroke-linecap="round" stroke-linejoin="round">
88520
+ <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
88521
+ <circle cx="9" cy="7" r="4"/>
88522
+ <path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
88523
+ <path d="M16 3.13a4 4 0 0 1 0 7.75"/>
88524
+ </svg>
88525
+ </button>
88483
88526
  <button class="um-icon-btn um-detail-btn" title="Ver Detalhes">
88484
88527
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
88485
88528
  stroke-linecap="round" stroke-linejoin="round">
@@ -88489,6 +88532,11 @@ var UserListTab = class {
88489
88532
  </td>
88490
88533
  `;
88491
88534
  tr.querySelector(".um-detail-btn").addEventListener("click", () => this.callbacks.onOpenUserDetail(user, false));
88535
+ tr.querySelector(".um-assign-btn").addEventListener("click", (e) => this.showAssignmentsPopup(user, e.currentTarget));
88536
+ tr.querySelector(".um-force-sync-btn").addEventListener("click", () => this.syncUserToGCDR(user));
88537
+ const syncIcon = tr.querySelector(".um-sync-icon");
88538
+ syncIcon.addEventListener("mouseenter", (e) => this.showSyncTooltip(user, e.currentTarget));
88539
+ syncIcon.addEventListener("mouseleave", () => this.hideSyncTooltip());
88492
88540
  tbody.appendChild(tr);
88493
88541
  }
88494
88542
  }
@@ -88512,9 +88560,321 @@ var UserListTab = class {
88512
88560
  if (loadEl) loadEl.style.display = on ? "" : "none";
88513
88561
  if (tableWrap) tableWrap.style.opacity = on ? "0.5" : "1";
88514
88562
  }
88563
+ buildStatusBadge(user) {
88564
+ const enabled = user.additionalInfo?.userCredentialsEnabled;
88565
+ if (enabled === false) {
88566
+ return `<span class="um-badge um-badge--blocked">Bloqueado</span>`;
88567
+ }
88568
+ return `<span class="um-badge um-badge--active">Ativo</span>`;
88569
+ }
88570
+ gcdrHeaders() {
88571
+ const orch = window.MyIOOrchestrator;
88572
+ return {
88573
+ "Content-Type": "application/json",
88574
+ "X-API-Key": orch?.gcdrApiKey || "",
88575
+ "X-Tenant-ID": orch?.gcdrTenantId || ""
88576
+ };
88577
+ }
88578
+ gcdrBase() {
88579
+ return window.MyIOOrchestrator?.gcdrApiBaseUrl || "";
88580
+ }
88581
+ unwrapList(json) {
88582
+ if (Array.isArray(json)) return json;
88583
+ const j = json;
88584
+ if (j?.data && typeof j.data === "object") {
88585
+ const d = j.data;
88586
+ if (Array.isArray(d.items)) return d.items;
88587
+ if (Array.isArray(d)) return d;
88588
+ }
88589
+ if (Array.isArray(j?.items)) return j.items;
88590
+ return [];
88591
+ }
88592
+ async showAssignmentsPopup(user, anchor) {
88593
+ document.querySelector(".um-assign-popup")?.remove();
88594
+ const popup = document.createElement("div");
88595
+ popup.className = "um-assign-popup";
88596
+ popup.setAttribute("data-theme", this.config.theme || "light");
88597
+ const displayName = [user.firstName, user.lastName].filter(Boolean).join(" ") || user.email;
88598
+ popup.innerHTML = `
88599
+ <div class="um-assign-popup-header">
88600
+ <span class="um-assign-popup-title">\u{1F511} Fun\xE7\xF5es \u2014 ${this.esc(displayName)}</span>
88601
+ <button class="um-assign-popup-close" type="button">\u2715</button>
88602
+ </div>
88603
+ <div class="um-assign-popup-body">
88604
+ <div class="um-assign-popup-loading" style="font-size:12px;color:var(--um-text-muted);padding:8px 0;">
88605
+ <span class="um-spinner" style="display:inline-block;"></span> Carregando...
88606
+ </div>
88607
+ </div>
88608
+ `;
88609
+ document.body.appendChild(popup);
88610
+ const rect = anchor.getBoundingClientRect();
88611
+ const popupW = 320;
88612
+ const left = Math.min(rect.right + 8, window.innerWidth - popupW - 12);
88613
+ popup.style.top = `${Math.max(rect.top - 10, 8)}px`;
88614
+ popup.style.left = `${left}px`;
88615
+ const close = () => popup.remove();
88616
+ popup.querySelector(".um-assign-popup-close").addEventListener("click", close);
88617
+ const outsideHandler = (e) => {
88618
+ if (!popup.contains(e.target) && e.target !== anchor) {
88619
+ close();
88620
+ document.removeEventListener("click", outsideHandler, true);
88621
+ }
88622
+ };
88623
+ setTimeout(() => document.addEventListener("click", outsideHandler, true), 50);
88624
+ const body = popup.querySelector(".um-assign-popup-body");
88625
+ const base = this.gcdrBase();
88626
+ if (!base) {
88627
+ body.innerHTML = `<div style="font-size:12px;color:var(--um-toast-err-text);padding:8px 0;">GCDR n\xE3o configurado.</div>`;
88628
+ return;
88629
+ }
88630
+ try {
88631
+ const res = await fetch(`${base}/authorization/users/${user.id.id}/assignments`, { headers: this.gcdrHeaders() });
88632
+ let assignments = [];
88633
+ if (res.ok) {
88634
+ const json = await res.json();
88635
+ assignments = Array.isArray(json) ? json : json.assignments ?? [];
88636
+ }
88637
+ if (assignments.length === 0) {
88638
+ body.innerHTML = `<div style="font-size:12px;color:var(--um-text-faint);padding:8px 0;">Nenhuma fun\xE7\xE3o atribu\xEDda.</div>`;
88639
+ return;
88640
+ }
88641
+ const statusColors = {
88642
+ active: "var(--um-badge-active-text)",
88643
+ expired: "var(--um-badge-blocked-text)",
88644
+ inactive: "var(--um-text-faint)"
88645
+ };
88646
+ body.innerHTML = assignments.map((a) => {
88647
+ const scopeLabel = a.scope === "*" ? "* (global)" : a.scope.startsWith("customer:") ? `Cliente (${a.scope.replace("customer:", "").slice(0, 8)}...)` : a.scope.startsWith("asset:") ? `Asset (${a.scope.replace("asset:", "").slice(0, 8)}...)` : this.esc(a.scope);
88648
+ const expires = a.expiresAt ? new Date(a.expiresAt).toLocaleDateString("pt-BR") : null;
88649
+ const color = statusColors[a.status] || "var(--um-text-faint)";
88650
+ return `
88651
+ <div class="um-assign-row">
88652
+ <span class="um-assign-role">${this.esc(a.roleDisplayName || a.roleKey)}</span>
88653
+ <span class="um-assign-meta">
88654
+ Escopo: ${scopeLabel}
88655
+ \xB7 <span style="color:${color};font-weight:600;">${a.status}</span>
88656
+ ${expires ? ` \xB7 Expira ${expires}` : ""}
88657
+ </span>
88658
+ </div>`;
88659
+ }).join("");
88660
+ } catch {
88661
+ body.innerHTML = `<div style="font-size:12px;color:var(--um-toast-err-text);padding:8px 0;">Erro ao carregar.</div>`;
88662
+ }
88663
+ }
88515
88664
  esc(s) {
88516
88665
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
88517
88666
  }
88667
+ // ── GCDR Sync Column ─────────────────────────────────────────────────────────
88668
+ async fetchGcdrConfigsBatch() {
88669
+ if (this.users.length === 0) return;
88670
+ const { tbBaseUrl, jwtToken } = this.config;
88671
+ await Promise.allSettled(this.users.map(async (user) => {
88672
+ const uid2 = user.id.id;
88673
+ try {
88674
+ const res = await fetch(
88675
+ `${tbBaseUrl}/api/plugins/telemetry/USER/${uid2}/values/attributes/SERVER_SCOPE?keys=gcdrUserConfigs`,
88676
+ { headers: { "X-Authorization": `Bearer ${jwtToken}` } }
88677
+ );
88678
+ let cfg = null;
88679
+ if (res.ok) {
88680
+ const attrs = await res.json();
88681
+ const entry = attrs.find((a) => a.key === "gcdrUserConfigs");
88682
+ if (entry?.value && typeof entry.value === "object") {
88683
+ cfg = entry.value;
88684
+ }
88685
+ }
88686
+ this.gcdrConfigs.set(uid2, cfg);
88687
+ this.updateSyncCell(uid2, cfg ? cfg.lastSyncResult ?? "none" : "none", cfg);
88688
+ } catch {
88689
+ this.updateSyncCell(uid2, "none", null);
88690
+ }
88691
+ }));
88692
+ }
88693
+ updateSyncCell(userId, state6, cfg = null) {
88694
+ const iconEl = this.el?.querySelector(`.um-sync-icon[data-sync-uid="${userId}"]`);
88695
+ if (!iconEl) return;
88696
+ iconEl.className = "um-sync-icon";
88697
+ if (state6 === "loading" || state6 === "syncing") {
88698
+ iconEl.innerHTML = `<span class="um-spinner" style="width:12px;height:12px;border-width:1.5px;display:block;margin:0 auto;"></span>`;
88699
+ return;
88700
+ }
88701
+ const gcdrStatus = cfg?.gcdrStatus;
88702
+ let dotClass = "um-sync-dot--none";
88703
+ let dotTitle = "Nunca sincronizado";
88704
+ if (state6 === "success") {
88705
+ dotClass = gcdrStatus === "ACTIVE" ? "um-sync-dot--active" : gcdrStatus === "INACTIVE" || gcdrStatus === "LOCKED" ? "um-sync-dot--warn" : "um-sync-dot--ok";
88706
+ dotTitle = `GCDR: ${gcdrStatus ?? "OK"}`;
88707
+ } else if (state6 === "error") {
88708
+ dotClass = "um-sync-dot--err";
88709
+ dotTitle = cfg?.lastError ?? "Erro";
88710
+ }
88711
+ iconEl.innerHTML = `<span class="um-sync-dot ${dotClass}" title="${this.esc(dotTitle)}"></span>`;
88712
+ }
88713
+ showSyncTooltip(user, anchor) {
88714
+ this.hideSyncTooltip();
88715
+ const uid2 = user.id.id;
88716
+ const cfg = this.gcdrConfigs.get(uid2);
88717
+ const tooltip = document.createElement("div");
88718
+ tooltip.className = "um-sync-tooltip";
88719
+ tooltip.setAttribute("data-theme", this.config.theme || "light");
88720
+ this.syncTooltipEl = tooltip;
88721
+ const syncing = this.gcdrSyncing.has(uid2);
88722
+ const displayName = [user.firstName, user.lastName].filter(Boolean).join(" ") || user.email;
88723
+ let bodyHtml;
88724
+ if (syncing) {
88725
+ bodyHtml = `<div class="um-sync-tooltip-row" style="justify-content:center;padding:8px 0;">
88726
+ <span class="um-spinner" style="width:13px;height:13px;"></span>&nbsp;Sincronizando...
88727
+ </div>`;
88728
+ } else if (!cfg) {
88729
+ bodyHtml = `<div class="um-sync-tooltip-row um-sync-tooltip-row--muted">Nunca sincronizado com GCDR.</div>`;
88730
+ } else {
88731
+ const syncedAt = cfg.syncedAt ? new Date(cfg.syncedAt).toLocaleString("pt-BR") : "\u2014";
88732
+ const updatedAt = cfg.updatedAt ? new Date(cfg.updatedAt).toLocaleString("pt-BR") : "\u2014";
88733
+ const resultColor = cfg.lastSyncResult === "success" ? "var(--um-badge-active-text)" : "var(--um-badge-blocked-text)";
88734
+ bodyHtml = `
88735
+ <div class="um-sync-tooltip-row">
88736
+ <span class="um-sync-tooltip-label">GCDR ID</span>
88737
+ <span class="um-sync-tooltip-value" style="font-family:monospace;font-size:10px;">${cfg.gcdrUserId ? cfg.gcdrUserId.slice(0, 16) + "\u2026" : "\u2014"}</span>
88738
+ </div>
88739
+ <div class="um-sync-tooltip-row">
88740
+ <span class="um-sync-tooltip-label">Status GCDR</span>
88741
+ <span class="um-sync-tooltip-value">${cfg.gcdrStatus ?? "\u2014"}</span>
88742
+ </div>
88743
+ <div class="um-sync-tooltip-row">
88744
+ <span class="um-sync-tooltip-label">\xDAltimo sync</span>
88745
+ <span class="um-sync-tooltip-value">${syncedAt}</span>
88746
+ </div>
88747
+ <div class="um-sync-tooltip-row">
88748
+ <span class="um-sync-tooltip-label">Atualizado</span>
88749
+ <span class="um-sync-tooltip-value">${updatedAt}</span>
88750
+ </div>
88751
+ <div class="um-sync-tooltip-row">
88752
+ <span class="um-sync-tooltip-label">Qtd syncs</span>
88753
+ <span class="um-sync-tooltip-value">${cfg.syncCount ?? 0}</span>
88754
+ </div>
88755
+ <div class="um-sync-tooltip-row">
88756
+ <span class="um-sync-tooltip-label">Resultado</span>
88757
+ <span class="um-sync-tooltip-value" style="color:${resultColor};font-weight:700;">${cfg.lastSyncResult === "success" ? "\u2713 Sucesso" : "\u2717 Erro"}</span>
88758
+ </div>
88759
+ ${cfg.lastError ? `<div class="um-sync-tooltip-row um-sync-tooltip-row--error">
88760
+ <span class="um-sync-tooltip-label">Erro</span>
88761
+ <span class="um-sync-tooltip-value">${this.esc(cfg.lastError)}</span>
88762
+ </div>` : ""}
88763
+ `;
88764
+ }
88765
+ tooltip.innerHTML = `
88766
+ <div class="um-sync-tooltip-header">
88767
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"
88768
+ stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;">
88769
+ <polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/>
88770
+ <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
88771
+ </svg>
88772
+ <span>GCDR Sync \u2014 ${this.esc(displayName)}</span>
88773
+ </div>
88774
+ <div class="um-sync-tooltip-body">${bodyHtml}</div>
88775
+ `;
88776
+ const backdrop = this.el.closest(".um-backdrop") ?? document.body;
88777
+ backdrop.appendChild(tooltip);
88778
+ const rect = anchor.getBoundingClientRect();
88779
+ const ttW = 260;
88780
+ let left = rect.right + 8;
88781
+ if (left + ttW > window.innerWidth - 8) left = rect.left - ttW - 8;
88782
+ tooltip.style.left = `${Math.max(4, left)}px`;
88783
+ tooltip.style.top = `${Math.max(4, rect.top - 10)}px`;
88784
+ }
88785
+ hideSyncTooltip() {
88786
+ this.syncTooltipEl?.remove();
88787
+ this.syncTooltipEl = null;
88788
+ }
88789
+ async syncUserToGCDR(user) {
88790
+ const uid2 = user.id.id;
88791
+ if (this.gcdrSyncing.has(uid2)) return;
88792
+ const base = this.gcdrBase();
88793
+ if (!base) {
88794
+ this.callbacks.showToast("GCDR n\xE3o configurado.", "error");
88795
+ return;
88796
+ }
88797
+ this.gcdrSyncing.add(uid2);
88798
+ this.updateSyncCell(uid2, "syncing");
88799
+ const prev = this.gcdrConfigs.get(uid2) ?? null;
88800
+ const syncCount = (prev?.syncCount ?? 0) + 1;
88801
+ const now = (/* @__PURE__ */ new Date()).toISOString();
88802
+ try {
88803
+ const searchRes = await fetch(
88804
+ `${base}/api/v1/users?search=${encodeURIComponent(user.email)}&customerId=${encodeURIComponent(this.config.customerId)}&limit=10`,
88805
+ { headers: this.gcdrHeaders() }
88806
+ );
88807
+ let gcdrUser = null;
88808
+ if (searchRes.ok) {
88809
+ const data = await searchRes.json();
88810
+ const items = Array.isArray(data) ? data : data?.data?.items ?? data?.items ?? [];
88811
+ gcdrUser = items.find((u) => u.email?.toLowerCase() === user.email.toLowerCase()) ?? null;
88812
+ }
88813
+ if (!gcdrUser) {
88814
+ const createRes = await fetch(`${base}/api/v1/users`, {
88815
+ method: "POST",
88816
+ headers: this.gcdrHeaders(),
88817
+ body: JSON.stringify({
88818
+ email: user.email,
88819
+ firstName: user.firstName || "",
88820
+ lastName: user.lastName || "",
88821
+ customerId: this.config.customerId,
88822
+ type: "CUSTOMER"
88823
+ })
88824
+ });
88825
+ if (!createRes.ok) throw new Error(`Criar usu\xE1rio GCDR: HTTP ${createRes.status}`);
88826
+ gcdrUser = await createRes.json();
88827
+ }
88828
+ const configs = {
88829
+ gcdrUserId: gcdrUser.id,
88830
+ gcdrStatus: gcdrUser.status,
88831
+ gcdrType: gcdrUser.type,
88832
+ syncedAt: now,
88833
+ syncCount,
88834
+ lastSyncResult: "success",
88835
+ lastError: null,
88836
+ createdAt: prev?.createdAt ?? now,
88837
+ updatedAt: now
88838
+ };
88839
+ await this.saveTBAttribute(uid2, configs);
88840
+ this.gcdrConfigs.set(uid2, configs);
88841
+ this.updateSyncCell(uid2, "success", configs);
88842
+ this.callbacks.showToast(`Sync GCDR conclu\xEDdo \u2014 ${user.email}`, "success");
88843
+ } catch (err) {
88844
+ const configs = {
88845
+ ...prev ?? {},
88846
+ syncedAt: now,
88847
+ syncCount,
88848
+ lastSyncResult: "error",
88849
+ lastError: String(err?.message ?? "Erro desconhecido"),
88850
+ updatedAt: now
88851
+ };
88852
+ try {
88853
+ await this.saveTBAttribute(uid2, configs);
88854
+ } catch {
88855
+ }
88856
+ this.gcdrConfigs.set(uid2, configs);
88857
+ this.updateSyncCell(uid2, "error", configs);
88858
+ this.callbacks.showToast(`Erro sync GCDR: ${err?.message || "Falha"}`, "error");
88859
+ } finally {
88860
+ this.gcdrSyncing.delete(uid2);
88861
+ }
88862
+ }
88863
+ async saveTBAttribute(userId, configs) {
88864
+ const { tbBaseUrl, jwtToken } = this.config;
88865
+ const res = await fetch(
88866
+ `${tbBaseUrl}/api/plugins/telemetry/USER/${userId}/attributes/SERVER_SCOPE`,
88867
+ {
88868
+ method: "POST",
88869
+ headers: {
88870
+ "X-Authorization": `Bearer ${jwtToken}`,
88871
+ "Content-Type": "application/json"
88872
+ },
88873
+ body: JSON.stringify({ gcdrUserConfigs: configs })
88874
+ }
88875
+ );
88876
+ if (!res.ok) throw new Error(`Salvar atributo TB: HTTP ${res.status}`);
88877
+ }
88518
88878
  };
88519
88879
 
88520
88880
  // src/components/premium-modals/user-management/tabs/NewUserTab.ts
@@ -90001,7 +90361,12 @@ var UserDetailTab = class {
90001
90361
  fetch(`${this.gcdrBase()}/roles?limit=100`, { headers: this.gcdrHeaders() }),
90002
90362
  fetch(`${this.gcdrBase()}/policies?limit=100`, { headers: this.gcdrHeaders() })
90003
90363
  ]);
90004
- this.assignments = assignRes.ok ? this.unwrapList(await assignRes.json()) : [];
90364
+ if (assignRes.ok) {
90365
+ const assignJson = await assignRes.json();
90366
+ this.assignments = Array.isArray(assignJson) ? assignJson : assignJson.assignments ?? [];
90367
+ } else {
90368
+ this.assignments = [];
90369
+ }
90005
90370
  this.availableRoles = rolesRes.ok ? this.unwrapList(await rolesRes.json()) : [];
90006
90371
  this.availablePolicies = policiesRes.ok ? this.unwrapList(await policiesRes.json()) : [];
90007
90372
  this.renderAssignments();
@@ -90028,11 +90393,12 @@ var UserDetailTab = class {
90028
90393
  const tbody = document.createElement("tbody");
90029
90394
  this.assignments.forEach((a) => {
90030
90395
  const tr = document.createElement("tr");
90031
- const statusColor = a.status === "active" ? "var(--um-badge-user-text)" : a.status === "expired" ? "var(--um-btn-danger-text)" : "var(--um-text-faint)";
90396
+ const statusColor = a.status === "active" ? "var(--um-badge-active-text)" : a.status === "expired" ? "var(--um-badge-blocked-text)" : "var(--um-text-faint)";
90032
90397
  const expiresAt = a.expiresAt ? new Date(a.expiresAt).toLocaleDateString("pt-BR") : "\u2014";
90398
+ const scopeLabel = a.scope === "*" ? "* (global)" : a.scope.startsWith("customer:") ? `Cliente (${a.scope.replace("customer:", "").slice(0, 8)}...)` : a.scope.startsWith("asset:") ? `Asset (${a.scope.replace("asset:", "").slice(0, 8)}...)` : a.scope;
90033
90399
  tr.innerHTML = `
90034
90400
  <td style="font-weight:500;">${this.esc(a.roleDisplayName || a.roleKey)}</td>
90035
- <td><code style="font-size:10px;">${this.esc(a.scope)}</code></td>
90401
+ <td style="font-size:12px;">${this.esc(scopeLabel)}</td>
90036
90402
  <td><span style="color:${statusColor};font-weight:600;">${a.status}</span></td>
90037
90403
  <td>${expiresAt}</td>
90038
90404
  <td style="text-align:center;"><button class="um-btn um-btn--danger um-btn--sm revoke-btn">Revogar</button></td>
@@ -90142,13 +90508,14 @@ var UserDetailTab = class {
90142
90508
  const expiresAt = expiresAtRaw ? new Date(expiresAtRaw).toISOString() : null;
90143
90509
  const reason = modal.querySelector("[name=reason]").value.trim() || null;
90144
90510
  const role = this.availableRoles.find((r) => r.id === roleId);
90511
+ const roleKey = role?.key || role?.id || roleId;
90145
90512
  const btn = modal.querySelector(".assign-save");
90146
90513
  btn.disabled = true;
90147
90514
  btn.textContent = "...";
90148
90515
  try {
90149
90516
  const body = {
90150
90517
  userId: this.user.id.id,
90151
- roleId,
90518
+ roleKey,
90152
90519
  scope,
90153
90520
  expiresAt,
90154
90521
  reason
@@ -91202,6 +91569,12 @@ var UserManagementModalView = class {
91202
91569
  --um-badge-admin-text: #3b5bdb;
91203
91570
  --um-badge-user-bg: #f0fdf4;
91204
91571
  --um-badge-user-text: #16a34a;
91572
+ --um-badge-active-bg: #dcfce7;
91573
+ --um-badge-active-text: #15803d;
91574
+ --um-badge-blocked-bg: #fee2e2;
91575
+ --um-badge-blocked-text: #b91c1c;
91576
+ --um-badge-pending-bg: #fef9c3;
91577
+ --um-badge-pending-text: #854d0e;
91205
91578
  --um-toast-ok-bg: #f0fdf4;
91206
91579
  --um-toast-ok-border: #22c55e;
91207
91580
  --um-toast-ok-text: #16a34a;
@@ -91262,6 +91635,12 @@ var UserManagementModalView = class {
91262
91635
  --um-badge-admin-text: #60a5fa;
91263
91636
  --um-badge-user-bg: #1a2d24;
91264
91637
  --um-badge-user-text: #4ade80;
91638
+ --um-badge-active-bg: #14532d;
91639
+ --um-badge-active-text: #86efac;
91640
+ --um-badge-blocked-bg: #450a0a;
91641
+ --um-badge-blocked-text: #fca5a5;
91642
+ --um-badge-pending-bg: #422006;
91643
+ --um-badge-pending-text: #fde68a;
91265
91644
  --um-toast-ok-bg: #1e3a2e;
91266
91645
  --um-toast-ok-border: #22c55e;
91267
91646
  --um-toast-ok-text: #4ade80;
@@ -91291,14 +91670,21 @@ var UserManagementModalView = class {
91291
91670
  .um-modal {
91292
91671
  background: var(--um-modal-bg);
91293
91672
  font-family: 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
91294
- border-radius: 14px; width: 92vw; max-width: 960px;
91673
+ border-radius: 14px; width: 96vw; max-width: 1200px;
91295
91674
  --modal-header-radius: 14px 14px 0 0;
91296
- aspect-ratio: 16/9; display: flex; flex-direction: column;
91675
+ height: 84vh; display: flex; flex-direction: column;
91297
91676
  box-shadow: var(--um-shadow); overflow: hidden; position: relative;
91298
91677
  }
91299
- @media (max-height: 600px) { .um-modal { aspect-ratio: unset; height: 90vh; } }
91678
+ @media (max-height: 600px) { .um-modal { height: 96vh; } }
91300
91679
 
91301
91680
  /* Header handled by ModalHeader (RFC-0121) */
91681
+ .um-modal .myio-modal-header__title {
91682
+ font-size: 14px !important;
91683
+ font-weight: 600 !important;
91684
+ }
91685
+ .um-modal .myio-modal-header__icon {
91686
+ font-size: 15px !important;
91687
+ }
91302
91688
 
91303
91689
  /* Force MyIO purple header regardless of light/dark theme \u2014 overrides myio-modal-header--light */
91304
91690
  .um-modal .myio-modal-header--light {
@@ -91397,16 +91783,83 @@ var UserManagementModalView = class {
91397
91783
  .um-table { width: 100%; border-collapse: collapse; font-size: 13px; }
91398
91784
  .um-table th {
91399
91785
  text-align: left; padding: 8px 12px;
91400
- color: var(--um-text-muted); font-weight: 500; font-size: 11px;
91786
+ color: var(--um-text-muted); font-weight: 600; font-size: 11px;
91401
91787
  text-transform: uppercase; letter-spacing: 0.04em;
91402
91788
  border-bottom: 1px solid var(--um-border);
91403
91789
  }
91404
91790
  .um-table td {
91405
- padding: 10px 12px; color: var(--um-text-secondary);
91791
+ padding: 10px 12px; color: var(--um-text-primary); font-weight: 500;
91406
91792
  border-bottom: 1px solid var(--um-border-sub);
91407
91793
  }
91408
91794
  .um-table tr:hover td { background: var(--um-bg-surface); }
91409
- .um-col-actions { width: 80px; text-align: center; }
91795
+ .um-col-actions { width: 100px; text-align: center; }
91796
+ .um-col-gcdr { width: 72px; text-align: center; }
91797
+
91798
+ /* Sync status dot */
91799
+ .um-sync-icon { display: inline-flex; align-items: center; justify-content: center; }
91800
+ .um-sync-dot {
91801
+ display: inline-block; width: 10px; height: 10px; border-radius: 50%;
91802
+ flex-shrink: 0;
91803
+ }
91804
+ .um-sync-dot--none { background: var(--um-text-faint); opacity: 0.4; }
91805
+ .um-sync-dot--ok,
91806
+ .um-sync-dot--active { background: var(--um-badge-active-text); }
91807
+ .um-sync-dot--warn { background: var(--um-badge-pending-text); }
91808
+ .um-sync-dot--err { background: var(--um-badge-blocked-text); }
91809
+
91810
+ /* Premium sync tooltip */
91811
+ .um-sync-tooltip {
91812
+ position: fixed; z-index: 100020;
91813
+ background: var(--um-modal-bg); border: 1px solid var(--um-border);
91814
+ border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,0.2);
91815
+ width: 260px; pointer-events: none;
91816
+ font-family: 'Nunito', -apple-system, sans-serif;
91817
+ }
91818
+ .um-sync-tooltip-header {
91819
+ display: flex; align-items: center; gap: 7px;
91820
+ padding: 8px 12px; background: var(--um-accent);
91821
+ border-radius: 10px 10px 0 0;
91822
+ font-size: 11px; font-weight: 700; color: #fff;
91823
+ }
91824
+ .um-sync-tooltip-body {
91825
+ padding: 10px 12px; display: flex; flex-direction: column; gap: 4px;
91826
+ }
91827
+ .um-sync-tooltip-row {
91828
+ display: flex; justify-content: space-between; align-items: flex-start;
91829
+ font-size: 11px; gap: 8px;
91830
+ }
91831
+ .um-sync-tooltip-row--muted { color: var(--um-text-faint); justify-content: center; padding: 4px 0; }
91832
+ .um-sync-tooltip-row--error .um-sync-tooltip-value { color: var(--um-badge-blocked-text); word-break: break-all; }
91833
+ .um-sync-tooltip-label { color: var(--um-text-muted); flex-shrink: 0; }
91834
+ .um-sync-tooltip-value { color: var(--um-text-primary); font-weight: 600; text-align: right; }
91835
+
91836
+ /* Assignments quick-view popup */
91837
+ .um-assign-popup {
91838
+ position: fixed; z-index: 100010;
91839
+ background: var(--um-modal-bg); border: 1px solid var(--um-border);
91840
+ border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,0.18);
91841
+ width: 320px; max-height: 340px; overflow-y: auto;
91842
+ font-family: 'Nunito', -apple-system, sans-serif;
91843
+ }
91844
+ .um-assign-popup-header {
91845
+ display: flex; align-items: center; justify-content: space-between;
91846
+ padding: 10px 14px; background: var(--um-accent);
91847
+ border-radius: 10px 10px 0 0;
91848
+ }
91849
+ .um-assign-popup-title { font-size: 12px; font-weight: 700; color: #fff; }
91850
+ .um-assign-popup-close {
91851
+ background: none; border: none; color: rgba(255,255,255,0.8);
91852
+ font-size: 14px; cursor: pointer; padding: 0 4px; line-height: 1;
91853
+ }
91854
+ .um-assign-popup-body { padding: 10px 14px; }
91855
+ .um-assign-row {
91856
+ display: flex; flex-direction: column; gap: 2px;
91857
+ padding: 7px 0; border-bottom: 1px solid var(--um-border-sub);
91858
+ font-size: 12px;
91859
+ }
91860
+ .um-assign-row:last-child { border-bottom: none; }
91861
+ .um-assign-role { font-weight: 600; color: var(--um-text-primary); }
91862
+ .um-assign-meta { color: var(--um-text-muted); font-size: 11px; }
91410
91863
  .um-row--highlight td { background: var(--um-row-highlight) !important; }
91411
91864
 
91412
91865
  .um-list-empty, .um-list-loading, .um-profiles-loading,
@@ -91442,8 +91895,11 @@ var UserManagementModalView = class {
91442
91895
  .um-btn--sm { padding: 6px 12px; font-size: 12px; }
91443
91896
 
91444
91897
  .um-badge { display: inline-block; font-size: 10px; font-weight: 600; padding: 2px 8px; border-radius: 9999px; }
91445
- .um-badge--admin { background: var(--um-badge-admin-bg); color: var(--um-badge-admin-text); }
91446
- .um-badge--user { background: var(--um-badge-user-bg); color: var(--um-badge-user-text); }
91898
+ .um-badge--admin { background: var(--um-badge-admin-bg); color: var(--um-badge-admin-text); }
91899
+ .um-badge--user { background: var(--um-badge-user-bg); color: var(--um-badge-user-text); }
91900
+ .um-badge--active { background: var(--um-badge-active-bg); color: var(--um-badge-active-text); }
91901
+ .um-badge--blocked { background: var(--um-badge-blocked-bg); color: var(--um-badge-blocked-text); }
91902
+ .um-badge--pending { background: var(--um-badge-pending-bg); color: var(--um-badge-pending-text); }
91447
91903
 
91448
91904
  .um-form { display: flex; flex-direction: column; gap: 14px;}
91449
91905
  .um-form-row { display: flex; gap: 12px; }
@@ -91738,6 +92194,272 @@ function openUserManagementModal(params) {
91738
92194
  controller.show();
91739
92195
  }
91740
92196
 
92197
+ // src/components/gcdr-auth/MyIOAuthContext.ts
92198
+ var MyIOAuthContext = class _MyIOAuthContext {
92199
+ allowedActions = /* @__PURE__ */ new Set();
92200
+ deniedActions = /* @__PURE__ */ new Set();
92201
+ snap;
92202
+ constructor(snap) {
92203
+ this.snap = snap;
92204
+ }
92205
+ // ── Public read-only state ────────────────────────────────────────────────
92206
+ get ready() {
92207
+ return this.snap.ready;
92208
+ }
92209
+ get gcdrUserId() {
92210
+ return this.snap.gcdrUserId;
92211
+ }
92212
+ get scope() {
92213
+ return this.snap.scope;
92214
+ }
92215
+ get error() {
92216
+ return this.snap.error;
92217
+ }
92218
+ get allowAll() {
92219
+ return this.snap.allowAll;
92220
+ }
92221
+ get assignments() {
92222
+ return this.snap.assignments;
92223
+ }
92224
+ // ── Permission checks ─────────────────────────────────────────────────────
92225
+ /**
92226
+ * Returns `true` if the current user is allowed to perform `action`.
92227
+ *
92228
+ * Evaluation order (deny-first):
92229
+ * 1. If allowAll (TENANT_ADMIN) → always true
92230
+ * 2. If action (or `*`) is in the deny set → false
92231
+ * 3. If action (or `*`) is in the allow set → true
92232
+ * 4. Otherwise → false (closed-by-default)
92233
+ *
92234
+ * Returns `false` while not yet ready.
92235
+ */
92236
+ can(action) {
92237
+ if (!this.snap.ready) return false;
92238
+ if (this.snap.allowAll) return true;
92239
+ if (this.deniedActions.has("*") || this.deniedActions.has(action)) return false;
92240
+ return this.allowedActions.has("*") || this.allowedActions.has(action);
92241
+ }
92242
+ /** True if the user can perform **at least one** of the given actions. */
92243
+ canAny(...actions) {
92244
+ return actions.some((a) => this.can(a));
92245
+ }
92246
+ /** True if the user can perform **all** of the given actions. */
92247
+ canAll(...actions) {
92248
+ return actions.every((a) => this.can(a));
92249
+ }
92250
+ // ── Factory ───────────────────────────────────────────────────────────────
92251
+ /**
92252
+ * Initialises the auth context.
92253
+ * Resolves immediately when `allowAll` is set or GCDR is not configured.
92254
+ * Always dispatches `myio:auth-ready` when done (success or error).
92255
+ */
92256
+ static async init(config) {
92257
+ const scope = config.scope ?? (config.customerId ? `customer:${config.customerId}` : "*");
92258
+ const ctx = new _MyIOAuthContext({
92259
+ ready: false,
92260
+ gcdrUserId: null,
92261
+ scope,
92262
+ allowAll: config.allowAll ?? false,
92263
+ error: null,
92264
+ assignments: []
92265
+ });
92266
+ if (config.allowAll) {
92267
+ ctx.snap.ready = true;
92268
+ ctx._dispatch();
92269
+ return ctx;
92270
+ }
92271
+ if (!config.gcdrApiBaseUrl) {
92272
+ ctx.snap.ready = true;
92273
+ ctx.snap.error = "GCDR not configured";
92274
+ ctx._dispatch();
92275
+ return ctx;
92276
+ }
92277
+ try {
92278
+ const hdrs = ctx._gcdrHeaders(config);
92279
+ const gcdrUserId = await ctx._resolveGcdrUserId(config, hdrs);
92280
+ if (!gcdrUserId) {
92281
+ ctx.snap.error = "GCDR user not found";
92282
+ ctx.snap.ready = true;
92283
+ ctx._dispatch();
92284
+ return ctx;
92285
+ }
92286
+ ctx.snap.gcdrUserId = gcdrUserId;
92287
+ const assignRes = await fetch(
92288
+ `${config.gcdrApiBaseUrl}/authorization/users/${gcdrUserId}/assignments`,
92289
+ { headers: hdrs }
92290
+ );
92291
+ let allAssignments = [];
92292
+ if (assignRes.ok) {
92293
+ const json = await assignRes.json();
92294
+ allAssignments = Array.isArray(json) ? json : json.assignments ?? [];
92295
+ }
92296
+ const relevant = allAssignments.filter(
92297
+ (a) => a.status === "active" && (a.scope === "*" || a.scope === scope)
92298
+ );
92299
+ ctx.snap.assignments = relevant;
92300
+ if (relevant.length === 0) {
92301
+ ctx.snap.ready = true;
92302
+ ctx._dispatch();
92303
+ return ctx;
92304
+ }
92305
+ const roleKeys = [...new Set(relevant.map((a) => a.roleKey))];
92306
+ const roles = await ctx._fetchRoles(config.gcdrApiBaseUrl, roleKeys, hdrs);
92307
+ const policyKeys = [...new Set(
92308
+ roles.flatMap((r) => [...r.policies ?? [], ...r.policyIds ?? []])
92309
+ )];
92310
+ if (policyKeys.length > 0) {
92311
+ const policies = await ctx._fetchPolicies(config.gcdrApiBaseUrl, policyKeys, hdrs);
92312
+ ctx._applyPolicies(policies);
92313
+ }
92314
+ } catch (err) {
92315
+ ctx.snap.error = err?.message ?? "Auth init failed";
92316
+ }
92317
+ ctx.snap.ready = true;
92318
+ ctx._dispatch();
92319
+ return ctx;
92320
+ }
92321
+ // ── Private helpers ───────────────────────────────────────────────────────
92322
+ _gcdrHeaders(config) {
92323
+ return {
92324
+ "Content-Type": "application/json",
92325
+ "X-API-Key": config.gcdrApiKey || "",
92326
+ "X-Tenant-ID": config.gcdrTenantId || ""
92327
+ };
92328
+ }
92329
+ /**
92330
+ * Two-step GCDR user lookup:
92331
+ * 1. Read `gcdrUserConfigs` from TB SERVER_SCOPE attribute (fast path if previously synced)
92332
+ * 2. Email search via GCDR API
92333
+ */
92334
+ async _resolveGcdrUserId(config, gcdrHeaders) {
92335
+ if (config.currentUserTbId && config.tbBaseUrl && config.jwtToken) {
92336
+ try {
92337
+ const res = await fetch(
92338
+ `${config.tbBaseUrl}/api/plugins/telemetry/USER/${config.currentUserTbId}/values/attributes/SERVER_SCOPE?keys=gcdrUserConfigs`,
92339
+ { headers: { "X-Authorization": `Bearer ${config.jwtToken}` } }
92340
+ );
92341
+ if (res.ok) {
92342
+ const attrs = await res.json();
92343
+ const gcdrId = attrs.find((a) => a.key === "gcdrUserConfigs")?.value?.gcdrUserId;
92344
+ if (gcdrId) return gcdrId;
92345
+ }
92346
+ } catch {
92347
+ }
92348
+ }
92349
+ if (!config.currentUserEmail) return null;
92350
+ try {
92351
+ const q = encodeURIComponent(config.currentUserEmail);
92352
+ const cid = config.customerId ? `&customerId=${encodeURIComponent(config.customerId)}` : "";
92353
+ const res = await fetch(
92354
+ `${config.gcdrApiBaseUrl}/api/v1/users?search=${q}${cid}&limit=10`,
92355
+ { headers: gcdrHeaders }
92356
+ );
92357
+ if (res.ok) {
92358
+ const data = await res.json();
92359
+ const items = Array.isArray(data) ? data : data?.data?.items ?? data?.items ?? [];
92360
+ const found = items.find(
92361
+ (u) => u.email?.toLowerCase() === config.currentUserEmail.toLowerCase()
92362
+ );
92363
+ if (found) return found.id;
92364
+ }
92365
+ } catch {
92366
+ }
92367
+ return null;
92368
+ }
92369
+ /**
92370
+ * Fetches roles — tries GET /roles bulk endpoint first, falls back to per-key.
92371
+ */
92372
+ async _fetchRoles(base, roleKeys, headers) {
92373
+ try {
92374
+ const res = await fetch(`${base}/roles`, { headers });
92375
+ if (res.ok) {
92376
+ const json = await res.json();
92377
+ const all = Array.isArray(json) ? json : json?.data?.items ?? json?.items ?? json?.data ?? [];
92378
+ return all.filter((r) => roleKeys.includes(r.key ?? r.id));
92379
+ }
92380
+ } catch {
92381
+ }
92382
+ const settled = await Promise.allSettled(
92383
+ roleKeys.map(
92384
+ (key) => fetch(`${base}/roles/${key}`, { headers }).then((r) => r.ok ? r.json() : null)
92385
+ )
92386
+ );
92387
+ return settled.filter((r) => r.status === "fulfilled" && r.value != null).map((r) => r.value);
92388
+ }
92389
+ /**
92390
+ * Fetches policies — tries GET /policies bulk endpoint first, falls back to per-key.
92391
+ */
92392
+ async _fetchPolicies(base, policyKeys, headers) {
92393
+ try {
92394
+ const res = await fetch(`${base}/policies`, { headers });
92395
+ if (res.ok) {
92396
+ const json = await res.json();
92397
+ const all = Array.isArray(json) ? json : json?.data?.items ?? json?.items ?? json?.data ?? [];
92398
+ return all.filter((p) => policyKeys.includes(p.key ?? p.id));
92399
+ }
92400
+ } catch {
92401
+ }
92402
+ const settled = await Promise.allSettled(
92403
+ policyKeys.map(
92404
+ (key) => fetch(`${base}/policies/${key}`, { headers }).then((r) => r.ok ? r.json() : null)
92405
+ )
92406
+ );
92407
+ return settled.filter((r) => r.status === "fulfilled" && r.value != null).map((r) => r.value);
92408
+ }
92409
+ /**
92410
+ * Merges all policy allow/deny lists into the context's permission sets.
92411
+ * Deny always wins over allow (deny-first strategy).
92412
+ */
92413
+ _applyPolicies(policies) {
92414
+ for (const policy of policies) {
92415
+ for (const action of policy.allow ?? []) this.allowedActions.add(action);
92416
+ for (const action of policy.deny ?? []) this.deniedActions.add(action);
92417
+ }
92418
+ }
92419
+ /** Dispatches `myio:auth-ready` so widgets can react without polling. */
92420
+ _dispatch() {
92421
+ window.dispatchEvent(
92422
+ new CustomEvent("myio:auth-ready", {
92423
+ detail: {
92424
+ ready: this.snap.ready,
92425
+ scope: this.snap.scope,
92426
+ allowAll: this.snap.allowAll,
92427
+ error: this.snap.error
92428
+ }
92429
+ })
92430
+ );
92431
+ }
92432
+ };
92433
+ async function initMyIOAuthContext(config) {
92434
+ return MyIOAuthContext.init(config);
92435
+ }
92436
+
92437
+ // src/components/gcdr-auth/permissions.ts
92438
+ var PERM = {
92439
+ // ── Alarm actions ────────────────────────────────────────────────────────
92440
+ ALARM_VIEW: "alarm:view",
92441
+ ALARM_ACK: "alarm:ack",
92442
+ ALARM_ESCALATE: "alarm:escalate",
92443
+ ALARM_SNOOZE: "alarm:snooze",
92444
+ ALARM_CLOSE: "alarm:close",
92445
+ // ── Reports ──────────────────────────────────────────────────────────────
92446
+ REPORT_VIEW: "report:view",
92447
+ REPORT_EXPORT: "report:export",
92448
+ // ── Settings modal ───────────────────────────────────────────────────────
92449
+ SETTINGS_VIEW: "settings:view",
92450
+ SETTINGS_EDIT: "settings:edit",
92451
+ // ── User management (GCDR) ───────────────────────────────────────────────
92452
+ USER_VIEW: "user:view",
92453
+ USER_MANAGE: "user:manage",
92454
+ // ── Integration config ───────────────────────────────────────────────────
92455
+ INTEGRATION_VIEW: "integration:view",
92456
+ INTEGRATION_EDIT: "integration:edit",
92457
+ // ── Dashboard config ─────────────────────────────────────────────────────
92458
+ DASHBOARD_CONFIG: "dashboard:configure",
92459
+ // ── Shopping / customer selector ─────────────────────────────────────────
92460
+ SHOPPING_SELECT: "shopping:select"
92461
+ };
92462
+
91741
92463
  // src/components/menu/types.ts
91742
92464
  var DEFAULT_LIGHT_THEME3 = {
91743
92465
  // Tabs
@@ -136086,7 +136808,7 @@ function bindFilterBar(card, bundle, getViewMode) {
136086
136808
  const lower = text.toLowerCase();
136087
136809
  const types = /* @__PURE__ */ new Set();
136088
136810
  bundle.devices.forEach((d) => {
136089
- const name = (d.displayName || d.name).toLowerCase();
136811
+ const name = (d.entityLabel || d.displayName || d.name).toLowerCase();
136090
136812
  if (!lower || name.includes(lower) || d.type.toLowerCase().includes(lower)) {
136091
136813
  types.add(d.type);
136092
136814
  }
@@ -136162,7 +136884,7 @@ function renderDevice(device, rules, viewMode = "granular") {
136162
136884
  <div class="abm-device-header">
136163
136885
  <span class="abm-chevron">\u25BE</span>
136164
136886
  <span style="font-size:16px;">\u{1F4E1}</span>
136165
- <span class="abm-device-name">${escHtml3(device.displayName || device.name)}</span>
136887
+ <span class="abm-device-name">${escHtml3(device.entityLabel || device.displayName || device.name)}</span>
136166
136888
  ${_alarmBadgeHtml(alarmCount)}
136167
136889
  <span class="abm-device-type">${escHtml3(device.type)}</span>
136168
136890
  </div>
@@ -136212,7 +136934,7 @@ function renderByRuleView(bundle) {
136212
136934
  return `
136213
136935
  <li class="abm-rule-group-device" data-device-id="${escHtml3(d.id)}">
136214
136936
  <span style="font-size:13px;">\u{1F4E1}</span>
136215
- <span>${escHtml3(d.displayName || d.name)}</span>
136937
+ <span>${escHtml3(d.entityLabel || d.displayName || d.name)}</span>
136216
136938
  <span style="font-size:11px;color:#888;">${escHtml3(d.type)}</span>
136217
136939
  ${overridePart}
136218
136940
  </li>`;
@@ -139160,6 +139882,7 @@ var version = package_default.version || "0.0.0";
139160
139882
  MenuShoppingView,
139161
139883
  MenuView,
139162
139884
  ModalHeader,
139885
+ MyIOAuthContext,
139163
139886
  MyIOChartModal,
139164
139887
  MyIODraggableCard,
139165
139888
  MyIOSelectionStore,
@@ -139188,6 +139911,7 @@ var version = package_default.version || "0.0.0";
139188
139911
  OperationalGeneralListController,
139189
139912
  OperationalGeneralListView,
139190
139913
  OperationalHeaderDevicesGridView,
139914
+ PERM,
139191
139915
  POWER_LIMITS_DEVICE_TYPES,
139192
139916
  POWER_LIMITS_STATUS_CONFIG,
139193
139917
  POWER_LIMITS_TELEMETRY_TYPES,
@@ -139456,6 +140180,7 @@ var version = package_default.version || "0.0.0";
139456
140180
  groupByDay,
139457
140181
  handleDeviceType,
139458
140182
  hasSelectedDays,
140183
+ initMyIOAuthContext,
139459
140184
  initOnOffTimelineTooltips,
139460
140185
  injectActionButtonStyles,
139461
140186
  injectAlarmsNotificationsPanelStyles,