myio-js-library 0.1.489 → 0.1.491

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.489",
1165
+ version: "0.1.491",
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
@@ -84015,7 +84027,10 @@ function openUpsellModal(params) {
84015
84027
  bulkRelationModal: { open: false, target: "CUSTOMER", selectedAssetId: "", selectedAssetName: "", search: "", newAssetName: "", assetsLoaded: false, overrideCustomerId: "", overrideCustomerName: "", customerSearch: "", customerPickerOpen: false },
84016
84028
  checkFixLoading: false,
84017
84029
  checkFixReport: null,
84018
- checkFixFilter: "all"
84030
+ checkFixFilter: "all",
84031
+ checkFixSort: { field: "name", order: "asc" },
84032
+ checkFixAdvancedFilter: { statuses: [], connStatuses: [], domains: [], missingIngestionId: false, missingCentralSlave: false },
84033
+ checkFixFilterOpen: false
84019
84034
  };
84020
84035
  const savedTheme = localStorage.getItem("myio-upsell-modal-theme");
84021
84036
  if (savedTheme) state6.theme = savedTheme;
@@ -84154,6 +84169,12 @@ function renderModal4(container, state6, modalId, t, error) {
84154
84169
  font-size: 14px; font-weight: 500; font-family: 'Roboto', Arial, sans-serif;
84155
84170
  display: flex; align-items: center; gap: 6px;
84156
84171
  " ${!state6.selectedCustomer ? 'disabled title="Selecione um Customer primeiro"' : ""}>\u{1F517} For\xE7ar Rela\xE7\xE3o (${state6.selectedDevices.length})</button>
84172
+ <button id="${modalId}-clear-gcdr-ids" style="
84173
+ background: #dc2626; color: white; border: none;
84174
+ padding: 8px 16px; border-radius: 6px; cursor: pointer;
84175
+ font-size: 14px; font-weight: 500; font-family: 'Roboto', Arial, sans-serif;
84176
+ display: flex; align-items: center; gap: 6px;
84177
+ " ${!state6.selectedCustomer ? 'disabled title="Selecione um Customer primeiro"' : ""}>\u{1F9F9} Clear GCDR IDs</button>
84157
84178
  <button id="${modalId}-custom-shortcut" style="
84158
84179
  background: #ef4444; color: white; border: none;
84159
84180
  padding: 8px 16px; border-radius: 6px; cursor: pointer;
@@ -84820,6 +84841,29 @@ function renderStep1(state6, modalId, colors2, t) {
84820
84841
  </div>
84821
84842
  `;
84822
84843
  }
84844
+ function _buildCfStatusDetail(r) {
84845
+ if (r.status === "ok") return "";
84846
+ const lines = [];
84847
+ if (r.status === "mismatch") {
84848
+ if (r.actual.type !== r.inferred.deviceType)
84849
+ lines.push(`TYPE: actual="${r.actual.type}" \u2260 expected="${r.inferred.deviceType}"`);
84850
+ if (r.actual.deviceType !== r.inferred.deviceType)
84851
+ lines.push(`deviceType: actual="${r.actual.deviceType}" \u2260 expected="${r.inferred.deviceType}"`);
84852
+ if (r.actual.deviceProfile !== r.inferred.deviceProfile)
84853
+ lines.push(`deviceProfile: actual="${r.actual.deviceProfile}" \u2260 expected="${r.inferred.deviceProfile}"`);
84854
+ } else if (r.status === "missing") {
84855
+ if (!r.actual.type) lines.push("TYPE: n\xE3o definido");
84856
+ if (!r.actual.deviceType) lines.push("deviceType: n\xE3o definido");
84857
+ if (!r.actual.deviceProfile) lines.push("deviceProfile: n\xE3o definido");
84858
+ } else if (r.status === "undefined") {
84859
+ lines.push(`Nome n\xE3o permite inferir o tipo de dispositivo: "${r.deviceName}"`);
84860
+ lines.push("Use Check & Fix manual ou ajuste o nome do dispositivo.");
84861
+ }
84862
+ if (!r.ingestionId) lines.push("ingestionId: ausente (n\xE3o integrado ao backend de ingest\xE3o)");
84863
+ if (!r.centralId) lines.push("centralId: ausente");
84864
+ if (r.connStatus === "offline") lines.push("Dispositivo offline no momento");
84865
+ return lines.join("\n") || r.status;
84866
+ }
84823
84867
  function renderCheckFixRow(r, state6, modalId, colors2, dupPairIds, dupIngestionIds) {
84824
84868
  const isSelectedSingle = state6.deviceSelectionMode === "single" && getEntityId(state6.selectedDevice) === r.deviceId;
84825
84869
  const isSelectedMulti = state6.deviceSelectionMode === "multi" && state6.selectedDevices.some((d) => getEntityId(d) === r.deviceId);
@@ -84883,7 +84927,10 @@ function renderCheckFixRow(r, state6, modalId, colors2, dupPairIds, dupIngestion
84883
84927
  <td style="${cell(dupIngestionIds.has(r.deviceId) ? "bad" : "none")} font-size:9px; font-family:monospace; max-width:110px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${r.ingestionId || ""}">${r.ingestionId || dash}</td>
84884
84928
  <td style="${cell(dupPairIds.has(r.deviceId) ? "bad" : "none")} font-size:9px; font-family:monospace; max-width:80px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${r.centralId || ""}">${r.centralId || dash}</td>
84885
84929
  <td style="${cell(dupPairIds.has(r.deviceId) ? "bad" : "none")} font-size:9px; font-family:monospace; max-width:50px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">${r.slaveId || dash}</td>
84886
- <td style="padding:4px 6px; font-size:10px; color:${STATUS_COLOR[r.status]}; font-weight:700; white-space:nowrap;">${STATUS_ICON[r.status]} ${r.status}</td>
84930
+ <td style="padding:4px 6px; font-size:10px; color:${STATUS_COLOR[r.status]}; font-weight:700; white-space:nowrap; cursor:${r.status !== "ok" ? "help" : "default"};"
84931
+ ${r.status !== "ok" ? `data-cf-status="${r.status}" data-cf-device="${encodeURIComponent(r.deviceName)}" data-cf-detail="${encodeURIComponent(_buildCfStatusDetail(r))}"` : ""}>
84932
+ ${STATUS_ICON[r.status]} ${r.status}
84933
+ </td>
84887
84934
  </tr>`;
84888
84935
  }
84889
84936
  function renderStep2(state6, modalId, colors2, t) {
@@ -85170,7 +85217,42 @@ function renderStep2(state6, modalId, colors2, t) {
85170
85217
  const multiCol = state6.deviceSelectionMode === "multi";
85171
85218
  if (state6.checkFixReport) {
85172
85219
  const idToRecord = new Map(state6.checkFixReport.records.map((r) => [r.deviceId, r]));
85173
- const cfRows = sortedDevices.map((d) => idToRecord.get(getEntityId(d))).filter((r) => r !== void 0).filter((r) => state6.checkFixFilter === "all" || r.status === state6.checkFixFilter);
85220
+ let cfRows = sortedDevices.map((d) => idToRecord.get(getEntityId(d))).filter((r) => r !== void 0).filter((r) => state6.checkFixFilter === "all" || r.status === state6.checkFixFilter);
85221
+ const af = state6.checkFixAdvancedFilter;
85222
+ if (af.statuses.length > 0) cfRows = cfRows.filter((r) => af.statuses.includes(r.status));
85223
+ if (af.connStatuses.length > 0) cfRows = cfRows.filter((r) => af.connStatuses.includes(r.connStatus || "null"));
85224
+ if (af.domains.length > 0) cfRows = cfRows.filter((r) => af.domains.includes(r.domain || "null"));
85225
+ if (af.missingIngestionId) cfRows = cfRows.filter((r) => !r.ingestionId);
85226
+ if (af.missingCentralSlave) cfRows = cfRows.filter((r) => !r.centralId || r.slaveId == null);
85227
+ const cfSortField = state6.checkFixSort.field;
85228
+ const cfSortOrder = state6.checkFixSort.order;
85229
+ cfRows = [...cfRows].sort((a, b) => {
85230
+ let va = "", vb = "";
85231
+ if (cfSortField === "name") {
85232
+ va = a.deviceName || "";
85233
+ vb = b.deviceName || "";
85234
+ } else if (cfSortField === "status") {
85235
+ va = a.status || "";
85236
+ vb = b.status || "";
85237
+ } else if (cfSortField === "connStatus") {
85238
+ va = a.connStatus || "";
85239
+ vb = b.connStatus || "";
85240
+ } else if (cfSortField === "deviceType") {
85241
+ va = a.actual.deviceType || "";
85242
+ vb = b.actual.deviceType || "";
85243
+ } else if (cfSortField === "deviceProfile") {
85244
+ va = a.actual.deviceProfile || "";
85245
+ vb = b.actual.deviceProfile || "";
85246
+ } else if (cfSortField === "ingestionId") {
85247
+ va = a.ingestionId || "";
85248
+ vb = b.ingestionId || "";
85249
+ } else if (cfSortField === "domain") {
85250
+ va = a.domain || "";
85251
+ vb = b.domain || "";
85252
+ }
85253
+ const cmp = va.toLowerCase().localeCompare(vb.toLowerCase());
85254
+ return cfSortOrder === "asc" ? cmp : -cmp;
85255
+ });
85174
85256
  const pairCount = /* @__PURE__ */ new Map();
85175
85257
  cfRows.forEach((r) => {
85176
85258
  if (r.centralId && r.slaveId != null) {
@@ -85195,27 +85277,115 @@ function renderStep2(state6, modalId, colors2, t) {
85195
85277
  if (ids.length > 1) ids.forEach((id) => dupIngestionIds.add(id));
85196
85278
  });
85197
85279
  const colSpan = multiCol ? 18 : 17;
85280
+ const cfSortHeader = (field, label, rowspan = 1, extraStyle = "") => {
85281
+ const isActive = state6.checkFixSort.field === field;
85282
+ const arrow = isActive ? state6.checkFixSort.order === "asc" ? "\u25B2" : "\u25BC" : "\u25BD";
85283
+ return `<th ${rowspan > 1 ? `rowspan="${rowspan}"` : ""} id="${modalId}-cf-sort-${field}" data-cf-sort="${field}" style="${thStyle(extraStyle)} cursor:pointer; user-select:none; ${isActive ? `background:rgba(62,26,125,0.18);` : ""}">
85284
+ <span style="display:flex;align-items:center;gap:3px;white-space:nowrap;">${label}<span style="font-size:7px;color:${isActive ? MYIO_PURPLE : colors2.textMuted};">${arrow}</span></span>
85285
+ </th>`;
85286
+ };
85287
+ const allDomains = [...new Set(state6.checkFixReport.records.map((r) => r.domain || "null"))].sort();
85288
+ const allConns = [...new Set(state6.checkFixReport.records.map((r) => r.connStatus || "null"))].sort();
85289
+ const hasAdvFilter = af.statuses.length > 0 || af.connStatuses.length > 0 || af.domains.length > 0 || af.missingIngestionId || af.missingCentralSlave;
85290
+ const filterPanel = state6.checkFixFilterOpen ? `
85291
+ <div id="${modalId}-cf-filter-panel" style="
85292
+ position:absolute; top:calc(100% + 4px); right:0; z-index:200;
85293
+ background:${colors2.cardBg}; border:1px solid ${colors2.border}; border-radius:10px;
85294
+ box-shadow:0 8px 32px rgba(0,0,0,0.22); padding:16px; min-width:300px;
85295
+ font-size:11px; color:${colors2.text};
85296
+ ">
85297
+ <div style="font-weight:700; font-size:12px; margin-bottom:12px; color:${colors2.text};">\u{1F527} Filtros Avan\xE7ados \u2014 Check &amp; Fix</div>
85298
+ <div style="margin-bottom:10px;">
85299
+ <div style="font-weight:600; margin-bottom:4px; color:${colors2.textMuted}; text-transform:uppercase; font-size:9px;">Status</div>
85300
+ <div style="display:flex;gap:6px;flex-wrap:wrap;">
85301
+ ${["ok", "mismatch", "missing", "undefined"].map((s) => {
85302
+ const icons = { ok: "\u2705", mismatch: "\u26A0\uFE0F", missing: "\u274C", undefined: "\u2753" };
85303
+ const active = af.statuses.includes(s);
85304
+ return `<button data-cf-flt-status="${s}" style="padding:3px 8px;border-radius:4px;font-size:10px;cursor:pointer;border:1px solid ${active ? MYIO_PURPLE : colors2.border};background:${active ? MYIO_PURPLE : colors2.inputBg};color:${active ? "white" : colors2.text};">${icons[s]} ${s}</button>`;
85305
+ }).join("")}
85306
+ </div>
85307
+ </div>
85308
+ <div style="margin-bottom:10px;">
85309
+ <div style="font-weight:600; margin-bottom:4px; color:${colors2.textMuted}; text-transform:uppercase; font-size:9px;">Conn. Status</div>
85310
+ <div style="display:flex;gap:6px;flex-wrap:wrap;">
85311
+ ${allConns.map((c) => {
85312
+ const active = af.connStatuses.includes(c);
85313
+ return `<button data-cf-flt-conn="${c}" style="padding:3px 8px;border-radius:4px;font-size:10px;cursor:pointer;border:1px solid ${active ? MYIO_PURPLE : colors2.border};background:${active ? MYIO_PURPLE : colors2.inputBg};color:${active ? "white" : colors2.text};">${c}</button>`;
85314
+ }).join("")}
85315
+ </div>
85316
+ </div>
85317
+ <div style="margin-bottom:10px;">
85318
+ <div style="font-weight:600; margin-bottom:4px; color:${colors2.textMuted}; text-transform:uppercase; font-size:9px;">Domain</div>
85319
+ <div style="display:flex;gap:6px;flex-wrap:wrap;">
85320
+ ${allDomains.map((d) => {
85321
+ const active = af.domains.includes(d);
85322
+ return `<button data-cf-flt-domain="${d}" style="padding:3px 8px;border-radius:4px;font-size:10px;cursor:pointer;border:1px solid ${active ? MYIO_PURPLE : colors2.border};background:${active ? MYIO_PURPLE : colors2.inputBg};color:${active ? "white" : colors2.text};">${d}</button>`;
85323
+ }).join("")}
85324
+ </div>
85325
+ </div>
85326
+ <div style="margin-bottom:12px;display:flex;gap:12px;flex-wrap:wrap;">
85327
+ <label style="display:flex;align-items:center;gap:5px;cursor:pointer;">
85328
+ <input type="checkbox" id="${modalId}-cf-flt-noing" ${af.missingIngestionId ? "checked" : ""} style="accent-color:${MYIO_PURPLE};"/>
85329
+ <span>Sem ingestionId</span>
85330
+ </label>
85331
+ <label style="display:flex;align-items:center;gap:5px;cursor:pointer;">
85332
+ <input type="checkbox" id="${modalId}-cf-flt-nocentral" ${af.missingCentralSlave ? "checked" : ""} style="accent-color:${MYIO_PURPLE};"/>
85333
+ <span>Sem centralId/slaveId</span>
85334
+ </label>
85335
+ </div>
85336
+ <div style="display:flex;gap:8px;justify-content:flex-end;">
85337
+ <button id="${modalId}-cf-flt-clear" style="padding:5px 12px;border-radius:5px;font-size:11px;cursor:pointer;background:transparent;border:1px solid ${colors2.border};color:${colors2.textMuted};">Limpar</button>
85338
+ <button id="${modalId}-cf-flt-close" style="padding:5px 12px;border-radius:5px;font-size:11px;cursor:pointer;background:${MYIO_PURPLE};border:none;color:white;font-weight:600;">Fechar</button>
85339
+ </div>
85340
+ </div>
85341
+ ` : "";
85198
85342
  return `
85343
+ <div style="position:relative;">
85344
+ <div style="display:flex;align-items:center;gap:6px;margin-bottom:6px;flex-wrap:wrap;">
85345
+ <span style="font-size:10px;color:${colors2.textMuted};">${cfRows.length} / ${state6.checkFixReport.records.length} registros</span>
85346
+ <div style="position:relative;margin-left:auto;">
85347
+ <button id="${modalId}-cf-filter-btn" style="padding:3px 10px;border-radius:5px;font-size:10px;cursor:pointer;border:1px solid ${hasAdvFilter ? MYIO_PURPLE : colors2.border};background:${hasAdvFilter ? MYIO_PURPLE : colors2.inputBg};color:${hasAdvFilter ? "white" : colors2.text};font-weight:${hasAdvFilter ? "700" : "400"};">
85348
+ \u{1F527} Filtros${hasAdvFilter ? ` (${af.statuses.length + af.connStatuses.length + af.domains.length + (af.missingIngestionId ? 1 : 0) + (af.missingCentralSlave ? 1 : 0)})` : ""}
85349
+ </button>
85350
+ ${filterPanel}
85351
+ </div>
85352
+ </div>
85199
85353
  <div id="${modalId}-device-list" style="
85200
85354
  overflow-x:auto; max-height:${gridHeight};
85201
85355
  border:1px solid ${colors2.border}; border-radius:8px; background:${colors2.surface};
85202
85356
  ">
85203
- <table style="border-collapse:collapse; min-width:100%;">
85357
+ <table style="border-collapse:collapse; table-layout:fixed; width:100%; min-width:1100px;">
85358
+ <colgroup>
85359
+ ${multiCol ? `<col style="width:28px"/>` : ""}
85360
+ <col style="width:150px"/>
85361
+ <col style="width:95px"/>
85362
+ <col style="width:70px"/><col style="width:70px"/>
85363
+ <col style="width:90px"/><col style="width:90px"/>
85364
+ <col style="width:90px"/><col style="width:90px"/>
85365
+ <col style="width:85px"/>
85366
+ <col style="width:110px"/>
85367
+ <col style="width:70px"/>
85368
+ <col style="width:110px"/>
85369
+ <col style="width:110px"/>
85370
+ <col style="width:80px"/>
85371
+ <col style="width:50px"/>
85372
+ <col style="width:90px"/>
85373
+ </colgroup>
85204
85374
  <thead>
85205
85375
  <tr>
85206
85376
  ${multiCol ? `<th rowspan="2" style="${thStyle("width:28px")}">\u2611</th>` : ""}
85207
- <th rowspan="2" style="${thStyle("min-width:140px")}">Name</th>
85377
+ ${cfSortHeader("name", "Name", 2, "min-width:140px")}
85208
85378
  <th rowspan="2" style="${thStyle("min-width:90px")}">Label</th>
85209
85379
  <th colspan="2" style="${thStyle("text-align:center")}">TYPE</th>
85210
85380
  <th colspan="2" style="${thStyle("text-align:center")}">DevType</th>
85211
85381
  <th colspan="2" style="${thStyle("text-align:center")}">DevProfile</th>
85212
85382
  <th colspan="2" style="${thStyle("text-align:center")}">Last Telemetry</th>
85213
- <th rowspan="2" style="${thStyle("min-width:65px")}">Conn.</th>
85383
+ ${cfSortHeader("connStatus", "Conn.", 2, "min-width:65px")}
85214
85384
  <th rowspan="2" style="${thStyle("min-width:110px")}">gcdrId</th>
85215
- <th rowspan="2" style="${thStyle("min-width:110px")}">ingestionId</th>
85385
+ ${cfSortHeader("ingestionId", "ingestionId", 2, "min-width:110px")}
85216
85386
  <th rowspan="2" style="${thStyle("min-width:80px")}">centralId</th>
85217
85387
  <th rowspan="2" style="${thStyle("min-width:50px")}">slave</th>
85218
- <th rowspan="2" style="${thStyle()}">Result</th>
85388
+ ${cfSortHeader("status", "Result", 2)}
85219
85389
  </tr>
85220
85390
  <tr>
85221
85391
  <th style="${thStyle()}">Exp</th><th style="${thStyle()}">Act</th>
@@ -85229,6 +85399,7 @@ function renderStep2(state6, modalId, colors2, t) {
85229
85399
  ${cfRows.length === 0 ? `<tr><td colspan="${colSpan}" style="padding:20px; text-align:center; color:${colors2.textMuted};">${t.noResults}</td></tr>` : cfRows.map((r) => renderCheckFixRow(r, state6, modalId, colors2, dupPairIds, dupIngestionIds)).join("")}
85230
85400
  </tbody>
85231
85401
  </table>
85402
+ </div>
85232
85403
  </div>`;
85233
85404
  }
85234
85405
  return `
@@ -86113,6 +86284,309 @@ function openRelationsDetailPanel(deviceId, deviceName, direction, rels, state6,
86113
86284
  renderPanel();
86114
86285
  void t;
86115
86286
  }
86287
+ function _setupCfStatusTooltip(container, modalId, colors2) {
86288
+ const TOOLTIP_ID = `${modalId}-cf-status-tip`;
86289
+ let destroyTimer = null;
86290
+ function ensureTip() {
86291
+ let tip2 = document.getElementById(TOOLTIP_ID);
86292
+ if (tip2) return tip2;
86293
+ tip2 = document.createElement("div");
86294
+ tip2.id = TOOLTIP_ID;
86295
+ tip2.style.cssText = `
86296
+ position:fixed; z-index:99990; display:none;
86297
+ background:${colors2.cardBg}; border:1px solid ${colors2.border};
86298
+ border-radius:10px; box-shadow:0 8px 28px rgba(0,0,0,0.26);
86299
+ padding:12px 14px; max-width:340px; font-size:11px; color:${colors2.text};
86300
+ line-height:1.6; pointer-events:auto;
86301
+ `;
86302
+ document.body.appendChild(tip2);
86303
+ return tip2;
86304
+ }
86305
+ function showTip(el2, status, deviceName, detail) {
86306
+ if (destroyTimer) {
86307
+ clearTimeout(destroyTimer);
86308
+ destroyTimer = null;
86309
+ }
86310
+ const tip2 = ensureTip();
86311
+ const STATUS_LABEL = { mismatch: "\u26A0\uFE0F Mismatch", missing: "\u274C Missing", undefined: "\u2753 Undefined" };
86312
+ const STATUS_COLOR_TIP = { mismatch: colors2.warning, missing: colors2.danger, undefined: colors2.textMuted };
86313
+ const lines = detail.split("\n").filter(Boolean);
86314
+ tip2.innerHTML = `
86315
+ <div style="font-weight:700; font-size:12px; color:${STATUS_COLOR_TIP[status] || colors2.text}; margin-bottom:6px;">${STATUS_LABEL[status] || status}</div>
86316
+ <div style="font-size:10px; color:${colors2.textMuted}; margin-bottom:8px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;" title="${deviceName}">${deviceName}</div>
86317
+ <div style="border-top:1px solid ${colors2.border}; padding-top:8px;">
86318
+ ${lines.map((l) => `<div style="margin-bottom:3px; font-family:monospace; font-size:10px;">${l}</div>`).join("")}
86319
+ </div>
86320
+ `;
86321
+ const rect = el2.getBoundingClientRect();
86322
+ const tipW = 340, tipH = 80;
86323
+ let left = rect.left;
86324
+ let top = rect.bottom + 6;
86325
+ if (left + tipW > window.innerWidth - 10) left = window.innerWidth - tipW - 10;
86326
+ if (top + tipH > window.innerHeight - 10) top = rect.top - tipH - 6;
86327
+ tip2.style.left = `${left}px`;
86328
+ tip2.style.top = `${top}px`;
86329
+ tip2.style.display = "block";
86330
+ }
86331
+ function hideTip(force = false) {
86332
+ if (force) {
86333
+ const tip2 = document.getElementById(TOOLTIP_ID);
86334
+ if (tip2) tip2.style.display = "none";
86335
+ if (destroyTimer) {
86336
+ clearTimeout(destroyTimer);
86337
+ destroyTimer = null;
86338
+ }
86339
+ return;
86340
+ }
86341
+ destroyTimer = setTimeout(() => {
86342
+ const tip2 = document.getElementById(TOOLTIP_ID);
86343
+ if (tip2) tip2.style.display = "none";
86344
+ destroyTimer = null;
86345
+ }, 2e3);
86346
+ }
86347
+ container.querySelectorAll("[data-cf-status]").forEach((cell) => {
86348
+ const el2 = cell;
86349
+ const status = el2.dataset.cfStatus;
86350
+ const device = decodeURIComponent(el2.dataset.cfDevice || "");
86351
+ const detail = decodeURIComponent(el2.dataset.cfDetail || "");
86352
+ el2.addEventListener("mouseenter", () => showTip(el2, status, device, detail));
86353
+ el2.addEventListener("mouseleave", () => hideTip());
86354
+ el2.addEventListener("focus", () => {
86355
+ if (destroyTimer) {
86356
+ clearTimeout(destroyTimer);
86357
+ destroyTimer = null;
86358
+ }
86359
+ showTip(el2, status, device, detail);
86360
+ });
86361
+ el2.addEventListener("blur", () => hideTip());
86362
+ });
86363
+ const tip = ensureTip();
86364
+ tip.addEventListener("mouseenter", () => {
86365
+ if (destroyTimer) {
86366
+ clearTimeout(destroyTimer);
86367
+ destroyTimer = null;
86368
+ }
86369
+ });
86370
+ tip.addEventListener("mouseleave", () => hideTip());
86371
+ }
86372
+ var GCDR_CLEAR_DEVICE_KEYS = ["gcdrDeviceId", "gcdrId", "gcdrSyncAt", "gcdrAssetId", "gcdrCustomerId"];
86373
+ var GCDR_CLEAR_ASSET_KEYS = ["gcdrAssetId", "gcdrId", "gcdrSyncAt", "gcdrParentAssetId", "gcdrCustomerId"];
86374
+ async function openClearGcdrIdsModal(state6) {
86375
+ if (!state6.selectedCustomer) return;
86376
+ const customerId = getEntityId(state6.selectedCustomer);
86377
+ const customerName = state6.selectedCustomer.name || state6.selectedCustomer.title || customerId;
86378
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
86379
+ const overlay = document.createElement("div");
86380
+ overlay.style.cssText = `
86381
+ position:fixed; inset:0; z-index:99999;
86382
+ background:rgba(0,0,0,0.55); backdrop-filter:blur(4px);
86383
+ display:flex; align-items:center; justify-content:center;
86384
+ font-family:'Roboto',Inter,system-ui,sans-serif;
86385
+ `;
86386
+ overlay.addEventListener("click", (e) => {
86387
+ if (e.target === overlay) close();
86388
+ });
86389
+ document.body.appendChild(overlay);
86390
+ function close() {
86391
+ if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
86392
+ }
86393
+ function renderShell(bodyHtml, footerHtml) {
86394
+ overlay.innerHTML = `
86395
+ <div style="
86396
+ background:#1e1e2e; color:#e5e7eb; border:1px solid rgba(255,255,255,0.1);
86397
+ border-radius:14px; box-shadow:0 16px 48px rgba(0,0,0,0.4);
86398
+ max-width:860px; width:92%; max-height:85vh; display:flex; flex-direction:column;
86399
+ ">
86400
+ <div style="padding:18px 22px; border-bottom:1px solid rgba(255,255,255,0.08); display:flex; justify-content:space-between; align-items:flex-start; flex-shrink:0;">
86401
+ <div>
86402
+ <div style="font-size:16px; font-weight:700; color:#f9fafb;">\u{1F9F9} Clear GCDR IDs</div>
86403
+ <div style="font-size:12px; color:#9ca3af; margin-top:2px;">Customer: ${customerName}</div>
86404
+ </div>
86405
+ <button id="gcdr-clear-x" style="background:none;border:none;color:#9ca3af;font-size:20px;cursor:pointer;padding:4px;line-height:1;">\u2715</button>
86406
+ </div>
86407
+ <div id="gcdr-clear-body" style="padding:18px 22px; overflow-y:auto; flex:1;">${bodyHtml}</div>
86408
+ <div id="gcdr-clear-footer" style="padding:14px 22px; border-top:1px solid rgba(255,255,255,0.08); display:flex; justify-content:flex-end; gap:10px; flex-shrink:0;">${footerHtml}</div>
86409
+ </div>`;
86410
+ overlay.querySelector("#gcdr-clear-x")?.addEventListener("click", close);
86411
+ }
86412
+ function btn(id, label, primary = false, disabled = false) {
86413
+ return `<button id="${id}" style="
86414
+ padding:8px 18px; border-radius:6px; font-size:13px; font-weight:600; cursor:${disabled ? "not-allowed" : "pointer"};
86415
+ ${primary ? "background:#dc2626; color:white; border:none;" : "background:transparent; color:#9ca3af; border:1px solid rgba(255,255,255,0.15);"}
86416
+ opacity:${disabled ? "0.5" : "1"};
86417
+ " ${disabled ? "disabled" : ""}>${label}</button>`;
86418
+ }
86419
+ function progressHtml(phase, done, total) {
86420
+ const pct = total > 0 ? Math.round(done / total * 100) : 0;
86421
+ return `
86422
+ <div style="padding:8px 0;">
86423
+ <div style="font-size:13px; font-weight:600; color:#d1d5db; margin-bottom:14px;">\u23F3 ${phase}</div>
86424
+ <div style="background:rgba(255,255,255,0.1); border-radius:8px; height:8px; overflow:hidden; margin-bottom:6px;">
86425
+ <div style="background:#dc2626; height:100%; width:${pct}%; border-radius:8px; transition:width 0.2s;"></div>
86426
+ </div>
86427
+ <div style="display:flex; justify-content:space-between; font-size:12px; color:#9ca3af;">
86428
+ <span>${done} / ${total} entidades</span><span style="font-weight:700; color:#dc2626;">${pct}%</span>
86429
+ </div>
86430
+ </div>`;
86431
+ }
86432
+ function keyBadges(present, keys) {
86433
+ return keys.filter((k) => present[k]).map((k) => `<span style="font-size:9px; background:rgba(220,38,38,0.25); color:#fca5a5; padding:1px 5px; border-radius:3px; margin:1px; display:inline-block;">${k}</span>`).join("");
86434
+ }
86435
+ function tableHtml(rows, keys) {
86436
+ if (rows.length === 0) return '<div style="font-size:12px;color:#6b7280;padding:6px 0;">Nenhum item a limpar.</div>';
86437
+ return `<div style="overflow-x:auto; margin-top:4px;">
86438
+ <table style="border-collapse:collapse; width:100%; font-size:11px;">
86439
+ <thead><tr>
86440
+ <th style="text-align:left; padding:5px 8px; border-bottom:1px solid rgba(255,255,255,0.08); color:#9ca3af; font-weight:600;">Nome TB</th>
86441
+ <th style="text-align:left; padding:5px 8px; border-bottom:1px solid rgba(255,255,255,0.08); color:#9ca3af; font-weight:600;">Chaves a remover</th>
86442
+ </tr></thead>
86443
+ <tbody>${rows.map((r) => `<tr>
86444
+ <td style="padding:5px 8px; border-bottom:1px solid rgba(255,255,255,0.05); color:#d1d5db;" title="${r.tbId}">
86445
+ ${r.name}<br><span style="color:#6b7280; font-family:monospace; font-size:9px;">${r.tbId.substring(0, 8)}\u2026</span>
86446
+ </td>
86447
+ <td style="padding:5px 8px; border-bottom:1px solid rgba(255,255,255,0.05);">${keyBadges(r.present, keys)}</td>
86448
+ </tr>`).join("")}</tbody>
86449
+ </table>
86450
+ </div>`;
86451
+ }
86452
+ function previewHtml(deviceRows, assetRows) {
86453
+ const devToClear = deviceRows.filter((r) => r.hasAny);
86454
+ const assetToClear = assetRows.filter((r) => r.hasAny);
86455
+ const badge = (txt, danger = false) => `<span style="font-size:11px; padding:3px 10px; border-radius:4px; background:${danger ? "rgba(220,38,38,0.2)" : "rgba(255,255,255,0.07)"}; color:${danger ? "#fca5a5" : "#9ca3af"};">${txt}</span>`;
86456
+ return `
86457
+ <div style="display:flex; gap:8px; flex-wrap:wrap; margin-bottom:14px;">
86458
+ ${badge(`\u{1F9F9} ${devToClear.length} devices a limpar`, devToClear.length > 0)}
86459
+ ${badge(`\u2713 ${deviceRows.length - devToClear.length} devices j\xE1 limpos`)}
86460
+ ${badge(`\u{1F9F9} ${assetToClear.length} assets a limpar`, assetToClear.length > 0)}
86461
+ ${badge(`\u2713 ${assetRows.length - assetToClear.length} assets j\xE1 limpos`)}
86462
+ </div>
86463
+ <div style="font-size:10px; font-weight:700; color:#9ca3af; text-transform:uppercase; letter-spacing:.5px; margin-bottom:4px;">
86464
+ \u{1F4DF} Devices (${deviceRows.length})
86465
+ <span style="font-size:9px; font-weight:400; text-transform:none; margin-left:6px;">chaves: ${GCDR_CLEAR_DEVICE_KEYS.join(", ")}</span>
86466
+ </div>
86467
+ ${tableHtml(devToClear, GCDR_CLEAR_DEVICE_KEYS)}
86468
+ <div style="font-size:10px; font-weight:700; color:#9ca3af; text-transform:uppercase; letter-spacing:.5px; margin:14px 0 4px;">
86469
+ \u{1F4C1} Assets (${assetRows.length})
86470
+ <span style="font-size:9px; font-weight:400; text-transform:none; margin-left:6px;">chaves: ${GCDR_CLEAR_ASSET_KEYS.join(", ")}</span>
86471
+ </div>
86472
+ ${tableHtml(assetToClear, GCDR_CLEAR_ASSET_KEYS)}
86473
+ ${devToClear.length + assetToClear.length === 0 ? '<div style="text-align:center; color:#6b7280; padding:16px 0; font-size:13px;">\u2705 Nenhum device ou asset possui essas chaves. Nada a limpar.</div>' : ""}`;
86474
+ }
86475
+ renderShell(progressHtml("Buscando devices e assets\u2026", 0, 0), "");
86476
+ try {
86477
+ const [devicesResp, assetsResp] = await Promise.all([
86478
+ tbFetch(state6, `/api/customer/${customerId}/devices?pageSize=1000&page=0`),
86479
+ tbFetch(state6, `/api/customer/${customerId}/assets?pageSize=1000&page=0`)
86480
+ ]);
86481
+ const tbDevices = devicesResp.data || [];
86482
+ const tbAssets = assetsResp.data || [];
86483
+ const total = tbDevices.length + tbAssets.length;
86484
+ const deviceRows = [];
86485
+ const assetRows = [];
86486
+ async function processBatch(items, entityType, keysToCheck, targetRows, fetchAttrs) {
86487
+ const chunks = [];
86488
+ for (let i = 0; i < items.length; i += 10) chunks.push(items.slice(i, i + 10));
86489
+ for (let ci = 0; ci < chunks.length; ci++) {
86490
+ if (ci > 0) await sleep(800);
86491
+ await Promise.all(chunks[ci].map(async (entity) => {
86492
+ const tbId = entity.id?.id || "";
86493
+ const name = entity.name || entity.label || tbId;
86494
+ let present = {};
86495
+ try {
86496
+ const attrs = await fetchAttrs(tbId);
86497
+ for (const k of keysToCheck) present[k] = attrs[k] != null && attrs[k] !== "";
86498
+ } catch {
86499
+ }
86500
+ targetRows.push({ tbId, name, entityType, present, hasAny: keysToCheck.some((k) => present[k]) });
86501
+ }));
86502
+ const done = deviceRows.length + assetRows.length;
86503
+ const bodyEl = overlay.querySelector("#gcdr-clear-body");
86504
+ if (bodyEl) bodyEl.innerHTML = progressHtml(`Lendo SERVER_SCOPE (${entityType})\u2026`, done, total);
86505
+ }
86506
+ }
86507
+ const fetchDeviceAttrs = async (id) => {
86508
+ const data = await tbFetch(
86509
+ state6,
86510
+ `/api/plugins/telemetry/DEVICE/${id}/values/attributes/SERVER_SCOPE`
86511
+ );
86512
+ return Object.fromEntries(data.map((a) => [a.key, a.value]));
86513
+ };
86514
+ const fetchAssetAttrs = async (id) => {
86515
+ const data = await tbFetch(
86516
+ state6,
86517
+ `/api/plugins/telemetry/ASSET/${id}/values/attributes/SERVER_SCOPE`
86518
+ );
86519
+ return Object.fromEntries(data.map((a) => [a.key, a.value]));
86520
+ };
86521
+ await processBatch(tbDevices, "DEVICE", GCDR_CLEAR_DEVICE_KEYS, deviceRows, fetchDeviceAttrs);
86522
+ await processBatch(tbAssets, "ASSET", GCDR_CLEAR_ASSET_KEYS, assetRows, fetchAssetAttrs);
86523
+ const devToClear = deviceRows.filter((r) => r.hasAny);
86524
+ const assetToClear = assetRows.filter((r) => r.hasAny);
86525
+ const totalToClear = devToClear.length + assetToClear.length;
86526
+ renderShell(
86527
+ previewHtml(deviceRows, assetRows),
86528
+ btn("gcdr-clear-cancel", "Cancelar") + btn("gcdr-clear-apply", `\u{1F9F9} Limpar ${totalToClear} entidade${totalToClear !== 1 ? "s" : ""}`, true, totalToClear === 0)
86529
+ );
86530
+ overlay.querySelector("#gcdr-clear-cancel")?.addEventListener("click", close);
86531
+ overlay.querySelector("#gcdr-clear-apply")?.addEventListener("click", async () => {
86532
+ let done3 = 0;
86533
+ overlay.querySelector("#gcdr-clear-footer").innerHTML = "";
86534
+ overlay.querySelector("#gcdr-clear-body").innerHTML = progressHtml("Removendo atributos GCDR\u2026", 0, totalToClear);
86535
+ async function execClear(rows, keys, entityType) {
86536
+ const results = [];
86537
+ const chunks = [];
86538
+ for (let i = 0; i < rows.length; i += 10) chunks.push(rows.slice(i, i + 10));
86539
+ for (let ci = 0; ci < chunks.length; ci++) {
86540
+ if (ci > 0) await sleep(800);
86541
+ await Promise.all(chunks[ci].map(async (row) => {
86542
+ const keysToDelete = keys.filter((k) => row.present[k]);
86543
+ try {
86544
+ await tbDelete(state6, `/api/plugins/telemetry/${entityType}/${row.tbId}/values/attributes/SERVER_SCOPE?keys=${encodeURIComponent(keysToDelete.join(","))}`);
86545
+ results.push({ name: row.name, ok: true, cleared: keysToDelete });
86546
+ } catch (err2) {
86547
+ results.push({ name: row.name, ok: false, error: err2.message });
86548
+ }
86549
+ done3++;
86550
+ const progEl = overlay.querySelector("#gcdr-clear-body");
86551
+ if (progEl) progEl.innerHTML = progressHtml("Removendo atributos GCDR\u2026", done3, totalToClear);
86552
+ }));
86553
+ }
86554
+ return results;
86555
+ }
86556
+ const devResults = await execClear(devToClear, GCDR_CLEAR_DEVICE_KEYS, "DEVICE");
86557
+ const assetResults = await execClear(assetToClear, GCDR_CLEAR_ASSET_KEYS, "ASSET");
86558
+ const allResults = [...devResults, ...assetResults];
86559
+ const ok = allResults.filter((r) => r.ok).length;
86560
+ const err = allResults.filter((r) => !r.ok).length;
86561
+ const resultHtml = `
86562
+ <div style="display:flex; gap:8px; margin-bottom:14px;">
86563
+ <span style="font-size:12px; padding:3px 10px; border-radius:4px; background:rgba(16,185,129,0.2); color:#6ee7b7;">\u2713 ${ok} limpos</span>
86564
+ ${err > 0 ? `<span style="font-size:12px; padding:3px 10px; border-radius:4px; background:rgba(220,38,38,0.2); color:#fca5a5;">\u2717 ${err} erros</span>` : ""}
86565
+ </div>
86566
+ ${[{ title: "\u{1F4DF} Devices", results: devResults }, { title: "\u{1F4C1} Assets", results: assetResults }].filter((s) => s.results.length > 0).map((s) => `
86567
+ <div style="font-size:10px; font-weight:700; color:#9ca3af; text-transform:uppercase; margin:10px 0 4px;">${s.title}</div>
86568
+ <ul style="list-style:none; margin:0; padding:0;">
86569
+ ${s.results.map((r) => `
86570
+ <li style="display:flex; align-items:flex-start; gap:8px; padding:5px 0; border-bottom:1px solid rgba(255,255,255,0.05);">
86571
+ <span>${r.ok ? "\u2705" : "\u274C"}</span>
86572
+ <div>
86573
+ <div style="font-size:11px; color:#d1d5db;">${r.name}</div>
86574
+ <div style="font-size:10px; color:${r.ok ? "#6ee7b7" : "#fca5a5"};">
86575
+ ${r.ok ? `Removidas: ${r.cleared?.join(", ")}` : r.error}
86576
+ </div>
86577
+ </div>
86578
+ </li>`).join("")}
86579
+ </ul>`).join("")}`;
86580
+ overlay.querySelector("#gcdr-clear-body").innerHTML = resultHtml;
86581
+ overlay.querySelector("#gcdr-clear-footer").innerHTML = btn("gcdr-clear-done", "Fechar");
86582
+ overlay.querySelector("#gcdr-clear-done")?.addEventListener("click", close);
86583
+ });
86584
+ } catch (err) {
86585
+ overlay.querySelector("#gcdr-clear-body").innerHTML = `<div style="color:#fca5a5; font-size:13px; padding:8px 0;">\u274C ${err.message}</div>`;
86586
+ overlay.querySelector("#gcdr-clear-footer").innerHTML = btn("gcdr-clear-err-close", "Fechar");
86587
+ overlay.querySelector("#gcdr-clear-err-close")?.addEventListener("click", close);
86588
+ }
86589
+ }
86116
86590
  function setupEventListeners3(container, state6, modalId, t, onClose) {
86117
86591
  const closeHandler = () => closeModal(container, onClose);
86118
86592
  const overlay = container.querySelector(".myio-upsell-modal-overlay");
@@ -86312,6 +86786,17 @@ function setupEventListeners3(container, state6, modalId, t, onClose) {
86312
86786
  if (e.key === "Escape") closeHandler();
86313
86787
  };
86314
86788
  document.addEventListener("keydown", escHandler);
86789
+ const cfFilterPanelCloseHandler = (e) => {
86790
+ if (!state6.checkFixFilterOpen) return;
86791
+ const btn = document.getElementById(`${modalId}-cf-filter-btn`);
86792
+ const panel = document.getElementById(`${modalId}-cf-filter-panel`);
86793
+ if (btn && !btn.contains(e.target) && panel && !panel.contains(e.target)) {
86794
+ state6.checkFixFilterOpen = false;
86795
+ renderModal4(container, state6, modalId, t);
86796
+ setupEventListeners3(container, state6, modalId, t, onClose);
86797
+ }
86798
+ };
86799
+ document.addEventListener("click", cfFilterPanelCloseHandler);
86315
86800
  document.getElementById(`${modalId}-load-relations`)?.addEventListener("click", () => {
86316
86801
  void loadDeviceRelations(state6, container, modalId, t, onClose);
86317
86802
  });
@@ -86458,6 +86943,82 @@ function setupEventListeners3(container, state6, modalId, t, onClose) {
86458
86943
  renderModal4(container, state6, modalId, t);
86459
86944
  setupEventListeners3(container, state6, modalId, t, onClose);
86460
86945
  });
86946
+ container.querySelectorAll("[data-cf-sort]").forEach((el2) => {
86947
+ el2.addEventListener("click", () => {
86948
+ const field = el2.dataset.cfSort;
86949
+ if (state6.checkFixSort.field === field) {
86950
+ state6.checkFixSort.order = state6.checkFixSort.order === "asc" ? "desc" : "asc";
86951
+ } else {
86952
+ state6.checkFixSort = { field, order: "asc" };
86953
+ }
86954
+ const listEl = document.getElementById(`${modalId}-device-list`);
86955
+ const savedScroll = listEl ? listEl.scrollTop : 0;
86956
+ renderModal4(container, state6, modalId, t);
86957
+ setupEventListeners3(container, state6, modalId, t, onClose);
86958
+ const elAfter = document.getElementById(`${modalId}-device-list`);
86959
+ if (elAfter) elAfter.scrollTop = savedScroll;
86960
+ });
86961
+ });
86962
+ document.getElementById(`${modalId}-cf-filter-btn`)?.addEventListener("click", (e) => {
86963
+ e.stopPropagation();
86964
+ state6.checkFixFilterOpen = !state6.checkFixFilterOpen;
86965
+ renderModal4(container, state6, modalId, t);
86966
+ setupEventListeners3(container, state6, modalId, t, onClose);
86967
+ });
86968
+ document.getElementById(`${modalId}-cf-flt-close`)?.addEventListener("click", () => {
86969
+ state6.checkFixFilterOpen = false;
86970
+ renderModal4(container, state6, modalId, t);
86971
+ setupEventListeners3(container, state6, modalId, t, onClose);
86972
+ });
86973
+ document.getElementById(`${modalId}-cf-flt-clear`)?.addEventListener("click", () => {
86974
+ state6.checkFixAdvancedFilter = { statuses: [], connStatuses: [], domains: [], missingIngestionId: false, missingCentralSlave: false };
86975
+ renderModal4(container, state6, modalId, t);
86976
+ setupEventListeners3(container, state6, modalId, t, onClose);
86977
+ });
86978
+ document.getElementById(`${modalId}-cf-flt-noing`)?.addEventListener("change", (e) => {
86979
+ state6.checkFixAdvancedFilter.missingIngestionId = e.target.checked;
86980
+ renderModal4(container, state6, modalId, t);
86981
+ setupEventListeners3(container, state6, modalId, t, onClose);
86982
+ });
86983
+ document.getElementById(`${modalId}-cf-flt-nocentral`)?.addEventListener("change", (e) => {
86984
+ state6.checkFixAdvancedFilter.missingCentralSlave = e.target.checked;
86985
+ renderModal4(container, state6, modalId, t);
86986
+ setupEventListeners3(container, state6, modalId, t, onClose);
86987
+ });
86988
+ const cfFilterPanel = document.getElementById(`${modalId}-cf-filter-panel`);
86989
+ if (cfFilterPanel) {
86990
+ cfFilterPanel.querySelectorAll("[data-cf-flt-status]").forEach((btn) => {
86991
+ btn.addEventListener("click", () => {
86992
+ const s = btn.dataset.cfFltStatus;
86993
+ const idx = state6.checkFixAdvancedFilter.statuses.indexOf(s);
86994
+ if (idx >= 0) state6.checkFixAdvancedFilter.statuses.splice(idx, 1);
86995
+ else state6.checkFixAdvancedFilter.statuses.push(s);
86996
+ renderModal4(container, state6, modalId, t);
86997
+ setupEventListeners3(container, state6, modalId, t, onClose);
86998
+ });
86999
+ });
87000
+ cfFilterPanel.querySelectorAll("[data-cf-flt-conn]").forEach((btn) => {
87001
+ btn.addEventListener("click", () => {
87002
+ const c = btn.dataset.cfFltConn;
87003
+ const idx = state6.checkFixAdvancedFilter.connStatuses.indexOf(c);
87004
+ if (idx >= 0) state6.checkFixAdvancedFilter.connStatuses.splice(idx, 1);
87005
+ else state6.checkFixAdvancedFilter.connStatuses.push(c);
87006
+ renderModal4(container, state6, modalId, t);
87007
+ setupEventListeners3(container, state6, modalId, t, onClose);
87008
+ });
87009
+ });
87010
+ cfFilterPanel.querySelectorAll("[data-cf-flt-domain]").forEach((btn) => {
87011
+ btn.addEventListener("click", () => {
87012
+ const d = btn.dataset.cfFltDomain;
87013
+ const idx = state6.checkFixAdvancedFilter.domains.indexOf(d);
87014
+ if (idx >= 0) state6.checkFixAdvancedFilter.domains.splice(idx, 1);
87015
+ else state6.checkFixAdvancedFilter.domains.push(d);
87016
+ renderModal4(container, state6, modalId, t);
87017
+ setupEventListeners3(container, state6, modalId, t, onClose);
87018
+ });
87019
+ });
87020
+ }
87021
+ _setupCfStatusTooltip(container, modalId, getThemeColors5(state6.theme));
86461
87022
  document.getElementById(`${modalId}-mode-single`)?.addEventListener("click", () => {
86462
87023
  if (state6.deviceSelectionMode !== "single") {
86463
87024
  state6.deviceSelectionMode = "single";
@@ -86533,26 +87094,22 @@ function setupEventListeners3(container, state6, modalId, t, onClose) {
86533
87094
  if (!deviceId) return;
86534
87095
  const device = state6.devices.find((d) => d.id?.id === deviceId);
86535
87096
  if (!device) return;
86536
- setTimeout(() => {
86537
- const isChecked = input.checked;
86538
- if (isChecked) {
86539
- if (!state6.selectedDevices.some((d) => d.id?.id === deviceId)) {
86540
- state6.selectedDevices.push(device);
86541
- }
86542
- } else {
86543
- state6.selectedDevices = state6.selectedDevices.filter((d) => d.id?.id !== deviceId);
87097
+ const isChecked = input.checked;
87098
+ if (isChecked) {
87099
+ if (!state6.selectedDevices.some((d) => d.id?.id === deviceId)) {
87100
+ state6.selectedDevices.push(device);
86544
87101
  }
86545
- const listEl = document.getElementById(`${modalId}-device-list`);
86546
- const savedScroll = listEl ? listEl.scrollTop : 0;
86547
- renderModal4(container, state6, modalId, t);
86548
- setupEventListeners3(container, state6, modalId, t, onClose);
86549
- requestAnimationFrame(() => {
86550
- requestAnimationFrame(() => {
86551
- const el2 = document.getElementById(`${modalId}-device-list`);
86552
- if (el2) el2.scrollTop = savedScroll;
86553
- });
86554
- });
86555
- }, 0);
87102
+ } else {
87103
+ state6.selectedDevices = state6.selectedDevices.filter((d) => d.id?.id !== deviceId);
87104
+ }
87105
+ const listEl = document.getElementById(`${modalId}-device-list`);
87106
+ const savedScroll = listEl ? listEl.scrollTop : 0;
87107
+ const containerScrollTop = container.scrollTop;
87108
+ renderModal4(container, state6, modalId, t);
87109
+ setupEventListeners3(container, state6, modalId, t, onClose);
87110
+ const elAfter = document.getElementById(`${modalId}-device-list`);
87111
+ if (elAfter) elAfter.scrollTop = savedScroll;
87112
+ container.scrollTop = containerScrollTop;
86556
87113
  };
86557
87114
  });
86558
87115
  document.getElementById(`${modalId}-bulk-attr`)?.addEventListener("click", () => {
@@ -86671,6 +87228,13 @@ function setupEventListeners3(container, state6, modalId, t, onClose) {
86671
87228
  if (!state6.selectedCustomer || state6.selectedDevices.length === 0) return;
86672
87229
  await handleBulkSyncIngestionId(state6, container, modalId, t, onClose);
86673
87230
  });
87231
+ document.getElementById(`${modalId}-clear-gcdr-ids`)?.addEventListener("click", () => {
87232
+ if (!state6.selectedCustomer) {
87233
+ alert("Selecione um Customer primeiro no Step 1");
87234
+ return;
87235
+ }
87236
+ void openClearGcdrIdsModal(state6);
87237
+ });
86674
87238
  document.getElementById(`${modalId}-bulk-relation`)?.addEventListener("click", () => {
86675
87239
  if (!state6.selectedCustomer) {
86676
87240
  alert("Selecione um Customer primeiro no Step 1");
@@ -87175,7 +87739,9 @@ function filterDeviceListVisual(container, devices, search, filters, sort) {
87175
87739
  return;
87176
87740
  }
87177
87741
  const matchesSearch = !search || device.name?.toLowerCase().includes(search) || device.label?.toLowerCase().includes(search) || device.type?.toLowerCase().includes(search) || device.serverAttrs?.deviceType?.toLowerCase().includes(search) || device.serverAttrs?.deviceProfile?.toLowerCase().includes(search);
87178
- item.style.display = matchesSearch ? "flex" : "none";
87742
+ const el2 = item;
87743
+ const isTableRow = el2.tagName === "TR";
87744
+ el2.style.display = matchesSearch ? isTableRow ? "" : "flex" : "none";
87179
87745
  });
87180
87746
  }
87181
87747
  async function tbFetch(state6, path) {
@@ -88789,7 +89355,7 @@ var UserListTab = class {
88789
89355
  const now = (/* @__PURE__ */ new Date()).toISOString();
88790
89356
  try {
88791
89357
  const searchRes = await fetch(
88792
- `${base}/api/v1/users?search=${encodeURIComponent(user.email)}&customerId=${encodeURIComponent(this.config.customerId)}&limit=10`,
89358
+ `${base}/users?search=${encodeURIComponent(user.email)}&customerId=${encodeURIComponent(this.config.customerId)}&limit=10`,
88793
89359
  { headers: this.gcdrHeaders() }
88794
89360
  );
88795
89361
  let gcdrUser = null;
@@ -88799,7 +89365,7 @@ var UserListTab = class {
88799
89365
  gcdrUser = items.find((u) => u.email?.toLowerCase() === user.email.toLowerCase()) ?? null;
88800
89366
  }
88801
89367
  if (!gcdrUser) {
88802
- const createRes = await fetch(`${base}/api/v1/users`, {
89368
+ const createRes = await fetch(`${base}/users`, {
88803
89369
  method: "POST",
88804
89370
  headers: this.gcdrHeaders(),
88805
89371
  body: JSON.stringify({
@@ -92182,6 +92748,272 @@ function openUserManagementModal(params) {
92182
92748
  controller.show();
92183
92749
  }
92184
92750
 
92751
+ // src/components/gcdr-auth/MyIOAuthContext.ts
92752
+ var MyIOAuthContext = class _MyIOAuthContext {
92753
+ allowedActions = /* @__PURE__ */ new Set();
92754
+ deniedActions = /* @__PURE__ */ new Set();
92755
+ snap;
92756
+ constructor(snap) {
92757
+ this.snap = snap;
92758
+ }
92759
+ // ── Public read-only state ────────────────────────────────────────────────
92760
+ get ready() {
92761
+ return this.snap.ready;
92762
+ }
92763
+ get gcdrUserId() {
92764
+ return this.snap.gcdrUserId;
92765
+ }
92766
+ get scope() {
92767
+ return this.snap.scope;
92768
+ }
92769
+ get error() {
92770
+ return this.snap.error;
92771
+ }
92772
+ get allowAll() {
92773
+ return this.snap.allowAll;
92774
+ }
92775
+ get assignments() {
92776
+ return this.snap.assignments;
92777
+ }
92778
+ // ── Permission checks ─────────────────────────────────────────────────────
92779
+ /**
92780
+ * Returns `true` if the current user is allowed to perform `action`.
92781
+ *
92782
+ * Evaluation order (deny-first):
92783
+ * 1. If allowAll (TENANT_ADMIN) → always true
92784
+ * 2. If action (or `*`) is in the deny set → false
92785
+ * 3. If action (or `*`) is in the allow set → true
92786
+ * 4. Otherwise → false (closed-by-default)
92787
+ *
92788
+ * Returns `false` while not yet ready.
92789
+ */
92790
+ can(action) {
92791
+ if (!this.snap.ready) return false;
92792
+ if (this.snap.allowAll) return true;
92793
+ if (this.deniedActions.has("*") || this.deniedActions.has(action)) return false;
92794
+ return this.allowedActions.has("*") || this.allowedActions.has(action);
92795
+ }
92796
+ /** True if the user can perform **at least one** of the given actions. */
92797
+ canAny(...actions) {
92798
+ return actions.some((a) => this.can(a));
92799
+ }
92800
+ /** True if the user can perform **all** of the given actions. */
92801
+ canAll(...actions) {
92802
+ return actions.every((a) => this.can(a));
92803
+ }
92804
+ // ── Factory ───────────────────────────────────────────────────────────────
92805
+ /**
92806
+ * Initialises the auth context.
92807
+ * Resolves immediately when `allowAll` is set or GCDR is not configured.
92808
+ * Always dispatches `myio:auth-ready` when done (success or error).
92809
+ */
92810
+ static async init(config) {
92811
+ const scope = config.scope ?? (config.customerId ? `customer:${config.customerId}` : "*");
92812
+ const ctx = new _MyIOAuthContext({
92813
+ ready: false,
92814
+ gcdrUserId: null,
92815
+ scope,
92816
+ allowAll: config.allowAll ?? false,
92817
+ error: null,
92818
+ assignments: []
92819
+ });
92820
+ if (config.allowAll) {
92821
+ ctx.snap.ready = true;
92822
+ ctx._dispatch();
92823
+ return ctx;
92824
+ }
92825
+ if (!config.gcdrApiBaseUrl) {
92826
+ ctx.snap.ready = true;
92827
+ ctx.snap.error = "GCDR not configured";
92828
+ ctx._dispatch();
92829
+ return ctx;
92830
+ }
92831
+ try {
92832
+ const hdrs = ctx._gcdrHeaders(config);
92833
+ const gcdrUserId = await ctx._resolveGcdrUserId(config, hdrs);
92834
+ if (!gcdrUserId) {
92835
+ ctx.snap.error = "GCDR user not found";
92836
+ ctx.snap.ready = true;
92837
+ ctx._dispatch();
92838
+ return ctx;
92839
+ }
92840
+ ctx.snap.gcdrUserId = gcdrUserId;
92841
+ const assignRes = await fetch(
92842
+ `${config.gcdrApiBaseUrl}/authorization/users/${gcdrUserId}/assignments`,
92843
+ { headers: hdrs }
92844
+ );
92845
+ let allAssignments = [];
92846
+ if (assignRes.ok) {
92847
+ const json = await assignRes.json();
92848
+ allAssignments = Array.isArray(json) ? json : json.assignments ?? [];
92849
+ }
92850
+ const relevant = allAssignments.filter(
92851
+ (a) => a.status === "active" && (a.scope === "*" || a.scope === scope)
92852
+ );
92853
+ ctx.snap.assignments = relevant;
92854
+ if (relevant.length === 0) {
92855
+ ctx.snap.ready = true;
92856
+ ctx._dispatch();
92857
+ return ctx;
92858
+ }
92859
+ const roleKeys = [...new Set(relevant.map((a) => a.roleKey))];
92860
+ const roles = await ctx._fetchRoles(config.gcdrApiBaseUrl, roleKeys, hdrs);
92861
+ const policyKeys = [...new Set(
92862
+ roles.flatMap((r) => [...r.policies ?? [], ...r.policyIds ?? []])
92863
+ )];
92864
+ if (policyKeys.length > 0) {
92865
+ const policies = await ctx._fetchPolicies(config.gcdrApiBaseUrl, policyKeys, hdrs);
92866
+ ctx._applyPolicies(policies);
92867
+ }
92868
+ } catch (err) {
92869
+ ctx.snap.error = err?.message ?? "Auth init failed";
92870
+ }
92871
+ ctx.snap.ready = true;
92872
+ ctx._dispatch();
92873
+ return ctx;
92874
+ }
92875
+ // ── Private helpers ───────────────────────────────────────────────────────
92876
+ _gcdrHeaders(config) {
92877
+ return {
92878
+ "Content-Type": "application/json",
92879
+ "X-API-Key": config.gcdrApiKey || "",
92880
+ "X-Tenant-ID": config.gcdrTenantId || ""
92881
+ };
92882
+ }
92883
+ /**
92884
+ * Two-step GCDR user lookup:
92885
+ * 1. Read `gcdrUserConfigs` from TB SERVER_SCOPE attribute (fast path if previously synced)
92886
+ * 2. Email search via GCDR API
92887
+ */
92888
+ async _resolveGcdrUserId(config, gcdrHeaders) {
92889
+ if (config.currentUserTbId && config.tbBaseUrl && config.jwtToken) {
92890
+ try {
92891
+ const res = await fetch(
92892
+ `${config.tbBaseUrl}/api/plugins/telemetry/USER/${config.currentUserTbId}/values/attributes/SERVER_SCOPE?keys=gcdrUserConfigs`,
92893
+ { headers: { "X-Authorization": `Bearer ${config.jwtToken}` } }
92894
+ );
92895
+ if (res.ok) {
92896
+ const attrs = await res.json();
92897
+ const gcdrId = attrs.find((a) => a.key === "gcdrUserConfigs")?.value?.gcdrUserId;
92898
+ if (gcdrId) return gcdrId;
92899
+ }
92900
+ } catch {
92901
+ }
92902
+ }
92903
+ if (!config.currentUserEmail) return null;
92904
+ try {
92905
+ const q = encodeURIComponent(config.currentUserEmail);
92906
+ const cid = config.customerId ? `&customerId=${encodeURIComponent(config.customerId)}` : "";
92907
+ const res = await fetch(
92908
+ `${config.gcdrApiBaseUrl}/api/v1/users?search=${q}${cid}&limit=10`,
92909
+ { headers: gcdrHeaders }
92910
+ );
92911
+ if (res.ok) {
92912
+ const data = await res.json();
92913
+ const items = Array.isArray(data) ? data : data?.data?.items ?? data?.items ?? [];
92914
+ const found = items.find(
92915
+ (u) => u.email?.toLowerCase() === config.currentUserEmail.toLowerCase()
92916
+ );
92917
+ if (found) return found.id;
92918
+ }
92919
+ } catch {
92920
+ }
92921
+ return null;
92922
+ }
92923
+ /**
92924
+ * Fetches roles — tries GET /roles bulk endpoint first, falls back to per-key.
92925
+ */
92926
+ async _fetchRoles(base, roleKeys, headers) {
92927
+ try {
92928
+ const res = await fetch(`${base}/roles`, { headers });
92929
+ if (res.ok) {
92930
+ const json = await res.json();
92931
+ const all = Array.isArray(json) ? json : json?.data?.items ?? json?.items ?? json?.data ?? [];
92932
+ return all.filter((r) => roleKeys.includes(r.key ?? r.id));
92933
+ }
92934
+ } catch {
92935
+ }
92936
+ const settled = await Promise.allSettled(
92937
+ roleKeys.map(
92938
+ (key) => fetch(`${base}/roles/${key}`, { headers }).then((r) => r.ok ? r.json() : null)
92939
+ )
92940
+ );
92941
+ return settled.filter((r) => r.status === "fulfilled" && r.value != null).map((r) => r.value);
92942
+ }
92943
+ /**
92944
+ * Fetches policies — tries GET /policies bulk endpoint first, falls back to per-key.
92945
+ */
92946
+ async _fetchPolicies(base, policyKeys, headers) {
92947
+ try {
92948
+ const res = await fetch(`${base}/policies`, { headers });
92949
+ if (res.ok) {
92950
+ const json = await res.json();
92951
+ const all = Array.isArray(json) ? json : json?.data?.items ?? json?.items ?? json?.data ?? [];
92952
+ return all.filter((p) => policyKeys.includes(p.key ?? p.id));
92953
+ }
92954
+ } catch {
92955
+ }
92956
+ const settled = await Promise.allSettled(
92957
+ policyKeys.map(
92958
+ (key) => fetch(`${base}/policies/${key}`, { headers }).then((r) => r.ok ? r.json() : null)
92959
+ )
92960
+ );
92961
+ return settled.filter((r) => r.status === "fulfilled" && r.value != null).map((r) => r.value);
92962
+ }
92963
+ /**
92964
+ * Merges all policy allow/deny lists into the context's permission sets.
92965
+ * Deny always wins over allow (deny-first strategy).
92966
+ */
92967
+ _applyPolicies(policies) {
92968
+ for (const policy of policies) {
92969
+ for (const action of policy.allow ?? []) this.allowedActions.add(action);
92970
+ for (const action of policy.deny ?? []) this.deniedActions.add(action);
92971
+ }
92972
+ }
92973
+ /** Dispatches `myio:auth-ready` so widgets can react without polling. */
92974
+ _dispatch() {
92975
+ window.dispatchEvent(
92976
+ new CustomEvent("myio:auth-ready", {
92977
+ detail: {
92978
+ ready: this.snap.ready,
92979
+ scope: this.snap.scope,
92980
+ allowAll: this.snap.allowAll,
92981
+ error: this.snap.error
92982
+ }
92983
+ })
92984
+ );
92985
+ }
92986
+ };
92987
+ async function initMyIOAuthContext(config) {
92988
+ return MyIOAuthContext.init(config);
92989
+ }
92990
+
92991
+ // src/components/gcdr-auth/permissions.ts
92992
+ var PERM = {
92993
+ // ── Alarm actions ────────────────────────────────────────────────────────
92994
+ ALARM_VIEW: "alarm:view",
92995
+ ALARM_ACK: "alarm:ack",
92996
+ ALARM_ESCALATE: "alarm:escalate",
92997
+ ALARM_SNOOZE: "alarm:snooze",
92998
+ ALARM_CLOSE: "alarm:close",
92999
+ // ── Reports ──────────────────────────────────────────────────────────────
93000
+ REPORT_VIEW: "report:view",
93001
+ REPORT_EXPORT: "report:export",
93002
+ // ── Settings modal ───────────────────────────────────────────────────────
93003
+ SETTINGS_VIEW: "settings:view",
93004
+ SETTINGS_EDIT: "settings:edit",
93005
+ // ── User management (GCDR) ───────────────────────────────────────────────
93006
+ USER_VIEW: "user:view",
93007
+ USER_MANAGE: "user:manage",
93008
+ // ── Integration config ───────────────────────────────────────────────────
93009
+ INTEGRATION_VIEW: "integration:view",
93010
+ INTEGRATION_EDIT: "integration:edit",
93011
+ // ── Dashboard config ─────────────────────────────────────────────────────
93012
+ DASHBOARD_CONFIG: "dashboard:configure",
93013
+ // ── Shopping / customer selector ─────────────────────────────────────────
93014
+ SHOPPING_SELECT: "shopping:select"
93015
+ };
93016
+
92185
93017
  // src/components/menu/types.ts
92186
93018
  var DEFAULT_LIGHT_THEME3 = {
92187
93019
  // Tabs
@@ -139604,6 +140436,7 @@ var version = package_default.version || "0.0.0";
139604
140436
  MenuShoppingView,
139605
140437
  MenuView,
139606
140438
  ModalHeader,
140439
+ MyIOAuthContext,
139607
140440
  MyIOChartModal,
139608
140441
  MyIODraggableCard,
139609
140442
  MyIOSelectionStore,
@@ -139632,6 +140465,7 @@ var version = package_default.version || "0.0.0";
139632
140465
  OperationalGeneralListController,
139633
140466
  OperationalGeneralListView,
139634
140467
  OperationalHeaderDevicesGridView,
140468
+ PERM,
139635
140469
  POWER_LIMITS_DEVICE_TYPES,
139636
140470
  POWER_LIMITS_STATUS_CONFIG,
139637
140471
  POWER_LIMITS_TELEMETRY_TYPES,
@@ -139900,6 +140734,7 @@ var version = package_default.version || "0.0.0";
139900
140734
  groupByDay,
139901
140735
  handleDeviceType,
139902
140736
  hasSelectedDays,
140737
+ initMyIOAuthContext,
139903
140738
  initOnOffTimelineTooltips,
139904
140739
  injectActionButtonStyles,
139905
140740
  injectAlarmsNotificationsPanelStyles,