myio-js-library 0.1.487 → 0.1.489

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
@@ -1159,7 +1159,7 @@ module.exports = __toCommonJS(index_exports);
1159
1159
  // package.json
1160
1160
  var package_default = {
1161
1161
  name: "myio-js-library",
1162
- version: "0.1.487",
1162
+ version: "0.1.489",
1163
1163
  description: "A clean, standalone JS SDK for MYIO projects",
1164
1164
  license: "MIT",
1165
1165
  repository: "github:gh-myio/myio-js-library",
@@ -88359,6 +88359,9 @@ var UserListTab = class {
88359
88359
  loading = false;
88360
88360
  users = [];
88361
88361
  highlightedUserId = null;
88362
+ gcdrConfigs = /* @__PURE__ */ new Map();
88363
+ gcdrSyncing = /* @__PURE__ */ new Set();
88364
+ syncTooltipEl = null;
88362
88365
  constructor(config, callbacks) {
88363
88366
  this.config = config;
88364
88367
  this.callbacks = callbacks;
@@ -88384,6 +88387,8 @@ var UserListTab = class {
88384
88387
  <th>Nome</th>
88385
88388
  <th>E-mail</th>
88386
88389
  <th>Perfil</th>
88390
+ <th>Status</th>
88391
+ <th class="um-col-gcdr">GCDR</th>
88387
88392
  <th class="um-col-actions">A\xE7\xF5es</th>
88388
88393
  </tr>
88389
88394
  </thead>
@@ -88449,6 +88454,7 @@ var UserListTab = class {
88449
88454
  this.totalPages = page.totalPages;
88450
88455
  this.renderRows();
88451
88456
  this.renderPagination(page);
88457
+ this.fetchGcdrConfigsBatch();
88452
88458
  } catch (err) {
88453
88459
  console.error("[UserListTab] fetchUsers error", err);
88454
88460
  this.callbacks.showToast("Erro ao carregar usu\xE1rios. Tente novamente.", "error");
@@ -88475,11 +88481,36 @@ var UserListTab = class {
88475
88481
  }
88476
88482
  const name = [user.firstName, user.lastName].filter(Boolean).join(" ") || "\u2014";
88477
88483
  const role = user.authority === "TENANT_ADMIN" ? "Admin" : "Usu\xE1rio";
88484
+ const statusBadge = this.buildStatusBadge(user);
88485
+ const uid2 = user.id.id;
88478
88486
  tr.innerHTML = `
88479
88487
  <td>${this.esc(name)}</td>
88480
88488
  <td>${this.esc(user.email)}</td>
88481
88489
  <td><span class="um-badge um-badge--${user.authority === "TENANT_ADMIN" ? "admin" : "user"}">${role}</span></td>
88490
+ <td>${statusBadge}</td>
88491
+ <td class="um-col-gcdr">
88492
+ <span class="um-sync-icon um-sync-icon--loading" data-sync-uid="${uid2}">
88493
+ <span class="um-spinner" style="width:12px;height:12px;border-width:1.5px;display:block;margin:0 auto;"></span>
88494
+ </span>
88495
+ <button class="um-icon-btn um-force-sync-btn" title="Sincronizar com GCDR" style="margin-left:2px;">
88496
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
88497
+ stroke-linecap="round" stroke-linejoin="round">
88498
+ <polyline points="23 4 23 10 17 10"/>
88499
+ <polyline points="1 20 1 14 7 14"/>
88500
+ <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"/>
88501
+ </svg>
88502
+ </button>
88503
+ </td>
88482
88504
  <td class="um-col-actions">
88505
+ <button class="um-icon-btn um-assign-btn" title="Ver Fun\xE7\xF5es / Pap\xE9is" style="margin-right:4px;">
88506
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
88507
+ stroke-linecap="round" stroke-linejoin="round">
88508
+ <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
88509
+ <circle cx="9" cy="7" r="4"/>
88510
+ <path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
88511
+ <path d="M16 3.13a4 4 0 0 1 0 7.75"/>
88512
+ </svg>
88513
+ </button>
88483
88514
  <button class="um-icon-btn um-detail-btn" title="Ver Detalhes">
88484
88515
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
88485
88516
  stroke-linecap="round" stroke-linejoin="round">
@@ -88489,6 +88520,11 @@ var UserListTab = class {
88489
88520
  </td>
88490
88521
  `;
88491
88522
  tr.querySelector(".um-detail-btn").addEventListener("click", () => this.callbacks.onOpenUserDetail(user, false));
88523
+ tr.querySelector(".um-assign-btn").addEventListener("click", (e) => this.showAssignmentsPopup(user, e.currentTarget));
88524
+ tr.querySelector(".um-force-sync-btn").addEventListener("click", () => this.syncUserToGCDR(user));
88525
+ const syncIcon = tr.querySelector(".um-sync-icon");
88526
+ syncIcon.addEventListener("mouseenter", (e) => this.showSyncTooltip(user, e.currentTarget));
88527
+ syncIcon.addEventListener("mouseleave", () => this.hideSyncTooltip());
88492
88528
  tbody.appendChild(tr);
88493
88529
  }
88494
88530
  }
@@ -88512,9 +88548,321 @@ var UserListTab = class {
88512
88548
  if (loadEl) loadEl.style.display = on ? "" : "none";
88513
88549
  if (tableWrap) tableWrap.style.opacity = on ? "0.5" : "1";
88514
88550
  }
88551
+ buildStatusBadge(user) {
88552
+ const enabled = user.additionalInfo?.userCredentialsEnabled;
88553
+ if (enabled === false) {
88554
+ return `<span class="um-badge um-badge--blocked">Bloqueado</span>`;
88555
+ }
88556
+ return `<span class="um-badge um-badge--active">Ativo</span>`;
88557
+ }
88558
+ gcdrHeaders() {
88559
+ const orch = window.MyIOOrchestrator;
88560
+ return {
88561
+ "Content-Type": "application/json",
88562
+ "X-API-Key": orch?.gcdrApiKey || "",
88563
+ "X-Tenant-ID": orch?.gcdrTenantId || ""
88564
+ };
88565
+ }
88566
+ gcdrBase() {
88567
+ return window.MyIOOrchestrator?.gcdrApiBaseUrl || "";
88568
+ }
88569
+ unwrapList(json) {
88570
+ if (Array.isArray(json)) return json;
88571
+ const j = json;
88572
+ if (j?.data && typeof j.data === "object") {
88573
+ const d = j.data;
88574
+ if (Array.isArray(d.items)) return d.items;
88575
+ if (Array.isArray(d)) return d;
88576
+ }
88577
+ if (Array.isArray(j?.items)) return j.items;
88578
+ return [];
88579
+ }
88580
+ async showAssignmentsPopup(user, anchor) {
88581
+ document.querySelector(".um-assign-popup")?.remove();
88582
+ const popup = document.createElement("div");
88583
+ popup.className = "um-assign-popup";
88584
+ popup.setAttribute("data-theme", this.config.theme || "light");
88585
+ const displayName = [user.firstName, user.lastName].filter(Boolean).join(" ") || user.email;
88586
+ popup.innerHTML = `
88587
+ <div class="um-assign-popup-header">
88588
+ <span class="um-assign-popup-title">\u{1F511} Fun\xE7\xF5es \u2014 ${this.esc(displayName)}</span>
88589
+ <button class="um-assign-popup-close" type="button">\u2715</button>
88590
+ </div>
88591
+ <div class="um-assign-popup-body">
88592
+ <div class="um-assign-popup-loading" style="font-size:12px;color:var(--um-text-muted);padding:8px 0;">
88593
+ <span class="um-spinner" style="display:inline-block;"></span> Carregando...
88594
+ </div>
88595
+ </div>
88596
+ `;
88597
+ document.body.appendChild(popup);
88598
+ const rect = anchor.getBoundingClientRect();
88599
+ const popupW = 320;
88600
+ const left = Math.min(rect.right + 8, window.innerWidth - popupW - 12);
88601
+ popup.style.top = `${Math.max(rect.top - 10, 8)}px`;
88602
+ popup.style.left = `${left}px`;
88603
+ const close = () => popup.remove();
88604
+ popup.querySelector(".um-assign-popup-close").addEventListener("click", close);
88605
+ const outsideHandler = (e) => {
88606
+ if (!popup.contains(e.target) && e.target !== anchor) {
88607
+ close();
88608
+ document.removeEventListener("click", outsideHandler, true);
88609
+ }
88610
+ };
88611
+ setTimeout(() => document.addEventListener("click", outsideHandler, true), 50);
88612
+ const body = popup.querySelector(".um-assign-popup-body");
88613
+ const base = this.gcdrBase();
88614
+ if (!base) {
88615
+ body.innerHTML = `<div style="font-size:12px;color:var(--um-toast-err-text);padding:8px 0;">GCDR n\xE3o configurado.</div>`;
88616
+ return;
88617
+ }
88618
+ try {
88619
+ const res = await fetch(`${base}/authorization/users/${user.id.id}/assignments`, { headers: this.gcdrHeaders() });
88620
+ let assignments = [];
88621
+ if (res.ok) {
88622
+ const json = await res.json();
88623
+ assignments = Array.isArray(json) ? json : json.assignments ?? [];
88624
+ }
88625
+ if (assignments.length === 0) {
88626
+ body.innerHTML = `<div style="font-size:12px;color:var(--um-text-faint);padding:8px 0;">Nenhuma fun\xE7\xE3o atribu\xEDda.</div>`;
88627
+ return;
88628
+ }
88629
+ const statusColors = {
88630
+ active: "var(--um-badge-active-text)",
88631
+ expired: "var(--um-badge-blocked-text)",
88632
+ inactive: "var(--um-text-faint)"
88633
+ };
88634
+ body.innerHTML = assignments.map((a) => {
88635
+ 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);
88636
+ const expires = a.expiresAt ? new Date(a.expiresAt).toLocaleDateString("pt-BR") : null;
88637
+ const color = statusColors[a.status] || "var(--um-text-faint)";
88638
+ return `
88639
+ <div class="um-assign-row">
88640
+ <span class="um-assign-role">${this.esc(a.roleDisplayName || a.roleKey)}</span>
88641
+ <span class="um-assign-meta">
88642
+ Escopo: ${scopeLabel}
88643
+ \xB7 <span style="color:${color};font-weight:600;">${a.status}</span>
88644
+ ${expires ? ` \xB7 Expira ${expires}` : ""}
88645
+ </span>
88646
+ </div>`;
88647
+ }).join("");
88648
+ } catch {
88649
+ body.innerHTML = `<div style="font-size:12px;color:var(--um-toast-err-text);padding:8px 0;">Erro ao carregar.</div>`;
88650
+ }
88651
+ }
88515
88652
  esc(s) {
88516
88653
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
88517
88654
  }
88655
+ // ── GCDR Sync Column ─────────────────────────────────────────────────────────
88656
+ async fetchGcdrConfigsBatch() {
88657
+ if (this.users.length === 0) return;
88658
+ const { tbBaseUrl, jwtToken } = this.config;
88659
+ await Promise.allSettled(this.users.map(async (user) => {
88660
+ const uid2 = user.id.id;
88661
+ try {
88662
+ const res = await fetch(
88663
+ `${tbBaseUrl}/api/plugins/telemetry/USER/${uid2}/values/attributes/SERVER_SCOPE?keys=gcdrUserConfigs`,
88664
+ { headers: { "X-Authorization": `Bearer ${jwtToken}` } }
88665
+ );
88666
+ let cfg = null;
88667
+ if (res.ok) {
88668
+ const attrs = await res.json();
88669
+ const entry = attrs.find((a) => a.key === "gcdrUserConfigs");
88670
+ if (entry?.value && typeof entry.value === "object") {
88671
+ cfg = entry.value;
88672
+ }
88673
+ }
88674
+ this.gcdrConfigs.set(uid2, cfg);
88675
+ this.updateSyncCell(uid2, cfg ? cfg.lastSyncResult ?? "none" : "none", cfg);
88676
+ } catch {
88677
+ this.updateSyncCell(uid2, "none", null);
88678
+ }
88679
+ }));
88680
+ }
88681
+ updateSyncCell(userId, state6, cfg = null) {
88682
+ const iconEl = this.el?.querySelector(`.um-sync-icon[data-sync-uid="${userId}"]`);
88683
+ if (!iconEl) return;
88684
+ iconEl.className = "um-sync-icon";
88685
+ if (state6 === "loading" || state6 === "syncing") {
88686
+ iconEl.innerHTML = `<span class="um-spinner" style="width:12px;height:12px;border-width:1.5px;display:block;margin:0 auto;"></span>`;
88687
+ return;
88688
+ }
88689
+ const gcdrStatus = cfg?.gcdrStatus;
88690
+ let dotClass = "um-sync-dot--none";
88691
+ let dotTitle = "Nunca sincronizado";
88692
+ if (state6 === "success") {
88693
+ dotClass = gcdrStatus === "ACTIVE" ? "um-sync-dot--active" : gcdrStatus === "INACTIVE" || gcdrStatus === "LOCKED" ? "um-sync-dot--warn" : "um-sync-dot--ok";
88694
+ dotTitle = `GCDR: ${gcdrStatus ?? "OK"}`;
88695
+ } else if (state6 === "error") {
88696
+ dotClass = "um-sync-dot--err";
88697
+ dotTitle = cfg?.lastError ?? "Erro";
88698
+ }
88699
+ iconEl.innerHTML = `<span class="um-sync-dot ${dotClass}" title="${this.esc(dotTitle)}"></span>`;
88700
+ }
88701
+ showSyncTooltip(user, anchor) {
88702
+ this.hideSyncTooltip();
88703
+ const uid2 = user.id.id;
88704
+ const cfg = this.gcdrConfigs.get(uid2);
88705
+ const tooltip = document.createElement("div");
88706
+ tooltip.className = "um-sync-tooltip";
88707
+ tooltip.setAttribute("data-theme", this.config.theme || "light");
88708
+ this.syncTooltipEl = tooltip;
88709
+ const syncing = this.gcdrSyncing.has(uid2);
88710
+ const displayName = [user.firstName, user.lastName].filter(Boolean).join(" ") || user.email;
88711
+ let bodyHtml;
88712
+ if (syncing) {
88713
+ bodyHtml = `<div class="um-sync-tooltip-row" style="justify-content:center;padding:8px 0;">
88714
+ <span class="um-spinner" style="width:13px;height:13px;"></span>&nbsp;Sincronizando...
88715
+ </div>`;
88716
+ } else if (!cfg) {
88717
+ bodyHtml = `<div class="um-sync-tooltip-row um-sync-tooltip-row--muted">Nunca sincronizado com GCDR.</div>`;
88718
+ } else {
88719
+ const syncedAt = cfg.syncedAt ? new Date(cfg.syncedAt).toLocaleString("pt-BR") : "\u2014";
88720
+ const updatedAt = cfg.updatedAt ? new Date(cfg.updatedAt).toLocaleString("pt-BR") : "\u2014";
88721
+ const resultColor = cfg.lastSyncResult === "success" ? "var(--um-badge-active-text)" : "var(--um-badge-blocked-text)";
88722
+ bodyHtml = `
88723
+ <div class="um-sync-tooltip-row">
88724
+ <span class="um-sync-tooltip-label">GCDR ID</span>
88725
+ <span class="um-sync-tooltip-value" style="font-family:monospace;font-size:10px;">${cfg.gcdrUserId ? cfg.gcdrUserId.slice(0, 16) + "\u2026" : "\u2014"}</span>
88726
+ </div>
88727
+ <div class="um-sync-tooltip-row">
88728
+ <span class="um-sync-tooltip-label">Status GCDR</span>
88729
+ <span class="um-sync-tooltip-value">${cfg.gcdrStatus ?? "\u2014"}</span>
88730
+ </div>
88731
+ <div class="um-sync-tooltip-row">
88732
+ <span class="um-sync-tooltip-label">\xDAltimo sync</span>
88733
+ <span class="um-sync-tooltip-value">${syncedAt}</span>
88734
+ </div>
88735
+ <div class="um-sync-tooltip-row">
88736
+ <span class="um-sync-tooltip-label">Atualizado</span>
88737
+ <span class="um-sync-tooltip-value">${updatedAt}</span>
88738
+ </div>
88739
+ <div class="um-sync-tooltip-row">
88740
+ <span class="um-sync-tooltip-label">Qtd syncs</span>
88741
+ <span class="um-sync-tooltip-value">${cfg.syncCount ?? 0}</span>
88742
+ </div>
88743
+ <div class="um-sync-tooltip-row">
88744
+ <span class="um-sync-tooltip-label">Resultado</span>
88745
+ <span class="um-sync-tooltip-value" style="color:${resultColor};font-weight:700;">${cfg.lastSyncResult === "success" ? "\u2713 Sucesso" : "\u2717 Erro"}</span>
88746
+ </div>
88747
+ ${cfg.lastError ? `<div class="um-sync-tooltip-row um-sync-tooltip-row--error">
88748
+ <span class="um-sync-tooltip-label">Erro</span>
88749
+ <span class="um-sync-tooltip-value">${this.esc(cfg.lastError)}</span>
88750
+ </div>` : ""}
88751
+ `;
88752
+ }
88753
+ tooltip.innerHTML = `
88754
+ <div class="um-sync-tooltip-header">
88755
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"
88756
+ stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;">
88757
+ <polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/>
88758
+ <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"/>
88759
+ </svg>
88760
+ <span>GCDR Sync \u2014 ${this.esc(displayName)}</span>
88761
+ </div>
88762
+ <div class="um-sync-tooltip-body">${bodyHtml}</div>
88763
+ `;
88764
+ const backdrop = this.el.closest(".um-backdrop") ?? document.body;
88765
+ backdrop.appendChild(tooltip);
88766
+ const rect = anchor.getBoundingClientRect();
88767
+ const ttW = 260;
88768
+ let left = rect.right + 8;
88769
+ if (left + ttW > window.innerWidth - 8) left = rect.left - ttW - 8;
88770
+ tooltip.style.left = `${Math.max(4, left)}px`;
88771
+ tooltip.style.top = `${Math.max(4, rect.top - 10)}px`;
88772
+ }
88773
+ hideSyncTooltip() {
88774
+ this.syncTooltipEl?.remove();
88775
+ this.syncTooltipEl = null;
88776
+ }
88777
+ async syncUserToGCDR(user) {
88778
+ const uid2 = user.id.id;
88779
+ if (this.gcdrSyncing.has(uid2)) return;
88780
+ const base = this.gcdrBase();
88781
+ if (!base) {
88782
+ this.callbacks.showToast("GCDR n\xE3o configurado.", "error");
88783
+ return;
88784
+ }
88785
+ this.gcdrSyncing.add(uid2);
88786
+ this.updateSyncCell(uid2, "syncing");
88787
+ const prev = this.gcdrConfigs.get(uid2) ?? null;
88788
+ const syncCount = (prev?.syncCount ?? 0) + 1;
88789
+ const now = (/* @__PURE__ */ new Date()).toISOString();
88790
+ try {
88791
+ const searchRes = await fetch(
88792
+ `${base}/api/v1/users?search=${encodeURIComponent(user.email)}&customerId=${encodeURIComponent(this.config.customerId)}&limit=10`,
88793
+ { headers: this.gcdrHeaders() }
88794
+ );
88795
+ let gcdrUser = null;
88796
+ if (searchRes.ok) {
88797
+ const data = await searchRes.json();
88798
+ const items = Array.isArray(data) ? data : data?.data?.items ?? data?.items ?? [];
88799
+ gcdrUser = items.find((u) => u.email?.toLowerCase() === user.email.toLowerCase()) ?? null;
88800
+ }
88801
+ if (!gcdrUser) {
88802
+ const createRes = await fetch(`${base}/api/v1/users`, {
88803
+ method: "POST",
88804
+ headers: this.gcdrHeaders(),
88805
+ body: JSON.stringify({
88806
+ email: user.email,
88807
+ firstName: user.firstName || "",
88808
+ lastName: user.lastName || "",
88809
+ customerId: this.config.customerId,
88810
+ type: "CUSTOMER"
88811
+ })
88812
+ });
88813
+ if (!createRes.ok) throw new Error(`Criar usu\xE1rio GCDR: HTTP ${createRes.status}`);
88814
+ gcdrUser = await createRes.json();
88815
+ }
88816
+ const configs = {
88817
+ gcdrUserId: gcdrUser.id,
88818
+ gcdrStatus: gcdrUser.status,
88819
+ gcdrType: gcdrUser.type,
88820
+ syncedAt: now,
88821
+ syncCount,
88822
+ lastSyncResult: "success",
88823
+ lastError: null,
88824
+ createdAt: prev?.createdAt ?? now,
88825
+ updatedAt: now
88826
+ };
88827
+ await this.saveTBAttribute(uid2, configs);
88828
+ this.gcdrConfigs.set(uid2, configs);
88829
+ this.updateSyncCell(uid2, "success", configs);
88830
+ this.callbacks.showToast(`Sync GCDR conclu\xEDdo \u2014 ${user.email}`, "success");
88831
+ } catch (err) {
88832
+ const configs = {
88833
+ ...prev ?? {},
88834
+ syncedAt: now,
88835
+ syncCount,
88836
+ lastSyncResult: "error",
88837
+ lastError: String(err?.message ?? "Erro desconhecido"),
88838
+ updatedAt: now
88839
+ };
88840
+ try {
88841
+ await this.saveTBAttribute(uid2, configs);
88842
+ } catch {
88843
+ }
88844
+ this.gcdrConfigs.set(uid2, configs);
88845
+ this.updateSyncCell(uid2, "error", configs);
88846
+ this.callbacks.showToast(`Erro sync GCDR: ${err?.message || "Falha"}`, "error");
88847
+ } finally {
88848
+ this.gcdrSyncing.delete(uid2);
88849
+ }
88850
+ }
88851
+ async saveTBAttribute(userId, configs) {
88852
+ const { tbBaseUrl, jwtToken } = this.config;
88853
+ const res = await fetch(
88854
+ `${tbBaseUrl}/api/plugins/telemetry/USER/${userId}/attributes/SERVER_SCOPE`,
88855
+ {
88856
+ method: "POST",
88857
+ headers: {
88858
+ "X-Authorization": `Bearer ${jwtToken}`,
88859
+ "Content-Type": "application/json"
88860
+ },
88861
+ body: JSON.stringify({ gcdrUserConfigs: configs })
88862
+ }
88863
+ );
88864
+ if (!res.ok) throw new Error(`Salvar atributo TB: HTTP ${res.status}`);
88865
+ }
88518
88866
  };
88519
88867
 
88520
88868
  // src/components/premium-modals/user-management/tabs/NewUserTab.ts
@@ -90001,7 +90349,12 @@ var UserDetailTab = class {
90001
90349
  fetch(`${this.gcdrBase()}/roles?limit=100`, { headers: this.gcdrHeaders() }),
90002
90350
  fetch(`${this.gcdrBase()}/policies?limit=100`, { headers: this.gcdrHeaders() })
90003
90351
  ]);
90004
- this.assignments = assignRes.ok ? this.unwrapList(await assignRes.json()) : [];
90352
+ if (assignRes.ok) {
90353
+ const assignJson = await assignRes.json();
90354
+ this.assignments = Array.isArray(assignJson) ? assignJson : assignJson.assignments ?? [];
90355
+ } else {
90356
+ this.assignments = [];
90357
+ }
90005
90358
  this.availableRoles = rolesRes.ok ? this.unwrapList(await rolesRes.json()) : [];
90006
90359
  this.availablePolicies = policiesRes.ok ? this.unwrapList(await policiesRes.json()) : [];
90007
90360
  this.renderAssignments();
@@ -90028,11 +90381,12 @@ var UserDetailTab = class {
90028
90381
  const tbody = document.createElement("tbody");
90029
90382
  this.assignments.forEach((a) => {
90030
90383
  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)";
90384
+ const statusColor = a.status === "active" ? "var(--um-badge-active-text)" : a.status === "expired" ? "var(--um-badge-blocked-text)" : "var(--um-text-faint)";
90032
90385
  const expiresAt = a.expiresAt ? new Date(a.expiresAt).toLocaleDateString("pt-BR") : "\u2014";
90386
+ 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
90387
  tr.innerHTML = `
90034
90388
  <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>
90389
+ <td style="font-size:12px;">${this.esc(scopeLabel)}</td>
90036
90390
  <td><span style="color:${statusColor};font-weight:600;">${a.status}</span></td>
90037
90391
  <td>${expiresAt}</td>
90038
90392
  <td style="text-align:center;"><button class="um-btn um-btn--danger um-btn--sm revoke-btn">Revogar</button></td>
@@ -90142,13 +90496,14 @@ var UserDetailTab = class {
90142
90496
  const expiresAt = expiresAtRaw ? new Date(expiresAtRaw).toISOString() : null;
90143
90497
  const reason = modal.querySelector("[name=reason]").value.trim() || null;
90144
90498
  const role = this.availableRoles.find((r) => r.id === roleId);
90499
+ const roleKey = role?.key || role?.id || roleId;
90145
90500
  const btn = modal.querySelector(".assign-save");
90146
90501
  btn.disabled = true;
90147
90502
  btn.textContent = "...";
90148
90503
  try {
90149
90504
  const body = {
90150
90505
  userId: this.user.id.id,
90151
- roleId,
90506
+ roleKey,
90152
90507
  scope,
90153
90508
  expiresAt,
90154
90509
  reason
@@ -91202,6 +91557,12 @@ var UserManagementModalView = class {
91202
91557
  --um-badge-admin-text: #3b5bdb;
91203
91558
  --um-badge-user-bg: #f0fdf4;
91204
91559
  --um-badge-user-text: #16a34a;
91560
+ --um-badge-active-bg: #dcfce7;
91561
+ --um-badge-active-text: #15803d;
91562
+ --um-badge-blocked-bg: #fee2e2;
91563
+ --um-badge-blocked-text: #b91c1c;
91564
+ --um-badge-pending-bg: #fef9c3;
91565
+ --um-badge-pending-text: #854d0e;
91205
91566
  --um-toast-ok-bg: #f0fdf4;
91206
91567
  --um-toast-ok-border: #22c55e;
91207
91568
  --um-toast-ok-text: #16a34a;
@@ -91262,6 +91623,12 @@ var UserManagementModalView = class {
91262
91623
  --um-badge-admin-text: #60a5fa;
91263
91624
  --um-badge-user-bg: #1a2d24;
91264
91625
  --um-badge-user-text: #4ade80;
91626
+ --um-badge-active-bg: #14532d;
91627
+ --um-badge-active-text: #86efac;
91628
+ --um-badge-blocked-bg: #450a0a;
91629
+ --um-badge-blocked-text: #fca5a5;
91630
+ --um-badge-pending-bg: #422006;
91631
+ --um-badge-pending-text: #fde68a;
91265
91632
  --um-toast-ok-bg: #1e3a2e;
91266
91633
  --um-toast-ok-border: #22c55e;
91267
91634
  --um-toast-ok-text: #4ade80;
@@ -91291,14 +91658,21 @@ var UserManagementModalView = class {
91291
91658
  .um-modal {
91292
91659
  background: var(--um-modal-bg);
91293
91660
  font-family: 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
91294
- border-radius: 14px; width: 92vw; max-width: 960px;
91661
+ border-radius: 14px; width: 96vw; max-width: 1200px;
91295
91662
  --modal-header-radius: 14px 14px 0 0;
91296
- aspect-ratio: 16/9; display: flex; flex-direction: column;
91663
+ height: 84vh; display: flex; flex-direction: column;
91297
91664
  box-shadow: var(--um-shadow); overflow: hidden; position: relative;
91298
91665
  }
91299
- @media (max-height: 600px) { .um-modal { aspect-ratio: unset; height: 90vh; } }
91666
+ @media (max-height: 600px) { .um-modal { height: 96vh; } }
91300
91667
 
91301
91668
  /* Header handled by ModalHeader (RFC-0121) */
91669
+ .um-modal .myio-modal-header__title {
91670
+ font-size: 14px !important;
91671
+ font-weight: 600 !important;
91672
+ }
91673
+ .um-modal .myio-modal-header__icon {
91674
+ font-size: 15px !important;
91675
+ }
91302
91676
 
91303
91677
  /* Force MyIO purple header regardless of light/dark theme \u2014 overrides myio-modal-header--light */
91304
91678
  .um-modal .myio-modal-header--light {
@@ -91397,16 +91771,83 @@ var UserManagementModalView = class {
91397
91771
  .um-table { width: 100%; border-collapse: collapse; font-size: 13px; }
91398
91772
  .um-table th {
91399
91773
  text-align: left; padding: 8px 12px;
91400
- color: var(--um-text-muted); font-weight: 500; font-size: 11px;
91774
+ color: var(--um-text-muted); font-weight: 600; font-size: 11px;
91401
91775
  text-transform: uppercase; letter-spacing: 0.04em;
91402
91776
  border-bottom: 1px solid var(--um-border);
91403
91777
  }
91404
91778
  .um-table td {
91405
- padding: 10px 12px; color: var(--um-text-secondary);
91779
+ padding: 10px 12px; color: var(--um-text-primary); font-weight: 500;
91406
91780
  border-bottom: 1px solid var(--um-border-sub);
91407
91781
  }
91408
91782
  .um-table tr:hover td { background: var(--um-bg-surface); }
91409
- .um-col-actions { width: 80px; text-align: center; }
91783
+ .um-col-actions { width: 100px; text-align: center; }
91784
+ .um-col-gcdr { width: 72px; text-align: center; }
91785
+
91786
+ /* Sync status dot */
91787
+ .um-sync-icon { display: inline-flex; align-items: center; justify-content: center; }
91788
+ .um-sync-dot {
91789
+ display: inline-block; width: 10px; height: 10px; border-radius: 50%;
91790
+ flex-shrink: 0;
91791
+ }
91792
+ .um-sync-dot--none { background: var(--um-text-faint); opacity: 0.4; }
91793
+ .um-sync-dot--ok,
91794
+ .um-sync-dot--active { background: var(--um-badge-active-text); }
91795
+ .um-sync-dot--warn { background: var(--um-badge-pending-text); }
91796
+ .um-sync-dot--err { background: var(--um-badge-blocked-text); }
91797
+
91798
+ /* Premium sync tooltip */
91799
+ .um-sync-tooltip {
91800
+ position: fixed; z-index: 100020;
91801
+ background: var(--um-modal-bg); border: 1px solid var(--um-border);
91802
+ border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,0.2);
91803
+ width: 260px; pointer-events: none;
91804
+ font-family: 'Nunito', -apple-system, sans-serif;
91805
+ }
91806
+ .um-sync-tooltip-header {
91807
+ display: flex; align-items: center; gap: 7px;
91808
+ padding: 8px 12px; background: var(--um-accent);
91809
+ border-radius: 10px 10px 0 0;
91810
+ font-size: 11px; font-weight: 700; color: #fff;
91811
+ }
91812
+ .um-sync-tooltip-body {
91813
+ padding: 10px 12px; display: flex; flex-direction: column; gap: 4px;
91814
+ }
91815
+ .um-sync-tooltip-row {
91816
+ display: flex; justify-content: space-between; align-items: flex-start;
91817
+ font-size: 11px; gap: 8px;
91818
+ }
91819
+ .um-sync-tooltip-row--muted { color: var(--um-text-faint); justify-content: center; padding: 4px 0; }
91820
+ .um-sync-tooltip-row--error .um-sync-tooltip-value { color: var(--um-badge-blocked-text); word-break: break-all; }
91821
+ .um-sync-tooltip-label { color: var(--um-text-muted); flex-shrink: 0; }
91822
+ .um-sync-tooltip-value { color: var(--um-text-primary); font-weight: 600; text-align: right; }
91823
+
91824
+ /* Assignments quick-view popup */
91825
+ .um-assign-popup {
91826
+ position: fixed; z-index: 100010;
91827
+ background: var(--um-modal-bg); border: 1px solid var(--um-border);
91828
+ border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,0.18);
91829
+ width: 320px; max-height: 340px; overflow-y: auto;
91830
+ font-family: 'Nunito', -apple-system, sans-serif;
91831
+ }
91832
+ .um-assign-popup-header {
91833
+ display: flex; align-items: center; justify-content: space-between;
91834
+ padding: 10px 14px; background: var(--um-accent);
91835
+ border-radius: 10px 10px 0 0;
91836
+ }
91837
+ .um-assign-popup-title { font-size: 12px; font-weight: 700; color: #fff; }
91838
+ .um-assign-popup-close {
91839
+ background: none; border: none; color: rgba(255,255,255,0.8);
91840
+ font-size: 14px; cursor: pointer; padding: 0 4px; line-height: 1;
91841
+ }
91842
+ .um-assign-popup-body { padding: 10px 14px; }
91843
+ .um-assign-row {
91844
+ display: flex; flex-direction: column; gap: 2px;
91845
+ padding: 7px 0; border-bottom: 1px solid var(--um-border-sub);
91846
+ font-size: 12px;
91847
+ }
91848
+ .um-assign-row:last-child { border-bottom: none; }
91849
+ .um-assign-role { font-weight: 600; color: var(--um-text-primary); }
91850
+ .um-assign-meta { color: var(--um-text-muted); font-size: 11px; }
91410
91851
  .um-row--highlight td { background: var(--um-row-highlight) !important; }
91411
91852
 
91412
91853
  .um-list-empty, .um-list-loading, .um-profiles-loading,
@@ -91442,8 +91883,11 @@ var UserManagementModalView = class {
91442
91883
  .um-btn--sm { padding: 6px 12px; font-size: 12px; }
91443
91884
 
91444
91885
  .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); }
91886
+ .um-badge--admin { background: var(--um-badge-admin-bg); color: var(--um-badge-admin-text); }
91887
+ .um-badge--user { background: var(--um-badge-user-bg); color: var(--um-badge-user-text); }
91888
+ .um-badge--active { background: var(--um-badge-active-bg); color: var(--um-badge-active-text); }
91889
+ .um-badge--blocked { background: var(--um-badge-blocked-bg); color: var(--um-badge-blocked-text); }
91890
+ .um-badge--pending { background: var(--um-badge-pending-bg); color: var(--um-badge-pending-text); }
91447
91891
 
91448
91892
  .um-form { display: flex; flex-direction: column; gap: 14px;}
91449
91893
  .um-form-row { display: flex; gap: 12px; }
@@ -136086,7 +136530,7 @@ function bindFilterBar(card, bundle, getViewMode) {
136086
136530
  const lower = text.toLowerCase();
136087
136531
  const types = /* @__PURE__ */ new Set();
136088
136532
  bundle.devices.forEach((d) => {
136089
- const name = (d.displayName || d.name).toLowerCase();
136533
+ const name = (d.entityLabel || d.displayName || d.name).toLowerCase();
136090
136534
  if (!lower || name.includes(lower) || d.type.toLowerCase().includes(lower)) {
136091
136535
  types.add(d.type);
136092
136536
  }
@@ -136162,7 +136606,7 @@ function renderDevice(device, rules, viewMode = "granular") {
136162
136606
  <div class="abm-device-header">
136163
136607
  <span class="abm-chevron">\u25BE</span>
136164
136608
  <span style="font-size:16px;">\u{1F4E1}</span>
136165
- <span class="abm-device-name">${escHtml3(device.displayName || device.name)}</span>
136609
+ <span class="abm-device-name">${escHtml3(device.entityLabel || device.displayName || device.name)}</span>
136166
136610
  ${_alarmBadgeHtml(alarmCount)}
136167
136611
  <span class="abm-device-type">${escHtml3(device.type)}</span>
136168
136612
  </div>
@@ -136212,7 +136656,7 @@ function renderByRuleView(bundle) {
136212
136656
  return `
136213
136657
  <li class="abm-rule-group-device" data-device-id="${escHtml3(d.id)}">
136214
136658
  <span style="font-size:13px;">\u{1F4E1}</span>
136215
- <span>${escHtml3(d.displayName || d.name)}</span>
136659
+ <span>${escHtml3(d.entityLabel || d.displayName || d.name)}</span>
136216
136660
  <span style="font-size:11px;color:#888;">${escHtml3(d.type)}</span>
136217
136661
  ${overridePart}
136218
136662
  </li>`;