myio-js-library 0.1.430 → 0.1.433
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 +1695 -199
- package/dist/index.d.cts +80 -1
- package/dist/index.js +1694 -199
- package/dist/myio-js-library.umd.js +1692 -199
- package/dist/myio-js-library.umd.min.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1075,6 +1075,7 @@ __export(index_exports, {
|
|
|
1075
1075
|
openTemperatureSettingsModal: () => openTemperatureSettingsModal,
|
|
1076
1076
|
openTutorialModal: () => openTutorialModal,
|
|
1077
1077
|
openUpsellModal: () => openUpsellModal,
|
|
1078
|
+
openUserManagementModal: () => openUserManagementModal,
|
|
1078
1079
|
openWelcomeModal: () => openWelcomeModal,
|
|
1079
1080
|
parseInputDateToDate: () => parseInputDateToDate,
|
|
1080
1081
|
periodKey: () => periodKey,
|
|
@@ -1137,7 +1138,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
1137
1138
|
// package.json
|
|
1138
1139
|
var package_default = {
|
|
1139
1140
|
name: "myio-js-library",
|
|
1140
|
-
version: "0.1.
|
|
1141
|
+
version: "0.1.432",
|
|
1141
1142
|
description: "A clean, standalone JS SDK for MYIO projects",
|
|
1142
1143
|
license: "MIT",
|
|
1143
1144
|
repository: "github:gh-myio/myio-js-library",
|
|
@@ -24519,7 +24520,8 @@ var EnergyDataFetcher = class {
|
|
|
24519
24520
|
*/
|
|
24520
24521
|
buildEnergyApiUrl(params) {
|
|
24521
24522
|
const baseUrl = this.config.dataApiHost;
|
|
24522
|
-
const
|
|
24523
|
+
const segment = params.readingType === "water" || params.readingType === "tank" ? "water" : "energy";
|
|
24524
|
+
const endpoint = `/api/v1/telemetry/devices/${params.ingestionId}/${segment}`;
|
|
24523
24525
|
const queryParams = new URLSearchParams({
|
|
24524
24526
|
startTime: params.startISO,
|
|
24525
24527
|
endTime: params.endISO,
|
|
@@ -28084,14 +28086,17 @@ var EnergyModalView = class {
|
|
|
28084
28086
|
const totalConsumption = this.currentEnergyData.consumption.reduce((sum, item) => sum + item.value, 0);
|
|
28085
28087
|
const now = /* @__PURE__ */ new Date();
|
|
28086
28088
|
const timestamp = now.toLocaleDateString("pt-BR") + " - " + now.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
|
|
28089
|
+
const rt = this.config.params.readingType;
|
|
28090
|
+
const unit = rt === "water" || rt === "tank" ? "m\xB3" : rt === "temperature" ? "\xB0C" : "kWh";
|
|
28091
|
+
const reportTitle = rt === "water" || rt === "tank" ? "WATER REPORT" : rt === "temperature" ? "TEMPERATURE REPORT" : "ENERGY REPORT";
|
|
28087
28092
|
const csvData = [
|
|
28088
|
-
[
|
|
28093
|
+
[`${reportTitle} - DEVICE DETAILS`, "", ""],
|
|
28089
28094
|
["Device", device.label, ""],
|
|
28090
28095
|
["Device ID", device.id, ""],
|
|
28091
28096
|
["Export Date", timestamp, ""],
|
|
28092
|
-
["Total Consumption", formatNumber(totalConsumption),
|
|
28097
|
+
["Total Consumption", formatNumber(totalConsumption), unit],
|
|
28093
28098
|
["", "", ""],
|
|
28094
|
-
["Date",
|
|
28099
|
+
["Date", `Consumption (${unit})`, ""],
|
|
28095
28100
|
...this.currentEnergyData.consumption.map((row) => [
|
|
28096
28101
|
formatDate(row.timestamp),
|
|
28097
28102
|
formatNumber(row.value),
|
|
@@ -28099,7 +28104,7 @@ var EnergyModalView = class {
|
|
|
28099
28104
|
])
|
|
28100
28105
|
];
|
|
28101
28106
|
const csvContent = toCsv(csvData);
|
|
28102
|
-
this.downloadCSV(csvContent,
|
|
28107
|
+
this.downloadCSV(csvContent, `${rt || "energy"}-report-${device.id}-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`);
|
|
28103
28108
|
}
|
|
28104
28109
|
/**
|
|
28105
28110
|
* Downloads CSV file
|
|
@@ -28239,7 +28244,7 @@ var EnergyModalView = class {
|
|
|
28239
28244
|
endDate,
|
|
28240
28245
|
label: this.config.params.deviceLabel || "Dispositivo",
|
|
28241
28246
|
locale: "pt-BR",
|
|
28242
|
-
readingType: this.config.params.readingType || "energy",
|
|
28247
|
+
readingType: (this.config.params.readingType === "tank" ? "water" : this.config.params.readingType) || "energy",
|
|
28243
28248
|
enableRealTimeMode: true,
|
|
28244
28249
|
realTimeInterval: 8e3,
|
|
28245
28250
|
realTimeAutoScroll: true
|
|
@@ -28854,7 +28859,8 @@ var EnergyModal = class {
|
|
|
28854
28859
|
ingestionId: this.context.resolved.ingestionId,
|
|
28855
28860
|
startISO,
|
|
28856
28861
|
endISO,
|
|
28857
|
-
granularity: this.params.granularity || "1d"
|
|
28862
|
+
granularity: this.params.granularity || "1d",
|
|
28863
|
+
readingType: this.params.readingType
|
|
28858
28864
|
});
|
|
28859
28865
|
console.log("[EnergyModal] Energy data loaded:", {
|
|
28860
28866
|
dataPoints: energyData.consumption.length,
|
|
@@ -28936,7 +28942,8 @@ var EnergyModal = class {
|
|
|
28936
28942
|
ingestionId: this.context.resolved.ingestionId,
|
|
28937
28943
|
startISO,
|
|
28938
28944
|
endISO,
|
|
28939
|
-
granularity: this.params.granularity || "1d"
|
|
28945
|
+
granularity: this.params.granularity || "1d",
|
|
28946
|
+
readingType: this.params.readingType
|
|
28940
28947
|
});
|
|
28941
28948
|
console.log("[EnergyModal] Energy data reloaded:", {
|
|
28942
28949
|
dataPoints: energyData.consumption.length,
|
|
@@ -29201,9 +29208,10 @@ var DeviceReportModal = class {
|
|
|
29201
29208
|
<label class="myio-label" for="date-range">Per\xEDodo</label>
|
|
29202
29209
|
<input type="text" id="date-range" class="myio-input" readonly placeholder="Selecione o per\xEDodo" style="width: 300px;">
|
|
29203
29210
|
</div>
|
|
29204
|
-
|
|
29205
|
-
|
|
29206
|
-
<button type="button" data-gran="
|
|
29211
|
+
<!-- granularity-toggle hidden: 1h not yet supported, default stays 1d -->
|
|
29212
|
+
<div id="granularity-toggle" role="group" aria-label="Granularidade" style="display:none;">
|
|
29213
|
+
<button type="button" data-gran="1d">1d</button>
|
|
29214
|
+
<button type="button" data-gran="1h">1h</button>
|
|
29207
29215
|
</div>
|
|
29208
29216
|
<button id="load-btn" class="myio-btn myio-btn-primary">
|
|
29209
29217
|
<span class="myio-spinner" id="load-spinner" style="display: none;"></span>
|
|
@@ -30456,9 +30464,10 @@ var AllReportModal = class {
|
|
|
30456
30464
|
<label class="myio-label" for="date-range">Per\xEDodo</label>
|
|
30457
30465
|
<input type="text" id="date-range" class="myio-input" readonly placeholder="Selecione o per\xEDodo" style="width: 300px;">
|
|
30458
30466
|
</div>
|
|
30459
|
-
|
|
30460
|
-
|
|
30461
|
-
<button type="button" data-gran="
|
|
30467
|
+
<!-- granularity-toggle hidden: 1h not yet supported, default stays 1d -->
|
|
30468
|
+
<div id="granularity-toggle" role="group" aria-label="Granularidade" style="display:none;">
|
|
30469
|
+
<button type="button" data-gran="1d">1d</button>
|
|
30470
|
+
<button type="button" data-gran="1h">1h</button>
|
|
30462
30471
|
</div>
|
|
30463
30472
|
<button id="load-btn" class="myio-btn myio-btn-primary">
|
|
30464
30473
|
<span class="myio-spinner" id="load-spinner" style="display: none;"></span>
|
|
@@ -31007,14 +31016,11 @@ var AllReportModal = class {
|
|
|
31007
31016
|
totalMappedConsumption += consumption;
|
|
31008
31017
|
rows.push(result);
|
|
31009
31018
|
}
|
|
31010
|
-
this.debugLog(
|
|
31011
|
-
|
|
31012
|
-
rows.length,
|
|
31013
|
-
|
|
31014
|
-
|
|
31015
|
-
"| total consumption:",
|
|
31016
|
-
totalMappedConsumption
|
|
31017
|
-
);
|
|
31019
|
+
this.debugLog("[AllReportModal] API-driven filter", {
|
|
31020
|
+
matched: rows.length,
|
|
31021
|
+
discarded: apiArray.length - rows.length,
|
|
31022
|
+
totalConsumption: totalMappedConsumption
|
|
31023
|
+
});
|
|
31018
31024
|
return rows;
|
|
31019
31025
|
}
|
|
31020
31026
|
parseConsumptionValue(item) {
|
|
@@ -49587,7 +49593,7 @@ function bindAnnotationsPanelEvents(panel, alarmId, currentUser, onCountChange,
|
|
|
49587
49593
|
}
|
|
49588
49594
|
|
|
49589
49595
|
// src/utils/AnnotationTooltip.ts
|
|
49590
|
-
var
|
|
49596
|
+
var CSS2 = `
|
|
49591
49597
|
.annot-tooltip {
|
|
49592
49598
|
position: fixed;
|
|
49593
49599
|
z-index: 99999;
|
|
@@ -49779,7 +49785,7 @@ function injectCSS7() {
|
|
|
49779
49785
|
}
|
|
49780
49786
|
const style = document.createElement("style");
|
|
49781
49787
|
style.id = id;
|
|
49782
|
-
style.textContent =
|
|
49788
|
+
style.textContent = CSS2;
|
|
49783
49789
|
document.head.appendChild(style);
|
|
49784
49790
|
_cssInjected = true;
|
|
49785
49791
|
}
|
|
@@ -55677,9 +55683,6 @@ function removeAlarmsNotificationsPanelStyles() {
|
|
|
55677
55683
|
}
|
|
55678
55684
|
|
|
55679
55685
|
// 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
55686
|
var PRIORITY_ORDER = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 };
|
|
55684
55687
|
var PRIORITY_COLORS = {
|
|
55685
55688
|
CRITICAL: "#dc2626",
|
|
@@ -55700,6 +55703,7 @@ var AlarmsTab = class {
|
|
|
55700
55703
|
activeAlarms = [];
|
|
55701
55704
|
initialCheckedRuleIds = /* @__PURE__ */ new Set();
|
|
55702
55705
|
alarmsUpdatedHandler = null;
|
|
55706
|
+
rulesFetchError = null;
|
|
55703
55707
|
constructor(config) {
|
|
55704
55708
|
this.config = config;
|
|
55705
55709
|
}
|
|
@@ -55707,30 +55711,35 @@ var AlarmsTab = class {
|
|
|
55707
55711
|
const { container } = this.config;
|
|
55708
55712
|
this.injectStyles();
|
|
55709
55713
|
container.innerHTML = this.getLoadingHTML();
|
|
55714
|
+
const orch = this.orch;
|
|
55715
|
+
if (!orch?.gcdrFetchCustomerRules) {
|
|
55716
|
+
this.showToast("AlarmsTab: MyIOOrchestrator n\xE3o inicializado");
|
|
55717
|
+
container.innerHTML = this.getErrorHTML("MyIOOrchestrator n\xE3o inicializado com API GCDR");
|
|
55718
|
+
return;
|
|
55719
|
+
}
|
|
55720
|
+
this.activeAlarms = this.readAlarmsFromASO();
|
|
55721
|
+
let rulesFetchError = null;
|
|
55710
55722
|
try {
|
|
55711
|
-
|
|
55712
|
-
const alarms = this.readAlarmsFromASO();
|
|
55713
|
-
const rules = this.config.prefetchedRules != null ? this.config.prefetchedRules : await this.fetchCustomerRules(gcdrBaseUrl);
|
|
55714
|
-
this.activeAlarms = alarms;
|
|
55715
|
-
this.customerRules = rules;
|
|
55723
|
+
this.customerRules = this.config.prefetchedRules != null ? this.config.prefetchedRules : await this.orch.gcdrFetchCustomerRules();
|
|
55716
55724
|
for (const rule of this.customerRules) {
|
|
55717
55725
|
if (rule.scope?.entityIds?.includes(this.config.gcdrDeviceId)) {
|
|
55718
55726
|
this.initialCheckedRuleIds.add(rule.id);
|
|
55719
55727
|
}
|
|
55720
55728
|
}
|
|
55721
|
-
container.innerHTML = this.renderTab();
|
|
55722
|
-
this.populateAlarmsGrid();
|
|
55723
|
-
this.attachTabListeners();
|
|
55724
|
-
this.alarmsUpdatedHandler = () => {
|
|
55725
|
-
const fresh = this.readAlarmsFromASO();
|
|
55726
|
-
this.activeAlarms = fresh;
|
|
55727
|
-
this.refreshAlarmsGridFromCurrentData();
|
|
55728
|
-
};
|
|
55729
|
-
window.addEventListener("myio:alarms-updated", this.alarmsUpdatedHandler);
|
|
55730
55729
|
} catch (err) {
|
|
55731
|
-
|
|
55732
|
-
|
|
55733
|
-
}
|
|
55730
|
+
rulesFetchError = this.classifyRulesError(err);
|
|
55731
|
+
this.customerRules = [];
|
|
55732
|
+
}
|
|
55733
|
+
this.rulesFetchError = rulesFetchError;
|
|
55734
|
+
container.innerHTML = this.renderTab();
|
|
55735
|
+
this.populateAlarmsGrid();
|
|
55736
|
+
this.attachTabListeners();
|
|
55737
|
+
this.alarmsUpdatedHandler = () => {
|
|
55738
|
+
const fresh = this.readAlarmsFromASO();
|
|
55739
|
+
this.activeAlarms = fresh;
|
|
55740
|
+
this.refreshAlarmsGridFromCurrentData();
|
|
55741
|
+
};
|
|
55742
|
+
window.addEventListener("myio:alarms-updated", this.alarmsUpdatedHandler);
|
|
55734
55743
|
}
|
|
55735
55744
|
destroy() {
|
|
55736
55745
|
if (this.alarmsUpdatedHandler) {
|
|
@@ -55749,9 +55758,7 @@ var AlarmsTab = class {
|
|
|
55749
55758
|
}
|
|
55750
55759
|
const prefetched = this.config.prefetchedAlarms;
|
|
55751
55760
|
if (prefetched != null) {
|
|
55752
|
-
return prefetched.filter(
|
|
55753
|
-
(a) => a.deviceId === this.config.gcdrDeviceId
|
|
55754
|
-
);
|
|
55761
|
+
return prefetched.filter((a) => a.deviceId === this.config.gcdrDeviceId);
|
|
55755
55762
|
}
|
|
55756
55763
|
return [];
|
|
55757
55764
|
}
|
|
@@ -55765,7 +55772,7 @@ var AlarmsTab = class {
|
|
|
55765
55772
|
mapAlarmToCard(alarm) {
|
|
55766
55773
|
return {
|
|
55767
55774
|
id: alarm.id,
|
|
55768
|
-
customerId: this.
|
|
55775
|
+
customerId: this.orch?.gcdrCustomerId ?? "",
|
|
55769
55776
|
customerName: "",
|
|
55770
55777
|
source: alarm.deviceName || "",
|
|
55771
55778
|
severity: alarm.severity || "LOW",
|
|
@@ -55779,121 +55786,12 @@ var AlarmsTab = class {
|
|
|
55779
55786
|
};
|
|
55780
55787
|
}
|
|
55781
55788
|
// ============================================================================
|
|
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
55789
|
// Alarm grid population
|
|
55891
55790
|
// ============================================================================
|
|
55892
55791
|
populateAlarmsGrid() {
|
|
55893
55792
|
const grid = this.config.container.querySelector("#at-alarms-grid");
|
|
55894
55793
|
if (!grid) return;
|
|
55895
55794
|
grid.innerHTML = "";
|
|
55896
|
-
const alarmsBaseUrl = this.config.alarmsApiBaseUrl || ALARMS_DEFAULT_BASE_URL;
|
|
55897
55795
|
const AlarmService2 = window.MyIOLibrary?.AlarmService;
|
|
55898
55796
|
const userEmail = window.MyIOUtils?.currentUserEmail || "";
|
|
55899
55797
|
for (const rawAlarm of this.activeAlarms) {
|
|
@@ -55904,22 +55802,22 @@ var AlarmsTab = class {
|
|
|
55904
55802
|
if (AlarmService2?.batchAcknowledge) {
|
|
55905
55803
|
await AlarmService2.batchAcknowledge([alarmId], userEmail);
|
|
55906
55804
|
} else {
|
|
55907
|
-
await this.
|
|
55805
|
+
await this.orch.gcdrPostAlarmAction(alarmId, "acknowledge");
|
|
55908
55806
|
}
|
|
55909
55807
|
} else if (action === "snooze") {
|
|
55910
55808
|
if (AlarmService2?.batchSilence) {
|
|
55911
55809
|
await AlarmService2.batchSilence([alarmId], userEmail, "4h");
|
|
55912
55810
|
} else {
|
|
55913
|
-
await this.
|
|
55811
|
+
await this.orch.gcdrPostAlarmAction(alarmId, "snooze");
|
|
55914
55812
|
}
|
|
55915
55813
|
} else if (action === "escalate") {
|
|
55916
55814
|
if (AlarmService2?.batchEscalate) {
|
|
55917
55815
|
await AlarmService2.batchEscalate([alarmId], userEmail);
|
|
55918
55816
|
} else {
|
|
55919
|
-
await this.
|
|
55817
|
+
await this.orch.gcdrPostAlarmAction(alarmId, "escalate");
|
|
55920
55818
|
}
|
|
55921
55819
|
}
|
|
55922
|
-
await this.refreshAlarmsGrid(
|
|
55820
|
+
await this.refreshAlarmsGrid();
|
|
55923
55821
|
};
|
|
55924
55822
|
doAction().catch(() => {
|
|
55925
55823
|
});
|
|
@@ -55933,25 +55831,25 @@ var AlarmsTab = class {
|
|
|
55933
55831
|
if (AlarmService2?.batchAcknowledge) {
|
|
55934
55832
|
await AlarmService2.batchAcknowledge([rawAlarm.id], userEmail);
|
|
55935
55833
|
} else {
|
|
55936
|
-
await this.
|
|
55834
|
+
await this.orch.gcdrPostAlarmAction(rawAlarm.id, "acknowledge");
|
|
55937
55835
|
}
|
|
55938
|
-
await this.refreshAlarmsGrid(
|
|
55836
|
+
await this.refreshAlarmsGrid();
|
|
55939
55837
|
},
|
|
55940
55838
|
onSnooze: async () => {
|
|
55941
55839
|
if (AlarmService2?.batchSilence) {
|
|
55942
55840
|
await AlarmService2.batchSilence([rawAlarm.id], userEmail, "4h");
|
|
55943
55841
|
} else {
|
|
55944
|
-
await this.
|
|
55842
|
+
await this.orch.gcdrPostAlarmAction(rawAlarm.id, "snooze");
|
|
55945
55843
|
}
|
|
55946
|
-
await this.refreshAlarmsGrid(
|
|
55844
|
+
await this.refreshAlarmsGrid();
|
|
55947
55845
|
},
|
|
55948
55846
|
onEscalate: async () => {
|
|
55949
55847
|
if (AlarmService2?.batchEscalate) {
|
|
55950
55848
|
await AlarmService2.batchEscalate([rawAlarm.id], userEmail);
|
|
55951
55849
|
} else {
|
|
55952
|
-
await this.
|
|
55850
|
+
await this.orch.gcdrPostAlarmAction(rawAlarm.id, "escalate");
|
|
55953
55851
|
}
|
|
55954
|
-
await this.refreshAlarmsGrid(
|
|
55852
|
+
await this.refreshAlarmsGrid();
|
|
55955
55853
|
},
|
|
55956
55854
|
onDetails: () => {
|
|
55957
55855
|
openAlarmDetailsModal(alarm, "light", "separado", onAction);
|
|
@@ -55966,7 +55864,7 @@ var AlarmsTab = class {
|
|
|
55966
55864
|
* The myio:alarms-updated event will update this component automatically.
|
|
55967
55865
|
* Also updates the grid immediately from the post-action ASO data.
|
|
55968
55866
|
*/
|
|
55969
|
-
async refreshAlarmsGrid(
|
|
55867
|
+
async refreshAlarmsGrid() {
|
|
55970
55868
|
const aso = window.AlarmServiceOrchestrator;
|
|
55971
55869
|
if (aso) {
|
|
55972
55870
|
await aso.refresh().catch(() => {
|
|
@@ -56042,7 +55940,32 @@ var AlarmsTab = class {
|
|
|
56042
55940
|
}
|
|
56043
55941
|
// ---------- Section 2: Parametrize rules (multi-select + save) ----------
|
|
56044
55942
|
renderSection2() {
|
|
56045
|
-
|
|
55943
|
+
if (this.rulesFetchError) {
|
|
55944
|
+
return `
|
|
55945
|
+
<div class="at-section">
|
|
55946
|
+
<div class="at-section-header">
|
|
55947
|
+
<span class="at-section-icon">\u2699\uFE0F</span>
|
|
55948
|
+
<div>
|
|
55949
|
+
<div class="at-section-title">Parametrizar Regras de Alarme</div>
|
|
55950
|
+
<div class="at-section-sub">N\xE3o foi poss\xEDvel carregar as regras</div>
|
|
55951
|
+
</div>
|
|
55952
|
+
</div>
|
|
55953
|
+
<div class="at-rules-error">
|
|
55954
|
+
<div class="at-rules-error-icon">\u26A0\uFE0F</div>
|
|
55955
|
+
<div class="at-rules-error-body">
|
|
55956
|
+
<div class="at-rules-error-title">${this.esc(this.rulesFetchError)}</div>
|
|
55957
|
+
<div class="at-rules-error-hint">Verifique a conex\xE3o com o servidor e tente reabrir este painel.</div>
|
|
55958
|
+
</div>
|
|
55959
|
+
</div>
|
|
55960
|
+
</div>
|
|
55961
|
+
`;
|
|
55962
|
+
}
|
|
55963
|
+
const deviceProfile = (this.config.deviceProfile ?? "").toUpperCase();
|
|
55964
|
+
const compatible = this.customerRules.filter(
|
|
55965
|
+
(rule) => rule.scopeProfiles && rule.scopeProfiles.length > 0 && rule.scopeProfiles.some((p) => p.toUpperCase() === deviceProfile)
|
|
55966
|
+
);
|
|
55967
|
+
const filtered = compatible.length > 0 ? compatible : this.customerRules;
|
|
55968
|
+
const sorted = [...filtered].sort(
|
|
56046
55969
|
(a, b) => (PRIORITY_ORDER[a.priority] ?? 99) - (PRIORITY_ORDER[b.priority] ?? 99)
|
|
56047
55970
|
);
|
|
56048
55971
|
return `
|
|
@@ -56066,6 +55989,26 @@ var AlarmsTab = class {
|
|
|
56066
55989
|
</div>
|
|
56067
55990
|
`;
|
|
56068
55991
|
}
|
|
55992
|
+
/**
|
|
55993
|
+
* Translates a raw rules-fetch error into a user-friendly message.
|
|
55994
|
+
* Hides internal HTTP codes and stack traces from the operator.
|
|
55995
|
+
*/
|
|
55996
|
+
classifyRulesError(err) {
|
|
55997
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
55998
|
+
if (/HTTP 5\d\d/i.test(raw)) {
|
|
55999
|
+
return "O servidor de regras est\xE1 temporariamente indispon\xEDvel. Tente novamente em instantes.";
|
|
56000
|
+
}
|
|
56001
|
+
if (/HTTP 401|HTTP 403|unauthorized|forbidden/i.test(raw)) {
|
|
56002
|
+
return "Sem permiss\xE3o para carregar as regras de alarme deste cliente.";
|
|
56003
|
+
}
|
|
56004
|
+
if (/HTTP 404/i.test(raw)) {
|
|
56005
|
+
return "Nenhuma regra de alarme encontrada para este cliente.";
|
|
56006
|
+
}
|
|
56007
|
+
if (/network|failed to fetch|net::/i.test(raw)) {
|
|
56008
|
+
return "Erro de rede ao carregar as regras. Verifique a conex\xE3o e tente novamente.";
|
|
56009
|
+
}
|
|
56010
|
+
return "N\xE3o foi poss\xEDvel carregar as regras de alarme.";
|
|
56011
|
+
}
|
|
56069
56012
|
renderCustomerRuleSelectable(rule) {
|
|
56070
56013
|
const checked = this.initialCheckedRuleIds.has(rule.id);
|
|
56071
56014
|
const color = PRIORITY_COLORS[rule.priority] ?? "#6b7280";
|
|
@@ -56135,7 +56078,6 @@ var AlarmsTab = class {
|
|
|
56135
56078
|
if (chipWrap.querySelector(".at-rule-edit-inline")) return;
|
|
56136
56079
|
const { metric, operator, value, valueHigh } = rule.alarmConfig;
|
|
56137
56080
|
const opLabel = OPERATOR_LABELS[operator] ?? operator;
|
|
56138
|
-
const baseUrl = this.config.gcdrApiBaseUrl || GCDR_DEFAULT_BASE_URL;
|
|
56139
56081
|
const statusId = `at-edit-status-${ruleId}`;
|
|
56140
56082
|
chipWrap.innerHTML = `
|
|
56141
56083
|
<div class="at-rule-edit-inline">
|
|
@@ -56156,7 +56098,9 @@ var AlarmsTab = class {
|
|
|
56156
56098
|
const confirmBtn = chipWrap.querySelector(".at-rule-edit-confirm");
|
|
56157
56099
|
const cancelBtn = chipWrap.querySelector(".at-rule-edit-cancel");
|
|
56158
56100
|
const statusEl = chipWrap.querySelector(`#${statusId}`);
|
|
56159
|
-
const inputLow = chipWrap.querySelector(
|
|
56101
|
+
const inputLow = chipWrap.querySelector(
|
|
56102
|
+
".at-rule-edit-input:not(.at-rule-edit-input--high)"
|
|
56103
|
+
);
|
|
56160
56104
|
const inputHigh = chipWrap.querySelector(".at-rule-edit-input--high");
|
|
56161
56105
|
const restoreChip = (v, vh) => {
|
|
56162
56106
|
const vhStr = vh != null ? ` \u2013 ${vh}` : "";
|
|
@@ -56184,7 +56128,7 @@ var AlarmsTab = class {
|
|
|
56184
56128
|
value: newVal,
|
|
56185
56129
|
...newValHigh !== void 0 && !isNaN(newValHigh) ? { valueHigh: newValHigh } : {}
|
|
56186
56130
|
};
|
|
56187
|
-
const ok = await this.
|
|
56131
|
+
const ok = await this.orch.gcdrPatchRuleValue(ruleId, updatedConfig);
|
|
56188
56132
|
if (ok) {
|
|
56189
56133
|
rule.alarmConfig = updatedConfig;
|
|
56190
56134
|
restoreChip(newVal, updatedConfig.valueHigh ?? null);
|
|
@@ -56203,7 +56147,6 @@ var AlarmsTab = class {
|
|
|
56203
56147
|
const container = this.config.container;
|
|
56204
56148
|
const saveBtn = container.querySelector("#at-save-btn");
|
|
56205
56149
|
const msgEl = container.querySelector("#at-save-msg");
|
|
56206
|
-
const baseUrl = this.config.gcdrApiBaseUrl || GCDR_DEFAULT_BASE_URL;
|
|
56207
56150
|
if (saveBtn) {
|
|
56208
56151
|
saveBtn.disabled = true;
|
|
56209
56152
|
saveBtn.textContent = "Salvando\u2026";
|
|
@@ -56230,7 +56173,7 @@ var AlarmsTab = class {
|
|
|
56230
56173
|
if (!rule) continue;
|
|
56231
56174
|
const ids = [...rule.scope?.entityIds ?? []];
|
|
56232
56175
|
if (!ids.includes(this.config.gcdrDeviceId)) ids.push(this.config.gcdrDeviceId);
|
|
56233
|
-
const ok = await this.
|
|
56176
|
+
const ok = await this.orch.gcdrPatchRuleScope(ruleId, ids);
|
|
56234
56177
|
if (ok) {
|
|
56235
56178
|
rule.scope = { ...rule.scope, entityIds: ids };
|
|
56236
56179
|
} else {
|
|
@@ -56240,8 +56183,17 @@ var AlarmsTab = class {
|
|
|
56240
56183
|
for (const ruleId of toRemove) {
|
|
56241
56184
|
const rule = ruleMap.get(ruleId);
|
|
56242
56185
|
if (!rule) continue;
|
|
56186
|
+
const confirmed = await this.confirmRuleUnassign(rule);
|
|
56187
|
+
if (!confirmed) {
|
|
56188
|
+
this.restoreRuleCheckbox(ruleId);
|
|
56189
|
+
continue;
|
|
56190
|
+
}
|
|
56191
|
+
const enqueued = await this.orch.gcdrEnqueueCloseAlarms(ruleId, this.config.gcdrDeviceId);
|
|
56192
|
+
if (!enqueued) {
|
|
56193
|
+
console.warn("[AlarmsTab] RFC-0191: enqueue-close failed for rule", ruleId);
|
|
56194
|
+
}
|
|
56243
56195
|
const ids = (rule.scope?.entityIds ?? []).filter((id) => id !== this.config.gcdrDeviceId);
|
|
56244
|
-
const ok = await this.
|
|
56196
|
+
const ok = await this.orch.gcdrPatchRuleScope(ruleId, ids);
|
|
56245
56197
|
if (ok) {
|
|
56246
56198
|
rule.scope = { ...rule.scope, entityIds: ids };
|
|
56247
56199
|
} else {
|
|
@@ -56268,9 +56220,89 @@ var AlarmsTab = class {
|
|
|
56268
56220
|
// ============================================================================
|
|
56269
56221
|
// Utilities
|
|
56270
56222
|
// ============================================================================
|
|
56223
|
+
/** Typed accessor to window.MyIOOrchestrator GCDR API methods. */
|
|
56224
|
+
get orch() {
|
|
56225
|
+
return window.MyIOOrchestrator;
|
|
56226
|
+
}
|
|
56271
56227
|
esc(str) {
|
|
56272
56228
|
return String(str ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
56273
56229
|
}
|
|
56230
|
+
// ============================================================================
|
|
56231
|
+
// RFC-0191: Confirmation modal + checkbox restore
|
|
56232
|
+
// ============================================================================
|
|
56233
|
+
/**
|
|
56234
|
+
* Renders a premium confirmation modal and waits for the user's decision.
|
|
56235
|
+
* Resolves true = confirmed (proceed with unassignment), false = cancelled.
|
|
56236
|
+
*/
|
|
56237
|
+
confirmRuleUnassign(rule) {
|
|
56238
|
+
return new Promise((resolve) => {
|
|
56239
|
+
const PRIORITY_COLORS_LOCAL = {
|
|
56240
|
+
CRITICAL: "#dc2626",
|
|
56241
|
+
HIGH: "#f59e0b",
|
|
56242
|
+
MEDIUM: "#3b82f6",
|
|
56243
|
+
LOW: "#6b7280"
|
|
56244
|
+
};
|
|
56245
|
+
const color = PRIORITY_COLORS_LOCAL[rule.priority] ?? "#6b7280";
|
|
56246
|
+
const overlay = document.createElement("div");
|
|
56247
|
+
overlay.className = "at-confirm-overlay";
|
|
56248
|
+
overlay.innerHTML = `
|
|
56249
|
+
<div class="at-confirm-modal">
|
|
56250
|
+
<div class="at-confirm-warning-bar"></div>
|
|
56251
|
+
<div class="at-confirm-body">
|
|
56252
|
+
<div class="at-confirm-icon">\u26A0\uFE0F</div>
|
|
56253
|
+
<div class="at-confirm-content">
|
|
56254
|
+
<div class="at-confirm-title">Aten\xE7\xE3o \u2014 Remo\xE7\xE3o de Regra de Alarme</div>
|
|
56255
|
+
<div class="at-confirm-text">
|
|
56256
|
+
Voc\xEA est\xE1 removendo este dispositivo da regra de alarme:
|
|
56257
|
+
</div>
|
|
56258
|
+
<div class="at-confirm-rule-name">
|
|
56259
|
+
${this.esc(rule.name)}
|
|
56260
|
+
<span class="at-confirm-priority-badge"
|
|
56261
|
+
style="background:${color}20;color:${color};">${this.esc(rule.priority)}</span>
|
|
56262
|
+
</div>
|
|
56263
|
+
<div class="at-confirm-text at-confirm-text--sub">
|
|
56264
|
+
Esta a\xE7\xE3o pode fechar alarmes abertos ou na fila associados a esta regra neste
|
|
56265
|
+
dispositivo. O motor de alarmes ir\xE1 enfileirar o fechamento; o hist\xF3rico e os
|
|
56266
|
+
registros de auditoria s\xE3o preservados.
|
|
56267
|
+
</div>
|
|
56268
|
+
</div>
|
|
56269
|
+
</div>
|
|
56270
|
+
<div class="at-confirm-footer">
|
|
56271
|
+
<button type="button" class="at-confirm-btn at-confirm-btn--cancel">Cancelar</button>
|
|
56272
|
+
<button type="button" class="at-confirm-btn at-confirm-btn--confirm">Confirmar e Remover Regra</button>
|
|
56273
|
+
</div>
|
|
56274
|
+
</div>
|
|
56275
|
+
`;
|
|
56276
|
+
const close = (result) => {
|
|
56277
|
+
overlay.remove();
|
|
56278
|
+
resolve(result);
|
|
56279
|
+
};
|
|
56280
|
+
overlay.querySelector(".at-confirm-btn--cancel").addEventListener("click", () => close(false));
|
|
56281
|
+
overlay.querySelector(".at-confirm-btn--confirm").addEventListener("click", () => close(true));
|
|
56282
|
+
overlay.addEventListener("click", (e) => {
|
|
56283
|
+
if (e.target === overlay) close(false);
|
|
56284
|
+
});
|
|
56285
|
+
document.body.appendChild(overlay);
|
|
56286
|
+
});
|
|
56287
|
+
}
|
|
56288
|
+
/**
|
|
56289
|
+
* Restores the checkbox of a rule to checked after the user cancels the confirmation.
|
|
56290
|
+
*/
|
|
56291
|
+
restoreRuleCheckbox(ruleId) {
|
|
56292
|
+
const cb = this.config.container.querySelector(
|
|
56293
|
+
`.at-rule-check[data-rule-id="${CSS.escape(ruleId)}"]`
|
|
56294
|
+
);
|
|
56295
|
+
if (!cb) return;
|
|
56296
|
+
cb.checked = true;
|
|
56297
|
+
cb.closest(".at-rule-row")?.classList.add("at-rule-row--checked");
|
|
56298
|
+
}
|
|
56299
|
+
showToast(msg) {
|
|
56300
|
+
const el2 = document.createElement("div");
|
|
56301
|
+
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;";
|
|
56302
|
+
el2.textContent = `\u26A0\uFE0F ${msg}`;
|
|
56303
|
+
document.body.appendChild(el2);
|
|
56304
|
+
setTimeout(() => el2.remove(), 4e3);
|
|
56305
|
+
}
|
|
56274
56306
|
getLoadingHTML() {
|
|
56275
56307
|
return `
|
|
56276
56308
|
<div style="padding:32px;text-align:center;color:#6c757d;">
|
|
@@ -56502,6 +56534,28 @@ var AlarmsTab = class {
|
|
|
56502
56534
|
.at-rule-edit-cancel:hover { background: #fecaca; }
|
|
56503
56535
|
.at-rule-edit-status { font-size: 11px; white-space: nowrap; }
|
|
56504
56536
|
|
|
56537
|
+
/* ===== Rules fetch error (Section 2) ===== */
|
|
56538
|
+
.at-rules-error {
|
|
56539
|
+
display: flex;
|
|
56540
|
+
align-items: flex-start;
|
|
56541
|
+
gap: 12px;
|
|
56542
|
+
padding: 16px 20px;
|
|
56543
|
+
}
|
|
56544
|
+
.at-rules-error-icon { font-size: 20px; flex-shrink: 0; line-height: 1.4; }
|
|
56545
|
+
.at-rules-error-body { flex: 1; min-width: 0; }
|
|
56546
|
+
.at-rules-error-title {
|
|
56547
|
+
font-size: 13px;
|
|
56548
|
+
font-weight: 500;
|
|
56549
|
+
color: #92400e;
|
|
56550
|
+
line-height: 1.5;
|
|
56551
|
+
}
|
|
56552
|
+
.at-rules-error-hint {
|
|
56553
|
+
font-size: 12px;
|
|
56554
|
+
color: #6b7280;
|
|
56555
|
+
margin-top: 4px;
|
|
56556
|
+
line-height: 1.4;
|
|
56557
|
+
}
|
|
56558
|
+
|
|
56505
56559
|
/* ===== Footer / save ===== */
|
|
56506
56560
|
.at-footer {
|
|
56507
56561
|
padding: 12px 20px;
|
|
@@ -56538,6 +56592,104 @@ var AlarmsTab = class {
|
|
|
56538
56592
|
margin: 0 auto 12px;
|
|
56539
56593
|
}
|
|
56540
56594
|
@keyframes at-spin { to { transform: rotate(360deg); } }
|
|
56595
|
+
|
|
56596
|
+
/* ===== RFC-0191: Rule unassign confirmation modal ===== */
|
|
56597
|
+
.at-confirm-overlay {
|
|
56598
|
+
position: fixed;
|
|
56599
|
+
inset: 0;
|
|
56600
|
+
background: rgba(0,0,0,0.55);
|
|
56601
|
+
display: flex;
|
|
56602
|
+
align-items: center;
|
|
56603
|
+
justify-content: center;
|
|
56604
|
+
z-index: 99999;
|
|
56605
|
+
}
|
|
56606
|
+
.at-confirm-modal {
|
|
56607
|
+
background: #fff;
|
|
56608
|
+
border-radius: 10px;
|
|
56609
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.22);
|
|
56610
|
+
max-width: 440px;
|
|
56611
|
+
width: calc(100% - 32px);
|
|
56612
|
+
overflow: hidden;
|
|
56613
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
56614
|
+
}
|
|
56615
|
+
.at-confirm-warning-bar {
|
|
56616
|
+
height: 4px;
|
|
56617
|
+
background: #f59e0b;
|
|
56618
|
+
}
|
|
56619
|
+
.at-confirm-body {
|
|
56620
|
+
display: flex;
|
|
56621
|
+
gap: 14px;
|
|
56622
|
+
padding: 20px 20px 16px;
|
|
56623
|
+
}
|
|
56624
|
+
.at-confirm-icon { font-size: 22px; flex-shrink: 0; line-height: 1.3; }
|
|
56625
|
+
.at-confirm-content { flex: 1; min-width: 0; }
|
|
56626
|
+
.at-confirm-title {
|
|
56627
|
+
font-size: 14px;
|
|
56628
|
+
font-weight: 700;
|
|
56629
|
+
color: #1a1a1a;
|
|
56630
|
+
margin-bottom: 8px;
|
|
56631
|
+
}
|
|
56632
|
+
.at-confirm-text {
|
|
56633
|
+
font-size: 13px;
|
|
56634
|
+
color: #374151;
|
|
56635
|
+
line-height: 1.5;
|
|
56636
|
+
margin-bottom: 8px;
|
|
56637
|
+
}
|
|
56638
|
+
.at-confirm-text--sub {
|
|
56639
|
+
color: #6b7280;
|
|
56640
|
+
font-size: 12px;
|
|
56641
|
+
margin-bottom: 0;
|
|
56642
|
+
}
|
|
56643
|
+
.at-confirm-rule-name {
|
|
56644
|
+
display: flex;
|
|
56645
|
+
align-items: center;
|
|
56646
|
+
gap: 8px;
|
|
56647
|
+
font-size: 13px;
|
|
56648
|
+
font-weight: 600;
|
|
56649
|
+
color: #1a1a1a;
|
|
56650
|
+
background: #f8f9fa;
|
|
56651
|
+
border-radius: 6px;
|
|
56652
|
+
padding: 6px 10px;
|
|
56653
|
+
margin-bottom: 10px;
|
|
56654
|
+
flex-wrap: wrap;
|
|
56655
|
+
}
|
|
56656
|
+
.at-confirm-priority-badge {
|
|
56657
|
+
font-size: 10px;
|
|
56658
|
+
font-weight: 700;
|
|
56659
|
+
padding: 2px 7px;
|
|
56660
|
+
border-radius: 4px;
|
|
56661
|
+
text-transform: uppercase;
|
|
56662
|
+
letter-spacing: 0.05em;
|
|
56663
|
+
flex-shrink: 0;
|
|
56664
|
+
}
|
|
56665
|
+
.at-confirm-footer {
|
|
56666
|
+
display: flex;
|
|
56667
|
+
align-items: center;
|
|
56668
|
+
justify-content: flex-end;
|
|
56669
|
+
gap: 10px;
|
|
56670
|
+
padding: 12px 20px;
|
|
56671
|
+
border-top: 1px solid #e9ecef;
|
|
56672
|
+
background: #fafafa;
|
|
56673
|
+
}
|
|
56674
|
+
.at-confirm-btn {
|
|
56675
|
+
border: none;
|
|
56676
|
+
border-radius: 6px;
|
|
56677
|
+
padding: 8px 16px;
|
|
56678
|
+
font-size: 13px;
|
|
56679
|
+
font-weight: 600;
|
|
56680
|
+
cursor: pointer;
|
|
56681
|
+
transition: background 0.15s, opacity 0.15s;
|
|
56682
|
+
}
|
|
56683
|
+
.at-confirm-btn--cancel {
|
|
56684
|
+
background: #f1f3f5;
|
|
56685
|
+
color: #374151;
|
|
56686
|
+
}
|
|
56687
|
+
.at-confirm-btn--cancel:hover { background: #e2e8f0; }
|
|
56688
|
+
.at-confirm-btn--confirm {
|
|
56689
|
+
background: #dc2626;
|
|
56690
|
+
color: #fff;
|
|
56691
|
+
}
|
|
56692
|
+
.at-confirm-btn--confirm:hover { background: #b91c1c; }
|
|
56541
56693
|
`;
|
|
56542
56694
|
document.head.appendChild(style);
|
|
56543
56695
|
}
|
|
@@ -56655,22 +56807,42 @@ var SettingsModalView = class {
|
|
|
56655
56807
|
async initAlarmsTab() {
|
|
56656
56808
|
const container = this.modal.querySelector("#alarms-tab-content");
|
|
56657
56809
|
if (!container) return;
|
|
56658
|
-
const { gcdrDeviceId,
|
|
56659
|
-
if (!gcdrDeviceId
|
|
56660
|
-
|
|
56810
|
+
const { gcdrDeviceId, prefetchedBundle, prefetchedAlarms, prefetchedRules, deviceId, jwtToken } = this.config;
|
|
56811
|
+
if (!gcdrDeviceId) {
|
|
56812
|
+
const tabBtn = this.modal.querySelector('.modal-tab[data-tab="alarms"]');
|
|
56813
|
+
if (tabBtn) tabBtn.classList.add("locked");
|
|
56814
|
+
container.innerHTML = `
|
|
56815
|
+
<div style="
|
|
56816
|
+
display:flex; flex-direction:column; align-items:center; justify-content:center;
|
|
56817
|
+
min-height:320px; padding:40px 24px; text-align:center;
|
|
56818
|
+
">
|
|
56819
|
+
<div style="
|
|
56820
|
+
width:72px; height:72px; border-radius:50%;
|
|
56821
|
+
background:#f3f4f6; border:1.5px solid #e5e7eb;
|
|
56822
|
+
display:flex; align-items:center; justify-content:center;
|
|
56823
|
+
font-size:32px; margin-bottom:20px; opacity:0.7;
|
|
56824
|
+
">\u{1F512}</div>
|
|
56825
|
+
<div style="font-size:15px; font-weight:600; color:#374151; margin-bottom:8px;">
|
|
56826
|
+
Alarmes n\xE3o dispon\xEDveis
|
|
56827
|
+
</div>
|
|
56828
|
+
<div style="font-size:13px; color:#9ca3af; max-width:320px; line-height:1.6;">
|
|
56829
|
+
Este dispositivo n\xE3o est\xE1 vinculado ao sistema GCDR.<br>
|
|
56830
|
+
O identificador <code style="font-size:11px;background:#f3f4f6;padding:1px 5px;border-radius:3px;">gcdrDeviceId</code>
|
|
56831
|
+
n\xE3o foi encontrado nos atributos do servidor.
|
|
56832
|
+
</div>
|
|
56833
|
+
</div>`;
|
|
56661
56834
|
return;
|
|
56662
56835
|
}
|
|
56663
56836
|
try {
|
|
56664
56837
|
this.alarmsTab = new AlarmsTab({
|
|
56665
56838
|
container,
|
|
56666
56839
|
gcdrDeviceId,
|
|
56667
|
-
gcdrCustomerId,
|
|
56668
|
-
gcdrTenantId,
|
|
56669
|
-
gcdrApiBaseUrl,
|
|
56670
56840
|
tbDeviceId: deviceId ?? "",
|
|
56671
56841
|
jwtToken: jwtToken ?? "",
|
|
56672
56842
|
prefetchedBundle: prefetchedBundle ?? null,
|
|
56673
|
-
prefetchedAlarms: prefetchedAlarms ?? null
|
|
56843
|
+
prefetchedAlarms: prefetchedAlarms ?? null,
|
|
56844
|
+
prefetchedRules: prefetchedRules ?? null,
|
|
56845
|
+
deviceProfile: this.config.deviceProfile
|
|
56674
56846
|
});
|
|
56675
56847
|
await this.alarmsTab.init();
|
|
56676
56848
|
console.log("[SettingsModalView] RFC-0180: Alarms tab initialized");
|
|
@@ -57677,6 +57849,12 @@ var SettingsModalView = class {
|
|
|
57677
57849
|
background: #fff;
|
|
57678
57850
|
}
|
|
57679
57851
|
|
|
57852
|
+
.modal-tab.locked {
|
|
57853
|
+
opacity: 0.5;
|
|
57854
|
+
cursor: not-allowed;
|
|
57855
|
+
pointer-events: none;
|
|
57856
|
+
}
|
|
57857
|
+
|
|
57680
57858
|
.modal-tab svg {
|
|
57681
57859
|
width: 16px;
|
|
57682
57860
|
height: 16px;
|
|
@@ -59567,6 +59745,8 @@ var SettingsController = class {
|
|
|
59567
59745
|
gcdrTenantId: params.gcdrTenantId,
|
|
59568
59746
|
gcdrApiBaseUrl: params.gcdrApiBaseUrl,
|
|
59569
59747
|
prefetchedBundle: params.prefetchedBundle,
|
|
59748
|
+
prefetchedAlarms: params.prefetchedAlarms,
|
|
59749
|
+
prefetchedRules: params.prefetchedRules,
|
|
59570
59750
|
// Device timestamps
|
|
59571
59751
|
createdTime: params.createdTime ?? null,
|
|
59572
59752
|
lastActivityTime: params.lastActivityTime ?? null
|
|
@@ -78314,7 +78494,8 @@ function openUpsellModal(params) {
|
|
|
78314
78494
|
label: 120,
|
|
78315
78495
|
type: 140,
|
|
78316
78496
|
createdTime: 90,
|
|
78317
|
-
relationTo:
|
|
78497
|
+
relationTo: 120,
|
|
78498
|
+
relationFrom: 110,
|
|
78318
78499
|
centralId: 80,
|
|
78319
78500
|
slaveId: 60,
|
|
78320
78501
|
deviceType: 80,
|
|
@@ -78330,7 +78511,9 @@ function openUpsellModal(params) {
|
|
|
78330
78511
|
telemetryLoadedCount: 0,
|
|
78331
78512
|
deviceRelations: [],
|
|
78332
78513
|
allRelations: [],
|
|
78333
|
-
|
|
78514
|
+
deviceRelToMap: /* @__PURE__ */ new Map(),
|
|
78515
|
+
deviceRelFromMap: /* @__PURE__ */ new Map(),
|
|
78516
|
+
relationsDetailModal: { open: false, deviceId: "", deviceName: "", direction: "to", relations: [], removing: null },
|
|
78334
78517
|
relationsLoaded: false,
|
|
78335
78518
|
relationsLoading: false,
|
|
78336
78519
|
deviceProfiles: [],
|
|
@@ -79557,11 +79740,13 @@ function renderStep2(state6, modalId, colors2, t) {
|
|
|
79557
79740
|
</div>`;
|
|
79558
79741
|
}
|
|
79559
79742
|
return `
|
|
79743
|
+
<div style="overflow-x:auto; overflow-y:visible;">
|
|
79560
79744
|
<div id="${modalId}-grid-header" style="
|
|
79561
79745
|
display:flex; align-items:center; gap:0; padding:8px 12px;
|
|
79562
79746
|
background:${colors2.cardBg}; border:1px solid ${colors2.border};
|
|
79563
79747
|
border-bottom:none; border-radius:8px 8px 0 0; font-size:9px;
|
|
79564
79748
|
font-weight:600; color:${colors2.textMuted}; text-transform:uppercase;
|
|
79749
|
+
min-width:max-content;
|
|
79565
79750
|
">
|
|
79566
79751
|
${multiCol ? `<div style="width:28px; text-align:center;">\u2611</div>` : ""}
|
|
79567
79752
|
<div style="width:28px;"></div>
|
|
@@ -79572,6 +79757,9 @@ function renderStep2(state6, modalId, colors2, t) {
|
|
|
79572
79757
|
<div style="width:${state6.columnWidths.relationTo}px; padding:0 6px; text-align:center; border-right:1px solid ${colors2.border};">
|
|
79573
79758
|
relTO ${!state6.relationsLoaded ? "\u23F3" : ""}
|
|
79574
79759
|
</div>
|
|
79760
|
+
<div style="width:${state6.columnWidths.relationFrom}px; padding:0 6px; text-align:center; border-right:1px solid ${colors2.border};">
|
|
79761
|
+
relFROM ${!state6.relationsLoaded ? "\u23F3" : ""}
|
|
79762
|
+
</div>
|
|
79575
79763
|
${renderSortableHeader("centralId", "centralId" + (!state6.deviceAttrsLoaded ? " \u{1F512}" : ""), "centralId", state6.columnWidths.centralId)}
|
|
79576
79764
|
${renderSortableHeader("slaveId", "slaveId" + (!state6.deviceAttrsLoaded ? " \u{1F512}" : ""), "slaveId", state6.columnWidths.slaveId)}
|
|
79577
79765
|
<div style="width:${state6.columnWidths.deviceType}px; padding:0 6px; text-align:center; border-right:1px solid ${colors2.border};">
|
|
@@ -79589,10 +79777,11 @@ function renderStep2(state6, modalId, colors2, t) {
|
|
|
79589
79777
|
<div style="width:24px;"></div>
|
|
79590
79778
|
</div>
|
|
79591
79779
|
<div style="
|
|
79592
|
-
max-height:${gridHeight}; overflow-y:auto; border:1px solid ${colors2.border};
|
|
79593
|
-
border-radius:0 0 8px 8px; background:${colors2.surface};
|
|
79780
|
+
max-height:${gridHeight}; overflow-y:auto; overflow-x:visible; border:1px solid ${colors2.border};
|
|
79781
|
+
border-radius:0 0 8px 8px; background:${colors2.surface}; min-width:max-content;
|
|
79594
79782
|
" id="${modalId}-device-list">
|
|
79595
79783
|
${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("")}
|
|
79784
|
+
</div>
|
|
79596
79785
|
</div>`;
|
|
79597
79786
|
})()}
|
|
79598
79787
|
`;
|
|
@@ -79752,11 +79941,24 @@ function renderDeviceRow(device, state6, modalId, colors2) {
|
|
|
79752
79941
|
<div style="width: ${state6.columnWidths.createdTime}px; padding: 0 6px; text-align: center; flex-shrink: 0;">
|
|
79753
79942
|
<span style="font-size: 9px; color: ${colors2.textMuted};">${createdTimeStr}</span>
|
|
79754
79943
|
</div>
|
|
79755
|
-
<div style="width: ${state6.columnWidths.relationTo}px; padding: 0 6px;
|
|
79944
|
+
<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;">
|
|
79945
|
+
${(() => {
|
|
79946
|
+
if (!state6.relationsLoaded) return `<span style="font-size: 8px; color: ${colors2.textMuted}; font-style: italic;">\u2014</span>`;
|
|
79947
|
+
const rels = state6.deviceRelToMap.get(deviceId) || [];
|
|
79948
|
+
if (rels.length === 0) return `<span style="font-size: 9px; color: ${colors2.textMuted};">\u2014</span>`;
|
|
79949
|
+
const first = rels[0];
|
|
79950
|
+
const more = rels.length - 1;
|
|
79951
|
+
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>` : ""}`;
|
|
79952
|
+
})()}
|
|
79953
|
+
</div>
|
|
79954
|
+
<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;">
|
|
79756
79955
|
${(() => {
|
|
79757
79956
|
if (!state6.relationsLoaded) return `<span style="font-size: 8px; color: ${colors2.textMuted}; font-style: italic;">\u2014</span>`;
|
|
79758
|
-
const
|
|
79759
|
-
|
|
79957
|
+
const rels = state6.deviceRelFromMap.get(deviceId) || [];
|
|
79958
|
+
if (rels.length === 0) return `<span style="font-size: 9px; color: ${colors2.textMuted};">\u2014</span>`;
|
|
79959
|
+
const first = rels[0];
|
|
79960
|
+
const more = rels.length - 1;
|
|
79961
|
+
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
79962
|
})()}
|
|
79761
79963
|
</div>
|
|
79762
79964
|
<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 +80518,109 @@ function renderEntityLabelRow(currentLabel, deviceName, colors2, modalId, t) {
|
|
|
80316
80518
|
</div>
|
|
80317
80519
|
`;
|
|
80318
80520
|
}
|
|
80521
|
+
function openRelationsDetailPanel(deviceId, deviceName, direction, rels, state6, t) {
|
|
80522
|
+
document.getElementById("myio-rel-detail-panel")?.remove();
|
|
80523
|
+
const isDark = state6.theme === "dark";
|
|
80524
|
+
const colors2 = {
|
|
80525
|
+
bg: isDark ? "#1f2937" : "#ffffff",
|
|
80526
|
+
border: isDark ? "#374151" : "#e5e7eb",
|
|
80527
|
+
text: isDark ? "#f9fafb" : "#111827",
|
|
80528
|
+
muted: isDark ? "#9ca3af" : "#6b7280",
|
|
80529
|
+
surface: isDark ? "#111827" : "#f9fafb",
|
|
80530
|
+
danger: "#dc2626",
|
|
80531
|
+
dangerBg: "#fee2e2"
|
|
80532
|
+
};
|
|
80533
|
+
const dirLabel = direction === "to" ? "relTO" : "relFROM";
|
|
80534
|
+
const dirDesc = direction === "to" ? "Entidades que apontam para este dispositivo" : "Entidades que este dispositivo aponta para";
|
|
80535
|
+
const typeColor = (et) => ({
|
|
80536
|
+
ASSET: ["#dbeafe", "#1e40af"],
|
|
80537
|
+
CUSTOMER: ["#d1fae5", "#065f46"],
|
|
80538
|
+
DEVICE: ["#ede9ff", "#4c1d95"]
|
|
80539
|
+
})[et] || ["#f3f4f6", "#374151"];
|
|
80540
|
+
let currentRels = [...rels];
|
|
80541
|
+
function renderPanel() {
|
|
80542
|
+
document.getElementById("myio-rel-detail-panel")?.remove();
|
|
80543
|
+
const panel = document.createElement("div");
|
|
80544
|
+
panel.id = "myio-rel-detail-panel";
|
|
80545
|
+
panel.style.cssText = `
|
|
80546
|
+
position:fixed; top:50%; left:50%; transform:translate(-50%,-50%);
|
|
80547
|
+
z-index:99999; background:${colors2.bg}; border:1px solid ${colors2.border};
|
|
80548
|
+
border-radius:12px; box-shadow:0 20px 60px rgba(0,0,0,.35);
|
|
80549
|
+
min-width:380px; max-width:500px; width:90vw; max-height:70vh;
|
|
80550
|
+
display:flex; flex-direction:column; font-family:system-ui,sans-serif;
|
|
80551
|
+
`;
|
|
80552
|
+
const rows = currentRels.map((rel, idx) => {
|
|
80553
|
+
const [tcBg, tcColor] = typeColor(rel.entityType);
|
|
80554
|
+
return `
|
|
80555
|
+
<div style="display:flex;align-items:center;gap:8px;padding:10px 16px;border-bottom:1px solid ${colors2.border}">
|
|
80556
|
+
<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>
|
|
80557
|
+
<div style="flex:1;overflow:hidden">
|
|
80558
|
+
<div style="font-size:12px;font-weight:600;color:${colors2.text};overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${rel.name}</div>
|
|
80559
|
+
<div style="font-size:10px;color:${colors2.muted};font-family:monospace">${rel.id}</div>
|
|
80560
|
+
<div style="font-size:10px;color:${colors2.muted}">tipo: <em>${rel.relationType}</em></div>
|
|
80561
|
+
</div>
|
|
80562
|
+
<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">
|
|
80563
|
+
Remover
|
|
80564
|
+
</button>
|
|
80565
|
+
</div>`;
|
|
80566
|
+
}).join("");
|
|
80567
|
+
panel.innerHTML = `
|
|
80568
|
+
<div style="padding:14px 16px 10px;border-bottom:1px solid ${colors2.border};display:flex;align-items:flex-start;justify-content:space-between;gap:8px">
|
|
80569
|
+
<div>
|
|
80570
|
+
<div style="font-size:13px;font-weight:700;color:${colors2.text}">${dirLabel} \u2014 ${deviceName}</div>
|
|
80571
|
+
<div style="font-size:11px;color:${colors2.muted};margin-top:2px">${dirDesc}</div>
|
|
80572
|
+
</div>
|
|
80573
|
+
<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>
|
|
80574
|
+
</div>
|
|
80575
|
+
<div style="overflow-y:auto;flex:1">
|
|
80576
|
+
${currentRels.length === 0 ? `<div style="padding:24px;text-align:center;color:${colors2.muted};font-size:12px">Nenhuma rela\xE7\xE3o</div>` : rows}
|
|
80577
|
+
</div>
|
|
80578
|
+
`;
|
|
80579
|
+
const overlay = document.createElement("div");
|
|
80580
|
+
overlay.id = "myio-rel-detail-overlay";
|
|
80581
|
+
overlay.style.cssText = "position:fixed;inset:0;z-index:99998;background:rgba(0,0,0,.3)";
|
|
80582
|
+
overlay.addEventListener("click", () => {
|
|
80583
|
+
panel.remove();
|
|
80584
|
+
overlay.remove();
|
|
80585
|
+
});
|
|
80586
|
+
document.body.appendChild(overlay);
|
|
80587
|
+
document.body.appendChild(panel);
|
|
80588
|
+
panel.querySelector("#myio-rel-panel-close")?.addEventListener("click", () => {
|
|
80589
|
+
panel.remove();
|
|
80590
|
+
overlay.remove();
|
|
80591
|
+
});
|
|
80592
|
+
panel.querySelectorAll("[data-rel-remove]").forEach((btn) => {
|
|
80593
|
+
btn.addEventListener("click", async () => {
|
|
80594
|
+
const idx = parseInt(btn.dataset.relRemove || "0", 10);
|
|
80595
|
+
const rel = currentRels[idx];
|
|
80596
|
+
if (!rel) return;
|
|
80597
|
+
btn.disabled = true;
|
|
80598
|
+
btn.textContent = "\u2026";
|
|
80599
|
+
try {
|
|
80600
|
+
const fromId = direction === "to" ? rel.id : deviceId;
|
|
80601
|
+
const fromType = direction === "to" ? rel.entityType : "DEVICE";
|
|
80602
|
+
const toId = direction === "to" ? deviceId : rel.id;
|
|
80603
|
+
const toType = direction === "to" ? "DEVICE" : rel.entityType;
|
|
80604
|
+
const token = state6.token;
|
|
80605
|
+
const base = state6.tbApiBase || "";
|
|
80606
|
+
await fetch(
|
|
80607
|
+
`${base}/api/relation?fromId=${fromId}&fromType=${fromType}&toId=${toId}&toType=${toType}&relationType=${encodeURIComponent(rel.relationType)}&typeGroup=COMMON`,
|
|
80608
|
+
{ method: "DELETE", headers: { "X-Authorization": `Bearer ${token}` } }
|
|
80609
|
+
);
|
|
80610
|
+
currentRels = currentRels.filter((_, i) => i !== idx);
|
|
80611
|
+
const map = direction === "to" ? state6.deviceRelToMap : state6.deviceRelFromMap;
|
|
80612
|
+
map.set(deviceId, [...currentRels]);
|
|
80613
|
+
renderPanel();
|
|
80614
|
+
} catch {
|
|
80615
|
+
btn.disabled = false;
|
|
80616
|
+
btn.textContent = "Remover";
|
|
80617
|
+
}
|
|
80618
|
+
});
|
|
80619
|
+
});
|
|
80620
|
+
}
|
|
80621
|
+
renderPanel();
|
|
80622
|
+
void t;
|
|
80623
|
+
}
|
|
80319
80624
|
function setupEventListeners3(container, state6, modalId, t, onClose) {
|
|
80320
80625
|
const closeHandler = () => closeModal(container, onClose);
|
|
80321
80626
|
const overlay = container.querySelector(".myio-upsell-modal-overlay");
|
|
@@ -80518,6 +80823,18 @@ function setupEventListeners3(container, state6, modalId, t, onClose) {
|
|
|
80518
80823
|
document.getElementById(`${modalId}-load-relations`)?.addEventListener("click", () => {
|
|
80519
80824
|
void loadDeviceRelations(state6, container, modalId, t, onClose);
|
|
80520
80825
|
});
|
|
80826
|
+
container.addEventListener("click", (e) => {
|
|
80827
|
+
const target = e.target;
|
|
80828
|
+
const btn = target.closest("[data-show-relto],[data-show-relfrom]");
|
|
80829
|
+
if (!btn) return;
|
|
80830
|
+
e.stopPropagation();
|
|
80831
|
+
const isTo = !!btn.dataset.showRelto;
|
|
80832
|
+
const deviceId = isTo ? btn.dataset.showRelto : btn.dataset.showRelfrom;
|
|
80833
|
+
const deviceName = decodeURIComponent(btn.dataset.deviceName || "");
|
|
80834
|
+
const direction = isTo ? "to" : "from";
|
|
80835
|
+
const rels = (isTo ? state6.deviceRelToMap : state6.deviceRelFromMap).get(deviceId) || [];
|
|
80836
|
+
openRelationsDetailPanel(deviceId, deviceName, direction, rels, state6, t);
|
|
80837
|
+
});
|
|
80521
80838
|
document.getElementById(`${modalId}-load-attrs`)?.addEventListener("click", async () => {
|
|
80522
80839
|
if (state6.attrsLoading) return;
|
|
80523
80840
|
console.log(
|
|
@@ -81892,7 +82209,9 @@ async function loadDevices(state6, container, modalId, t, onClose) {
|
|
|
81892
82209
|
state6.telemetryLoadedCount = 0;
|
|
81893
82210
|
state6.relationsLoaded = false;
|
|
81894
82211
|
state6.relationsLoading = false;
|
|
81895
|
-
state6.
|
|
82212
|
+
state6.deviceRelToMap = /* @__PURE__ */ new Map();
|
|
82213
|
+
state6.deviceRelFromMap = /* @__PURE__ */ new Map();
|
|
82214
|
+
state6.relationsDetailModal = { open: false, deviceId: "", deviceName: "", direction: "to", relations: [], removing: null };
|
|
81896
82215
|
state6.isLoading = false;
|
|
81897
82216
|
renderModal4(container, state6, modalId, t);
|
|
81898
82217
|
setupEventListeners3(container, state6, modalId, t, onClose);
|
|
@@ -81915,8 +82234,9 @@ async function loadDeviceRelations(state6, container, modalId, t, onClose) {
|
|
|
81915
82234
|
}
|
|
81916
82235
|
const assets = state6.customerAssets;
|
|
81917
82236
|
const BATCH_SIZE = 20;
|
|
81918
|
-
const
|
|
81919
|
-
|
|
82237
|
+
const deviceRelToMap = /* @__PURE__ */ new Map();
|
|
82238
|
+
const deviceRelFromMap = /* @__PURE__ */ new Map();
|
|
82239
|
+
showBusyProgress("Carregando rela\xE7\xF5es (relTO)...", assets.length);
|
|
81920
82240
|
for (let i = 0; i < assets.length; i += BATCH_SIZE) {
|
|
81921
82241
|
const batch = assets.slice(i, i + BATCH_SIZE);
|
|
81922
82242
|
await Promise.all(
|
|
@@ -81928,7 +82248,10 @@ async function loadDeviceRelations(state6, container, modalId, t, onClose) {
|
|
|
81928
82248
|
);
|
|
81929
82249
|
rels.forEach((rel) => {
|
|
81930
82250
|
if (rel.to.entityType === "DEVICE") {
|
|
81931
|
-
|
|
82251
|
+
const entry = { id: asset.id, name: asset.name, entityType: "ASSET", relationType: rel.type || "Contains" };
|
|
82252
|
+
const list = deviceRelToMap.get(rel.to.id) || [];
|
|
82253
|
+
list.push(entry);
|
|
82254
|
+
deviceRelToMap.set(rel.to.id, list);
|
|
81932
82255
|
}
|
|
81933
82256
|
});
|
|
81934
82257
|
} catch {
|
|
@@ -81937,7 +82260,34 @@ async function loadDeviceRelations(state6, container, modalId, t, onClose) {
|
|
|
81937
82260
|
);
|
|
81938
82261
|
updateBusyProgress(Math.min(i + BATCH_SIZE, assets.length));
|
|
81939
82262
|
}
|
|
81940
|
-
state6.
|
|
82263
|
+
const allDeviceIds = state6.devices.map((d) => getEntityId(d));
|
|
82264
|
+
showBusyProgress("Carregando rela\xE7\xF5es (relFROM)...", allDeviceIds.length);
|
|
82265
|
+
for (let i = 0; i < allDeviceIds.length; i += BATCH_SIZE) {
|
|
82266
|
+
const batch = allDeviceIds.slice(i, i + BATCH_SIZE);
|
|
82267
|
+
await Promise.all(
|
|
82268
|
+
batch.map(async (devId) => {
|
|
82269
|
+
try {
|
|
82270
|
+
const rels = await tbFetch(
|
|
82271
|
+
state6,
|
|
82272
|
+
`/api/relations?fromId=${devId}&fromType=DEVICE`
|
|
82273
|
+
);
|
|
82274
|
+
if (rels.length > 0) {
|
|
82275
|
+
const entries = rels.map((rel) => ({
|
|
82276
|
+
id: rel.to.id,
|
|
82277
|
+
name: rel.toName || rel.to.id,
|
|
82278
|
+
entityType: rel.to.entityType,
|
|
82279
|
+
relationType: rel.type || "Contains"
|
|
82280
|
+
}));
|
|
82281
|
+
deviceRelFromMap.set(devId, entries);
|
|
82282
|
+
}
|
|
82283
|
+
} catch {
|
|
82284
|
+
}
|
|
82285
|
+
})
|
|
82286
|
+
);
|
|
82287
|
+
updateBusyProgress(Math.min(i + BATCH_SIZE, allDeviceIds.length));
|
|
82288
|
+
}
|
|
82289
|
+
state6.deviceRelToMap = deviceRelToMap;
|
|
82290
|
+
state6.deviceRelFromMap = deviceRelFromMap;
|
|
81941
82291
|
state6.relationsLoaded = true;
|
|
81942
82292
|
state6.relationsLoading = false;
|
|
81943
82293
|
hideBusyProgress();
|
|
@@ -82413,6 +82763,1151 @@ ${errors.slice(0, 5).join("\n")}` + (errors.length > 5 ? `
|
|
|
82413
82763
|
renderModal4(container, state6, modalId, t);
|
|
82414
82764
|
}
|
|
82415
82765
|
|
|
82766
|
+
// src/components/premium-modals/user-management/tabs/UserListTab.ts
|
|
82767
|
+
var UserListTab = class {
|
|
82768
|
+
config;
|
|
82769
|
+
callbacks;
|
|
82770
|
+
el;
|
|
82771
|
+
currentPage = 0;
|
|
82772
|
+
totalPages = 0;
|
|
82773
|
+
searchQuery = "";
|
|
82774
|
+
searchTimer = null;
|
|
82775
|
+
loading = false;
|
|
82776
|
+
users = [];
|
|
82777
|
+
highlightedUserId = null;
|
|
82778
|
+
constructor(config, callbacks) {
|
|
82779
|
+
this.config = config;
|
|
82780
|
+
this.callbacks = callbacks;
|
|
82781
|
+
}
|
|
82782
|
+
render() {
|
|
82783
|
+
this.el = document.createElement("div");
|
|
82784
|
+
this.el.className = "um-tab-content um-user-list";
|
|
82785
|
+
this.el.innerHTML = `
|
|
82786
|
+
<div class="um-list-toolbar">
|
|
82787
|
+
<div class="um-search-wrap">
|
|
82788
|
+
<svg class="um-search-icon" width="14" height="14" viewBox="0 0 24 24" fill="none"
|
|
82789
|
+
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
82790
|
+
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
|
|
82791
|
+
</svg>
|
|
82792
|
+
<input type="text" class="um-search-input" placeholder="Buscar por nome ou e-mail..." />
|
|
82793
|
+
</div>
|
|
82794
|
+
<button class="um-btn um-btn--primary um-btn--sm um-new-user-btn">+ Novo Usu\xE1rio</button>
|
|
82795
|
+
</div>
|
|
82796
|
+
<div class="um-table-wrap">
|
|
82797
|
+
<table class="um-table">
|
|
82798
|
+
<thead>
|
|
82799
|
+
<tr>
|
|
82800
|
+
<th>Nome</th>
|
|
82801
|
+
<th>E-mail</th>
|
|
82802
|
+
<th>Perfil</th>
|
|
82803
|
+
<th class="um-col-actions">A\xE7\xF5es</th>
|
|
82804
|
+
</tr>
|
|
82805
|
+
</thead>
|
|
82806
|
+
<tbody class="um-tbody"></tbody>
|
|
82807
|
+
</table>
|
|
82808
|
+
<div class="um-list-empty" style="display:none">Nenhum usu\xE1rio encontrado.</div>
|
|
82809
|
+
<div class="um-list-loading" style="display:none">
|
|
82810
|
+
<span class="um-spinner"></span> Carregando...
|
|
82811
|
+
</div>
|
|
82812
|
+
</div>
|
|
82813
|
+
<div class="um-pagination" style="display:none">
|
|
82814
|
+
<button class="um-btn um-btn--ghost um-btn--sm um-prev-btn">\u2190 Anterior</button>
|
|
82815
|
+
<span class="um-page-info"></span>
|
|
82816
|
+
<button class="um-btn um-btn--ghost um-btn--sm um-next-btn">Pr\xF3xima \u2192</button>
|
|
82817
|
+
</div>
|
|
82818
|
+
`;
|
|
82819
|
+
const searchInput = this.el.querySelector(".um-search-input");
|
|
82820
|
+
searchInput.addEventListener("input", () => {
|
|
82821
|
+
if (this.searchTimer) clearTimeout(this.searchTimer);
|
|
82822
|
+
this.searchTimer = setTimeout(() => {
|
|
82823
|
+
this.searchQuery = searchInput.value.trim();
|
|
82824
|
+
this.currentPage = 0;
|
|
82825
|
+
this.fetchUsers();
|
|
82826
|
+
}, 300);
|
|
82827
|
+
});
|
|
82828
|
+
this.el.querySelector(".um-new-user-btn").addEventListener("click", () => {
|
|
82829
|
+
this.callbacks.onSwitchToNewUser();
|
|
82830
|
+
});
|
|
82831
|
+
this.el.querySelector(".um-prev-btn").addEventListener("click", () => {
|
|
82832
|
+
if (this.currentPage > 0) {
|
|
82833
|
+
this.currentPage--;
|
|
82834
|
+
this.fetchUsers();
|
|
82835
|
+
}
|
|
82836
|
+
});
|
|
82837
|
+
this.el.querySelector(".um-next-btn").addEventListener("click", () => {
|
|
82838
|
+
if (this.currentPage < this.totalPages - 1) {
|
|
82839
|
+
this.currentPage++;
|
|
82840
|
+
this.fetchUsers();
|
|
82841
|
+
}
|
|
82842
|
+
});
|
|
82843
|
+
this.fetchUsers();
|
|
82844
|
+
return this.el;
|
|
82845
|
+
}
|
|
82846
|
+
/** Called by NewUserTab/UserDetailTab after a mutation so we refresh the list */
|
|
82847
|
+
refresh(highlightUserId) {
|
|
82848
|
+
if (highlightUserId) this.highlightedUserId = highlightUserId;
|
|
82849
|
+
this.fetchUsers();
|
|
82850
|
+
}
|
|
82851
|
+
async fetchUsers() {
|
|
82852
|
+
if (this.loading) return;
|
|
82853
|
+
this.loading = true;
|
|
82854
|
+
this.setLoading(true);
|
|
82855
|
+
try {
|
|
82856
|
+
const { tbBaseUrl, jwtToken, customerId } = this.config;
|
|
82857
|
+
const q = encodeURIComponent(this.searchQuery);
|
|
82858
|
+
const url = `${tbBaseUrl}/api/customer/${customerId}/users?pageSize=20&page=${this.currentPage}${q ? `&textSearch=${q}` : ""}`;
|
|
82859
|
+
const res = await fetch(url, {
|
|
82860
|
+
headers: { "X-Authorization": `Bearer ${jwtToken}` }
|
|
82861
|
+
});
|
|
82862
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
82863
|
+
const page = await res.json();
|
|
82864
|
+
this.users = page.data;
|
|
82865
|
+
this.totalPages = page.totalPages;
|
|
82866
|
+
this.renderRows();
|
|
82867
|
+
this.renderPagination(page);
|
|
82868
|
+
} catch (err) {
|
|
82869
|
+
console.error("[UserListTab] fetchUsers error", err);
|
|
82870
|
+
this.callbacks.showToast("Erro ao carregar usu\xE1rios. Tente novamente.", "error");
|
|
82871
|
+
} finally {
|
|
82872
|
+
this.loading = false;
|
|
82873
|
+
this.setLoading(false);
|
|
82874
|
+
}
|
|
82875
|
+
}
|
|
82876
|
+
renderRows() {
|
|
82877
|
+
const tbody = this.el.querySelector(".um-tbody");
|
|
82878
|
+
const empty = this.el.querySelector(".um-list-empty");
|
|
82879
|
+
tbody.innerHTML = "";
|
|
82880
|
+
if (this.users.length === 0) {
|
|
82881
|
+
empty.style.display = "";
|
|
82882
|
+
return;
|
|
82883
|
+
}
|
|
82884
|
+
empty.style.display = "none";
|
|
82885
|
+
for (const user of this.users) {
|
|
82886
|
+
const tr = document.createElement("tr");
|
|
82887
|
+
tr.dataset.userId = user.id.id;
|
|
82888
|
+
if (this.highlightedUserId === user.id.id) {
|
|
82889
|
+
tr.classList.add("um-row--highlight");
|
|
82890
|
+
this.highlightedUserId = null;
|
|
82891
|
+
}
|
|
82892
|
+
const name = [user.firstName, user.lastName].filter(Boolean).join(" ") || "\u2014";
|
|
82893
|
+
const role = user.authority === "TENANT_ADMIN" ? "Admin" : "Usu\xE1rio";
|
|
82894
|
+
tr.innerHTML = `
|
|
82895
|
+
<td>${this.esc(name)}</td>
|
|
82896
|
+
<td>${this.esc(user.email)}</td>
|
|
82897
|
+
<td><span class="um-badge um-badge--${user.authority === "TENANT_ADMIN" ? "admin" : "user"}">${role}</span></td>
|
|
82898
|
+
<td class="um-col-actions">
|
|
82899
|
+
<button class="um-icon-btn um-edit-btn" title="Editar">
|
|
82900
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
|
82901
|
+
stroke-linecap="round" stroke-linejoin="round">
|
|
82902
|
+
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
|
82903
|
+
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
82904
|
+
</svg>
|
|
82905
|
+
</button>
|
|
82906
|
+
<button class="um-icon-btn um-view-btn" title="Ver Detalhes">
|
|
82907
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
|
82908
|
+
stroke-linecap="round" stroke-linejoin="round">
|
|
82909
|
+
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
|
|
82910
|
+
<circle cx="12" cy="12" r="3"/>
|
|
82911
|
+
</svg>
|
|
82912
|
+
</button>
|
|
82913
|
+
</td>
|
|
82914
|
+
`;
|
|
82915
|
+
tr.querySelector(".um-edit-btn").addEventListener("click", () => this.callbacks.onOpenUserDetail(user, true));
|
|
82916
|
+
tr.querySelector(".um-view-btn").addEventListener("click", () => this.callbacks.onOpenUserDetail(user, false));
|
|
82917
|
+
tbody.appendChild(tr);
|
|
82918
|
+
}
|
|
82919
|
+
}
|
|
82920
|
+
renderPagination(page) {
|
|
82921
|
+
const pag = this.el.querySelector(".um-pagination");
|
|
82922
|
+
const info = this.el.querySelector(".um-page-info");
|
|
82923
|
+
const prevBtn = this.el.querySelector(".um-prev-btn");
|
|
82924
|
+
const nextBtn = this.el.querySelector(".um-next-btn");
|
|
82925
|
+
if (page.totalPages <= 1) {
|
|
82926
|
+
pag.style.display = "none";
|
|
82927
|
+
return;
|
|
82928
|
+
}
|
|
82929
|
+
pag.style.display = "";
|
|
82930
|
+
info.textContent = `P\xE1gina ${this.currentPage + 1} de ${page.totalPages}`;
|
|
82931
|
+
prevBtn.disabled = this.currentPage === 0;
|
|
82932
|
+
nextBtn.disabled = !page.hasNext;
|
|
82933
|
+
}
|
|
82934
|
+
setLoading(on) {
|
|
82935
|
+
const loadEl = this.el?.querySelector(".um-list-loading");
|
|
82936
|
+
const tableWrap = this.el?.querySelector(".um-table-wrap");
|
|
82937
|
+
if (loadEl) loadEl.style.display = on ? "" : "none";
|
|
82938
|
+
if (tableWrap) tableWrap.style.opacity = on ? "0.5" : "1";
|
|
82939
|
+
}
|
|
82940
|
+
esc(s) {
|
|
82941
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
82942
|
+
}
|
|
82943
|
+
};
|
|
82944
|
+
|
|
82945
|
+
// src/components/premium-modals/user-management/tabs/NewUserTab.ts
|
|
82946
|
+
var NewUserTab = class {
|
|
82947
|
+
config;
|
|
82948
|
+
callbacks;
|
|
82949
|
+
el;
|
|
82950
|
+
submitting = false;
|
|
82951
|
+
constructor(config, callbacks) {
|
|
82952
|
+
this.config = config;
|
|
82953
|
+
this.callbacks = callbacks;
|
|
82954
|
+
}
|
|
82955
|
+
render() {
|
|
82956
|
+
this.el = document.createElement("div");
|
|
82957
|
+
this.el.className = "um-tab-content um-new-user";
|
|
82958
|
+
this.el.innerHTML = `
|
|
82959
|
+
<form class="um-form" novalidate>
|
|
82960
|
+
<div class="um-form-row">
|
|
82961
|
+
<div class="um-form-group">
|
|
82962
|
+
<label class="um-label">Nome <span class="um-req">*</span></label>
|
|
82963
|
+
<input type="text" class="um-input" name="firstName" autocomplete="off" />
|
|
82964
|
+
<span class="um-field-error" data-for="firstName"></span>
|
|
82965
|
+
</div>
|
|
82966
|
+
<div class="um-form-group">
|
|
82967
|
+
<label class="um-label">Sobrenome <span class="um-req">*</span></label>
|
|
82968
|
+
<input type="text" class="um-input" name="lastName" autocomplete="off" />
|
|
82969
|
+
<span class="um-field-error" data-for="lastName"></span>
|
|
82970
|
+
</div>
|
|
82971
|
+
</div>
|
|
82972
|
+
<div class="um-form-group">
|
|
82973
|
+
<label class="um-label">E-mail <span class="um-req">*</span></label>
|
|
82974
|
+
<input type="email" class="um-input" name="email" autocomplete="off" />
|
|
82975
|
+
<span class="um-field-error" data-for="email"></span>
|
|
82976
|
+
</div>
|
|
82977
|
+
<div class="um-form-group">
|
|
82978
|
+
<label class="um-label">Telefone</label>
|
|
82979
|
+
<input type="text" class="um-input" name="phone" autocomplete="off" />
|
|
82980
|
+
</div>
|
|
82981
|
+
<div class="um-form-group">
|
|
82982
|
+
<label class="um-label">Descri\xE7\xE3o</label>
|
|
82983
|
+
<textarea class="um-input um-textarea" name="description" rows="2"></textarea>
|
|
82984
|
+
</div>
|
|
82985
|
+
<div class="um-form-group um-form-group--check">
|
|
82986
|
+
<label class="um-check-label">
|
|
82987
|
+
<input type="checkbox" name="sendActivationMail" checked />
|
|
82988
|
+
Enviar e-mail de ativa\xE7\xE3o
|
|
82989
|
+
</label>
|
|
82990
|
+
</div>
|
|
82991
|
+
<div class="um-form-actions">
|
|
82992
|
+
<button type="button" class="um-btn um-btn--ghost um-cancel-btn">Cancelar</button>
|
|
82993
|
+
<button type="submit" class="um-btn um-btn--primary um-submit-btn">Criar Usu\xE1rio</button>
|
|
82994
|
+
</div>
|
|
82995
|
+
</form>
|
|
82996
|
+
`;
|
|
82997
|
+
const form = this.el.querySelector(".um-form");
|
|
82998
|
+
form.addEventListener("submit", (e) => {
|
|
82999
|
+
e.preventDefault();
|
|
83000
|
+
this.handleSubmit();
|
|
83001
|
+
});
|
|
83002
|
+
this.el.querySelector(".um-cancel-btn").addEventListener("click", () => this.callbacks.onCancel());
|
|
83003
|
+
return this.el;
|
|
83004
|
+
}
|
|
83005
|
+
reset() {
|
|
83006
|
+
const form = this.el?.querySelector(".um-form");
|
|
83007
|
+
if (form) {
|
|
83008
|
+
form.reset();
|
|
83009
|
+
this.el.querySelectorAll(".um-field-error").forEach((el2) => el2.textContent = "");
|
|
83010
|
+
}
|
|
83011
|
+
}
|
|
83012
|
+
validate(data) {
|
|
83013
|
+
const errors = {};
|
|
83014
|
+
if (!data.firstName.trim()) errors.firstName = "Nome \xE9 obrigat\xF3rio.";
|
|
83015
|
+
if (!data.lastName.trim()) errors.lastName = "Sobrenome \xE9 obrigat\xF3rio.";
|
|
83016
|
+
if (!data.email.trim()) {
|
|
83017
|
+
errors.email = "E-mail \xE9 obrigat\xF3rio.";
|
|
83018
|
+
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
|
|
83019
|
+
errors.email = "E-mail inv\xE1lido.";
|
|
83020
|
+
}
|
|
83021
|
+
return errors;
|
|
83022
|
+
}
|
|
83023
|
+
showErrors(errors) {
|
|
83024
|
+
this.el.querySelectorAll("[data-for]").forEach((el2) => {
|
|
83025
|
+
el2.textContent = errors[el2.dataset.for] || "";
|
|
83026
|
+
});
|
|
83027
|
+
}
|
|
83028
|
+
async handleSubmit() {
|
|
83029
|
+
if (this.submitting) return;
|
|
83030
|
+
const form = this.el.querySelector(".um-form");
|
|
83031
|
+
const fd = new FormData(form);
|
|
83032
|
+
const data = {
|
|
83033
|
+
firstName: fd.get("firstName") || "",
|
|
83034
|
+
lastName: fd.get("lastName") || "",
|
|
83035
|
+
email: fd.get("email") || "",
|
|
83036
|
+
phone: fd.get("phone") || "",
|
|
83037
|
+
description: fd.get("description") || ""
|
|
83038
|
+
};
|
|
83039
|
+
const sendActivation = fd.get("sendActivationMail") === "on";
|
|
83040
|
+
const errors = this.validate(data);
|
|
83041
|
+
this.showErrors(errors);
|
|
83042
|
+
if (Object.keys(errors).length > 0) return;
|
|
83043
|
+
this.submitting = true;
|
|
83044
|
+
const submitBtn = this.el.querySelector(".um-submit-btn");
|
|
83045
|
+
submitBtn.disabled = true;
|
|
83046
|
+
submitBtn.textContent = "Criando...";
|
|
83047
|
+
try {
|
|
83048
|
+
const { tbBaseUrl, jwtToken, customerId, tenantId } = this.config;
|
|
83049
|
+
const url = `${tbBaseUrl}/api/user?sendActivationMail=${sendActivation}`;
|
|
83050
|
+
const body = {
|
|
83051
|
+
email: data.email,
|
|
83052
|
+
firstName: data.firstName,
|
|
83053
|
+
lastName: data.lastName,
|
|
83054
|
+
phone: data.phone || void 0,
|
|
83055
|
+
authority: "CUSTOMER_USER",
|
|
83056
|
+
customerId: { id: customerId, entityType: "CUSTOMER" },
|
|
83057
|
+
tenantId: { id: tenantId, entityType: "TENANT" },
|
|
83058
|
+
additionalInfo: data.description ? { description: data.description } : void 0
|
|
83059
|
+
};
|
|
83060
|
+
const res = await fetch(url, {
|
|
83061
|
+
method: "POST",
|
|
83062
|
+
headers: {
|
|
83063
|
+
"X-Authorization": `Bearer ${jwtToken}`,
|
|
83064
|
+
"Content-Type": "application/json"
|
|
83065
|
+
},
|
|
83066
|
+
body: JSON.stringify(body)
|
|
83067
|
+
});
|
|
83068
|
+
if (!res.ok) {
|
|
83069
|
+
const errText = await res.text().catch(() => "");
|
|
83070
|
+
throw new Error(`HTTP ${res.status}${errText ? ": " + errText.slice(0, 120) : ""}`);
|
|
83071
|
+
}
|
|
83072
|
+
const created = await res.json();
|
|
83073
|
+
this.callbacks.showToast(`Usu\xE1rio ${data.firstName} ${data.lastName} criado com sucesso!`, "success");
|
|
83074
|
+
this.reset();
|
|
83075
|
+
this.callbacks.onCreated(created?.id?.id || "");
|
|
83076
|
+
} catch (err) {
|
|
83077
|
+
console.error("[NewUserTab] create user error", err);
|
|
83078
|
+
this.callbacks.showToast("Erro ao criar usu\xE1rio. Verifique os dados e tente novamente.", "error");
|
|
83079
|
+
} finally {
|
|
83080
|
+
this.submitting = false;
|
|
83081
|
+
submitBtn.disabled = false;
|
|
83082
|
+
submitBtn.textContent = "Criar Usu\xE1rio";
|
|
83083
|
+
}
|
|
83084
|
+
}
|
|
83085
|
+
};
|
|
83086
|
+
|
|
83087
|
+
// src/components/premium-modals/user-management/tabs/ProfileManagementTab.ts
|
|
83088
|
+
var ProfileManagementTab = class {
|
|
83089
|
+
config;
|
|
83090
|
+
el;
|
|
83091
|
+
constructor(config) {
|
|
83092
|
+
this.config = config;
|
|
83093
|
+
}
|
|
83094
|
+
render() {
|
|
83095
|
+
this.el = document.createElement("div");
|
|
83096
|
+
this.el.className = "um-tab-content um-profiles";
|
|
83097
|
+
this.el.innerHTML = `
|
|
83098
|
+
<div class="um-profiles-header">
|
|
83099
|
+
<h3 class="um-profiles-title">Perfis configurados para este cliente</h3>
|
|
83100
|
+
</div>
|
|
83101
|
+
<div class="um-profiles-body">
|
|
83102
|
+
<div class="um-profiles-loading">
|
|
83103
|
+
<span class="um-spinner"></span> Carregando perfis...
|
|
83104
|
+
</div>
|
|
83105
|
+
<table class="um-table um-profiles-table" style="display:none">
|
|
83106
|
+
<thead>
|
|
83107
|
+
<tr>
|
|
83108
|
+
<th>Nome do Grupo</th>
|
|
83109
|
+
<th>Tipo</th>
|
|
83110
|
+
<th>Membros</th>
|
|
83111
|
+
</tr>
|
|
83112
|
+
</thead>
|
|
83113
|
+
<tbody class="um-profiles-tbody"></tbody>
|
|
83114
|
+
</table>
|
|
83115
|
+
<div class="um-profiles-empty" style="display:none">Nenhum perfil encontrado.</div>
|
|
83116
|
+
<div class="um-profiles-error" style="display:none"></div>
|
|
83117
|
+
</div>
|
|
83118
|
+
<div class="um-profiles-notice">
|
|
83119
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
83120
|
+
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
83121
|
+
<circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/>
|
|
83122
|
+
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
|
83123
|
+
</svg>
|
|
83124
|
+
Edi\xE7\xE3o de perfis estar\xE1 dispon\xEDvel em vers\xE3o futura.
|
|
83125
|
+
</div>
|
|
83126
|
+
`;
|
|
83127
|
+
this.loadGroups();
|
|
83128
|
+
return this.el;
|
|
83129
|
+
}
|
|
83130
|
+
async loadGroups() {
|
|
83131
|
+
const { tbBaseUrl, jwtToken, customerId } = this.config;
|
|
83132
|
+
const loadingEl = this.el.querySelector(".um-profiles-loading");
|
|
83133
|
+
const tableEl = this.el.querySelector(".um-profiles-table");
|
|
83134
|
+
const emptyEl = this.el.querySelector(".um-profiles-empty");
|
|
83135
|
+
const errorEl = this.el.querySelector(".um-profiles-error");
|
|
83136
|
+
const tbody = this.el.querySelector(".um-profiles-tbody");
|
|
83137
|
+
try {
|
|
83138
|
+
const url = `${tbBaseUrl}/api/entityGroups/CUSTOMER/${customerId}/USER`;
|
|
83139
|
+
const res = await fetch(url, {
|
|
83140
|
+
headers: { "X-Authorization": `Bearer ${jwtToken}` }
|
|
83141
|
+
});
|
|
83142
|
+
loadingEl.style.display = "none";
|
|
83143
|
+
if (!res.ok) {
|
|
83144
|
+
if (res.status === 403 || res.status === 404) {
|
|
83145
|
+
emptyEl.style.display = "";
|
|
83146
|
+
emptyEl.textContent = "Gest\xE3o de grupos n\xE3o dispon\xEDvel nesta edi\xE7\xE3o do ThingsBoard.";
|
|
83147
|
+
return;
|
|
83148
|
+
}
|
|
83149
|
+
throw new Error(`HTTP ${res.status}`);
|
|
83150
|
+
}
|
|
83151
|
+
const groups = await res.json();
|
|
83152
|
+
if (!groups || groups.length === 0) {
|
|
83153
|
+
emptyEl.style.display = "";
|
|
83154
|
+
return;
|
|
83155
|
+
}
|
|
83156
|
+
for (const g of groups) {
|
|
83157
|
+
const tr = document.createElement("tr");
|
|
83158
|
+
const memberCount = g.additionalInfo?.memberCount ?? "\u2014";
|
|
83159
|
+
tr.innerHTML = `
|
|
83160
|
+
<td>${this.esc(g.name || "\u2014")}</td>
|
|
83161
|
+
<td>USER_GROUP</td>
|
|
83162
|
+
<td>${memberCount}</td>
|
|
83163
|
+
`;
|
|
83164
|
+
tbody.appendChild(tr);
|
|
83165
|
+
}
|
|
83166
|
+
tableEl.style.display = "";
|
|
83167
|
+
} catch (err) {
|
|
83168
|
+
console.warn("[ProfileManagementTab] loadGroups error", err);
|
|
83169
|
+
loadingEl.style.display = "none";
|
|
83170
|
+
errorEl.style.display = "";
|
|
83171
|
+
errorEl.textContent = "N\xE3o foi poss\xEDvel carregar os perfis neste momento.";
|
|
83172
|
+
}
|
|
83173
|
+
}
|
|
83174
|
+
esc(s) {
|
|
83175
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
83176
|
+
}
|
|
83177
|
+
};
|
|
83178
|
+
|
|
83179
|
+
// src/components/premium-modals/user-management/types.ts
|
|
83180
|
+
function buildUserTabLabel(user) {
|
|
83181
|
+
const first = (user.firstName || user.email.split("@")[0] || "?").toUpperCase();
|
|
83182
|
+
const lastInitial = user.lastName ? user.lastName[0].toUpperCase() + "." : "";
|
|
83183
|
+
return lastInitial ? `${first} ${lastInitial}` : first;
|
|
83184
|
+
}
|
|
83185
|
+
|
|
83186
|
+
// src/components/premium-modals/user-management/tabs/UserDetailTab.ts
|
|
83187
|
+
var UserDetailTab = class {
|
|
83188
|
+
config;
|
|
83189
|
+
callbacks;
|
|
83190
|
+
user;
|
|
83191
|
+
el;
|
|
83192
|
+
mode = "view";
|
|
83193
|
+
saving = false;
|
|
83194
|
+
constructor(config, user, callbacks) {
|
|
83195
|
+
this.config = config;
|
|
83196
|
+
this.user = user;
|
|
83197
|
+
this.callbacks = callbacks;
|
|
83198
|
+
}
|
|
83199
|
+
get tabLabel() {
|
|
83200
|
+
return buildUserTabLabel(this.user);
|
|
83201
|
+
}
|
|
83202
|
+
render() {
|
|
83203
|
+
this.el = document.createElement("div");
|
|
83204
|
+
this.el.className = "um-tab-content um-user-detail";
|
|
83205
|
+
this.renderContent();
|
|
83206
|
+
return this.el;
|
|
83207
|
+
}
|
|
83208
|
+
/** Called after tab is re-activated when user already has an open tab */
|
|
83209
|
+
focus() {
|
|
83210
|
+
this.el?.querySelector(".um-detail-card")?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
83211
|
+
}
|
|
83212
|
+
renderContent() {
|
|
83213
|
+
this.el.innerHTML = "";
|
|
83214
|
+
if (this.mode === "view") {
|
|
83215
|
+
this.el.appendChild(this.buildViewMode());
|
|
83216
|
+
} else {
|
|
83217
|
+
this.el.appendChild(this.buildEditMode());
|
|
83218
|
+
}
|
|
83219
|
+
}
|
|
83220
|
+
buildViewMode() {
|
|
83221
|
+
const u = this.user;
|
|
83222
|
+
const name = [u.firstName, u.lastName].filter(Boolean).join(" ") || "\u2014";
|
|
83223
|
+
const role = u.authority === "TENANT_ADMIN" ? "Admin" : "Usu\xE1rio";
|
|
83224
|
+
const createdAt = u.createdTime ? new Date(u.createdTime).toLocaleDateString("pt-BR") : "\u2014";
|
|
83225
|
+
const card = document.createElement("div");
|
|
83226
|
+
card.className = "um-detail-card";
|
|
83227
|
+
card.innerHTML = `
|
|
83228
|
+
<div class="um-detail-section">
|
|
83229
|
+
<div class="um-detail-row"><span class="um-detail-label">Nome</span><span class="um-detail-value">${this.esc(name)}</span></div>
|
|
83230
|
+
<div class="um-detail-row"><span class="um-detail-label">E-mail</span><span class="um-detail-value">${this.esc(u.email)}</span></div>
|
|
83231
|
+
<div class="um-detail-row"><span class="um-detail-label">Telefone</span><span class="um-detail-value">${this.esc(u.phone || "\u2014")}</span></div>
|
|
83232
|
+
<div class="um-detail-row"><span class="um-detail-label">Perfil</span><span class="um-detail-value">
|
|
83233
|
+
<span class="um-badge um-badge--${u.authority === "TENANT_ADMIN" ? "admin" : "user"}">${role}</span>
|
|
83234
|
+
</span></div>
|
|
83235
|
+
<div class="um-detail-row"><span class="um-detail-label">Criado em</span><span class="um-detail-value">${createdAt}</span></div>
|
|
83236
|
+
${u.additionalInfo?.description ? `<div class="um-detail-row"><span class="um-detail-label">Descri\xE7\xE3o</span><span class="um-detail-value">${this.esc(String(u.additionalInfo.description))}</span></div>` : ""}
|
|
83237
|
+
</div>
|
|
83238
|
+
<div class="um-detail-actions">
|
|
83239
|
+
<button class="um-btn um-btn--secondary um-detail-edit-btn">Editar</button>
|
|
83240
|
+
<button class="um-btn um-btn--ghost um-detail-reset-btn">Redefinir Senha</button>
|
|
83241
|
+
<button class="um-btn um-btn--danger um-detail-delete-btn">Excluir</button>
|
|
83242
|
+
</div>
|
|
83243
|
+
`;
|
|
83244
|
+
card.querySelector(".um-detail-edit-btn").addEventListener("click", () => {
|
|
83245
|
+
this.mode = "edit";
|
|
83246
|
+
this.renderContent();
|
|
83247
|
+
});
|
|
83248
|
+
card.querySelector(".um-detail-reset-btn").addEventListener("click", () => this.handleResetPassword());
|
|
83249
|
+
card.querySelector(".um-detail-delete-btn").addEventListener("click", () => this.handleDelete());
|
|
83250
|
+
return card;
|
|
83251
|
+
}
|
|
83252
|
+
buildEditMode() {
|
|
83253
|
+
const u = this.user;
|
|
83254
|
+
const card = document.createElement("div");
|
|
83255
|
+
card.className = "um-detail-card um-detail-card--edit";
|
|
83256
|
+
card.innerHTML = `
|
|
83257
|
+
<form class="um-form" novalidate>
|
|
83258
|
+
<div class="um-form-row">
|
|
83259
|
+
<div class="um-form-group">
|
|
83260
|
+
<label class="um-label">Nome <span class="um-req">*</span></label>
|
|
83261
|
+
<input type="text" class="um-input" name="firstName" value="${this.esc(u.firstName || "")}" autocomplete="off" />
|
|
83262
|
+
<span class="um-field-error" data-for="firstName"></span>
|
|
83263
|
+
</div>
|
|
83264
|
+
<div class="um-form-group">
|
|
83265
|
+
<label class="um-label">Sobrenome</label>
|
|
83266
|
+
<input type="text" class="um-input" name="lastName" value="${this.esc(u.lastName || "")}" autocomplete="off" />
|
|
83267
|
+
</div>
|
|
83268
|
+
</div>
|
|
83269
|
+
<div class="um-form-group">
|
|
83270
|
+
<label class="um-label">E-mail <span class="um-req">*</span></label>
|
|
83271
|
+
<input type="email" class="um-input" name="email" value="${this.esc(u.email)}" autocomplete="off" />
|
|
83272
|
+
<span class="um-field-error" data-for="email"></span>
|
|
83273
|
+
</div>
|
|
83274
|
+
<div class="um-form-group">
|
|
83275
|
+
<label class="um-label">Telefone</label>
|
|
83276
|
+
<input type="text" class="um-input" name="phone" value="${this.esc(u.phone || "")}" autocomplete="off" />
|
|
83277
|
+
</div>
|
|
83278
|
+
<div class="um-form-group">
|
|
83279
|
+
<label class="um-label">Descri\xE7\xE3o</label>
|
|
83280
|
+
<textarea class="um-input um-textarea" name="description" rows="2">${this.esc(String(u.additionalInfo?.description || ""))}</textarea>
|
|
83281
|
+
</div>
|
|
83282
|
+
<div class="um-form-actions">
|
|
83283
|
+
<button type="button" class="um-btn um-btn--ghost um-cancel-edit-btn">Cancelar</button>
|
|
83284
|
+
<button type="submit" class="um-btn um-btn--primary um-save-btn">Salvar Altera\xE7\xF5es</button>
|
|
83285
|
+
</div>
|
|
83286
|
+
</form>
|
|
83287
|
+
`;
|
|
83288
|
+
card.querySelector(".um-form").addEventListener("submit", (e) => {
|
|
83289
|
+
e.preventDefault();
|
|
83290
|
+
this.handleSave(card);
|
|
83291
|
+
});
|
|
83292
|
+
card.querySelector(".um-cancel-edit-btn").addEventListener("click", () => {
|
|
83293
|
+
this.mode = "view";
|
|
83294
|
+
this.renderContent();
|
|
83295
|
+
});
|
|
83296
|
+
return card;
|
|
83297
|
+
}
|
|
83298
|
+
async handleSave(card) {
|
|
83299
|
+
if (this.saving) return;
|
|
83300
|
+
const form = card.querySelector(".um-form");
|
|
83301
|
+
const fd = new FormData(form);
|
|
83302
|
+
const firstName = (fd.get("firstName") || "").trim();
|
|
83303
|
+
const email = (fd.get("email") || "").trim();
|
|
83304
|
+
const errors = {};
|
|
83305
|
+
if (!firstName) errors.firstName = "Nome \xE9 obrigat\xF3rio.";
|
|
83306
|
+
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) errors.email = "E-mail inv\xE1lido.";
|
|
83307
|
+
card.querySelectorAll("[data-for]").forEach((el2) => {
|
|
83308
|
+
el2.textContent = errors[el2.dataset.for] || "";
|
|
83309
|
+
});
|
|
83310
|
+
if (Object.keys(errors).length > 0) return;
|
|
83311
|
+
this.saving = true;
|
|
83312
|
+
const saveBtn = card.querySelector(".um-save-btn");
|
|
83313
|
+
saveBtn.disabled = true;
|
|
83314
|
+
saveBtn.textContent = "Salvando...";
|
|
83315
|
+
try {
|
|
83316
|
+
const { tbBaseUrl, jwtToken } = this.config;
|
|
83317
|
+
const description = (fd.get("description") || "").trim();
|
|
83318
|
+
const updatedUser = {
|
|
83319
|
+
...this.user,
|
|
83320
|
+
firstName,
|
|
83321
|
+
lastName: (fd.get("lastName") || "").trim() || void 0,
|
|
83322
|
+
email,
|
|
83323
|
+
phone: (fd.get("phone") || "").trim() || void 0,
|
|
83324
|
+
additionalInfo: {
|
|
83325
|
+
...this.user.additionalInfo,
|
|
83326
|
+
description: description || void 0
|
|
83327
|
+
}
|
|
83328
|
+
};
|
|
83329
|
+
const res = await fetch(`${tbBaseUrl}/api/user`, {
|
|
83330
|
+
method: "POST",
|
|
83331
|
+
headers: {
|
|
83332
|
+
"X-Authorization": `Bearer ${jwtToken}`,
|
|
83333
|
+
"Content-Type": "application/json"
|
|
83334
|
+
},
|
|
83335
|
+
body: JSON.stringify(updatedUser)
|
|
83336
|
+
});
|
|
83337
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
83338
|
+
const saved = await res.json();
|
|
83339
|
+
this.user = saved;
|
|
83340
|
+
this.mode = "view";
|
|
83341
|
+
this.renderContent();
|
|
83342
|
+
this.callbacks.showToast("Usu\xE1rio atualizado com sucesso!", "success");
|
|
83343
|
+
this.callbacks.onUpdated(saved);
|
|
83344
|
+
} catch (err) {
|
|
83345
|
+
console.error("[UserDetailTab] handleSave error", err);
|
|
83346
|
+
this.callbacks.showToast("Erro ao salvar. Tente novamente.", "error");
|
|
83347
|
+
} finally {
|
|
83348
|
+
this.saving = false;
|
|
83349
|
+
saveBtn.disabled = false;
|
|
83350
|
+
saveBtn.textContent = "Salvar Altera\xE7\xF5es";
|
|
83351
|
+
}
|
|
83352
|
+
}
|
|
83353
|
+
handleResetPassword() {
|
|
83354
|
+
this.showConfirmDialog({
|
|
83355
|
+
title: "Redefinir Senha",
|
|
83356
|
+
message: `Enviar e-mail de redefini\xE7\xE3o de senha para <strong>${this.esc(this.user.email)}</strong>?`,
|
|
83357
|
+
confirmLabel: "Enviar E-mail",
|
|
83358
|
+
confirmClass: "um-btn--secondary",
|
|
83359
|
+
onConfirm: async () => {
|
|
83360
|
+
try {
|
|
83361
|
+
const { tbBaseUrl, jwtToken } = this.config;
|
|
83362
|
+
const res = await fetch(`${tbBaseUrl}/api/noauth/resetPasswordByEmail`, {
|
|
83363
|
+
method: "POST",
|
|
83364
|
+
headers: {
|
|
83365
|
+
"X-Authorization": `Bearer ${jwtToken}`,
|
|
83366
|
+
"Content-Type": "application/json"
|
|
83367
|
+
},
|
|
83368
|
+
body: JSON.stringify({ email: this.user.email })
|
|
83369
|
+
});
|
|
83370
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
83371
|
+
this.callbacks.showToast("E-mail de redefini\xE7\xE3o de senha enviado!", "success");
|
|
83372
|
+
} catch (err) {
|
|
83373
|
+
console.error("[UserDetailTab] resetPassword error", err);
|
|
83374
|
+
this.callbacks.showToast("Erro ao enviar e-mail de redefini\xE7\xE3o.", "error");
|
|
83375
|
+
}
|
|
83376
|
+
}
|
|
83377
|
+
});
|
|
83378
|
+
}
|
|
83379
|
+
handleDelete() {
|
|
83380
|
+
const name = [this.user.firstName, this.user.lastName].filter(Boolean).join(" ") || this.user.email;
|
|
83381
|
+
this.showConfirmDialog({
|
|
83382
|
+
title: "Excluir Usu\xE1rio",
|
|
83383
|
+
message: `Tem certeza que deseja excluir o usu\xE1rio <strong>${this.esc(name)}</strong>? Esta a\xE7\xE3o n\xE3o pode ser desfeita.`,
|
|
83384
|
+
confirmLabel: "Excluir",
|
|
83385
|
+
confirmClass: "um-btn--danger",
|
|
83386
|
+
onConfirm: async () => {
|
|
83387
|
+
try {
|
|
83388
|
+
const { tbBaseUrl, jwtToken } = this.config;
|
|
83389
|
+
const res = await fetch(`${tbBaseUrl}/api/user/${this.user.id.id}`, {
|
|
83390
|
+
method: "DELETE",
|
|
83391
|
+
headers: { "X-Authorization": `Bearer ${jwtToken}` }
|
|
83392
|
+
});
|
|
83393
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
83394
|
+
this.callbacks.showToast(`Usu\xE1rio ${name} exclu\xEDdo.`, "success");
|
|
83395
|
+
this.callbacks.onDeleted();
|
|
83396
|
+
} catch (err) {
|
|
83397
|
+
console.error("[UserDetailTab] delete error", err);
|
|
83398
|
+
this.callbacks.showToast("Erro ao excluir usu\xE1rio. Verifique as permiss\xF5es.", "error");
|
|
83399
|
+
}
|
|
83400
|
+
}
|
|
83401
|
+
});
|
|
83402
|
+
}
|
|
83403
|
+
showConfirmDialog(opts) {
|
|
83404
|
+
const overlay = document.createElement("div");
|
|
83405
|
+
overlay.className = "um-confirm-overlay";
|
|
83406
|
+
overlay.style.cssText = "position:fixed;inset:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:100001;";
|
|
83407
|
+
const modal = document.createElement("div");
|
|
83408
|
+
modal.className = "um-confirm-modal";
|
|
83409
|
+
modal.style.cssText = "background:#1e2433;border:1px solid #3a4160;border-radius:12px;padding:24px;max-width:440px;width:90%;box-shadow:0 24px 64px rgba(0,0,0,0.5);";
|
|
83410
|
+
modal.innerHTML = `
|
|
83411
|
+
<h4 style="margin:0 0 12px;font-size:16px;font-weight:600;color:#e2e8f0;">${opts.title}</h4>
|
|
83412
|
+
<p style="margin:0 0 20px;font-size:14px;color:#94a3b8;line-height:1.5;">${opts.message}</p>
|
|
83413
|
+
<div style="display:flex;gap:8px;justify-content:flex-end;">
|
|
83414
|
+
<button class="um-btn um-btn--ghost um-confirm-cancel">Cancelar</button>
|
|
83415
|
+
<button class="um-btn ${opts.confirmClass} um-confirm-ok">${opts.confirmLabel}</button>
|
|
83416
|
+
</div>
|
|
83417
|
+
`;
|
|
83418
|
+
overlay.appendChild(modal);
|
|
83419
|
+
document.body.appendChild(overlay);
|
|
83420
|
+
const close = () => overlay.remove();
|
|
83421
|
+
overlay.addEventListener("click", (e) => {
|
|
83422
|
+
if (e.target === overlay) close();
|
|
83423
|
+
});
|
|
83424
|
+
modal.querySelector(".um-confirm-cancel").addEventListener("click", close);
|
|
83425
|
+
modal.querySelector(".um-confirm-ok").addEventListener("click", async () => {
|
|
83426
|
+
const btn = modal.querySelector(".um-confirm-ok");
|
|
83427
|
+
btn.disabled = true;
|
|
83428
|
+
btn.textContent = "...";
|
|
83429
|
+
close();
|
|
83430
|
+
await opts.onConfirm();
|
|
83431
|
+
});
|
|
83432
|
+
}
|
|
83433
|
+
esc(s) {
|
|
83434
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
83435
|
+
}
|
|
83436
|
+
};
|
|
83437
|
+
|
|
83438
|
+
// src/components/premium-modals/user-management/UserManagementModalView.ts
|
|
83439
|
+
var UserManagementModalView = class {
|
|
83440
|
+
config;
|
|
83441
|
+
backdrop;
|
|
83442
|
+
modalEl;
|
|
83443
|
+
tabBarEl;
|
|
83444
|
+
contentEl;
|
|
83445
|
+
toastEl;
|
|
83446
|
+
tabs = [];
|
|
83447
|
+
activeTabKey = "user-list";
|
|
83448
|
+
userListTab;
|
|
83449
|
+
newUserTab;
|
|
83450
|
+
constructor(config) {
|
|
83451
|
+
this.config = config;
|
|
83452
|
+
}
|
|
83453
|
+
render() {
|
|
83454
|
+
this.injectStyles();
|
|
83455
|
+
this.buildDOM();
|
|
83456
|
+
this.buildTabs();
|
|
83457
|
+
this.activateTab("user-list");
|
|
83458
|
+
document.body.appendChild(this.backdrop);
|
|
83459
|
+
}
|
|
83460
|
+
destroy() {
|
|
83461
|
+
this.backdrop?.remove();
|
|
83462
|
+
}
|
|
83463
|
+
showToast(msg, type = "success") {
|
|
83464
|
+
if (!this.toastEl) return;
|
|
83465
|
+
this.toastEl.textContent = msg;
|
|
83466
|
+
this.toastEl.className = `um-toast um-toast--${type} um-toast--visible`;
|
|
83467
|
+
setTimeout(() => {
|
|
83468
|
+
this.toastEl.classList.remove("um-toast--visible");
|
|
83469
|
+
}, 3500);
|
|
83470
|
+
}
|
|
83471
|
+
// ── DOM ──────────────────────────────────────────────────────────────────────
|
|
83472
|
+
buildDOM() {
|
|
83473
|
+
this.backdrop = document.createElement("div");
|
|
83474
|
+
this.backdrop.className = "um-backdrop";
|
|
83475
|
+
this.modalEl = document.createElement("div");
|
|
83476
|
+
this.modalEl.className = "um-modal";
|
|
83477
|
+
const customerName = this.config.customerName || "";
|
|
83478
|
+
const header = document.createElement("div");
|
|
83479
|
+
header.className = "um-modal-header";
|
|
83480
|
+
header.innerHTML = `
|
|
83481
|
+
<div class="um-modal-header-left">
|
|
83482
|
+
<span class="um-modal-icon">
|
|
83483
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
83484
|
+
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
83485
|
+
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
|
|
83486
|
+
<circle cx="9" cy="7" r="4"/>
|
|
83487
|
+
<path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
|
|
83488
|
+
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
|
|
83489
|
+
</svg>
|
|
83490
|
+
</span>
|
|
83491
|
+
<span class="um-modal-title">Gest\xE3o de Usu\xE1rios</span>
|
|
83492
|
+
${customerName ? `<span class="um-modal-subtitle">\xB7 ${this.esc(customerName)}</span>` : ""}
|
|
83493
|
+
</div>
|
|
83494
|
+
<button class="um-modal-close" title="Fechar">
|
|
83495
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
83496
|
+
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
83497
|
+
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
83498
|
+
</svg>
|
|
83499
|
+
</button>
|
|
83500
|
+
`;
|
|
83501
|
+
header.querySelector(".um-modal-close").addEventListener("click", () => this.close());
|
|
83502
|
+
this.tabBarEl = document.createElement("div");
|
|
83503
|
+
this.tabBarEl.className = "um-tab-bar";
|
|
83504
|
+
this.contentEl = document.createElement("div");
|
|
83505
|
+
this.contentEl.className = "um-modal-content";
|
|
83506
|
+
this.toastEl = document.createElement("div");
|
|
83507
|
+
this.toastEl.className = "um-toast";
|
|
83508
|
+
this.modalEl.appendChild(header);
|
|
83509
|
+
this.modalEl.appendChild(this.tabBarEl);
|
|
83510
|
+
this.modalEl.appendChild(this.contentEl);
|
|
83511
|
+
this.modalEl.appendChild(this.toastEl);
|
|
83512
|
+
this.backdrop.appendChild(this.modalEl);
|
|
83513
|
+
this.backdrop.addEventListener("click", (e) => {
|
|
83514
|
+
if (e.target === this.backdrop) this.close();
|
|
83515
|
+
});
|
|
83516
|
+
}
|
|
83517
|
+
close() {
|
|
83518
|
+
this.config.onClose();
|
|
83519
|
+
this.destroy();
|
|
83520
|
+
}
|
|
83521
|
+
// ── Tabs ─────────────────────────────────────────────────────────────────────
|
|
83522
|
+
buildTabs() {
|
|
83523
|
+
const config = this.config;
|
|
83524
|
+
this.userListTab = new UserListTab(config, {
|
|
83525
|
+
onOpenUserDetail: (user, editMode) => this.openUserDetailTab(user, editMode),
|
|
83526
|
+
onSwitchToNewUser: () => this.activateTab("new-user"),
|
|
83527
|
+
showToast: (msg, type) => this.showToast(msg, type)
|
|
83528
|
+
});
|
|
83529
|
+
this.tabs.push({
|
|
83530
|
+
key: "user-list",
|
|
83531
|
+
label: "Usu\xE1rios",
|
|
83532
|
+
closeable: false,
|
|
83533
|
+
getEl: () => this.userListTab.render()
|
|
83534
|
+
});
|
|
83535
|
+
this.newUserTab = new NewUserTab(config, {
|
|
83536
|
+
onCreated: (userId) => {
|
|
83537
|
+
this.activateTab("user-list");
|
|
83538
|
+
this.userListTab.refresh(userId);
|
|
83539
|
+
},
|
|
83540
|
+
onCancel: () => this.activateTab("user-list"),
|
|
83541
|
+
showToast: (msg, type) => this.showToast(msg, type)
|
|
83542
|
+
});
|
|
83543
|
+
this.tabs.push({
|
|
83544
|
+
key: "new-user",
|
|
83545
|
+
label: "Novo Usu\xE1rio",
|
|
83546
|
+
closeable: false,
|
|
83547
|
+
getEl: () => this.newUserTab.render(),
|
|
83548
|
+
onActivate: () => this.newUserTab.reset()
|
|
83549
|
+
});
|
|
83550
|
+
const profileTab = new ProfileManagementTab(config);
|
|
83551
|
+
this.tabs.push({
|
|
83552
|
+
key: "profiles",
|
|
83553
|
+
label: "Perfis",
|
|
83554
|
+
closeable: false,
|
|
83555
|
+
getEl: () => profileTab.render()
|
|
83556
|
+
});
|
|
83557
|
+
this.rebuildTabBar();
|
|
83558
|
+
}
|
|
83559
|
+
openUserDetailTab(user, editMode = false) {
|
|
83560
|
+
const existingKey = `user-detail-${user.id.id}`;
|
|
83561
|
+
const existing = this.tabs.find((t) => t.key === existingKey);
|
|
83562
|
+
if (existing) {
|
|
83563
|
+
this.activateTab(existingKey);
|
|
83564
|
+
existing.detailTab?.focus();
|
|
83565
|
+
return;
|
|
83566
|
+
}
|
|
83567
|
+
const detailTab = new UserDetailTab(this.config, user, {
|
|
83568
|
+
onDeleted: () => {
|
|
83569
|
+
this.removeTab(existingKey);
|
|
83570
|
+
this.userListTab.refresh();
|
|
83571
|
+
},
|
|
83572
|
+
onUpdated: (updated) => {
|
|
83573
|
+
this.userListTab.refresh(updated.id.id);
|
|
83574
|
+
const entry = this.tabs.find((t) => t.key === existingKey);
|
|
83575
|
+
if (entry) {
|
|
83576
|
+
entry.label = detailTab.tabLabel;
|
|
83577
|
+
this.rebuildTabBar();
|
|
83578
|
+
}
|
|
83579
|
+
},
|
|
83580
|
+
showToast: (msg, type) => this.showToast(msg, type)
|
|
83581
|
+
});
|
|
83582
|
+
let el2 = null;
|
|
83583
|
+
this.tabs.push({
|
|
83584
|
+
key: existingKey,
|
|
83585
|
+
label: detailTab.tabLabel,
|
|
83586
|
+
closeable: true,
|
|
83587
|
+
userId: user.id.id,
|
|
83588
|
+
detailTab,
|
|
83589
|
+
getEl: () => {
|
|
83590
|
+
el2 = detailTab.render();
|
|
83591
|
+
if (editMode) {
|
|
83592
|
+
setTimeout(() => el2?.querySelector(".um-detail-edit-btn")?.click(), 0);
|
|
83593
|
+
}
|
|
83594
|
+
return el2;
|
|
83595
|
+
}
|
|
83596
|
+
});
|
|
83597
|
+
this.rebuildTabBar();
|
|
83598
|
+
this.activateTab(existingKey);
|
|
83599
|
+
}
|
|
83600
|
+
removeTab(key) {
|
|
83601
|
+
const idx = this.tabs.findIndex((t) => t.key === key);
|
|
83602
|
+
if (idx === -1) return;
|
|
83603
|
+
this.tabs.splice(idx, 1);
|
|
83604
|
+
if (this.activeTabKey === key) {
|
|
83605
|
+
this.activateTab("user-list");
|
|
83606
|
+
} else {
|
|
83607
|
+
this.rebuildTabBar();
|
|
83608
|
+
}
|
|
83609
|
+
}
|
|
83610
|
+
activateTab(key) {
|
|
83611
|
+
const entry = this.tabs.find((t) => t.key === key);
|
|
83612
|
+
if (!entry) return;
|
|
83613
|
+
this.activeTabKey = key;
|
|
83614
|
+
entry.onActivate?.();
|
|
83615
|
+
this.contentEl.innerHTML = "";
|
|
83616
|
+
this.contentEl.appendChild(entry.getEl());
|
|
83617
|
+
this.rebuildTabBar();
|
|
83618
|
+
}
|
|
83619
|
+
rebuildTabBar() {
|
|
83620
|
+
this.tabBarEl.innerHTML = "";
|
|
83621
|
+
for (const tab of this.tabs) {
|
|
83622
|
+
const btn = document.createElement("button");
|
|
83623
|
+
btn.className = `um-tab-btn${tab.key === this.activeTabKey ? " um-tab-btn--active" : ""}`;
|
|
83624
|
+
btn.textContent = tab.label;
|
|
83625
|
+
btn.addEventListener("click", () => this.activateTab(tab.key));
|
|
83626
|
+
if (tab.closeable) {
|
|
83627
|
+
const close = document.createElement("span");
|
|
83628
|
+
close.className = "um-tab-close";
|
|
83629
|
+
close.innerHTML = "\u2715";
|
|
83630
|
+
close.title = "Fechar aba";
|
|
83631
|
+
close.addEventListener("click", (e) => {
|
|
83632
|
+
e.stopPropagation();
|
|
83633
|
+
this.removeTab(tab.key);
|
|
83634
|
+
});
|
|
83635
|
+
btn.appendChild(close);
|
|
83636
|
+
}
|
|
83637
|
+
this.tabBarEl.appendChild(btn);
|
|
83638
|
+
}
|
|
83639
|
+
}
|
|
83640
|
+
// ── Styles ───────────────────────────────────────────────────────────────────
|
|
83641
|
+
injectStyles() {
|
|
83642
|
+
if (document.getElementById("um-styles")) return;
|
|
83643
|
+
const style = document.createElement("style");
|
|
83644
|
+
style.id = "um-styles";
|
|
83645
|
+
style.textContent = `
|
|
83646
|
+
/* === UserManagement Modal === */
|
|
83647
|
+
.um-backdrop {
|
|
83648
|
+
position: fixed; inset: 0;
|
|
83649
|
+
background: rgba(0,0,0,0.55);
|
|
83650
|
+
backdrop-filter: blur(4px);
|
|
83651
|
+
display: flex; align-items: center; justify-content: center;
|
|
83652
|
+
z-index: 99999;
|
|
83653
|
+
padding: 16px;
|
|
83654
|
+
}
|
|
83655
|
+
.um-modal {
|
|
83656
|
+
background: #131929;
|
|
83657
|
+
border: 1px solid #2a3352;
|
|
83658
|
+
border-radius: 14px;
|
|
83659
|
+
width: 92vw; max-width: 960px;
|
|
83660
|
+
aspect-ratio: 16/9;
|
|
83661
|
+
display: flex; flex-direction: column;
|
|
83662
|
+
box-shadow: 0 32px 80px rgba(0,0,0,0.6);
|
|
83663
|
+
overflow: hidden;
|
|
83664
|
+
position: relative;
|
|
83665
|
+
}
|
|
83666
|
+
@media (max-height: 600px) { .um-modal { aspect-ratio: unset; height: 90vh; } }
|
|
83667
|
+
|
|
83668
|
+
/* Header */
|
|
83669
|
+
.um-modal-header {
|
|
83670
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
83671
|
+
padding: 14px 20px; border-bottom: 1px solid #1e2d4a;
|
|
83672
|
+
flex-shrink: 0;
|
|
83673
|
+
}
|
|
83674
|
+
.um-modal-header-left { display: flex; align-items: center; gap: 10px; }
|
|
83675
|
+
.um-modal-icon { color: #60a5fa; display: flex; }
|
|
83676
|
+
.um-modal-title { font-size: 15px; font-weight: 600; color: #e2e8f0; }
|
|
83677
|
+
.um-modal-subtitle { font-size: 13px; color: #64748b; }
|
|
83678
|
+
.um-modal-close {
|
|
83679
|
+
background: none; border: none; cursor: pointer;
|
|
83680
|
+
color: #64748b; padding: 4px; border-radius: 6px;
|
|
83681
|
+
display: flex; align-items: center; justify-content: center;
|
|
83682
|
+
transition: color 0.15s, background 0.15s;
|
|
83683
|
+
}
|
|
83684
|
+
.um-modal-close:hover { color: #e2e8f0; background: #1e2d4a; }
|
|
83685
|
+
|
|
83686
|
+
/* Tab bar */
|
|
83687
|
+
.um-tab-bar {
|
|
83688
|
+
display: flex; gap: 2px; padding: 0 16px;
|
|
83689
|
+
border-bottom: 1px solid #1e2d4a; flex-shrink: 0;
|
|
83690
|
+
overflow-x: auto; scrollbar-width: none;
|
|
83691
|
+
}
|
|
83692
|
+
.um-tab-bar::-webkit-scrollbar { display: none; }
|
|
83693
|
+
.um-tab-btn {
|
|
83694
|
+
background: none; border: none; cursor: pointer;
|
|
83695
|
+
color: #64748b; font-size: 12px; font-weight: 500;
|
|
83696
|
+
padding: 10px 14px; border-bottom: 2px solid transparent;
|
|
83697
|
+
white-space: nowrap; display: flex; align-items: center; gap: 6px;
|
|
83698
|
+
transition: color 0.15s, border-color 0.15s;
|
|
83699
|
+
}
|
|
83700
|
+
.um-tab-btn:hover { color: #94a3b8; }
|
|
83701
|
+
.um-tab-btn--active { color: #60a5fa; border-bottom-color: #60a5fa; }
|
|
83702
|
+
.um-tab-close {
|
|
83703
|
+
font-size: 9px; color: #475569; padding: 2px 3px;
|
|
83704
|
+
border-radius: 3px; line-height: 1; cursor: pointer;
|
|
83705
|
+
transition: background 0.15s, color 0.15s;
|
|
83706
|
+
}
|
|
83707
|
+
.um-tab-close:hover { background: #3a4160; color: #e2e8f0; }
|
|
83708
|
+
|
|
83709
|
+
/* Content */
|
|
83710
|
+
.um-modal-content {
|
|
83711
|
+
flex: 1; overflow-y: auto; padding: 20px;
|
|
83712
|
+
scrollbar-width: thin; scrollbar-color: #2a3352 transparent;
|
|
83713
|
+
}
|
|
83714
|
+
.um-modal-content::-webkit-scrollbar { width: 6px; }
|
|
83715
|
+
.um-modal-content::-webkit-scrollbar-thumb { background: #2a3352; border-radius: 3px; }
|
|
83716
|
+
|
|
83717
|
+
/* Toast */
|
|
83718
|
+
.um-toast {
|
|
83719
|
+
position: absolute; bottom: 16px; right: 16px;
|
|
83720
|
+
background: #1e3a2e; border: 1px solid #22c55e;
|
|
83721
|
+
color: #4ade80; font-size: 12px; font-weight: 500;
|
|
83722
|
+
padding: 10px 16px; border-radius: 8px;
|
|
83723
|
+
opacity: 0; transform: translateY(8px);
|
|
83724
|
+
transition: opacity 0.2s, transform 0.2s;
|
|
83725
|
+
pointer-events: none; z-index: 10;
|
|
83726
|
+
max-width: 300px;
|
|
83727
|
+
}
|
|
83728
|
+
.um-toast--error { background: #3b1a1a; border-color: #ef4444; color: #f87171; }
|
|
83729
|
+
.um-toast--visible { opacity: 1; transform: translateY(0); }
|
|
83730
|
+
|
|
83731
|
+
/* Tab content */
|
|
83732
|
+
.um-tab-content { min-height: 100%; }
|
|
83733
|
+
|
|
83734
|
+
/* Toolbar */
|
|
83735
|
+
.um-list-toolbar {
|
|
83736
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
83737
|
+
gap: 12px; margin-bottom: 16px;
|
|
83738
|
+
}
|
|
83739
|
+
.um-search-wrap {
|
|
83740
|
+
position: relative; flex: 1; max-width: 360px;
|
|
83741
|
+
}
|
|
83742
|
+
.um-search-icon {
|
|
83743
|
+
position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
|
|
83744
|
+
color: #475569; pointer-events: none;
|
|
83745
|
+
}
|
|
83746
|
+
.um-search-input {
|
|
83747
|
+
width: 100%; background: #1a2235; border: 1px solid #2a3352;
|
|
83748
|
+
border-radius: 8px; padding: 7px 12px 7px 32px;
|
|
83749
|
+
color: #e2e8f0; font-size: 13px; outline: none;
|
|
83750
|
+
transition: border-color 0.15s;
|
|
83751
|
+
}
|
|
83752
|
+
.um-search-input:focus { border-color: #3b5bdb; }
|
|
83753
|
+
.um-search-input::placeholder { color: #475569; }
|
|
83754
|
+
|
|
83755
|
+
/* Table */
|
|
83756
|
+
.um-table-wrap { position: relative; overflow-x: auto; }
|
|
83757
|
+
.um-table {
|
|
83758
|
+
width: 100%; border-collapse: collapse; font-size: 13px;
|
|
83759
|
+
}
|
|
83760
|
+
.um-table th {
|
|
83761
|
+
text-align: left; padding: 8px 12px;
|
|
83762
|
+
color: #64748b; font-weight: 500; font-size: 11px;
|
|
83763
|
+
text-transform: uppercase; letter-spacing: 0.04em;
|
|
83764
|
+
border-bottom: 1px solid #1e2d4a;
|
|
83765
|
+
}
|
|
83766
|
+
.um-table td {
|
|
83767
|
+
padding: 10px 12px; color: #c4ccd9;
|
|
83768
|
+
border-bottom: 1px solid #0f1829;
|
|
83769
|
+
}
|
|
83770
|
+
.um-table tr:hover td { background: #0f1829; }
|
|
83771
|
+
.um-col-actions { width: 80px; text-align: center; }
|
|
83772
|
+
.um-row--highlight td { background: #0e2a1e !important; }
|
|
83773
|
+
|
|
83774
|
+
/* List states */
|
|
83775
|
+
.um-list-empty, .um-list-loading, .um-profiles-loading,
|
|
83776
|
+
.um-profiles-empty, .um-profiles-error {
|
|
83777
|
+
text-align: center; padding: 32px 16px; color: #475569; font-size: 13px;
|
|
83778
|
+
}
|
|
83779
|
+
|
|
83780
|
+
/* Pagination */
|
|
83781
|
+
.um-pagination {
|
|
83782
|
+
display: flex; align-items: center; justify-content: center;
|
|
83783
|
+
gap: 16px; margin-top: 16px; font-size: 12px; color: #64748b;
|
|
83784
|
+
}
|
|
83785
|
+
|
|
83786
|
+
/* Icon buttons */
|
|
83787
|
+
.um-icon-btn {
|
|
83788
|
+
background: none; border: none; cursor: pointer;
|
|
83789
|
+
color: #475569; padding: 5px; border-radius: 6px;
|
|
83790
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
83791
|
+
transition: color 0.15s, background 0.15s;
|
|
83792
|
+
}
|
|
83793
|
+
.um-icon-btn:hover { color: #60a5fa; background: #1a2a45; }
|
|
83794
|
+
|
|
83795
|
+
/* Buttons */
|
|
83796
|
+
.um-btn {
|
|
83797
|
+
border: none; cursor: pointer; font-size: 13px; font-weight: 500;
|
|
83798
|
+
padding: 8px 16px; border-radius: 8px; transition: opacity 0.15s, background 0.15s;
|
|
83799
|
+
}
|
|
83800
|
+
.um-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
83801
|
+
.um-btn--primary { background: #3b5bdb; color: #fff; }
|
|
83802
|
+
.um-btn--primary:hover:not(:disabled) { background: #4c6ef5; }
|
|
83803
|
+
.um-btn--secondary { background: #1e2d4a; color: #93c5fd; border: 1px solid #2a3b60; }
|
|
83804
|
+
.um-btn--secondary:hover:not(:disabled) { background: #253456; }
|
|
83805
|
+
.um-btn--ghost { background: transparent; color: #64748b; border: 1px solid #2a3352; }
|
|
83806
|
+
.um-btn--ghost:hover:not(:disabled) { background: #1a2235; color: #94a3b8; }
|
|
83807
|
+
.um-btn--danger { background: #7f1d1d; color: #fca5a5; border: 1px solid #991b1b; }
|
|
83808
|
+
.um-btn--danger:hover:not(:disabled) { background: #991b1b; }
|
|
83809
|
+
.um-btn--sm { padding: 6px 12px; font-size: 12px; }
|
|
83810
|
+
|
|
83811
|
+
/* Badges */
|
|
83812
|
+
.um-badge {
|
|
83813
|
+
display: inline-block; font-size: 10px; font-weight: 600;
|
|
83814
|
+
padding: 2px 8px; border-radius: 9999px;
|
|
83815
|
+
}
|
|
83816
|
+
.um-badge--admin { background: #1e3a5f; color: #60a5fa; }
|
|
83817
|
+
.um-badge--user { background: #1a2d24; color: #4ade80; }
|
|
83818
|
+
|
|
83819
|
+
/* Forms */
|
|
83820
|
+
.um-form { display: flex; flex-direction: column; gap: 14px; max-width: 560px; }
|
|
83821
|
+
.um-form-row { display: flex; gap: 12px; }
|
|
83822
|
+
.um-form-row .um-form-group { flex: 1; }
|
|
83823
|
+
.um-form-group { display: flex; flex-direction: column; gap: 5px; }
|
|
83824
|
+
.um-form-group--check { flex-direction: row; align-items: center; }
|
|
83825
|
+
.um-label { font-size: 12px; font-weight: 500; color: #94a3b8; }
|
|
83826
|
+
.um-req { color: #f87171; }
|
|
83827
|
+
.um-input {
|
|
83828
|
+
background: #1a2235; border: 1px solid #2a3352;
|
|
83829
|
+
border-radius: 8px; padding: 8px 12px;
|
|
83830
|
+
color: #e2e8f0; font-size: 13px; outline: none; width: 100%;
|
|
83831
|
+
transition: border-color 0.15s; box-sizing: border-box;
|
|
83832
|
+
}
|
|
83833
|
+
.um-input:focus { border-color: #3b5bdb; }
|
|
83834
|
+
.um-textarea { resize: vertical; min-height: 64px; }
|
|
83835
|
+
.um-field-error { font-size: 11px; color: #f87171; min-height: 14px; }
|
|
83836
|
+
.um-check-label { font-size: 13px; color: #94a3b8; cursor: pointer; display: flex; align-items: center; gap: 8px; }
|
|
83837
|
+
.um-form-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 8px; }
|
|
83838
|
+
|
|
83839
|
+
/* Detail card */
|
|
83840
|
+
.um-detail-card { max-width: 560px; }
|
|
83841
|
+
.um-detail-section { border: 1px solid #1e2d4a; border-radius: 10px; overflow: hidden; margin-bottom: 20px; }
|
|
83842
|
+
.um-detail-row {
|
|
83843
|
+
display: flex; align-items: flex-start; gap: 16px;
|
|
83844
|
+
padding: 11px 16px; border-bottom: 1px solid #0f1829;
|
|
83845
|
+
}
|
|
83846
|
+
.um-detail-row:last-child { border-bottom: none; }
|
|
83847
|
+
.um-detail-label { width: 100px; font-size: 12px; color: #475569; font-weight: 500; flex-shrink: 0; padding-top: 1px; }
|
|
83848
|
+
.um-detail-value { font-size: 13px; color: #c4ccd9; flex: 1; }
|
|
83849
|
+
.um-detail-actions { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
83850
|
+
|
|
83851
|
+
/* Profiles */
|
|
83852
|
+
.um-profiles-header { margin-bottom: 16px; }
|
|
83853
|
+
.um-profiles-title { font-size: 14px; font-weight: 600; color: #c4ccd9; margin: 0; }
|
|
83854
|
+
.um-profiles-notice {
|
|
83855
|
+
display: flex; align-items: center; gap: 8px;
|
|
83856
|
+
margin-top: 16px; padding: 10px 14px;
|
|
83857
|
+
background: #1a2537; border: 1px solid #2a3b60; border-radius: 8px;
|
|
83858
|
+
font-size: 12px; color: #64748b;
|
|
83859
|
+
}
|
|
83860
|
+
|
|
83861
|
+
/* Spinner */
|
|
83862
|
+
.um-spinner {
|
|
83863
|
+
display: inline-block; width: 14px; height: 14px;
|
|
83864
|
+
border: 2px solid #2a3352; border-top-color: #60a5fa;
|
|
83865
|
+
border-radius: 50%; animation: um-spin 0.7s linear infinite;
|
|
83866
|
+
}
|
|
83867
|
+
@keyframes um-spin { to { transform: rotate(360deg); } }
|
|
83868
|
+
`;
|
|
83869
|
+
document.head.appendChild(style);
|
|
83870
|
+
}
|
|
83871
|
+
esc(s) {
|
|
83872
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
83873
|
+
}
|
|
83874
|
+
};
|
|
83875
|
+
|
|
83876
|
+
// src/components/premium-modals/user-management/UserManagementController.ts
|
|
83877
|
+
var UserManagementController = class {
|
|
83878
|
+
view;
|
|
83879
|
+
constructor(params) {
|
|
83880
|
+
const config = {
|
|
83881
|
+
customerId: params.customerId,
|
|
83882
|
+
tenantId: params.tenantId,
|
|
83883
|
+
customerName: params.customerName || "",
|
|
83884
|
+
jwtToken: params.jwtToken,
|
|
83885
|
+
tbBaseUrl: params.tbBaseUrl.replace(/\/$/, ""),
|
|
83886
|
+
currentUser: params.currentUser,
|
|
83887
|
+
onClose: () => {
|
|
83888
|
+
}
|
|
83889
|
+
};
|
|
83890
|
+
this.view = new UserManagementModalView(config);
|
|
83891
|
+
}
|
|
83892
|
+
show() {
|
|
83893
|
+
this.view.render();
|
|
83894
|
+
}
|
|
83895
|
+
};
|
|
83896
|
+
|
|
83897
|
+
// src/components/premium-modals/user-management/openUserManagementModal.ts
|
|
83898
|
+
function openUserManagementModal(params) {
|
|
83899
|
+
if (!params.jwtToken) {
|
|
83900
|
+
console.warn("[openUserManagementModal] jwtToken is required");
|
|
83901
|
+
return;
|
|
83902
|
+
}
|
|
83903
|
+
if (!params.customerId) {
|
|
83904
|
+
console.warn("[openUserManagementModal] customerId is required");
|
|
83905
|
+
return;
|
|
83906
|
+
}
|
|
83907
|
+
const controller = new UserManagementController(params);
|
|
83908
|
+
controller.show();
|
|
83909
|
+
}
|
|
83910
|
+
|
|
82416
83911
|
// src/components/menu/types.ts
|
|
82417
83912
|
var DEFAULT_LIGHT_THEME3 = {
|
|
82418
83913
|
// Tabs
|
|
@@ -125426,8 +126921,8 @@ async function openGCDRSyncModal(params) {
|
|
|
125426
126921
|
|
|
125427
126922
|
// src/components/premium-modals/alarm-bundle-map/openAlarmBundleMapModal.ts
|
|
125428
126923
|
var MODAL_ID3 = "myio-alarm-bundle-map-modal";
|
|
125429
|
-
var
|
|
125430
|
-
var
|
|
126924
|
+
var GCDR_INTEGRATION_API_KEY = "gcdr_cust_tb_integration_key_2026";
|
|
126925
|
+
var GCDR_DEFAULT_BASE_URL = "https://gcdr-api.a.myio-bas.com";
|
|
125431
126926
|
var TEAL2 = "#0a6d5e";
|
|
125432
126927
|
var TEAL_DARK2 = "#084f44";
|
|
125433
126928
|
var STYLES3 = `
|
|
@@ -125726,7 +127221,7 @@ async function fetchBundle(customerTB_ID, gcdrTenantId, baseUrl) {
|
|
|
125726
127221
|
const response = await fetch(url, {
|
|
125727
127222
|
method: "GET",
|
|
125728
127223
|
headers: {
|
|
125729
|
-
"X-API-Key":
|
|
127224
|
+
"X-API-Key": GCDR_INTEGRATION_API_KEY,
|
|
125730
127225
|
"X-Tenant-ID": gcdrTenantId,
|
|
125731
127226
|
"Accept": "application/json"
|
|
125732
127227
|
}
|
|
@@ -125757,7 +127252,7 @@ async function openAlarmBundleMapModal(params) {
|
|
|
125757
127252
|
alert("GCDR Tenant ID n\xE3o encontrado. Configure o atributo gcdrTenantId no cliente ThingsBoard.");
|
|
125758
127253
|
return;
|
|
125759
127254
|
}
|
|
125760
|
-
const baseUrl = params.gcdrApiBaseUrl ||
|
|
127255
|
+
const baseUrl = params.gcdrApiBaseUrl || GCDR_DEFAULT_BASE_URL;
|
|
125761
127256
|
const overlay = document.createElement("div");
|
|
125762
127257
|
overlay.id = MODAL_ID3;
|
|
125763
127258
|
const card = document.createElement("div");
|
|
@@ -127378,6 +128873,7 @@ var version = package_default.version || "0.0.0";
|
|
|
127378
128873
|
openTemperatureSettingsModal,
|
|
127379
128874
|
openTutorialModal,
|
|
127380
128875
|
openUpsellModal,
|
|
128876
|
+
openUserManagementModal,
|
|
127381
128877
|
openWelcomeModal,
|
|
127382
128878
|
parseInputDateToDate,
|
|
127383
128879
|
periodKey,
|