myio-js-library 0.1.430 → 0.1.432

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
@@ -1137,7 +1137,7 @@ module.exports = __toCommonJS(index_exports);
1137
1137
  // package.json
1138
1138
  var package_default = {
1139
1139
  name: "myio-js-library",
1140
- version: "0.1.430",
1140
+ version: "0.1.432",
1141
1141
  description: "A clean, standalone JS SDK for MYIO projects",
1142
1142
  license: "MIT",
1143
1143
  repository: "github:gh-myio/myio-js-library",
@@ -31007,14 +31007,11 @@ var AllReportModal = class {
31007
31007
  totalMappedConsumption += consumption;
31008
31008
  rows.push(result);
31009
31009
  }
31010
- this.debugLog(
31011
- "[AllReportModal] API-driven filter \u2014 matched:",
31012
- rows.length,
31013
- "| discarded:",
31014
- apiArray.length - rows.length,
31015
- "| total consumption:",
31016
- totalMappedConsumption
31017
- );
31010
+ this.debugLog("[AllReportModal] API-driven filter", {
31011
+ matched: rows.length,
31012
+ discarded: apiArray.length - rows.length,
31013
+ totalConsumption: totalMappedConsumption
31014
+ });
31018
31015
  return rows;
31019
31016
  }
31020
31017
  parseConsumptionValue(item) {
@@ -55677,9 +55674,6 @@ function removeAlarmsNotificationsPanelStyles() {
55677
55674
  }
55678
55675
 
55679
55676
  // src/components/premium-modals/settings/alarms/AlarmsTab.ts
55680
- var GCDR_INTEGRATION_API_KEY = "gcdr_cust_tb_integration_key_2026";
55681
- var GCDR_DEFAULT_BASE_URL = "https://gcdr-api.a.myio-bas.com";
55682
- var ALARMS_DEFAULT_BASE_URL = "https://alarms-api.a.myio-bas.com";
55683
55677
  var PRIORITY_ORDER = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 };
55684
55678
  var PRIORITY_COLORS = {
55685
55679
  CRITICAL: "#dc2626",
@@ -55707,10 +55701,15 @@ var AlarmsTab = class {
55707
55701
  const { container } = this.config;
55708
55702
  this.injectStyles();
55709
55703
  container.innerHTML = this.getLoadingHTML();
55704
+ const orch = this.orch;
55705
+ if (!orch?.gcdrFetchCustomerRules) {
55706
+ this.showToast("AlarmsTab: MyIOOrchestrator n\xE3o inicializado");
55707
+ container.innerHTML = this.getErrorHTML("MyIOOrchestrator n\xE3o inicializado com API GCDR");
55708
+ return;
55709
+ }
55710
55710
  try {
55711
- const gcdrBaseUrl = this.config.gcdrApiBaseUrl || GCDR_DEFAULT_BASE_URL;
55712
55711
  const alarms = this.readAlarmsFromASO();
55713
- const rules = this.config.prefetchedRules != null ? this.config.prefetchedRules : await this.fetchCustomerRules(gcdrBaseUrl);
55712
+ const rules = this.config.prefetchedRules != null ? this.config.prefetchedRules : await this.orch.gcdrFetchCustomerRules();
55714
55713
  this.activeAlarms = alarms;
55715
55714
  this.customerRules = rules;
55716
55715
  for (const rule of this.customerRules) {
@@ -55749,9 +55748,7 @@ var AlarmsTab = class {
55749
55748
  }
55750
55749
  const prefetched = this.config.prefetchedAlarms;
55751
55750
  if (prefetched != null) {
55752
- return prefetched.filter(
55753
- (a) => a.deviceId === this.config.gcdrDeviceId
55754
- );
55751
+ return prefetched.filter((a) => a.deviceId === this.config.gcdrDeviceId);
55755
55752
  }
55756
55753
  return [];
55757
55754
  }
@@ -55765,7 +55762,7 @@ var AlarmsTab = class {
55765
55762
  mapAlarmToCard(alarm) {
55766
55763
  return {
55767
55764
  id: alarm.id,
55768
- customerId: this.config.gcdrCustomerId,
55765
+ customerId: this.orch?.gcdrCustomerId ?? "",
55769
55766
  customerName: "",
55770
55767
  source: alarm.deviceName || "",
55771
55768
  severity: alarm.severity || "LOW",
@@ -55779,121 +55776,12 @@ var AlarmsTab = class {
55779
55776
  };
55780
55777
  }
55781
55778
  // ============================================================================
55782
- // Data fetching
55783
- // ============================================================================
55784
- async fetchActiveAlarms(baseUrl) {
55785
- const url = `${baseUrl}/api/v1/alarms?deviceId=${encodeURIComponent(this.config.gcdrDeviceId)}&state=OPEN,ACK,ESCALATED,SNOOZED&limit=100&page=1`;
55786
- const response = await fetch(url, {
55787
- method: "GET",
55788
- headers: {
55789
- "X-API-Key": this.config.alarmsApiKey ?? GCDR_INTEGRATION_API_KEY,
55790
- "X-Tenant-ID": this.config.gcdrTenantId,
55791
- Accept: "application/json"
55792
- }
55793
- });
55794
- if (response.status === 404) return [];
55795
- if (!response.ok) {
55796
- throw new Error(`Alarms API error (${response.status}): ${response.statusText}`);
55797
- }
55798
- const json = await response.json();
55799
- if (Array.isArray(json.data)) return json.data;
55800
- return json.items ?? json.data?.items ?? [];
55801
- }
55802
- async fetchCustomerRules(baseUrl) {
55803
- const url = `${baseUrl}/api/v1/customers/${encodeURIComponent(this.config.gcdrCustomerId)}/rules`;
55804
- const response = await fetch(url, {
55805
- method: "GET",
55806
- headers: {
55807
- "X-API-Key": this.config.gcdrApiKey ?? GCDR_INTEGRATION_API_KEY,
55808
- "X-Tenant-ID": this.config.gcdrTenantId,
55809
- Accept: "application/json"
55810
- }
55811
- });
55812
- if (response.status === 404) return [];
55813
- if (!response.ok) {
55814
- throw new Error(`GCDR error fetching rules (${response.status}): ${response.statusText}`);
55815
- }
55816
- const json = await response.json();
55817
- return json.items ?? json.data?.items ?? [];
55818
- }
55819
- async postAlarmAction(baseUrl, alarmId, action) {
55820
- try {
55821
- const response = await fetch(`${baseUrl}/api/v1/alarms/${encodeURIComponent(alarmId)}/${action}`, {
55822
- method: "POST",
55823
- headers: {
55824
- "X-API-Key": this.config.alarmsApiKey ?? GCDR_INTEGRATION_API_KEY,
55825
- "X-Tenant-ID": this.config.gcdrTenantId
55826
- }
55827
- });
55828
- return response.ok;
55829
- } catch {
55830
- return false;
55831
- }
55832
- }
55833
- async patchRuleScope(baseUrl, ruleId, entityIds) {
55834
- try {
55835
- const url = `${baseUrl}/api/v1/rules/${encodeURIComponent(ruleId)}`;
55836
- const resolvedKey = this.config.gcdrApiKey ?? GCDR_INTEGRATION_API_KEY;
55837
- const body = { scope: { type: "DEVICE", entityIds } };
55838
- console.group("[AlarmsTab] patchRuleScope");
55839
- console.log("url :", url);
55840
- console.log("ruleId :", ruleId);
55841
- console.log("apiKey (used):", resolvedKey);
55842
- console.log("tenantId :", this.config.gcdrTenantId);
55843
- console.log("body :", body);
55844
- console.groupEnd();
55845
- const response = await fetch(url, {
55846
- method: "PATCH",
55847
- headers: {
55848
- "X-API-Key": resolvedKey,
55849
- "X-Tenant-ID": this.config.gcdrTenantId,
55850
- "Content-Type": "application/json"
55851
- },
55852
- body: JSON.stringify(body)
55853
- });
55854
- console.log(`[AlarmsTab] patchRuleScope response: ${response.status} ${response.statusText} ok=${response.ok}`);
55855
- return response.ok;
55856
- } catch (err) {
55857
- console.error("[AlarmsTab] patchRuleScope error:", err);
55858
- return false;
55859
- }
55860
- }
55861
- async patchRuleValue(baseUrl, ruleId, alarmConfig) {
55862
- try {
55863
- const url = `${baseUrl}/api/v1/rules/${encodeURIComponent(ruleId)}`;
55864
- const resolvedKey = this.config.gcdrApiKey ?? GCDR_INTEGRATION_API_KEY;
55865
- const body = { alarmConfig };
55866
- console.group("[AlarmsTab] patchRuleValue");
55867
- console.log("url :", url);
55868
- console.log("ruleId :", ruleId);
55869
- console.log("apiKey (used):", resolvedKey);
55870
- console.log("tenantId :", this.config.gcdrTenantId);
55871
- console.log("body :", body);
55872
- console.groupEnd();
55873
- const response = await fetch(url, {
55874
- method: "PATCH",
55875
- headers: {
55876
- "X-API-Key": resolvedKey,
55877
- "X-Tenant-ID": this.config.gcdrTenantId,
55878
- "Content-Type": "application/json"
55879
- },
55880
- body: JSON.stringify(body)
55881
- });
55882
- console.log(`[AlarmsTab] patchRuleValue response: ${response.status} ${response.statusText} ok=${response.ok}`);
55883
- return response.ok;
55884
- } catch (err) {
55885
- console.error("[AlarmsTab] patchRuleValue error:", err);
55886
- return false;
55887
- }
55888
- }
55889
- // ============================================================================
55890
55779
  // Alarm grid population
55891
55780
  // ============================================================================
55892
55781
  populateAlarmsGrid() {
55893
55782
  const grid = this.config.container.querySelector("#at-alarms-grid");
55894
55783
  if (!grid) return;
55895
55784
  grid.innerHTML = "";
55896
- const alarmsBaseUrl = this.config.alarmsApiBaseUrl || ALARMS_DEFAULT_BASE_URL;
55897
55785
  const AlarmService2 = window.MyIOLibrary?.AlarmService;
55898
55786
  const userEmail = window.MyIOUtils?.currentUserEmail || "";
55899
55787
  for (const rawAlarm of this.activeAlarms) {
@@ -55904,22 +55792,22 @@ var AlarmsTab = class {
55904
55792
  if (AlarmService2?.batchAcknowledge) {
55905
55793
  await AlarmService2.batchAcknowledge([alarmId], userEmail);
55906
55794
  } else {
55907
- await this.postAlarmAction(alarmsBaseUrl, alarmId, "acknowledge");
55795
+ await this.orch.gcdrPostAlarmAction(alarmId, "acknowledge");
55908
55796
  }
55909
55797
  } else if (action === "snooze") {
55910
55798
  if (AlarmService2?.batchSilence) {
55911
55799
  await AlarmService2.batchSilence([alarmId], userEmail, "4h");
55912
55800
  } else {
55913
- await this.postAlarmAction(alarmsBaseUrl, alarmId, "snooze");
55801
+ await this.orch.gcdrPostAlarmAction(alarmId, "snooze");
55914
55802
  }
55915
55803
  } else if (action === "escalate") {
55916
55804
  if (AlarmService2?.batchEscalate) {
55917
55805
  await AlarmService2.batchEscalate([alarmId], userEmail);
55918
55806
  } else {
55919
- await this.postAlarmAction(alarmsBaseUrl, alarmId, "escalate");
55807
+ await this.orch.gcdrPostAlarmAction(alarmId, "escalate");
55920
55808
  }
55921
55809
  }
55922
- await this.refreshAlarmsGrid(alarmsBaseUrl);
55810
+ await this.refreshAlarmsGrid();
55923
55811
  };
55924
55812
  doAction().catch(() => {
55925
55813
  });
@@ -55933,25 +55821,25 @@ var AlarmsTab = class {
55933
55821
  if (AlarmService2?.batchAcknowledge) {
55934
55822
  await AlarmService2.batchAcknowledge([rawAlarm.id], userEmail);
55935
55823
  } else {
55936
- await this.postAlarmAction(alarmsBaseUrl, rawAlarm.id, "acknowledge");
55824
+ await this.orch.gcdrPostAlarmAction(rawAlarm.id, "acknowledge");
55937
55825
  }
55938
- await this.refreshAlarmsGrid(alarmsBaseUrl);
55826
+ await this.refreshAlarmsGrid();
55939
55827
  },
55940
55828
  onSnooze: async () => {
55941
55829
  if (AlarmService2?.batchSilence) {
55942
55830
  await AlarmService2.batchSilence([rawAlarm.id], userEmail, "4h");
55943
55831
  } else {
55944
- await this.postAlarmAction(alarmsBaseUrl, rawAlarm.id, "snooze");
55832
+ await this.orch.gcdrPostAlarmAction(rawAlarm.id, "snooze");
55945
55833
  }
55946
- await this.refreshAlarmsGrid(alarmsBaseUrl);
55834
+ await this.refreshAlarmsGrid();
55947
55835
  },
55948
55836
  onEscalate: async () => {
55949
55837
  if (AlarmService2?.batchEscalate) {
55950
55838
  await AlarmService2.batchEscalate([rawAlarm.id], userEmail);
55951
55839
  } else {
55952
- await this.postAlarmAction(alarmsBaseUrl, rawAlarm.id, "escalate");
55840
+ await this.orch.gcdrPostAlarmAction(rawAlarm.id, "escalate");
55953
55841
  }
55954
- await this.refreshAlarmsGrid(alarmsBaseUrl);
55842
+ await this.refreshAlarmsGrid();
55955
55843
  },
55956
55844
  onDetails: () => {
55957
55845
  openAlarmDetailsModal(alarm, "light", "separado", onAction);
@@ -55966,7 +55854,7 @@ var AlarmsTab = class {
55966
55854
  * The myio:alarms-updated event will update this component automatically.
55967
55855
  * Also updates the grid immediately from the post-action ASO data.
55968
55856
  */
55969
- async refreshAlarmsGrid(_alarmsBaseUrl) {
55857
+ async refreshAlarmsGrid() {
55970
55858
  const aso = window.AlarmServiceOrchestrator;
55971
55859
  if (aso) {
55972
55860
  await aso.refresh().catch(() => {
@@ -56135,7 +56023,6 @@ var AlarmsTab = class {
56135
56023
  if (chipWrap.querySelector(".at-rule-edit-inline")) return;
56136
56024
  const { metric, operator, value, valueHigh } = rule.alarmConfig;
56137
56025
  const opLabel = OPERATOR_LABELS[operator] ?? operator;
56138
- const baseUrl = this.config.gcdrApiBaseUrl || GCDR_DEFAULT_BASE_URL;
56139
56026
  const statusId = `at-edit-status-${ruleId}`;
56140
56027
  chipWrap.innerHTML = `
56141
56028
  <div class="at-rule-edit-inline">
@@ -56156,7 +56043,9 @@ var AlarmsTab = class {
56156
56043
  const confirmBtn = chipWrap.querySelector(".at-rule-edit-confirm");
56157
56044
  const cancelBtn = chipWrap.querySelector(".at-rule-edit-cancel");
56158
56045
  const statusEl = chipWrap.querySelector(`#${statusId}`);
56159
- const inputLow = chipWrap.querySelector(".at-rule-edit-input:not(.at-rule-edit-input--high)");
56046
+ const inputLow = chipWrap.querySelector(
56047
+ ".at-rule-edit-input:not(.at-rule-edit-input--high)"
56048
+ );
56160
56049
  const inputHigh = chipWrap.querySelector(".at-rule-edit-input--high");
56161
56050
  const restoreChip = (v, vh) => {
56162
56051
  const vhStr = vh != null ? ` \u2013 ${vh}` : "";
@@ -56184,7 +56073,7 @@ var AlarmsTab = class {
56184
56073
  value: newVal,
56185
56074
  ...newValHigh !== void 0 && !isNaN(newValHigh) ? { valueHigh: newValHigh } : {}
56186
56075
  };
56187
- const ok = await this.patchRuleValue(baseUrl, ruleId, updatedConfig);
56076
+ const ok = await this.orch.gcdrPatchRuleValue(ruleId, updatedConfig);
56188
56077
  if (ok) {
56189
56078
  rule.alarmConfig = updatedConfig;
56190
56079
  restoreChip(newVal, updatedConfig.valueHigh ?? null);
@@ -56203,7 +56092,6 @@ var AlarmsTab = class {
56203
56092
  const container = this.config.container;
56204
56093
  const saveBtn = container.querySelector("#at-save-btn");
56205
56094
  const msgEl = container.querySelector("#at-save-msg");
56206
- const baseUrl = this.config.gcdrApiBaseUrl || GCDR_DEFAULT_BASE_URL;
56207
56095
  if (saveBtn) {
56208
56096
  saveBtn.disabled = true;
56209
56097
  saveBtn.textContent = "Salvando\u2026";
@@ -56230,7 +56118,7 @@ var AlarmsTab = class {
56230
56118
  if (!rule) continue;
56231
56119
  const ids = [...rule.scope?.entityIds ?? []];
56232
56120
  if (!ids.includes(this.config.gcdrDeviceId)) ids.push(this.config.gcdrDeviceId);
56233
- const ok = await this.patchRuleScope(baseUrl, ruleId, ids);
56121
+ const ok = await this.orch.gcdrPatchRuleScope(ruleId, ids);
56234
56122
  if (ok) {
56235
56123
  rule.scope = { ...rule.scope, entityIds: ids };
56236
56124
  } else {
@@ -56241,7 +56129,7 @@ var AlarmsTab = class {
56241
56129
  const rule = ruleMap.get(ruleId);
56242
56130
  if (!rule) continue;
56243
56131
  const ids = (rule.scope?.entityIds ?? []).filter((id) => id !== this.config.gcdrDeviceId);
56244
- const ok = await this.patchRuleScope(baseUrl, ruleId, ids);
56132
+ const ok = await this.orch.gcdrPatchRuleScope(ruleId, ids);
56245
56133
  if (ok) {
56246
56134
  rule.scope = { ...rule.scope, entityIds: ids };
56247
56135
  } else {
@@ -56268,9 +56156,20 @@ var AlarmsTab = class {
56268
56156
  // ============================================================================
56269
56157
  // Utilities
56270
56158
  // ============================================================================
56159
+ /** Typed accessor to window.MyIOOrchestrator GCDR API methods. */
56160
+ get orch() {
56161
+ return window.MyIOOrchestrator;
56162
+ }
56271
56163
  esc(str) {
56272
56164
  return String(str ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
56273
56165
  }
56166
+ showToast(msg) {
56167
+ const el2 = document.createElement("div");
56168
+ el2.style.cssText = "position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:#dc2626;color:#fff;font-size:13px;font-weight:600;padding:10px 20px;border-radius:8px;z-index:99999;box-shadow:0 4px 12px rgba(0,0,0,0.25);pointer-events:none;font-family:Inter,system-ui,-apple-system,sans-serif;white-space:nowrap;";
56169
+ el2.textContent = `\u26A0\uFE0F ${msg}`;
56170
+ document.body.appendChild(el2);
56171
+ setTimeout(() => el2.remove(), 4e3);
56172
+ }
56274
56173
  getLoadingHTML() {
56275
56174
  return `
56276
56175
  <div style="padding:32px;text-align:center;color:#6c757d;">
@@ -56655,18 +56554,36 @@ var SettingsModalView = class {
56655
56554
  async initAlarmsTab() {
56656
56555
  const container = this.modal.querySelector("#alarms-tab-content");
56657
56556
  if (!container) return;
56658
- const { gcdrDeviceId, gcdrCustomerId, gcdrTenantId, gcdrApiBaseUrl, prefetchedBundle, prefetchedAlarms, deviceId, jwtToken } = this.config;
56659
- if (!gcdrDeviceId || !gcdrCustomerId || !gcdrTenantId) {
56660
- container.innerHTML = '<p style="color:#6c757d;padding:20px;text-align:center;font-style:italic;">Alarm data not available (missing GCDR identifiers).</p>';
56557
+ const { gcdrDeviceId, prefetchedBundle, prefetchedAlarms, deviceId, jwtToken } = this.config;
56558
+ if (!gcdrDeviceId) {
56559
+ const tabBtn = this.modal.querySelector('.modal-tab[data-tab="alarms"]');
56560
+ if (tabBtn) tabBtn.classList.add("locked");
56561
+ container.innerHTML = `
56562
+ <div style="
56563
+ display:flex; flex-direction:column; align-items:center; justify-content:center;
56564
+ min-height:320px; padding:40px 24px; text-align:center;
56565
+ ">
56566
+ <div style="
56567
+ width:72px; height:72px; border-radius:50%;
56568
+ background:#f3f4f6; border:1.5px solid #e5e7eb;
56569
+ display:flex; align-items:center; justify-content:center;
56570
+ font-size:32px; margin-bottom:20px; opacity:0.7;
56571
+ ">\u{1F512}</div>
56572
+ <div style="font-size:15px; font-weight:600; color:#374151; margin-bottom:8px;">
56573
+ Alarmes n\xE3o dispon\xEDveis
56574
+ </div>
56575
+ <div style="font-size:13px; color:#9ca3af; max-width:320px; line-height:1.6;">
56576
+ Este dispositivo n\xE3o est\xE1 vinculado ao sistema GCDR.<br>
56577
+ O identificador <code style="font-size:11px;background:#f3f4f6;padding:1px 5px;border-radius:3px;">gcdrDeviceId</code>
56578
+ n\xE3o foi encontrado nos atributos do servidor.
56579
+ </div>
56580
+ </div>`;
56661
56581
  return;
56662
56582
  }
56663
56583
  try {
56664
56584
  this.alarmsTab = new AlarmsTab({
56665
56585
  container,
56666
56586
  gcdrDeviceId,
56667
- gcdrCustomerId,
56668
- gcdrTenantId,
56669
- gcdrApiBaseUrl,
56670
56587
  tbDeviceId: deviceId ?? "",
56671
56588
  jwtToken: jwtToken ?? "",
56672
56589
  prefetchedBundle: prefetchedBundle ?? null,
@@ -57677,6 +57594,12 @@ var SettingsModalView = class {
57677
57594
  background: #fff;
57678
57595
  }
57679
57596
 
57597
+ .modal-tab.locked {
57598
+ opacity: 0.5;
57599
+ cursor: not-allowed;
57600
+ pointer-events: none;
57601
+ }
57602
+
57680
57603
  .modal-tab svg {
57681
57604
  width: 16px;
57682
57605
  height: 16px;
@@ -78314,7 +78237,8 @@ function openUpsellModal(params) {
78314
78237
  label: 120,
78315
78238
  type: 140,
78316
78239
  createdTime: 90,
78317
- relationTo: 110,
78240
+ relationTo: 120,
78241
+ relationFrom: 110,
78318
78242
  centralId: 80,
78319
78243
  slaveId: 60,
78320
78244
  deviceType: 80,
@@ -78330,7 +78254,9 @@ function openUpsellModal(params) {
78330
78254
  telemetryLoadedCount: 0,
78331
78255
  deviceRelations: [],
78332
78256
  allRelations: [],
78333
- deviceToAssetMap: /* @__PURE__ */ new Map(),
78257
+ deviceRelToMap: /* @__PURE__ */ new Map(),
78258
+ deviceRelFromMap: /* @__PURE__ */ new Map(),
78259
+ relationsDetailModal: { open: false, deviceId: "", deviceName: "", direction: "to", relations: [], removing: null },
78334
78260
  relationsLoaded: false,
78335
78261
  relationsLoading: false,
78336
78262
  deviceProfiles: [],
@@ -79557,11 +79483,13 @@ function renderStep2(state6, modalId, colors2, t) {
79557
79483
  </div>`;
79558
79484
  }
79559
79485
  return `
79486
+ <div style="overflow-x:auto; overflow-y:visible;">
79560
79487
  <div id="${modalId}-grid-header" style="
79561
79488
  display:flex; align-items:center; gap:0; padding:8px 12px;
79562
79489
  background:${colors2.cardBg}; border:1px solid ${colors2.border};
79563
79490
  border-bottom:none; border-radius:8px 8px 0 0; font-size:9px;
79564
79491
  font-weight:600; color:${colors2.textMuted}; text-transform:uppercase;
79492
+ min-width:max-content;
79565
79493
  ">
79566
79494
  ${multiCol ? `<div style="width:28px; text-align:center;">\u2611</div>` : ""}
79567
79495
  <div style="width:28px;"></div>
@@ -79572,6 +79500,9 @@ function renderStep2(state6, modalId, colors2, t) {
79572
79500
  <div style="width:${state6.columnWidths.relationTo}px; padding:0 6px; text-align:center; border-right:1px solid ${colors2.border};">
79573
79501
  relTO ${!state6.relationsLoaded ? "\u23F3" : ""}
79574
79502
  </div>
79503
+ <div style="width:${state6.columnWidths.relationFrom}px; padding:0 6px; text-align:center; border-right:1px solid ${colors2.border};">
79504
+ relFROM ${!state6.relationsLoaded ? "\u23F3" : ""}
79505
+ </div>
79575
79506
  ${renderSortableHeader("centralId", "centralId" + (!state6.deviceAttrsLoaded ? " \u{1F512}" : ""), "centralId", state6.columnWidths.centralId)}
79576
79507
  ${renderSortableHeader("slaveId", "slaveId" + (!state6.deviceAttrsLoaded ? " \u{1F512}" : ""), "slaveId", state6.columnWidths.slaveId)}
79577
79508
  <div style="width:${state6.columnWidths.deviceType}px; padding:0 6px; text-align:center; border-right:1px solid ${colors2.border};">
@@ -79589,10 +79520,11 @@ function renderStep2(state6, modalId, colors2, t) {
79589
79520
  <div style="width:24px;"></div>
79590
79521
  </div>
79591
79522
  <div style="
79592
- max-height:${gridHeight}; overflow-y:auto; border:1px solid ${colors2.border};
79593
- border-radius:0 0 8px 8px; background:${colors2.surface};
79523
+ max-height:${gridHeight}; overflow-y:auto; overflow-x:visible; border:1px solid ${colors2.border};
79524
+ border-radius:0 0 8px 8px; background:${colors2.surface}; min-width:max-content;
79594
79525
  " id="${modalId}-device-list">
79595
79526
  ${sortedDevices.length === 0 ? `<div style="padding:40px; text-align:center; color:${colors2.textMuted};">${t.noResults}</div>` : sortedDevices.map((device) => renderDeviceRow(device, state6, modalId, colors2)).join("")}
79527
+ </div>
79596
79528
  </div>`;
79597
79529
  })()}
79598
79530
  `;
@@ -79752,11 +79684,24 @@ function renderDeviceRow(device, state6, modalId, colors2) {
79752
79684
  <div style="width: ${state6.columnWidths.createdTime}px; padding: 0 6px; text-align: center; flex-shrink: 0;">
79753
79685
  <span style="font-size: 9px; color: ${colors2.textMuted};">${createdTimeStr}</span>
79754
79686
  </div>
79755
- <div style="width: ${state6.columnWidths.relationTo}px; padding: 0 6px; text-align: center; flex-shrink: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;">
79687
+ <div style="width: ${state6.columnWidths.relationTo}px; padding: 0 6px; flex-shrink: 0; overflow: hidden; display:flex; align-items:center; justify-content:center; gap:3px;">
79756
79688
  ${(() => {
79757
79689
  if (!state6.relationsLoaded) return `<span style="font-size: 8px; color: ${colors2.textMuted}; font-style: italic;">\u2014</span>`;
79758
- const assetName = state6.deviceToAssetMap.get(deviceId);
79759
- return assetName ? `<span style="font-size: 9px; color: ${colors2.text};" title="${assetName}">${assetName}</span>` : `<span style="font-size: 9px; color: ${colors2.textMuted};">\u2014</span>`;
79690
+ const rels = state6.deviceRelToMap.get(deviceId) || [];
79691
+ if (rels.length === 0) return `<span style="font-size: 9px; color: ${colors2.textMuted};">\u2014</span>`;
79692
+ const first = rels[0];
79693
+ const more = rels.length - 1;
79694
+ return `<span style="font-size:9px;color:${colors2.text};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:${more > 0 ? 68 : 108}px" title="${first.name}">${first.name}</span>${more > 0 ? `<button data-show-relto="${deviceId}" data-device-name="${encodeURIComponent(device.name || "")}" style="flex-shrink:0;font-size:9px;background:#ede9ff;color:#4c1d95;border:none;border-radius:3px;padding:1px 4px;cursor:pointer;font-weight:700;line-height:1.4">+${more}</button>` : ""}`;
79695
+ })()}
79696
+ </div>
79697
+ <div style="width: ${state6.columnWidths.relationFrom}px; padding: 0 6px; flex-shrink: 0; overflow: hidden; display:flex; align-items:center; justify-content:center; gap:3px;">
79698
+ ${(() => {
79699
+ if (!state6.relationsLoaded) return `<span style="font-size: 8px; color: ${colors2.textMuted}; font-style: italic;">\u2014</span>`;
79700
+ const rels = state6.deviceRelFromMap.get(deviceId) || [];
79701
+ if (rels.length === 0) return `<span style="font-size: 9px; color: ${colors2.textMuted};">\u2014</span>`;
79702
+ const first = rels[0];
79703
+ const more = rels.length - 1;
79704
+ return `<span style="font-size:9px;color:${colors2.text};overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:${more > 0 ? 68 : 98}px" title="${first.name}">${first.name}</span>${more > 0 ? `<button data-show-relfrom="${deviceId}" data-device-name="${encodeURIComponent(device.name || "")}" style="flex-shrink:0;font-size:9px;background:#dbeafe;color:#1e40af;border:none;border-radius:3px;padding:1px 4px;cursor:pointer;font-weight:700;line-height:1.4">+${more}</button>` : ""}`;
79760
79705
  })()}
79761
79706
  </div>
79762
79707
  <div style="width: ${state6.columnWidths.centralId}px; padding: 0 6px; text-align: center; flex-shrink: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;">
@@ -80316,6 +80261,109 @@ function renderEntityLabelRow(currentLabel, deviceName, colors2, modalId, t) {
80316
80261
  </div>
80317
80262
  `;
80318
80263
  }
80264
+ function openRelationsDetailPanel(deviceId, deviceName, direction, rels, state6, t) {
80265
+ document.getElementById("myio-rel-detail-panel")?.remove();
80266
+ const isDark = state6.theme === "dark";
80267
+ const colors2 = {
80268
+ bg: isDark ? "#1f2937" : "#ffffff",
80269
+ border: isDark ? "#374151" : "#e5e7eb",
80270
+ text: isDark ? "#f9fafb" : "#111827",
80271
+ muted: isDark ? "#9ca3af" : "#6b7280",
80272
+ surface: isDark ? "#111827" : "#f9fafb",
80273
+ danger: "#dc2626",
80274
+ dangerBg: "#fee2e2"
80275
+ };
80276
+ const dirLabel = direction === "to" ? "relTO" : "relFROM";
80277
+ const dirDesc = direction === "to" ? "Entidades que apontam para este dispositivo" : "Entidades que este dispositivo aponta para";
80278
+ const typeColor = (et) => ({
80279
+ ASSET: ["#dbeafe", "#1e40af"],
80280
+ CUSTOMER: ["#d1fae5", "#065f46"],
80281
+ DEVICE: ["#ede9ff", "#4c1d95"]
80282
+ })[et] || ["#f3f4f6", "#374151"];
80283
+ let currentRels = [...rels];
80284
+ function renderPanel() {
80285
+ document.getElementById("myio-rel-detail-panel")?.remove();
80286
+ const panel = document.createElement("div");
80287
+ panel.id = "myio-rel-detail-panel";
80288
+ panel.style.cssText = `
80289
+ position:fixed; top:50%; left:50%; transform:translate(-50%,-50%);
80290
+ z-index:99999; background:${colors2.bg}; border:1px solid ${colors2.border};
80291
+ border-radius:12px; box-shadow:0 20px 60px rgba(0,0,0,.35);
80292
+ min-width:380px; max-width:500px; width:90vw; max-height:70vh;
80293
+ display:flex; flex-direction:column; font-family:system-ui,sans-serif;
80294
+ `;
80295
+ const rows = currentRels.map((rel, idx) => {
80296
+ const [tcBg, tcColor] = typeColor(rel.entityType);
80297
+ return `
80298
+ <div style="display:flex;align-items:center;gap:8px;padding:10px 16px;border-bottom:1px solid ${colors2.border}">
80299
+ <span style="font-size:9px;background:${tcBg};color:${tcColor};padding:2px 6px;border-radius:3px;font-weight:700;white-space:nowrap;flex-shrink:0">${rel.entityType}</span>
80300
+ <div style="flex:1;overflow:hidden">
80301
+ <div style="font-size:12px;font-weight:600;color:${colors2.text};overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${rel.name}</div>
80302
+ <div style="font-size:10px;color:${colors2.muted};font-family:monospace">${rel.id}</div>
80303
+ <div style="font-size:10px;color:${colors2.muted}">tipo: <em>${rel.relationType}</em></div>
80304
+ </div>
80305
+ <button data-rel-remove="${idx}" style="flex-shrink:0;font-size:11px;background:${colors2.dangerBg};color:${colors2.danger};border:1px solid ${colors2.danger};border-radius:5px;padding:3px 8px;cursor:pointer;font-weight:600">
80306
+ Remover
80307
+ </button>
80308
+ </div>`;
80309
+ }).join("");
80310
+ panel.innerHTML = `
80311
+ <div style="padding:14px 16px 10px;border-bottom:1px solid ${colors2.border};display:flex;align-items:flex-start;justify-content:space-between;gap:8px">
80312
+ <div>
80313
+ <div style="font-size:13px;font-weight:700;color:${colors2.text}">${dirLabel} \u2014 ${deviceName}</div>
80314
+ <div style="font-size:11px;color:${colors2.muted};margin-top:2px">${dirDesc}</div>
80315
+ </div>
80316
+ <button id="myio-rel-panel-close" style="font-size:16px;background:none;border:none;cursor:pointer;color:${colors2.muted};padding:0 4px;line-height:1">\u2715</button>
80317
+ </div>
80318
+ <div style="overflow-y:auto;flex:1">
80319
+ ${currentRels.length === 0 ? `<div style="padding:24px;text-align:center;color:${colors2.muted};font-size:12px">Nenhuma rela\xE7\xE3o</div>` : rows}
80320
+ </div>
80321
+ `;
80322
+ const overlay = document.createElement("div");
80323
+ overlay.id = "myio-rel-detail-overlay";
80324
+ overlay.style.cssText = "position:fixed;inset:0;z-index:99998;background:rgba(0,0,0,.3)";
80325
+ overlay.addEventListener("click", () => {
80326
+ panel.remove();
80327
+ overlay.remove();
80328
+ });
80329
+ document.body.appendChild(overlay);
80330
+ document.body.appendChild(panel);
80331
+ panel.querySelector("#myio-rel-panel-close")?.addEventListener("click", () => {
80332
+ panel.remove();
80333
+ overlay.remove();
80334
+ });
80335
+ panel.querySelectorAll("[data-rel-remove]").forEach((btn) => {
80336
+ btn.addEventListener("click", async () => {
80337
+ const idx = parseInt(btn.dataset.relRemove || "0", 10);
80338
+ const rel = currentRels[idx];
80339
+ if (!rel) return;
80340
+ btn.disabled = true;
80341
+ btn.textContent = "\u2026";
80342
+ try {
80343
+ const fromId = direction === "to" ? rel.id : deviceId;
80344
+ const fromType = direction === "to" ? rel.entityType : "DEVICE";
80345
+ const toId = direction === "to" ? deviceId : rel.id;
80346
+ const toType = direction === "to" ? "DEVICE" : rel.entityType;
80347
+ const token = state6.token;
80348
+ const base = state6.tbApiBase || "";
80349
+ await fetch(
80350
+ `${base}/api/relation?fromId=${fromId}&fromType=${fromType}&toId=${toId}&toType=${toType}&relationType=${encodeURIComponent(rel.relationType)}&typeGroup=COMMON`,
80351
+ { method: "DELETE", headers: { "X-Authorization": `Bearer ${token}` } }
80352
+ );
80353
+ currentRels = currentRels.filter((_, i) => i !== idx);
80354
+ const map = direction === "to" ? state6.deviceRelToMap : state6.deviceRelFromMap;
80355
+ map.set(deviceId, [...currentRels]);
80356
+ renderPanel();
80357
+ } catch {
80358
+ btn.disabled = false;
80359
+ btn.textContent = "Remover";
80360
+ }
80361
+ });
80362
+ });
80363
+ }
80364
+ renderPanel();
80365
+ void t;
80366
+ }
80319
80367
  function setupEventListeners3(container, state6, modalId, t, onClose) {
80320
80368
  const closeHandler = () => closeModal(container, onClose);
80321
80369
  const overlay = container.querySelector(".myio-upsell-modal-overlay");
@@ -80518,6 +80566,18 @@ function setupEventListeners3(container, state6, modalId, t, onClose) {
80518
80566
  document.getElementById(`${modalId}-load-relations`)?.addEventListener("click", () => {
80519
80567
  void loadDeviceRelations(state6, container, modalId, t, onClose);
80520
80568
  });
80569
+ container.addEventListener("click", (e) => {
80570
+ const target = e.target;
80571
+ const btn = target.closest("[data-show-relto],[data-show-relfrom]");
80572
+ if (!btn) return;
80573
+ e.stopPropagation();
80574
+ const isTo = !!btn.dataset.showRelto;
80575
+ const deviceId = isTo ? btn.dataset.showRelto : btn.dataset.showRelfrom;
80576
+ const deviceName = decodeURIComponent(btn.dataset.deviceName || "");
80577
+ const direction = isTo ? "to" : "from";
80578
+ const rels = (isTo ? state6.deviceRelToMap : state6.deviceRelFromMap).get(deviceId) || [];
80579
+ openRelationsDetailPanel(deviceId, deviceName, direction, rels, state6, t);
80580
+ });
80521
80581
  document.getElementById(`${modalId}-load-attrs`)?.addEventListener("click", async () => {
80522
80582
  if (state6.attrsLoading) return;
80523
80583
  console.log(
@@ -81892,7 +81952,9 @@ async function loadDevices(state6, container, modalId, t, onClose) {
81892
81952
  state6.telemetryLoadedCount = 0;
81893
81953
  state6.relationsLoaded = false;
81894
81954
  state6.relationsLoading = false;
81895
- state6.deviceToAssetMap = /* @__PURE__ */ new Map();
81955
+ state6.deviceRelToMap = /* @__PURE__ */ new Map();
81956
+ state6.deviceRelFromMap = /* @__PURE__ */ new Map();
81957
+ state6.relationsDetailModal = { open: false, deviceId: "", deviceName: "", direction: "to", relations: [], removing: null };
81896
81958
  state6.isLoading = false;
81897
81959
  renderModal4(container, state6, modalId, t);
81898
81960
  setupEventListeners3(container, state6, modalId, t, onClose);
@@ -81915,8 +81977,9 @@ async function loadDeviceRelations(state6, container, modalId, t, onClose) {
81915
81977
  }
81916
81978
  const assets = state6.customerAssets;
81917
81979
  const BATCH_SIZE = 20;
81918
- const deviceToAssetMap = /* @__PURE__ */ new Map();
81919
- showBusyProgress("Carregando rela\xE7\xF5es...", assets.length);
81980
+ const deviceRelToMap = /* @__PURE__ */ new Map();
81981
+ const deviceRelFromMap = /* @__PURE__ */ new Map();
81982
+ showBusyProgress("Carregando rela\xE7\xF5es (relTO)...", assets.length);
81920
81983
  for (let i = 0; i < assets.length; i += BATCH_SIZE) {
81921
81984
  const batch = assets.slice(i, i + BATCH_SIZE);
81922
81985
  await Promise.all(
@@ -81928,7 +81991,10 @@ async function loadDeviceRelations(state6, container, modalId, t, onClose) {
81928
81991
  );
81929
81992
  rels.forEach((rel) => {
81930
81993
  if (rel.to.entityType === "DEVICE") {
81931
- deviceToAssetMap.set(rel.to.id, asset.name);
81994
+ const entry = { id: asset.id, name: asset.name, entityType: "ASSET", relationType: rel.type || "Contains" };
81995
+ const list = deviceRelToMap.get(rel.to.id) || [];
81996
+ list.push(entry);
81997
+ deviceRelToMap.set(rel.to.id, list);
81932
81998
  }
81933
81999
  });
81934
82000
  } catch {
@@ -81937,7 +82003,34 @@ async function loadDeviceRelations(state6, container, modalId, t, onClose) {
81937
82003
  );
81938
82004
  updateBusyProgress(Math.min(i + BATCH_SIZE, assets.length));
81939
82005
  }
81940
- state6.deviceToAssetMap = deviceToAssetMap;
82006
+ const allDeviceIds = state6.devices.map((d) => getEntityId(d));
82007
+ showBusyProgress("Carregando rela\xE7\xF5es (relFROM)...", allDeviceIds.length);
82008
+ for (let i = 0; i < allDeviceIds.length; i += BATCH_SIZE) {
82009
+ const batch = allDeviceIds.slice(i, i + BATCH_SIZE);
82010
+ await Promise.all(
82011
+ batch.map(async (devId) => {
82012
+ try {
82013
+ const rels = await tbFetch(
82014
+ state6,
82015
+ `/api/relations?fromId=${devId}&fromType=DEVICE`
82016
+ );
82017
+ if (rels.length > 0) {
82018
+ const entries = rels.map((rel) => ({
82019
+ id: rel.to.id,
82020
+ name: rel.toName || rel.to.id,
82021
+ entityType: rel.to.entityType,
82022
+ relationType: rel.type || "Contains"
82023
+ }));
82024
+ deviceRelFromMap.set(devId, entries);
82025
+ }
82026
+ } catch {
82027
+ }
82028
+ })
82029
+ );
82030
+ updateBusyProgress(Math.min(i + BATCH_SIZE, allDeviceIds.length));
82031
+ }
82032
+ state6.deviceRelToMap = deviceRelToMap;
82033
+ state6.deviceRelFromMap = deviceRelFromMap;
81941
82034
  state6.relationsLoaded = true;
81942
82035
  state6.relationsLoading = false;
81943
82036
  hideBusyProgress();
@@ -125426,8 +125519,8 @@ async function openGCDRSyncModal(params) {
125426
125519
 
125427
125520
  // src/components/premium-modals/alarm-bundle-map/openAlarmBundleMapModal.ts
125428
125521
  var MODAL_ID3 = "myio-alarm-bundle-map-modal";
125429
- var GCDR_INTEGRATION_API_KEY2 = "gcdr_cust_tb_integration_key_2026";
125430
- var GCDR_DEFAULT_BASE_URL2 = "https://gcdr-api.a.myio-bas.com";
125522
+ var GCDR_INTEGRATION_API_KEY = "gcdr_cust_tb_integration_key_2026";
125523
+ var GCDR_DEFAULT_BASE_URL = "https://gcdr-api.a.myio-bas.com";
125431
125524
  var TEAL2 = "#0a6d5e";
125432
125525
  var TEAL_DARK2 = "#084f44";
125433
125526
  var STYLES3 = `
@@ -125726,7 +125819,7 @@ async function fetchBundle(customerTB_ID, gcdrTenantId, baseUrl) {
125726
125819
  const response = await fetch(url, {
125727
125820
  method: "GET",
125728
125821
  headers: {
125729
- "X-API-Key": GCDR_INTEGRATION_API_KEY2,
125822
+ "X-API-Key": GCDR_INTEGRATION_API_KEY,
125730
125823
  "X-Tenant-ID": gcdrTenantId,
125731
125824
  "Accept": "application/json"
125732
125825
  }
@@ -125757,7 +125850,7 @@ async function openAlarmBundleMapModal(params) {
125757
125850
  alert("GCDR Tenant ID n\xE3o encontrado. Configure o atributo gcdrTenantId no cliente ThingsBoard.");
125758
125851
  return;
125759
125852
  }
125760
- const baseUrl = params.gcdrApiBaseUrl || GCDR_DEFAULT_BASE_URL2;
125853
+ const baseUrl = params.gcdrApiBaseUrl || GCDR_DEFAULT_BASE_URL;
125761
125854
  const overlay = document.createElement("div");
125762
125855
  overlay.id = MODAL_ID3;
125763
125856
  const card = document.createElement("div");