myio-js-library 0.1.485 → 0.1.486

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.485",
1162
+ version: "0.1.486",
1163
1163
  description: "A clean, standalone JS SDK for MYIO projects",
1164
1164
  license: "MIT",
1165
1165
  repository: "github:gh-myio/myio-js-library",
@@ -89876,6 +89876,7 @@ var UserDetailTab = class {
89876
89876
  // RFC-0197: Assignments section state
89877
89877
  assignments = [];
89878
89878
  availableRoles = [];
89879
+ availablePolicies = [];
89879
89880
  assignmentsEl = null;
89880
89881
  assignmentsVersion = 0;
89881
89882
  constructor(config, user, callbacks) {
@@ -89969,8 +89970,8 @@ var UserDetailTab = class {
89969
89970
  const section = document.createElement("div");
89970
89971
  section.style.cssText = "margin-top:20px;border:1px solid var(--um-border);border-radius:10px;overflow:hidden;";
89971
89972
  const sectionHeader = document.createElement("div");
89972
- sectionHeader.style.cssText = "display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:var(--um-bg-surface);border-bottom:1px solid var(--um-border);";
89973
- sectionHeader.innerHTML = `<span style="font-size:13px;font-weight:600;color:var(--um-text-secondary);">\u{1F511} Atribui\xE7\xF5es de Fun\xE7\xF5es</span>`;
89973
+ sectionHeader.style.cssText = "display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:var(--um-accent);border-bottom:1px solid var(--um-btn-2-border);";
89974
+ sectionHeader.innerHTML = `<span style="font-size:13px;font-weight:600;color:#fff;">\u{1F511} Atribui\xE7\xF5es de Fun\xE7\xF5es</span>`;
89974
89975
  const addBtn = document.createElement("button");
89975
89976
  addBtn.className = "um-btn um-btn--secondary um-btn--sm";
89976
89977
  addBtn.textContent = "+ Atribuir Fun\xE7\xE3o";
@@ -89988,12 +89989,14 @@ var UserDetailTab = class {
89988
89989
  async loadAssignments() {
89989
89990
  const userId = this.user.id.id;
89990
89991
  try {
89991
- const [assignRes, rolesRes] = await Promise.all([
89992
+ const [assignRes, rolesRes, policiesRes] = await Promise.all([
89992
89993
  fetch(`${this.gcdrBase()}/authorization/users/${userId}/assignments`, { headers: this.gcdrHeaders() }),
89993
- fetch(`${this.gcdrBase()}/roles?limit=100`, { headers: this.gcdrHeaders() })
89994
+ fetch(`${this.gcdrBase()}/roles?limit=100`, { headers: this.gcdrHeaders() }),
89995
+ fetch(`${this.gcdrBase()}/policies?limit=100`, { headers: this.gcdrHeaders() })
89994
89996
  ]);
89995
89997
  this.assignments = assignRes.ok ? this.unwrapList(await assignRes.json()) : [];
89996
89998
  this.availableRoles = rolesRes.ok ? this.unwrapList(await rolesRes.json()) : [];
89999
+ this.availablePolicies = policiesRes.ok ? this.unwrapList(await policiesRes.json()) : [];
89997
90000
  this.renderAssignments();
89998
90001
  } catch (err) {
89999
90002
  console.error("[UserDetailTab] loadAssignments error", err);
@@ -90058,6 +90061,10 @@ var UserDetailTab = class {
90058
90061
  </select>
90059
90062
  <span class="um-field-error" data-for="roleId"></span>
90060
90063
  </div>
90064
+ <div class="um-assign-policies-preview" style="display:none;border:1px solid var(--um-border);border-radius:8px;padding:12px;background:var(--um-bg-surface);">
90065
+ <div style="font-size:11px;font-weight:600;color:var(--um-text-muted);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:8px;">Pol\xEDticas inclu\xEDdas</div>
90066
+ <div class="um-assign-policies-list" style="display:flex;flex-direction:column;gap:6px;"></div>
90067
+ </div>
90061
90068
  <div class="um-form-group">
90062
90069
  <label class="um-label">Escopo <span class="um-req">*</span></label>
90063
90070
  <select class="um-input" name="scope">
@@ -90080,6 +90087,29 @@ var UserDetailTab = class {
90080
90087
  `;
90081
90088
  overlay.appendChild(modal);
90082
90089
  document.body.appendChild(overlay);
90090
+ const roleSelect = modal.querySelector("[name=roleId]");
90091
+ const policiesPreview = modal.querySelector(".um-assign-policies-preview");
90092
+ const policiesList = modal.querySelector(".um-assign-policies-list");
90093
+ roleSelect.addEventListener("change", () => {
90094
+ const role = this.availableRoles.find((r) => r.id === roleSelect.value);
90095
+ const policyRefs = role?.policies ?? role?.policyIds ?? [];
90096
+ if (!role || policyRefs.length === 0) {
90097
+ policiesPreview.style.display = "none";
90098
+ return;
90099
+ }
90100
+ const policyByKey = new Map(this.availablePolicies.flatMap((p) => [
90101
+ [p.key || p.id, p],
90102
+ [p.id, p]
90103
+ ]));
90104
+ const matched = policyRefs.map((ref) => policyByKey.get(ref)).filter(Boolean);
90105
+ policiesList.innerHTML = matched.length > 0 ? matched.map((p) => `
90106
+ <div style="display:flex;align-items:flex-start;gap:8px;padding:6px 8px;background:var(--um-bg-input);border-radius:6px;">
90107
+ <span style="font-size:11px;font-weight:600;background:var(--um-btn-2-bg);color:var(--um-btn-2-text);border:1px solid var(--um-btn-2-border);border-radius:9999px;padding:2px 8px;white-space:nowrap;">${this.esc(p.displayName)}</span>
90108
+ ${p.description ? `<span style="font-size:11px;color:var(--um-text-muted);padding-top:2px;">${this.esc(p.description)}</span>` : ""}
90109
+ </div>`).join("") : policyRefs.map((ref) => `
90110
+ <span style="font-size:11px;font-family:monospace;background:var(--um-btn-2-bg);color:var(--um-btn-2-text);border:1px solid var(--um-btn-2-border);border-radius:9999px;padding:2px 8px;">${this.esc(ref)}</span>`).join("");
90111
+ policiesPreview.style.display = "block";
90112
+ });
90083
90113
  const close = () => overlay.remove();
90084
90114
  overlay.addEventListener("click", (e) => {
90085
90115
  if (e.target === overlay) close();
@@ -90697,7 +90727,8 @@ var RolesTab = class {
90697
90727
  const item = document.createElement("div");
90698
90728
  item.className = `gm-accordion-item${this.expandedId === r.id ? " gm-accordion-item--open" : ""}`;
90699
90729
  const sysBadge = r.isSystem ? `<span class="gm-badge gm-badge--count">SISTEMA</span>` : "";
90700
- const policyCount = r.policyIds?.length || 0;
90730
+ const policyKeys = r.policies ?? r.policyIds ?? [];
90731
+ const policyCount = policyKeys.length;
90701
90732
  const policyBadge = `<span class="gm-badge gm-badge--domain">${policyCount} pol\xEDticas</span>`;
90702
90733
  item.innerHTML = `
90703
90734
  <div class="gm-accordion-header">
@@ -90737,20 +90768,26 @@ var RolesTab = class {
90737
90768
  return item;
90738
90769
  }
90739
90770
  buildRolePoliciesHtml(r) {
90740
- if (!r.policyIds?.length) {
90771
+ const policyKeys = r.policies ?? r.policyIds ?? [];
90772
+ if (!policyKeys.length) {
90741
90773
  return `<div class="gm-panel-section"><span class="gm-empty-inline">Nenhuma pol\xEDtica associada.</span></div>`;
90742
90774
  }
90743
- const policyMap = new Map(this.policies.map((p) => [p.id, p]));
90744
- const items = r.policyIds.map((pid) => {
90745
- const p = policyMap.get(pid);
90775
+ const policyByKey = new Map(this.policies.flatMap((p) => [
90776
+ [p.key || p.id, p],
90777
+ [p.id, p]
90778
+ ]));
90779
+ const items = policyKeys.map((ref) => {
90780
+ const p = policyByKey.get(ref);
90746
90781
  if (!p)
90747
- return `<div style="font-size:12px;color:var(--um-text-faint);padding:3px 0;">ID: ${this.esc(pid)}</div>`;
90748
- const allowStr = p.allow?.join(", ") || "\u2014";
90782
+ return `<div style="padding:6px 0;border-bottom:1px solid var(--um-border-sub);">
90783
+ <span style="font-size:11px;font-family:monospace;background:var(--um-btn-2-bg);color:var(--um-btn-2-text);border:1px solid var(--um-btn-2-border);border-radius:9999px;padding:2px 8px;">${this.esc(ref)}</span>
90784
+ </div>`;
90785
+ const allowStr = p.allow?.length ? p.allow.join(", ") : "\u2014";
90749
90786
  return `<div style="padding:6px 0;border-bottom:1px solid var(--um-border-sub);">
90750
- <div style="font-size:12px;font-weight:600;color:var(--um-text-secondary);">${this.esc(p.displayName)}</div>
90751
- ${p.description ? `<div style="font-size:11px;color:var(--um-text-faint);">${this.esc(p.description)}</div>` : ""}
90752
- <div style="font-size:11px;color:var(--um-badge-user-text);margin-top:2px;">allow: ${this.esc(allowStr)}</div>
90753
- </div>`;
90787
+ <div style="font-size:12px;font-weight:600;color:var(--um-text-secondary);">${this.esc(p.displayName)}</div>
90788
+ ${p.description ? `<div style="font-size:11px;color:var(--um-text-faint);">${this.esc(p.description)}</div>` : ""}
90789
+ <div style="font-size:11px;color:var(--um-badge-user-text);margin-top:2px;">allow: ${this.esc(allowStr)}</div>
90790
+ </div>`;
90754
90791
  }).join("");
90755
90792
  return `<div class="gm-panel-section">
90756
90793
  <div class="gm-panel-section-header"><span class="gm-panel-section-title">\u{1F4CB} Pol\xEDticas</span></div>
@@ -90766,10 +90803,12 @@ var RolesTab = class {
90766
90803
  const modal = document.createElement("div");
90767
90804
  modal.className = "um-modal";
90768
90805
  modal.style.cssText = "padding: 24px; width: min(520px, 92vw); max-height: 80vh; height: auto; aspect-ratio: unset; overflow-y: auto; display: block;";
90806
+ const existingPolicies = existing?.policies ?? existing?.policyIds ?? [];
90769
90807
  const policiesCheckboxes = this.policies.map((p) => {
90770
- const checked = existing?.policyIds?.includes(p.id) ? " checked" : "";
90808
+ const ref = p.key || p.id;
90809
+ const checked = existingPolicies.includes(ref) ? " checked" : "";
90771
90810
  return `<label style="display:flex;align-items:center;gap:8px;padding:4px 0;font-size:12px;color:var(--um-text-secondary);cursor:pointer;">
90772
- <input type="checkbox" class="um-role-policy-chk" value="${this.esc(p.id)}"${checked} />
90811
+ <input type="checkbox" class="um-role-policy-chk" value="${this.esc(ref)}"${checked} />
90773
90812
  ${this.esc(p.displayName)}${p.description ? ` <span style="color:var(--um-text-faint);">\u2014 ${this.esc(p.description)}</span>` : ""}
90774
90813
  </label>`;
90775
90814
  }).join("");
@@ -90813,14 +90852,14 @@ var RolesTab = class {
90813
90852
  }
90814
90853
  errEl.textContent = "";
90815
90854
  const description = modal.querySelector("[name=description]").value.trim();
90816
- const policyIds = Array.from(
90855
+ const policies = Array.from(
90817
90856
  modal.querySelectorAll(".um-role-policy-chk:checked")
90818
90857
  ).map((c) => c.value);
90819
90858
  const btn = modal.querySelector(".role-save");
90820
90859
  btn.disabled = true;
90821
90860
  btn.textContent = "...";
90822
90861
  try {
90823
- const body = { name, description: description || void 0, policyIds };
90862
+ const body = { name, description: description || void 0, policies };
90824
90863
  const url = isEdit ? `${this.gcdrBase()}/roles/${existing.id}` : `${this.gcdrBase()}/roles`;
90825
90864
  const res = await fetch(url, {
90826
90865
  method: isEdit ? "PUT" : "POST",
@@ -91125,12 +91164,12 @@ var UserManagementModalView = class {
91125
91164
  --um-text-secondary: #374151;
91126
91165
  --um-text-muted: #64748b;
91127
91166
  --um-text-faint: #94a3b8;
91128
- --um-accent: #3b5bdb;
91129
- --um-accent-hover: #4c6ef5;
91130
- --um-btn-2-bg: #eff6ff;
91131
- --um-btn-2-text: #3b5bdb;
91132
- --um-btn-2-border: #bfdbfe;
91133
- --um-btn-2-hover: #dbeafe;
91167
+ --um-accent: #3f1a7d;
91168
+ --um-accent-hover: #5a2bab;
91169
+ --um-btn-2-bg: #f4effa;
91170
+ --um-btn-2-text: #3f1a7d;
91171
+ --um-btn-2-border: #c9a8e8;
91172
+ --um-btn-2-hover: #e8d8f5;
91134
91173
  --um-btn-ghost-border: #e2e8f0;
91135
91174
  --um-btn-ghost-hover: #f1f5f9;
91136
91175
  --um-btn-ghost-text-hover:#475569;
@@ -91149,14 +91188,14 @@ var UserManagementModalView = class {
91149
91188
  --um-toast-err-border: #ef4444;
91150
91189
  --um-toast-err-text: #dc2626;
91151
91190
  --um-spinner-border: #cbd5e1;
91152
- --um-spinner-top: #3b5bdb;
91191
+ --um-spinner-top: #3f1a7d;
91153
91192
  --um-toggle-off: #cbd5e1;
91154
- --um-notice-bg: #eff6ff;
91155
- --um-notice-border: #bfdbfe;
91193
+ --um-notice-bg: #f4effa;
91194
+ --um-notice-border: #c9a8e8;
91156
91195
  --um-shadow: 0 32px 80px rgba(0,0,0,0.15);
91157
91196
  --um-row-highlight: #f0fdf4;
91158
- --um-gm-badge-domain-bg: #eff6ff;
91159
- --um-gm-badge-domain-txt: #3b5bdb;
91197
+ --um-gm-badge-domain-bg: #f4effa;
91198
+ --um-gm-badge-domain-txt: #3f1a7d;
91160
91199
  --um-gm-badge-count-bg: #f1f5f9;
91161
91200
  --um-gm-badge-count-txt: #64748b;
91162
91201
  --um-gm-form-card-bg: #f8fafc;
@@ -91185,12 +91224,12 @@ var UserManagementModalView = class {
91185
91224
  --um-text-secondary: #c4ccd9;
91186
91225
  --um-text-muted: #64748b;
91187
91226
  --um-text-faint: #475569;
91188
- --um-accent: #60a5fa;
91189
- --um-accent-hover: #93c5fd;
91190
- --um-btn-2-bg: #1e2d4a;
91191
- --um-btn-2-text: #93c5fd;
91192
- --um-btn-2-border: #2a3b60;
91193
- --um-btn-2-hover: #253456;
91227
+ --um-accent: #a78bdb;
91228
+ --um-accent-hover: #c3a8f0;
91229
+ --um-btn-2-bg: #2a1a45;
91230
+ --um-btn-2-text: #c3a8f0;
91231
+ --um-btn-2-border: #4a2a80;
91232
+ --um-btn-2-hover: #3a2060;
91194
91233
  --um-btn-ghost-border: #2a3352;
91195
91234
  --um-btn-ghost-hover: #1a2235;
91196
91235
  --um-btn-ghost-text-hover:#94a3b8;
@@ -91209,14 +91248,14 @@ var UserManagementModalView = class {
91209
91248
  --um-toast-err-border: #ef4444;
91210
91249
  --um-toast-err-text: #f87171;
91211
91250
  --um-spinner-border: #2a3352;
91212
- --um-spinner-top: #60a5fa;
91251
+ --um-spinner-top: #a78bdb;
91213
91252
  --um-toggle-off: #2a3352;
91214
- --um-notice-bg: #1a2537;
91215
- --um-notice-border: #2a3b60;
91253
+ --um-notice-bg: #1e1235;
91254
+ --um-notice-border: #4a2a80;
91216
91255
  --um-shadow: 0 32px 80px rgba(0,0,0,0.6);
91217
91256
  --um-row-highlight: #0e2a1e;
91218
- --um-gm-badge-domain-bg: #1a2537;
91219
- --um-gm-badge-domain-txt: #60a5fa;
91257
+ --um-gm-badge-domain-bg: #2a1a45;
91258
+ --um-gm-badge-domain-txt: #c3a8f0;
91220
91259
  --um-gm-badge-count-bg: #1e2d4a;
91221
91260
  --um-gm-badge-count-txt: #94a3b8;
91222
91261
  --um-gm-form-card-bg: #0f1829;
@@ -91239,6 +91278,20 @@ var UserManagementModalView = class {
91239
91278
 
91240
91279
  /* Header handled by ModalHeader (RFC-0121) */
91241
91280
 
91281
+ /* Force MyIO purple header regardless of light/dark theme \u2014 overrides myio-modal-header--light */
91282
+ .um-modal .myio-modal-header--light {
91283
+ background: #3f1a7d !important;
91284
+ border-bottom-color: #2e1260 !important;
91285
+ }
91286
+ .um-modal .myio-modal-header--light .myio-modal-header__title { color: #fff !important; }
91287
+ .um-modal .myio-modal-header--light .myio-modal-header__btn { color: rgba(255,255,255,0.8) !important; }
91288
+ .um-modal .myio-modal-header--light .myio-modal-header__btn:hover {
91289
+ background: rgba(255,255,255,0.15) !important; color: #fff !important;
91290
+ }
91291
+ .um-modal .myio-modal-header--light .myio-modal-header__btn--close:hover {
91292
+ background: rgba(239,68,68,0.3) !important; color: #fecaca !important;
91293
+ }
91294
+
91242
91295
  /* Maximize button \u2014 CSS window icon (emoji renders as plain square on some platforms) */
91243
91296
  #um-modal-maximize { font-size: 0 !important; position: relative; }
91244
91297
  #um-modal-maximize::before {
@@ -135492,6 +135545,50 @@ var STYLES3 = `
135492
135545
  font-size: 12px; font-weight: 600; color: #e67e22;
135493
135546
  background: #fef3e8; border-radius: 4px; padding: 1px 6px;
135494
135547
  }
135548
+ /* Collapse/expand */
135549
+ #${MODAL_ID3} .abm-device-card.collapsed .abm-rule-list { display: none; }
135550
+ #${MODAL_ID3} .abm-rule-group.collapsed .abm-rule-group-devices,
135551
+ #${MODAL_ID3} .abm-rule-group.collapsed .abm-rule-detail-wrap { display: none; }
135552
+ #${MODAL_ID3} .abm-chevron {
135553
+ font-size: 11px; color: #94a3b8; transition: transform 0.18s; flex-shrink: 0; user-select: none;
135554
+ }
135555
+ #${MODAL_ID3} .abm-device-card.collapsed .abm-chevron,
135556
+ #${MODAL_ID3} .abm-rule-group.collapsed .abm-chevron { transform: rotate(-90deg); }
135557
+ #${MODAL_ID3} .abm-device-header { cursor: pointer; }
135558
+ #${MODAL_ID3} .abm-device-header:hover { background: #f0f9f7; }
135559
+ #${MODAL_ID3} .abm-rule-group-header { cursor: pointer; }
135560
+ #${MODAL_ID3} .abm-rule-group-header:hover { background: #e6f4f1; }
135561
+ /* Alarm badge */
135562
+ #${MODAL_ID3} .abm-alarm-badge {
135563
+ display: inline-flex; align-items: center; gap: 3px;
135564
+ background: #fee2e2; color: #b91c1c; border-radius: 10px;
135565
+ font-size: 10px; font-weight: 700; padding: 1px 7px; flex-shrink: 0;
135566
+ }
135567
+ #${MODAL_ID3} .abm-alarm-badge.zero { background: #f0f9f7; color: #6b7280; }
135568
+ /* Filter bar */
135569
+ #${MODAL_ID3} .abm-filter-bar {
135570
+ display: flex; gap: 8px; align-items: center; margin-bottom: 14px;
135571
+ padding: 10px 12px; background: #f8faf9; border: 1px solid #e0eceb; border-radius: 8px;
135572
+ }
135573
+ #${MODAL_ID3} .abm-filter-input {
135574
+ flex: 1; border: 1px solid #d1d5db; border-radius: 6px; padding: 6px 10px;
135575
+ font-size: 12px; outline: none; transition: border-color 0.15s;
135576
+ }
135577
+ #${MODAL_ID3} .abm-filter-input:focus { border-color: ${TEAL2}; }
135578
+ #${MODAL_ID3} .abm-filter-select {
135579
+ border: 1px solid #d1d5db; border-radius: 6px; padding: 5px 8px;
135580
+ font-size: 12px; outline: none; min-width: 140px; max-width: 220px;
135581
+ background: #fff; cursor: pointer;
135582
+ }
135583
+ #${MODAL_ID3} .abm-filter-count {
135584
+ font-size: 11px; color: #64748b; white-space: nowrap; flex-shrink: 0;
135585
+ }
135586
+ #${MODAL_ID3} .abm-filter-clear {
135587
+ background: none; border: 1px solid #e0eceb; border-radius: 6px; cursor: pointer;
135588
+ font-size: 11px; color: #64748b; padding: 4px 8px; white-space: nowrap; flex-shrink: 0;
135589
+ transition: background 0.12s;
135590
+ }
135591
+ #${MODAL_ID3} .abm-filter-clear:hover { background: #f0f4f3; color: #1e293b; }
135495
135592
  /* Confirmation modal \u2014 value edit overwrite warning */
135496
135593
  .abm-confirm-overlay {
135497
135594
  position: fixed; inset: 0;
@@ -135922,6 +136019,8 @@ function bindRuleEditEvents(card, bundle, gcdrTenantId, baseUrl, getViewMode) {
135922
136019
  if (!ruleId) return;
135923
136020
  const rule = bundle.rules[ruleId];
135924
136021
  if (!rule) return;
136022
+ editBtn.closest(".abm-device-card")?.classList.remove("collapsed");
136023
+ editBtn.closest(".abm-rule-group")?.classList.remove("collapsed");
135925
136024
  const viewMode = getViewMode();
135926
136025
  if (viewMode === "por-regra") {
135927
136026
  const ruleGroup = editBtn.closest(".abm-rule-group");
@@ -135940,13 +136039,109 @@ function bindRuleEditEvents(card, bundle, gcdrTenantId, baseUrl, getViewMode) {
135940
136039
  openGranularValueEdit(ruleItem, rule, device, bundle, gcdrTenantId, baseUrl);
135941
136040
  });
135942
136041
  }
136042
+ function bindCollapseEvents(card) {
136043
+ card.querySelectorAll(".abm-device-header").forEach((header) => {
136044
+ header.addEventListener("click", (e) => {
136045
+ if (e.target.closest("button")) return;
136046
+ header.closest(".abm-device-card")?.classList.toggle("collapsed");
136047
+ });
136048
+ });
136049
+ card.querySelectorAll(".abm-rule-group-header").forEach((header) => {
136050
+ header.addEventListener("click", (e) => {
136051
+ if (e.target.closest("button")) return;
136052
+ header.closest(".abm-rule-group")?.classList.toggle("collapsed");
136053
+ });
136054
+ });
136055
+ }
136056
+ function bindFilterBar(card, bundle, getViewMode) {
136057
+ const input = card.querySelector("#abm-filter-input");
136058
+ const select = card.querySelector("#abm-filter-select");
136059
+ const countEl = card.querySelector("#abm-filter-count");
136060
+ const clearBtn = card.querySelector("#abm-filter-clear");
136061
+ if (!input) return;
136062
+ const updateSelectOptions = (text) => {
136063
+ if (!select) return;
136064
+ const lower = text.toLowerCase();
136065
+ const types = /* @__PURE__ */ new Set();
136066
+ bundle.devices.forEach((d) => {
136067
+ const name = (d.displayName || d.name).toLowerCase();
136068
+ if (!lower || name.includes(lower) || d.type.toLowerCase().includes(lower)) {
136069
+ types.add(d.type);
136070
+ }
136071
+ });
136072
+ const prev = new Set(Array.from(select.selectedOptions).map((o) => o.value));
136073
+ select.innerHTML = Array.from(types).sort().map(
136074
+ (t) => `<option value="${escHtml3(t)}"${prev.has(t) ? " selected" : ""}>${escHtml3(t)}</option>`
136075
+ ).join("");
136076
+ select.size = Math.min(types.size, 4) || 1;
136077
+ };
136078
+ const applyFilter = () => {
136079
+ const text = input.value.toLowerCase().trim();
136080
+ const selectedTypes = new Set(Array.from(select?.selectedOptions ?? []).map((o) => o.value));
136081
+ const mode = getViewMode();
136082
+ if (mode === "granular") {
136083
+ let visible = 0;
136084
+ card.querySelectorAll(".abm-device-card").forEach((dc) => {
136085
+ const name = (dc.querySelector(".abm-device-name")?.textContent ?? "").toLowerCase();
136086
+ const type = dc.querySelector(".abm-device-type")?.textContent ?? "";
136087
+ const ruleNames = Array.from(dc.querySelectorAll(".abm-rule-name")).map((el2) => (el2.textContent ?? "").toLowerCase());
136088
+ const matchText = !text || name.includes(text) || type.toLowerCase().includes(text) || ruleNames.some((r) => r.includes(text));
136089
+ const matchType = selectedTypes.size === 0 || selectedTypes.has(type);
136090
+ dc.style.display = matchText && matchType ? "" : "none";
136091
+ if (matchText && matchType) visible++;
136092
+ });
136093
+ if (countEl) countEl.textContent = `${visible} dispositivo(s)`;
136094
+ } else {
136095
+ let visible = 0;
136096
+ card.querySelectorAll(".abm-rule-group").forEach((rg) => {
136097
+ const ruleName = (rg.querySelector(".abm-rule-group-name")?.textContent ?? "").toLowerCase();
136098
+ const deviceTexts = Array.from(rg.querySelectorAll(".abm-rule-group-device span")).map((el2) => (el2.textContent ?? "").toLowerCase());
136099
+ const matchText = !text || ruleName.includes(text) || deviceTexts.some((t) => t.includes(text));
136100
+ rg.style.display = matchText ? "" : "none";
136101
+ if (matchText) visible++;
136102
+ });
136103
+ if (countEl) countEl.textContent = `${visible} regra(s)`;
136104
+ }
136105
+ updateSelectOptions(text);
136106
+ };
136107
+ input.addEventListener("input", applyFilter);
136108
+ select?.addEventListener("change", applyFilter);
136109
+ clearBtn?.addEventListener("click", () => {
136110
+ input.value = "";
136111
+ if (select) Array.from(select.options).forEach((o) => {
136112
+ o.selected = false;
136113
+ });
136114
+ applyFilter();
136115
+ });
136116
+ updateSelectOptions("");
136117
+ const totalDevices = bundle.devices.length;
136118
+ if (countEl) countEl.textContent = `${totalDevices} dispositivo(s)`;
136119
+ }
136120
+ var _OFFLINE_ALARM_TYPES = ["DEVICE OFFLINE", "DISPOSITIVO OFFLINE"];
136121
+ function _getActiveAlarmCount(gcdrDeviceId) {
136122
+ const aso = window.AlarmServiceOrchestrator;
136123
+ if (!aso?.deviceAlarmMap) return 0;
136124
+ const alarms = aso.deviceAlarmMap.get(gcdrDeviceId) || [];
136125
+ const showOffline = window.MyIOOrchestrator?.showOfflineAlarms === true;
136126
+ return showOffline ? alarms.length : alarms.filter((a) => {
136127
+ const t = (a.title ?? "").toUpperCase();
136128
+ return !(_OFFLINE_ALARM_TYPES.some((ex) => t.startsWith(ex)) || a.alarmType === "connectivity");
136129
+ }).length;
136130
+ }
136131
+ function _alarmBadgeHtml(count) {
136132
+ if (count === 0) return `<span class="abm-alarm-badge zero">0 \u{1F514}</span>`;
136133
+ return `<span class="abm-alarm-badge">\u{1F534} ${count}</span>`;
136134
+ }
135943
136135
  function renderDevice(device, rules, viewMode = "granular") {
135944
136136
  const deviceRules = (device.ruleIds ?? []).map((rid) => rules[rid]).filter(Boolean);
136137
+ const alarmCount = _getActiveAlarmCount(device.id);
135945
136138
  return `
135946
- <div class="abm-device-card">
136139
+ <div class="abm-device-card collapsed" data-device-id="${escHtml3(device.id)}">
135947
136140
  <div class="abm-device-header">
136141
+ <span class="abm-chevron">\u25BE</span>
135948
136142
  <span style="font-size:16px;">\u{1F4E1}</span>
135949
136143
  <span class="abm-device-name">${escHtml3(device.displayName || device.name)}</span>
136144
+ ${_alarmBadgeHtml(alarmCount)}
135950
136145
  <span class="abm-device-type">${escHtml3(device.type)}</span>
135951
136146
  </div>
135952
136147
  <ul class="abm-rule-list">
@@ -135973,11 +136168,14 @@ function renderByRuleView(bundle) {
135973
136168
  for (const [baseRuleId, devs] of ruleDevicesMap) {
135974
136169
  const baseRule = rules[baseRuleId];
135975
136170
  if (!baseRule) continue;
136171
+ const groupAlarmCount = devs.reduce((sum, d) => sum + _getActiveAlarmCount(d.id), 0);
135976
136172
  groups += `
135977
- <div class="abm-rule-group">
136173
+ <div class="abm-rule-group collapsed" data-rule-id="${escHtml3(baseRuleId)}">
135978
136174
  <div class="abm-rule-group-header">
136175
+ <span class="abm-chevron">\u25BE</span>
135979
136176
  <span style="font-size:16px;">\u{1F514}</span>
135980
136177
  <span class="abm-rule-group-name">${escHtml3(baseRule.name)}</span>
136178
+ ${_alarmBadgeHtml(groupAlarmCount)}
135981
136179
  <button class="abm-edit-rule-btn" data-edit-rule="${escHtml3(baseRuleId)}" title="Editar dias/hor\xE1rio">\u270F\uFE0F</button>
135982
136180
  </div>
135983
136181
  <div class="abm-rule-detail" style="padding:8px 14px;" data-rule-id="${escHtml3(baseRuleId)}">
@@ -136041,7 +136239,6 @@ function renderBundle(bundle, viewMode = "granular") {
136041
136239
  <div class="abm-summary-bar">
136042
136240
  <div class="abm-summary-item"><div class="abm-summary-num">${deviceCount}</div><div class="abm-summary-label">Dispositivos</div></div>
136043
136241
  <div class="abm-summary-item"><div class="abm-summary-num">${ruleCount}</div><div class="abm-summary-label">Regras</div></div>
136044
- <div class="abm-summary-item"><div class="abm-summary-num">${assets.length}</div><div class="abm-summary-label">Assets</div></div>
136045
136242
  </div>
136046
136243
  ${renderByRuleView(bundle)}
136047
136244
  `;
@@ -136075,10 +136272,6 @@ function renderBundle(bundle, viewMode = "granular") {
136075
136272
  <div class="abm-summary-num">${ruleCount}</div>
136076
136273
  <div class="abm-summary-label">Regras</div>
136077
136274
  </div>
136078
- <div class="abm-summary-item">
136079
- <div class="abm-summary-num">${assets.length}</div>
136080
- <div class="abm-summary-label">Assets</div>
136081
- </div>
136082
136275
  </div>
136083
136276
  ${groups}
136084
136277
  `;
@@ -136169,7 +136362,21 @@ async function openAlarmBundleMapModal(params) {
136169
136362
  const customerName = bundle.customer.displayName || bundle.customer.name || "";
136170
136363
  const rerenderBundle = () => {
136171
136364
  render3(renderBundle(bundle, viewMode), customerName, true);
136365
+ const body = card.querySelector(".abm-body");
136366
+ if (body) {
136367
+ const filterBarEl = document.createElement("div");
136368
+ filterBarEl.className = "abm-filter-bar";
136369
+ filterBarEl.innerHTML = `
136370
+ <input class="abm-filter-input" type="text" id="abm-filter-input" placeholder="Buscar dispositivo, regra, tipo..." />
136371
+ <select class="abm-filter-select" id="abm-filter-select" multiple size="1"></select>
136372
+ <span class="abm-filter-count" id="abm-filter-count"></span>
136373
+ <button class="abm-filter-clear" id="abm-filter-clear" type="button">\u2715 Limpar</button>
136374
+ `;
136375
+ body.insertBefore(filterBarEl, body.firstChild);
136376
+ }
136172
136377
  bindRuleEditEvents(card, bundle, params.gcdrTenantId, baseUrl, () => viewMode);
136378
+ bindCollapseEvents(card);
136379
+ bindFilterBar(card, bundle, () => viewMode);
136173
136380
  card.querySelectorAll("[data-view]").forEach((btn) => {
136174
136381
  btn.addEventListener("click", () => {
136175
136382
  const newMode = btn.getAttribute("data-view");
package/dist/index.d.cts CHANGED
@@ -10435,11 +10435,12 @@ interface TBUserPage {
10435
10435
  }
10436
10436
  interface GCDRPolicy {
10437
10437
  id: string;
10438
+ key?: string;
10438
10439
  displayName: string;
10439
10440
  description?: string;
10440
10441
  allow: string[];
10441
10442
  deny: string[];
10442
- riskLevel?: 'LOW' | 'MEDIUM' | 'HIGH';
10443
+ riskLevel?: 'low' | 'medium' | 'high' | 'critical' | 'LOW' | 'MEDIUM' | 'HIGH';
10443
10444
  /** System policies are immutable */
10444
10445
  isSystem?: boolean;
10445
10446
  createdAt?: string;
@@ -10447,10 +10448,14 @@ interface GCDRPolicy {
10447
10448
  }
10448
10449
  interface GCDRRole {
10449
10450
  id: string;
10451
+ key?: string;
10450
10452
  displayName: string;
10451
10453
  description?: string;
10452
- /** IDs of associated policies */
10453
- policyIds: string[];
10454
+ /** Policy keys as returned by the GCDR API (e.g. "policy:alarm-management") */
10455
+ policies?: string[];
10456
+ /** Legacy field — use `policies` instead */
10457
+ policyIds?: string[];
10458
+ riskLevel?: 'low' | 'medium' | 'high' | 'critical';
10454
10459
  isSystem?: boolean;
10455
10460
  createdAt?: string;
10456
10461
  updatedAt?: string;