myio-js-library 0.1.556 → 0.1.557

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -908,7 +908,7 @@ module.exports = __toCommonJS(src_exports);
908
908
  // package.json
909
909
  var package_default = {
910
910
  name: "myio-js-library",
911
- version: "0.1.556",
911
+ version: "0.1.557",
912
912
  description: "A clean, standalone JS SDK for MYIO projects",
913
913
  license: "MIT",
914
914
  repository: "github:gh-myio/myio-js-library",
@@ -53080,6 +53080,444 @@ function escapeHtml4(text2) {
53080
53080
  return div.innerHTML;
53081
53081
  }
53082
53082
 
53083
+ // src/services/alarm/AlarmApiClient.ts
53084
+ var AlarmApiClient = class {
53085
+ baseUrl = "";
53086
+ apiKey = "";
53087
+ configure(baseUrl, apiKey) {
53088
+ if (baseUrl) this.baseUrl = baseUrl;
53089
+ if (apiKey) this.apiKey = apiKey;
53090
+ }
53091
+ get headers() {
53092
+ return {
53093
+ "Content-Type": "application/json",
53094
+ "X-API-Key": this.apiKey
53095
+ };
53096
+ }
53097
+ async request(url, options) {
53098
+ const response = await fetch(url, {
53099
+ method: "GET",
53100
+ headers: this.headers,
53101
+ ...options
53102
+ });
53103
+ if (!response.ok) {
53104
+ throw new Error(`Alarms API error: ${response.status} ${response.statusText}`);
53105
+ }
53106
+ return response.json();
53107
+ }
53108
+ // Some endpoints return { success: true, data: T[] } — unwrap safely
53109
+ unwrapArray(raw) {
53110
+ if (Array.isArray(raw)) return raw;
53111
+ if (raw !== null && typeof raw === "object" && "data" in raw) {
53112
+ const d = raw.data;
53113
+ return Array.isArray(d) ? d : [];
53114
+ }
53115
+ return [];
53116
+ }
53117
+ // -----------------------------------------------------------------------
53118
+ // Availability (existing endpoint)
53119
+ // -----------------------------------------------------------------------
53120
+ async getAvailability(params) {
53121
+ const query = new URLSearchParams({
53122
+ customerId: params.customerId,
53123
+ startAt: params.startAt,
53124
+ endAt: params.endAt,
53125
+ includeByDevice: String(params.includeByDevice ?? true)
53126
+ });
53127
+ if (params.deviceType) query.set("deviceType", params.deviceType);
53128
+ if (params.deviceIds) query.set("deviceIds", params.deviceIds);
53129
+ const raw = await this.request(
53130
+ `${this.baseUrl}/alarms/stats/availability?${query.toString()}`
53131
+ );
53132
+ return raw?.data ?? raw;
53133
+ }
53134
+ // -----------------------------------------------------------------------
53135
+ // Alarm list (GET /alarms)
53136
+ // -----------------------------------------------------------------------
53137
+ async getAlarms(params = {}) {
53138
+ const query = new URLSearchParams();
53139
+ if (params.state?.length) query.set("state", params.state.join(","));
53140
+ if (params.severity?.length) query.set("severity", params.severity.join(","));
53141
+ if (params.alarmType) query.set("alarmType", params.alarmType);
53142
+ if (params.from) query.set("from", params.from);
53143
+ if (params.to) query.set("to", params.to);
53144
+ if (params.customerId) query.set("customerId", params.customerId);
53145
+ query.set("limit", String(params.limit ?? 100));
53146
+ if (params.page && params.page > 1) query.set("page", String(params.page));
53147
+ const qs = query.toString();
53148
+ return this.request(
53149
+ `${this.baseUrl}/alarms${qs ? `?${qs}` : ""}`
53150
+ );
53151
+ }
53152
+ // -----------------------------------------------------------------------
53153
+ // Stats (GET /api/v1/alarms/stats)
53154
+ // -----------------------------------------------------------------------
53155
+ async getAlarmStats(tenantId, period) {
53156
+ const query = new URLSearchParams({ tenantId, period });
53157
+ const raw = await this.request(
53158
+ `${this.baseUrl}/alarms/stats?${query.toString()}`
53159
+ );
53160
+ return raw?.data ?? raw;
53161
+ }
53162
+ // -----------------------------------------------------------------------
53163
+ // Trend (GET /api/v1/alarms/stats/trend)
53164
+ // -----------------------------------------------------------------------
53165
+ async getAlarmTrend(tenantId, period, groupBy) {
53166
+ const query = new URLSearchParams({ tenantId, period, groupBy });
53167
+ const raw = await this.request(
53168
+ `${this.baseUrl}/alarms/stats/trend?${query.toString()}`
53169
+ );
53170
+ return this.unwrapArray(raw);
53171
+ }
53172
+ // -----------------------------------------------------------------------
53173
+ // Top offenders (GET /api/v1/alarms/stats/top-offenders)
53174
+ // -----------------------------------------------------------------------
53175
+ async getTopOffenders(tenantId, limit = 5) {
53176
+ const query = new URLSearchParams({ tenantId, limit: String(limit) });
53177
+ const raw = await this.request(
53178
+ `${this.baseUrl}/alarms/stats/top-offenders?${query.toString()}`
53179
+ );
53180
+ return this.unwrapArray(raw);
53181
+ }
53182
+ // -----------------------------------------------------------------------
53183
+ // Per-device stats (GET /api/v1/alarms/stats/by-device)
53184
+ // -----------------------------------------------------------------------
53185
+ async getAlarmStatsByDevice(tenantId) {
53186
+ const query = new URLSearchParams({ tenantId });
53187
+ const raw = await this.request(
53188
+ `${this.baseUrl}/alarms/stats/by-device?${query.toString()}`
53189
+ );
53190
+ return this.unwrapArray(raw);
53191
+ }
53192
+ // -----------------------------------------------------------------------
53193
+ // Alarm history (GET /alarms/:id/history)
53194
+ // -----------------------------------------------------------------------
53195
+ async getAlarmHistory(id) {
53196
+ const raw = await this.request(`${this.baseUrl}/alarms/${encodeURIComponent(id)}/history`);
53197
+ return this.unwrapArray(raw);
53198
+ }
53199
+ // -----------------------------------------------------------------------
53200
+ // Alarm actions (POST /alarms/:id/...)
53201
+ // -----------------------------------------------------------------------
53202
+ async acknowledgeAlarm(id, acknowledgedBy, note) {
53203
+ await this.request(`${this.baseUrl}/alarms/${id}/ack`, {
53204
+ method: "POST",
53205
+ body: JSON.stringify({ acknowledgedBy, note })
53206
+ });
53207
+ }
53208
+ async silenceAlarm(id, silencedBy, duration, reason) {
53209
+ await this.request(`${this.baseUrl}/alarms/${id}/silence`, {
53210
+ method: "POST",
53211
+ body: JSON.stringify({ silencedBy, duration, reason })
53212
+ });
53213
+ }
53214
+ async escalateAlarm(id, escalatedBy, reason) {
53215
+ await this.request(`${this.baseUrl}/alarms/${id}/escalate`, {
53216
+ method: "POST",
53217
+ body: JSON.stringify({ escalatedBy, reason })
53218
+ });
53219
+ }
53220
+ async closeAlarm(id, closedBy, resolution) {
53221
+ await this.request(`${this.baseUrl}/alarms/${id}/close`, {
53222
+ method: "POST",
53223
+ body: JSON.stringify({ closedBy, resolution })
53224
+ });
53225
+ }
53226
+ // -----------------------------------------------------------------------
53227
+ // Batch alarm actions (POST /alarms/batch/*)
53228
+ // Up to 100 IDs per call; response contains succeeded/failed arrays.
53229
+ // -----------------------------------------------------------------------
53230
+ async batchAcknowledge(alarmIds, acknowledgedBy) {
53231
+ const res = await this.request(
53232
+ `${this.baseUrl}/alarms/batch/ack`,
53233
+ { method: "POST", body: JSON.stringify({ alarmIds, acknowledgedBy }) }
53234
+ );
53235
+ return res.data;
53236
+ }
53237
+ async batchSilence(alarmIds, silencedBy, duration) {
53238
+ const res = await this.request(
53239
+ `${this.baseUrl}/alarms/batch/silence`,
53240
+ { method: "POST", body: JSON.stringify({ alarmIds, silencedBy, duration }) }
53241
+ );
53242
+ return res.data;
53243
+ }
53244
+ async batchEscalate(alarmIds, escalatedBy) {
53245
+ const res = await this.request(
53246
+ `${this.baseUrl}/alarms/batch/escalate`,
53247
+ { method: "POST", body: JSON.stringify({ alarmIds, escalatedBy }) }
53248
+ );
53249
+ return res.data;
53250
+ }
53251
+ async batchClose(alarmIds, closedBy, resolution) {
53252
+ const res = await this.request(
53253
+ `${this.baseUrl}/alarms/batch/close`,
53254
+ { method: "POST", body: JSON.stringify({ alarmIds, closedBy, resolution }) }
53255
+ );
53256
+ return res.data;
53257
+ }
53258
+ };
53259
+
53260
+ // src/services/alarm/AlarmService.ts
53261
+ function isFresh(entry, ttlMs) {
53262
+ return !!entry && Date.now() - entry.timestamp < ttlMs;
53263
+ }
53264
+ function mapApiAlarm(api, customerMap) {
53265
+ return {
53266
+ id: api.id,
53267
+ customerId: api.customerId ?? api.tenantId,
53268
+ customerName: customerMap?.get(api.customerId ?? "") ?? "",
53269
+ source: api.deviceName || api.deviceId,
53270
+ severity: api.severity,
53271
+ state: api.state,
53272
+ title: api.title,
53273
+ description: api.description ?? "",
53274
+ tags: {
53275
+ alarmType: api.alarmType,
53276
+ deviceType: api.deviceType
53277
+ },
53278
+ firstOccurrence: api.raisedAt || api.raised_at || api.createdAt || void 0,
53279
+ lastOccurrence: api.lastUpdatedAt || api.updatedAt || api.updated_at || void 0,
53280
+ occurrenceCount: api.occurrenceCount ?? api.dispatchCount ?? 1,
53281
+ acknowledgedAt: api.acknowledgedAt || void 0,
53282
+ acknowledgedBy: api.acknowledgedBy,
53283
+ snoozedUntil: api.silencedUntil || api.snoozedUntil || void 0,
53284
+ closedAt: api.closedAt || void 0,
53285
+ closedBy: api.closedBy,
53286
+ closedReason: api.resolution,
53287
+ triggerValue: api.metadata?.value,
53288
+ centralId: api.centralId || void 0,
53289
+ deviceId: api.deviceId || void 0
53290
+ };
53291
+ }
53292
+ function mapApiStats(api) {
53293
+ return {
53294
+ total: api.total,
53295
+ bySeverity: {
53296
+ CRITICAL: api.bySeverity?.CRITICAL ?? 0,
53297
+ HIGH: api.bySeverity?.HIGH ?? 0,
53298
+ MEDIUM: api.bySeverity?.MEDIUM ?? 0,
53299
+ LOW: api.bySeverity?.LOW ?? 0,
53300
+ INFO: api.bySeverity?.INFO ?? 0
53301
+ },
53302
+ byState: {
53303
+ OPEN: api.byState?.OPEN ?? 0,
53304
+ ACK: api.byState?.ACK ?? 0,
53305
+ SNOOZED: api.byState?.SNOOZED ?? 0,
53306
+ ESCALATED: api.byState?.ESCALATED ?? 0,
53307
+ CLOSED: api.byState?.CLOSED ?? 0
53308
+ },
53309
+ openCritical: api.openCritical,
53310
+ openHigh: api.openHigh,
53311
+ last24Hours: api.last24Hours
53312
+ };
53313
+ }
53314
+ function mapApiTrend(apiTrend) {
53315
+ return (apiTrend || []).map((point) => ({
53316
+ label: point.label || point.period,
53317
+ timestamp: point.timestamp || new Date(point.period).getTime() || 0,
53318
+ total: point.total ?? point.count,
53319
+ bySeverity: point.bySeverity ? {
53320
+ CRITICAL: point.bySeverity.CRITICAL ?? 0,
53321
+ HIGH: point.bySeverity.HIGH ?? 0,
53322
+ MEDIUM: point.bySeverity.MEDIUM ?? 0,
53323
+ LOW: point.bySeverity.LOW ?? 0,
53324
+ INFO: point.bySeverity.INFO ?? 0
53325
+ } : void 0
53326
+ }));
53327
+ }
53328
+ function mapApiTopOffenders(items, deviceNameMap) {
53329
+ return (items || []).map((item) => ({
53330
+ name: deviceNameMap.get(item.deviceId) ?? item.deviceName ?? item.deviceId,
53331
+ location: item.customerName ?? "",
53332
+ downtime: item.downtimeHours ?? (item.totalDowntimeMinutes ?? 0) / 60,
53333
+ percentage: 0
53334
+ // computed by caller if needed
53335
+ }));
53336
+ }
53337
+ var AlarmServiceClass = class {
53338
+ client = new AlarmApiClient();
53339
+ cacheTtlMs = 3 * 60 * 1e3;
53340
+ // 3 min default; overridable via configure()
53341
+ availabilityCache = /* @__PURE__ */ new Map();
53342
+ alarmsCache = /* @__PURE__ */ new Map();
53343
+ statsCache = /* @__PURE__ */ new Map();
53344
+ trendCache = /* @__PURE__ */ new Map();
53345
+ // -----------------------------------------------------------------------
53346
+ // Availability (MTBF / MTTR per device)
53347
+ // -----------------------------------------------------------------------
53348
+ /**
53349
+ * Fetches MTBF, MTTR, and Availability metrics per device and fleet-wide.
53350
+ * Results are cached for 60 seconds.
53351
+ */
53352
+ async getAvailability(customerId, startAt, endAt) {
53353
+ if (!customerId) {
53354
+ throw new Error("AlarmService.getAvailability: customerId is required");
53355
+ }
53356
+ const cacheKey = `avail:${customerId}:${startAt}:${endAt}`;
53357
+ const cached = this.availabilityCache.get(cacheKey);
53358
+ if (isFresh(cached, this.cacheTtlMs)) return cached.data;
53359
+ const data = await this.client.getAvailability({
53360
+ customerId,
53361
+ startAt,
53362
+ endAt,
53363
+ includeByDevice: true
53364
+ });
53365
+ this.availabilityCache.set(cacheKey, { data, timestamp: Date.now() });
53366
+ return data;
53367
+ }
53368
+ // -----------------------------------------------------------------------
53369
+ // Alarm list
53370
+ // -----------------------------------------------------------------------
53371
+ /**
53372
+ * Fetch alarm list with optional filters, auto-paginating until hasMore is false.
53373
+ * Returns { data: Alarm[], summary: AlarmListSummary } with all pages merged.
53374
+ * The summary comes from page 1 (reflects full dataset totals).
53375
+ */
53376
+ async getAlarms(params = {}, customerMap) {
53377
+ const cacheKey = `alarms:${JSON.stringify(params)}`;
53378
+ const cached = this.alarmsCache.get(cacheKey);
53379
+ if (isFresh(cached, this.cacheTtlMs)) return cached.data;
53380
+ let allData = [];
53381
+ let summary;
53382
+ let totalPages = 1;
53383
+ let page = 1;
53384
+ while (page <= totalPages) {
53385
+ const response = await this.client.getAlarms({ ...params, page });
53386
+ const pageData = (response.data || []).map((a) => mapApiAlarm(a, customerMap));
53387
+ allData = allData.concat(pageData);
53388
+ if (page === 1) {
53389
+ summary = response.summary;
53390
+ totalPages = response.pagination?.totalPages ?? 1;
53391
+ }
53392
+ page++;
53393
+ }
53394
+ const result = { data: allData, summary };
53395
+ this.alarmsCache.set(cacheKey, { data: result, timestamp: Date.now() });
53396
+ return result;
53397
+ }
53398
+ // -----------------------------------------------------------------------
53399
+ // Alarm statistics
53400
+ // -----------------------------------------------------------------------
53401
+ /**
53402
+ * Fetch aggregated alarm statistics for a tenant and time period.
53403
+ * Returns mapped AlarmStats (from src/types/alarm.ts).
53404
+ */
53405
+ async getAlarmStats(tenantId, period) {
53406
+ const cacheKey = `stats:${tenantId}:${period}`;
53407
+ const cached = this.statsCache.get(cacheKey);
53408
+ if (isFresh(cached, this.cacheTtlMs)) return cached.data;
53409
+ const raw = await this.client.getAlarmStats(tenantId, period);
53410
+ const data = mapApiStats(raw);
53411
+ this.statsCache.set(cacheKey, { data, timestamp: Date.now() });
53412
+ return data;
53413
+ }
53414
+ // -----------------------------------------------------------------------
53415
+ // Alarm trend
53416
+ // -----------------------------------------------------------------------
53417
+ /**
53418
+ * Fetch alarm trend data points.
53419
+ * Returns mapped AlarmTrendDataPoint[] (from src/types/alarm.ts).
53420
+ */
53421
+ async getAlarmTrend(tenantId, period, groupBy) {
53422
+ const cacheKey = `trend:${tenantId}:${period}:${groupBy}`;
53423
+ const cached = this.trendCache.get(cacheKey);
53424
+ if (isFresh(cached, this.cacheTtlMs)) return cached.data;
53425
+ const raw = await this.client.getAlarmTrend(tenantId, period, groupBy);
53426
+ const data = mapApiTrend(raw);
53427
+ this.trendCache.set(cacheKey, { data, timestamp: Date.now() });
53428
+ return data;
53429
+ }
53430
+ // -----------------------------------------------------------------------
53431
+ // Top downtime / offenders
53432
+ // -----------------------------------------------------------------------
53433
+ /**
53434
+ * Fetch top devices by alarm count / downtime.
53435
+ * Returns mapped DowntimeEntry[] (from src/types/operational.ts).
53436
+ *
53437
+ * @param deviceNameMap - Optional map of deviceId → friendly name from ThingsBoard cache
53438
+ */
53439
+ async getTopDowntime(tenantId, deviceNameMap = /* @__PURE__ */ new Map(), limit = 5) {
53440
+ const items = await this.client.getTopOffenders(tenantId, limit);
53441
+ return mapApiTopOffenders(items, deviceNameMap);
53442
+ }
53443
+ // -----------------------------------------------------------------------
53444
+ // Per-device alarm counts
53445
+ // -----------------------------------------------------------------------
53446
+ /**
53447
+ * Fetch alarm counts per device.
53448
+ * Returns a Map<deviceId, alarmCount>.
53449
+ */
53450
+ async getDeviceAlarmCounts(tenantId) {
53451
+ const items = await this.client.getAlarmStatsByDevice(tenantId);
53452
+ const map = /* @__PURE__ */ new Map();
53453
+ (items || []).forEach((item) => map.set(item.deviceId, item.alarmCount));
53454
+ return map;
53455
+ }
53456
+ /**
53457
+ * Histórico de transições do alarme (abertura/reabertura/fechamento/ack…).
53458
+ * Falha de rede/API devolve [] — o detalhe do alarme degrada para só a abertura.
53459
+ */
53460
+ async getAlarmHistory(id) {
53461
+ try {
53462
+ return await this.client.getAlarmHistory(id);
53463
+ } catch {
53464
+ return [];
53465
+ }
53466
+ }
53467
+ // -----------------------------------------------------------------------
53468
+ // Alarm actions
53469
+ // -----------------------------------------------------------------------
53470
+ async acknowledgeAlarm(id, userEmail, note) {
53471
+ return this.client.acknowledgeAlarm(id, userEmail, note);
53472
+ }
53473
+ async silenceAlarm(id, userEmail, duration, reason) {
53474
+ return this.client.silenceAlarm(id, userEmail, duration, reason);
53475
+ }
53476
+ async escalateAlarm(id, userEmail, reason) {
53477
+ return this.client.escalateAlarm(id, userEmail, reason);
53478
+ }
53479
+ async closeAlarm(id, userEmail, resolution) {
53480
+ return this.client.closeAlarm(id, userEmail, resolution);
53481
+ }
53482
+ // -----------------------------------------------------------------------
53483
+ // Batch alarm actions (POST /alarms/batch/*)
53484
+ // -----------------------------------------------------------------------
53485
+ async batchAcknowledge(alarmIds, userEmail) {
53486
+ return this.client.batchAcknowledge(alarmIds, userEmail);
53487
+ }
53488
+ async batchSilence(alarmIds, userEmail, duration) {
53489
+ return this.client.batchSilence(alarmIds, userEmail, duration);
53490
+ }
53491
+ async batchEscalate(alarmIds, userEmail) {
53492
+ return this.client.batchEscalate(alarmIds, userEmail);
53493
+ }
53494
+ async batchClose(alarmIds, userEmail, resolution) {
53495
+ return this.client.batchClose(alarmIds, userEmail, resolution);
53496
+ }
53497
+ // -----------------------------------------------------------------------
53498
+ // Configuration
53499
+ // -----------------------------------------------------------------------
53500
+ /**
53501
+ * Set the Alarms API base URL (e.g. from MAIN_VIEW orchestrator settings).
53502
+ * Clears the cache so subsequent calls use the new endpoint.
53503
+ */
53504
+ configure(baseUrl, cacheTtlMs, apiKey) {
53505
+ this.client.configure(baseUrl ?? "", apiKey);
53506
+ if (cacheTtlMs && cacheTtlMs > 0) this.cacheTtlMs = cacheTtlMs;
53507
+ this.clearCache();
53508
+ }
53509
+ // -----------------------------------------------------------------------
53510
+ // Cache management
53511
+ // -----------------------------------------------------------------------
53512
+ clearCache() {
53513
+ this.availabilityCache.clear();
53514
+ this.alarmsCache.clear();
53515
+ this.statsCache.clear();
53516
+ this.trendCache.clear();
53517
+ }
53518
+ };
53519
+ var AlarmService = new AlarmServiceClass();
53520
+
53083
53521
  // src/components/AlarmsNotificationsPanel/AlarmDetailsModal.ts
53084
53522
  function fmt(isoString) {
53085
53523
  if (!isoString) return "-";
@@ -53109,112 +53547,155 @@ function escHtml2(s) {
53109
53547
  if (s == null) return "";
53110
53548
  return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
53111
53549
  }
53112
- function seededRand(seed) {
53113
- let s = (seed ^ 3735928559) >>> 0;
53114
- return () => {
53115
- s = Math.imul(s ^ s >>> 16, 73244475);
53116
- s = Math.imul(s ^ s >>> 16, 73244475);
53117
- s = (s ^ s >>> 16) >>> 0;
53118
- return s / 4294967295;
53550
+ var EVENT_LABEL = {
53551
+ raised: "Abertura",
53552
+ reopened: "Reabertura",
53553
+ closed: "Fechamento",
53554
+ acknowledged: "Reconhecido",
53555
+ silenced: "Adiado",
53556
+ escalated: "Escalado",
53557
+ other: "Atualiza\xE7\xE3o"
53558
+ };
53559
+ function classifyHistoryEvent(ev) {
53560
+ const action = String(ev.action || "").toUpperCase();
53561
+ const next = String(ev.newState || "").toUpperCase();
53562
+ const prev = String(ev.previousState || "").toUpperCase();
53563
+ if (next === "OPEN") return prev && prev !== "OPEN" ? prev === "CLOSED" ? "reopened" : "other" : "raised";
53564
+ if (next === "CLOSED" || /CLOSE/.test(action)) return "closed";
53565
+ if (next === "ACK" || next === "ACKNOWLEDGED" || /ACK/.test(action)) return "acknowledged";
53566
+ if (next === "SNOOZED" || next === "SILENCED" || /SILENC|SNOOZ/.test(action)) return "silenced";
53567
+ if (next === "ESCALATED" || /ESCALAT/.test(action)) return "escalated";
53568
+ if (/CREAT|RAISE/.test(action)) return "raised";
53569
+ return "other";
53570
+ }
53571
+ async function loadAlarmEvents(alarm) {
53572
+ const ids = alarm._groupAlarmIds?.length ? alarm._groupAlarmIds : [alarm.id];
53573
+ const aso = window.AlarmServiceOrchestrator?.alarms || [];
53574
+ const byId = new Map(aso.map((a) => [a?.id, a]));
53575
+ const single = ids.length === 1;
53576
+ const events = [];
53577
+ await Promise.all(
53578
+ ids.slice(0, 50).map(async (id) => {
53579
+ const raw = byId.get(id);
53580
+ const device = raw?.deviceName || alarm.source || "";
53581
+ const title = raw?.title || alarm.title;
53582
+ const raisedMs = new Date(raw?.raisedAt || (single ? alarm.firstOccurrence : "") || "").getTime();
53583
+ const hasRaise = Number.isFinite(raisedMs) && raisedMs > 0;
53584
+ if (hasRaise) events.push({ ts: raisedMs, kind: "raised", alarmId: id, device, title, occurrence: true });
53585
+ const hist = await AlarmService.getAlarmHistory(id);
53586
+ for (const ev of hist) {
53587
+ const ts = new Date(ev.timestamp).getTime();
53588
+ if (!Number.isFinite(ts)) continue;
53589
+ const kind = classifyHistoryEvent(ev);
53590
+ if (kind === "raised" && hasRaise && Math.abs(ts - raisedMs) < 6e4) continue;
53591
+ events.push({
53592
+ ts,
53593
+ kind,
53594
+ alarmId: id,
53595
+ device,
53596
+ title,
53597
+ actor: ev.actor,
53598
+ occurrence: kind === "raised" || kind === "reopened"
53599
+ });
53600
+ }
53601
+ })
53602
+ );
53603
+ if (!events.some((e) => e.occurrence)) {
53604
+ const ms = new Date(alarm.firstOccurrence).getTime();
53605
+ if (Number.isFinite(ms) && ms > 0) {
53606
+ events.push({ ts: ms, kind: "raised", alarmId: alarm.id, device: alarm.source, title: alarm.title, occurrence: true });
53607
+ }
53608
+ }
53609
+ return events.sort((a, b) => a.ts - b.ts);
53610
+ }
53611
+ function bucketEvents(occ, devices, period, fromMs, toMs) {
53612
+ const start = new Date(fromMs);
53613
+ if (period === "hora") start.setMinutes(0, 0, 0);
53614
+ else start.setHours(0, 0, 0, 0);
53615
+ if (period === "semana") start.setDate(start.getDate() - start.getDay());
53616
+ if (period === "mes") start.setDate(1);
53617
+ const keys = [];
53618
+ const cursor = new Date(start);
53619
+ while (cursor.getTime() <= toMs && keys.length < 5e3) {
53620
+ keys.push(cursor.getTime());
53621
+ if (period === "hora") cursor.setHours(cursor.getHours() + 1);
53622
+ else if (period === "dia") cursor.setDate(cursor.getDate() + 1);
53623
+ else if (period === "semana") cursor.setDate(cursor.getDate() + 7);
53624
+ else cursor.setMonth(cursor.getMonth() + 1);
53625
+ }
53626
+ const maxBuckets = period === "hora" ? 72 : period === "dia" ? 92 : period === "semana" ? 104 : 36;
53627
+ const ks = keys.slice(-maxBuckets);
53628
+ const months = ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"];
53629
+ const dm = (d) => `${String(d.getDate()).padStart(2, "0")}/${String(d.getMonth() + 1).padStart(2, "0")}`;
53630
+ const label = (ms) => {
53631
+ const d = new Date(ms);
53632
+ if (period === "hora") return `${dm(d)} ${String(d.getHours()).padStart(2, "0")}h`;
53633
+ if (period === "mes") return `${months[d.getMonth()]}/${String(d.getFullYear()).slice(2)}`;
53634
+ return dm(d);
53119
53635
  };
53636
+ const buckets = ks.map((k) => ({ label: label(k), total: 0, byDevice: {} }));
53637
+ for (const e of occ) {
53638
+ let idx = -1;
53639
+ for (let i = ks.length - 1; i >= 0; i--) {
53640
+ if (ks[i] <= e.ts) {
53641
+ idx = i;
53642
+ break;
53643
+ }
53644
+ }
53645
+ if (idx < 0) continue;
53646
+ buckets[idx].total++;
53647
+ const dev = devices.includes(e.device) ? e.device : devices[0] ?? e.device;
53648
+ buckets[idx].byDevice[dev] = (buckets[idx].byDevice[dev] || 0) + 1;
53649
+ }
53650
+ return buckets;
53120
53651
  }
53121
- function hashStr(s) {
53122
- let h3 = 0;
53123
- for (let i = 0; i < s.length; i++) h3 = Math.imul(31, h3) + s.charCodeAt(i) | 0;
53124
- return Math.abs(h3);
53652
+ function dowFromEvents(occ) {
53653
+ const v = new Array(7).fill(0);
53654
+ occ.forEach((e) => v[new Date(e.ts).getDay()]++);
53655
+ return v;
53125
53656
  }
53126
- function generateBuckets(alarm, devices, period = "mes") {
53127
- const count = alarm.occurrenceCount || 1;
53128
- let firstMs = new Date(alarm.firstOccurrence).getTime();
53129
- let lastMs = new Date(alarm.lastOccurrence).getTime();
53130
- if (isNaN(firstMs) || isNaN(lastMs) || firstMs <= 0 || lastMs <= 0) {
53131
- lastMs = Date.now();
53132
- firstMs = lastMs - 7 * 864e5;
53133
- }
53134
- const durMs = Math.max(lastMs - firstMs, 36e5);
53135
- let numBuckets;
53136
- let bucketMs;
53137
- let labelFn;
53138
- const nowMs = Date.now();
53139
- if (period === "hora") {
53140
- bucketMs = 36e5;
53141
- numBuckets = Math.max(2, Math.min(24, Math.ceil(durMs / bucketMs)));
53142
- labelFn = (ms) => `${String(new Date(ms).getHours()).padStart(2, "0")}h`;
53143
- } else if (period === "dia") {
53144
- bucketMs = 864e5;
53145
- numBuckets = Math.max(1, Math.min(31, Math.ceil(durMs / bucketMs)));
53146
- labelFn = (ms) => {
53147
- const d = new Date(ms);
53148
- return `${String(d.getDate()).padStart(2, "0")}/${String(d.getMonth() + 1).padStart(2, "0")}`;
53149
- };
53150
- } else if (period === "semana") {
53151
- bucketMs = 7 * 864e5;
53152
- numBuckets = Math.max(1, Math.min(26, Math.ceil(durMs / bucketMs)));
53153
- labelFn = (ms) => {
53154
- const d = new Date(ms);
53155
- return `${String(d.getDate()).padStart(2, "0")}/${String(d.getMonth() + 1).padStart(2, "0")}`;
53156
- };
53157
- } else {
53158
- bucketMs = 30 * 864e5;
53159
- numBuckets = Math.max(1, Math.min(24, Math.ceil(durMs / bucketMs)));
53160
- const months = ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"];
53161
- labelFn = (ms) => months[new Date(ms).getMonth()];
53162
- }
53163
- while (numBuckets > 1 && firstMs + (numBuckets - 1) * bucketMs > nowMs) numBuckets--;
53164
- const seed = hashStr(alarm.id + period);
53165
- const rand = seededRand(seed);
53166
- const weights = Array.from({ length: numBuckets }, (_, i) => {
53167
- const center = (numBuckets - 1) / 2;
53168
- const bell = 1 - 0.45 * Math.abs(i - center) / (center || 1);
53169
- return Math.max(0.1, bell * (0.6 + rand() * 0.8));
53170
- });
53171
- const wSum = weights.reduce((a, b) => a + b, 0);
53172
- const totals = new Array(numBuckets).fill(0);
53173
- let remaining = count;
53174
- for (let i = 0; i < numBuckets - 1; i++) {
53175
- const v = Math.round(weights[i] / wSum * count);
53176
- totals[i] = Math.min(v, remaining);
53177
- remaining -= totals[i];
53178
- }
53179
- totals[numBuckets - 1] = Math.max(0, remaining);
53180
- return Array.from({ length: numBuckets }, (_, i) => {
53181
- const ts = firstMs + i * bucketMs;
53182
- const total = totals[i];
53183
- const byDevice = {};
53184
- if (devices.length === 1) {
53185
- byDevice[devices[0]] = total;
53186
- } else {
53187
- let rem = total;
53188
- for (let d = 0; d < devices.length - 1; d++) {
53189
- const share = Math.round(rand() * rem * (1 / (devices.length - d)));
53190
- byDevice[devices[d]] = share;
53191
- rem -= share;
53657
+ function hodFromEvents(occ) {
53658
+ const v = new Array(24).fill(0);
53659
+ occ.forEach((e) => v[new Date(e.ts).getHours()]++);
53660
+ return v;
53661
+ }
53662
+ function resolveDeviceIdentity(alarm, src) {
53663
+ const orch = window.MyIOOrchestrator;
53664
+ const nameToLabel = orch?.entityNameToLabelMap;
53665
+ const gcdrToLabel = orch?.gcdrDeviceNameMap;
53666
+ let label = null;
53667
+ if (alarm.deviceId && parseDevices(alarm.source).length <= 1) label = gcdrToLabel?.get?.(alarm.deviceId) || null;
53668
+ if (!label && nameToLabel?.has?.(src)) label = nameToLabel.get(src) || null;
53669
+ if (!label) label = src;
53670
+ let name = nameToLabel?.has?.(src) ? src : null;
53671
+ if (!name && nameToLabel) {
53672
+ for (const [n, l] of nameToLabel) {
53673
+ if (l === label) {
53674
+ name = n;
53675
+ break;
53192
53676
  }
53193
- byDevice[devices[devices.length - 1]] = Math.max(0, rem);
53194
53677
  }
53195
- return { label: labelFn(ts), total, byDevice };
53196
- });
53678
+ }
53679
+ return { label, name: name && name !== label ? name : null };
53197
53680
  }
53198
- function generateDowDistribution(alarm) {
53199
- const count = alarm.occurrenceCount || 1;
53200
- const rand = seededRand(hashStr(alarm.id + "dow"));
53201
- const weights = [0.2, 1, 0.95, 0.9, 0.85, 0.8, 0.3];
53202
- const wSum = weights.reduce((a, b) => a + b, 0);
53203
- const vals = weights.map((w) => Math.round(w / wSum * count * (0.7 + rand() * 0.6)));
53204
- return vals;
53681
+ function resolveDeviceLabel(alarm, src) {
53682
+ return resolveDeviceIdentity(alarm, src).label;
53205
53683
  }
53206
- function generateHodDistribution(alarm) {
53207
- const count = alarm.occurrenceCount || 1;
53208
- const rand = seededRand(hashStr(alarm.id + "hod"));
53209
- const weights = Array.from({ length: 24 }, (_, h3) => {
53210
- if (h3 < 6) return 0.05 + rand() * 0.05;
53211
- if (h3 < 10) return 0.3 + rand() * 0.3;
53212
- if (h3 <= 19) return 0.7 + rand() * 0.6;
53213
- if (h3 <= 22) return 0.3 + rand() * 0.3;
53214
- return 0.05 + rand() * 0.05;
53215
- });
53216
- const wSum = weights.reduce((a, b) => a + b, 0);
53217
- return weights.map((w) => Math.max(0, Math.round(w / wSum * count)));
53684
+ function brandColor(fallback = "#7c3aed") {
53685
+ const theme = window.MyIOUtils?.theme;
53686
+ const vars = typeof theme?.cssVars === "function" ? theme.cssVars() : theme;
53687
+ const c = vars?.["--myio-brand-700"];
53688
+ return typeof c === "string" && /^#[0-9a-f]{6}$/i.test(c.trim()) ? c.trim() : fallback;
53689
+ }
53690
+ var COPY_SVG = '<svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>';
53691
+ function deviceIdentityHtml(alarm, src) {
53692
+ const { label, name } = resolveDeviceIdentity(alarm, src);
53693
+ const copyValue = name || src;
53694
+ return `<span class="adm-dev-id">
53695
+ <span class="adm-dev-label">${escHtml2(label)}</span>
53696
+ ${name ? `<span class="adm-dev-name">${escHtml2(name)}</span>` : ""}
53697
+ <button type="button" class="adm-copy-btn" data-copy="${escHtml2(copyValue)}" title="Copiar nome do dispositivo">${COPY_SVG}</button>
53698
+ </span>`;
53218
53699
  }
53219
53700
  var DEV_COLORS = [
53220
53701
  "#7c3aed",
@@ -53304,8 +53785,8 @@ function createDowChart(canvas, vals) {
53304
53785
  datasets: [{
53305
53786
  label: "Ocorr\xEAncias",
53306
53787
  data: vals,
53307
- backgroundColor: "#8b5cf6bb",
53308
- borderColor: "#7c3aed",
53788
+ backgroundColor: brandColor("#8b5cf6") + "bb",
53789
+ borderColor: brandColor(),
53309
53790
  borderWidth: 1,
53310
53791
  borderRadius: 3
53311
53792
  }]
@@ -53347,8 +53828,8 @@ function createHodChart(canvas, vals) {
53347
53828
  datasets: [{
53348
53829
  label: "Ocorr\xEAncias",
53349
53830
  data: vals,
53350
- backgroundColor: "#3b82f6bb",
53351
- borderColor: "#2563eb",
53831
+ backgroundColor: brandColor("#3b82f6") + "bb",
53832
+ borderColor: brandColor("#2563eb"),
53352
53833
  borderWidth: 1,
53353
53834
  borderRadius: 2
53354
53835
  }]
@@ -53409,12 +53890,12 @@ var HIDE_ANNOTATIONS_TAB = true;
53409
53890
  function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction) {
53410
53891
  const sev = SEVERITY_CONFIG[alarm.severity];
53411
53892
  const st = STATE_CONFIG[alarm.state];
53412
- const count = alarm.occurrenceCount || 1;
53413
53893
  const firstMs = new Date(alarm.firstOccurrence).getTime();
53414
53894
  const lastMs = new Date(alarm.lastOccurrence).getTime();
53415
- const durMs = isNaN(firstMs) || isNaN(lastMs) ? 0 : Math.max(0, lastMs - firstMs);
53416
- const durLabel = durationLabel(durMs);
53417
- const avgFreq = count > 1 ? durationLabel(durMs / (count - 1)) + " entre ocorr\xEAncias" : "Evento \xFAnico";
53895
+ const isClosedState = alarm.state === "CLOSED";
53896
+ const closedMs = alarm.closedAt ? new Date(alarm.closedAt).getTime() : NaN;
53897
+ const ageMs = isNaN(firstMs) ? NaN : isClosedState ? (Number.isFinite(closedMs) ? closedMs : lastMs) - firstMs : Date.now() - firstMs;
53898
+ const ageKpiLabel = isClosedState ? "Dura\xE7\xE3o" : "Aberto h\xE1";
53418
53899
  const devices = parseDevices(alarm.source);
53419
53900
  const deviceCount = devices.length;
53420
53901
  const row = (label, value) => value ? `<div class="adm-row"><span class="adm-row-label">${label}</span><span class="adm-row-value">${escHtml2(value)}</span></div>` : "";
@@ -53423,48 +53904,30 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53423
53904
  <div class="adm-device-row">
53424
53905
  <span class="adm-device-row-index">${i + 1}</span>
53425
53906
  <div class="adm-device-icon-sm">
53426
- <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#7c3aed" stroke-width="2">
53907
+ <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2">
53427
53908
  <rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/>
53428
53909
  </svg>
53429
53910
  </div>
53430
- <span class="adm-device-row-name">${escHtml2(dev)}</span>
53911
+ <span class="adm-device-row-name">${deviceIdentityHtml(alarm, dev)}</span>
53431
53912
  <span class="adm-device-row-sub">${escHtml2(alarm.customerName || "-")}</span>
53432
53913
  </div>`
53433
53914
  ).join("");
53434
- const initialPeriod = durMs < 864e5 ? "hora" : durMs < 7 * 864e5 ? "dia" : durMs < 30 * 864e5 ? "semana" : "mes";
53435
- const dowVals = generateDowDistribution(alarm);
53436
- const hodVals = generateHodDistribution(alarm);
53915
+ const spanMs = Number.isFinite(ageMs) ? ageMs : 0;
53916
+ const initialPeriod = spanMs < 3 * 864e5 ? "hora" : spanMs < 45 * 864e5 ? "dia" : spanMs < 180 * 864e5 ? "semana" : "mes";
53437
53917
  const chartSingleColor = sev.text || "#7c3aed";
53918
+ let events = null;
53919
+ const occEvents = () => (events || []).filter((e) => e.occurrence);
53920
+ const onEventsReady = [];
53438
53921
  const annotCount = getActiveAnnotationCount(alarm.id);
53439
- const sourceDisplay = deviceCount > 1 ? `${deviceCount} dispositivos` : escHtml2(alarm.source);
53922
+ const sourceDisplay = deviceCount > 1 ? `${deviceCount} dispositivos` : escHtml2(resolveDeviceLabel(alarm, alarm.source) || alarm.source);
53440
53923
  const overlay = document.createElement("div");
53441
53924
  overlay.className = "adm-overlay";
53442
53925
  overlay.setAttribute("data-theme", themeMode);
53443
53926
  overlay.innerHTML = `
53444
53927
  <div class="adm-drawer" role="dialog" aria-modal="true" aria-label="Detalhes do alarme">
53445
53928
 
53446
- <!-- \u2500\u2500 Header \u2500\u2500 -->
53929
+ <!-- \u2500\u2500 Header (t\xEDtulo/maximizar/fechar v\xEAm da moldura padr\xE3o; aqui s\xF3 os badges) \u2500\u2500 -->
53447
53930
  <div class="adm-header">
53448
- <div class="adm-header-top">
53449
- <div class="adm-title" title="${escHtml2(alarm.title)}">${escHtml2(alarm.title)}</div>
53450
- <div class="adm-header-actions">
53451
- <button class="adm-header-btn" id="admMaximize" title="Maximizar">
53452
- <svg class="adm-icon-expand" viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
53453
- <polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/>
53454
- <line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/>
53455
- </svg>
53456
- <svg class="adm-icon-compress" viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none">
53457
- <polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/>
53458
- <line x1="10" y1="14" x2="3" y2="21"/><line x1="21" y1="3" x2="14" y2="10"/>
53459
- </svg>
53460
- </button>
53461
- <button class="adm-header-btn adm-close" id="admClose" title="Fechar (Esc)">
53462
- <svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2.5">
53463
- <path d="M18 6L6 18M6 6l12 12"/>
53464
- </svg>
53465
- </button>
53466
- </div>
53467
- </div>
53468
53931
  <div class="adm-badges">
53469
53932
  <span class="adm-badge-severity" style="background:${sev.bg};color:${sev.text}">${sev.icon} ${sev.label}</span>
53470
53933
  <span class="adm-badge-state" style="color:${st.color}">${st.label}</span>
@@ -53480,7 +53943,7 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53480
53943
  </button>
53481
53944
  <button class="adm-tab" data-panel="timeline">
53482
53945
  <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="4.22" y1="4.22" x2="7.05" y2="7.05"/><line x1="16.95" y1="16.95" x2="19.78" y2="19.78"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/><line x1="4.22" y1="19.78" x2="7.05" y2="16.95"/><line x1="16.95" y1="7.05" x2="19.78" y2="4.22"/></svg>
53483
- Timeline <span class="adm-tab-badge">${count}</span>
53946
+ Timeline <span class="adm-tab-badge" id="admTlTabBadge">\u2026</span>
53484
53947
  </button>
53485
53948
  <button class="adm-tab" data-panel="dispositivos">
53486
53949
  <svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor"><path d="M20 3H4a1 1 0 00-1 1v12a1 1 0 001 1h8v2H8v2h8v-2h-4v-2h8a1 1 0 001-1V4a1 1 0 00-1-1zm-1 12H5V5h14v10z"/></svg>
@@ -53502,17 +53965,17 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53502
53965
  <!-- RESUMO -->
53503
53966
  <div class="adm-panel is-active" data-panel="resumo">
53504
53967
  ${groupMode !== "separado" ? `<div class="adm-kpi-grid">
53505
- <div class="adm-kpi">
53506
- <div class="adm-kpi-value" style="color:${sev.text}">${count}</div>
53968
+ <div class="adm-kpi" title="Abertura + reaberturas registradas no hist\xF3rico do alarme">
53969
+ <div class="adm-kpi-value" id="admKpiOcc" style="color:${sev.text}">\u2026</div>
53507
53970
  <div class="adm-kpi-label">Ocorr\xEAncias</div>
53508
53971
  </div>
53509
53972
  <div class="adm-kpi">
53510
53973
  <div class="adm-kpi-value">${deviceCount}</div>
53511
53974
  <div class="adm-kpi-label">Dispositivos</div>
53512
53975
  </div>
53513
- <div class="adm-kpi">
53514
- <div class="adm-kpi-value">${durLabel}</div>
53515
- <div class="adm-kpi-label">Dura\xE7\xE3o</div>
53976
+ <div class="adm-kpi" title="${isClosedState ? "Da abertura ao fechamento" : "Desde a abertura do alarme at\xE9 agora"}">
53977
+ <div class="adm-kpi-value">${Number.isFinite(ageMs) ? durationLabel(ageMs) : "-"}</div>
53978
+ <div class="adm-kpi-label">${ageKpiLabel}</div>
53516
53979
  </div>
53517
53980
  </div>` : ""}
53518
53981
 
@@ -53529,15 +53992,16 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53529
53992
  <div class="adm-section">
53530
53993
  <div class="adm-section-title">Identifica\xE7\xE3o</div>
53531
53994
  ${row("Shopping", alarm.customerName)}
53532
- ${row("Dispositivo(s)", alarm.source)}
53995
+ ${devices.length ? `<div class="adm-row"><span class="adm-row-label">Dispositivo(s)</span><span class="adm-row-value adm-row-value--devices">${devices.map((d) => deviceIdentityHtml(alarm, d)).join("")}</span></div>` : ""}
53533
53996
  ${alarm.triggerValue != null ? row("Valor do disparo", String(alarm.triggerValue)) : ""}
53534
53997
  ${groupMode === "separado" ? row("ID", alarm.id) : ""}
53535
53998
  </div>
53536
53999
 
53537
54000
  <div class="adm-section">
53538
54001
  <div class="adm-section-title">Datas</div>
53539
- ${row("Primeira ocorr\xEAncia", fmt(alarm.firstOccurrence))}
53540
- ${row("\xDAltima ocorr\xEAncia", fmt(alarm.lastOccurrence))}
54002
+ ${row("Aberto em", fmt(alarm.firstOccurrence))}
54003
+ <div id="admReopenRows"></div>
54004
+ ${row("\xDAltima atualiza\xE7\xE3o", fmt(alarm.lastOccurrence))}
53541
54005
  ${alarm.acknowledgedAt ? row("Reconhecido em", fmt(alarm.acknowledgedAt)) : ""}
53542
54006
  ${alarm.acknowledgedBy ? row("Reconhecido por", alarm.acknowledgedBy) : ""}
53543
54007
  ${alarm.snoozedUntil ? row("Adiado at\xE9", fmt(alarm.snoozedUntil)) : ""}
@@ -53562,7 +54026,7 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53562
54026
  <div class="adm-section-title">IDs de Alarme (${alarm._groupAlarmIds.length})</div>
53563
54027
  <div class="adm-alarm-ids-list" style="display:flex;flex-wrap:wrap;gap:6px;">
53564
54028
  ${alarm._groupAlarmIds.map(
53565
- (rawId) => `<span class="adm-alarm-id-chip" title="Clique para copiar" data-copy-id="${escHtml2(rawId)}" style="display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid rgba(124,58,237,0.25);background:rgba(124,58,237,0.08);border-radius:6px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:#4c1d95;cursor:pointer;user-select:all;">${escHtml2(rawId)}</span>`
54029
+ (rawId) => `<span class="adm-alarm-id-chip" title="Clique para copiar" data-copy-id="${escHtml2(rawId)}" style="display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid color-mix(in srgb, var(--myio-brand-700, #7c3aed) 25%, transparent);background:color-mix(in srgb, var(--myio-brand-700, #7c3aed) 8%, transparent);border-radius:6px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--myio-brand-700, #4c1d95);cursor:pointer;user-select:all;">${escHtml2(rawId)}</span>`
53566
54030
  ).join("")}
53567
54031
  </div>
53568
54032
  </div>` : ""}
@@ -53584,7 +54048,7 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53584
54048
  <div class="adm-panel" data-panel="timeline">
53585
54049
  <div class="adm-tl-header" id="admTlHeader">
53586
54050
  <div class="adm-tl-toolbar">
53587
- <span class="adm-tl-summary" id="admTlSummary">${count} ocorr\xEAncia${count !== 1 ? "s" : ""}</span>
54051
+ <span class="adm-tl-summary" id="admTlSummary">Carregando hist\xF3rico\u2026</span>
53588
54052
  <button class="adm-tl-batch-toggle" id="admTlBatchToggle" title="A\xE7\xF5es em lote">
53589
54053
  <svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="4" height="4" rx="1"/><path d="M10 7h11"/><rect x="3" y="12" width="4" height="4" rx="1"/><path d="M10 14h11"/><rect x="3" y="19" width="4" height="4" rx="1"/><path d="M10 21h11"/></svg>
53590
54054
  A\xE7\xE3o em lote
@@ -53610,7 +54074,7 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53610
54074
  <label class="adm-report-label">De</label>
53611
54075
  <input type="date" class="adm-report-date-input" id="admRptStart" value="${fmtDateInput(firstMs)}">
53612
54076
  <label class="adm-report-label">At\xE9</label>
53613
- <input type="date" class="adm-report-date-input" id="admRptEnd" value="${fmtDateInput(lastMs)}">
54077
+ <input type="date" class="adm-report-date-input" id="admRptEnd" value="${fmtDateInput(Date.now())}">
53614
54078
  <button class="adm-report-emit-btn" id="admRptEmit">
53615
54079
  <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>
53616
54080
  Carregar
@@ -53661,7 +54125,8 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53661
54125
  </div>
53662
54126
 
53663
54127
  <div class="adm-section">
53664
- <div class="adm-section-title">Tend\xEAncia de ocorr\xEAncias no per\xEDodo</div>
54128
+ <div class="adm-section-title">Ocorr\xEAncias no per\xEDodo</div>
54129
+ <div class="adm-chart-note" id="admChartNote">Carregando hist\xF3rico\u2026</div>
53665
54130
  <div class="adm-chart-wrapper">
53666
54131
  <canvas id="admMainChart" class="adm-chart-canvas"></canvas>
53667
54132
  </div>
@@ -53719,21 +54184,45 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53719
54184
  let activeChartType = "bar";
53720
54185
  let activeVizMode = "total";
53721
54186
  let activePeriod = initialPeriod;
54187
+ const noteEl = graficoPanel.querySelector("#admChartNote");
54188
+ const updateNote = () => {
54189
+ if (!noteEl) return;
54190
+ if (!events) {
54191
+ noteEl.textContent = "Carregando hist\xF3rico\u2026";
54192
+ return;
54193
+ }
54194
+ const n = occEvents().length;
54195
+ noteEl.textContent = `${n} ocorr\xEAncia${n !== 1 ? "s" : ""} registrada${n !== 1 ? "s" : ""} (abertura + reaberturas do hist\xF3rico do alarme).` + (n < 3 ? " Poucos eventos para indicar tend\xEAncia." : "");
54196
+ };
53722
54197
  const buildMain = () => {
53723
- const nb = generateBuckets(alarm, devices, activePeriod);
54198
+ updateNote();
54199
+ if (!events) return;
53724
54200
  const canvas = graficoPanel.querySelector("#admMainChart");
53725
54201
  if (!canvas) return;
54202
+ const occ = occEvents();
54203
+ const fromMs = occ.length ? occ[0].ts : Number.isFinite(firstMs) ? firstMs : Date.now();
54204
+ const nb = bucketEvents(occ, devices, activePeriod, fromMs, Date.now());
53726
54205
  if (chartInstances[0]) {
53727
54206
  chartInstances[0].destroy();
53728
54207
  chartInstances[0] = null;
53729
54208
  }
53730
54209
  chartInstances[0] = createMainChart(canvas, nb, devices, activeChartType, activeVizMode, chartSingleColor);
53731
54210
  };
53732
- const initSecondary = () => {
54211
+ const buildSecondary = () => {
54212
+ if (!events) return;
54213
+ const occ = occEvents();
53733
54214
  const dowCanvas = graficoPanel.querySelector("#admDowChart");
53734
- if (dowCanvas && !chartInstances[1]) chartInstances[1] = createDowChart(dowCanvas, dowVals);
54215
+ if (chartInstances[1]) {
54216
+ chartInstances[1].destroy();
54217
+ chartInstances[1] = null;
54218
+ }
54219
+ if (dowCanvas) chartInstances[1] = createDowChart(dowCanvas, dowFromEvents(occ));
53735
54220
  const hodCanvas = graficoPanel.querySelector("#admHodChart");
53736
- if (hodCanvas && !chartInstances[2]) chartInstances[2] = createHodChart(hodCanvas, hodVals);
54221
+ if (chartInstances[2]) {
54222
+ chartInstances[2].destroy();
54223
+ chartInstances[2] = null;
54224
+ }
54225
+ if (hodCanvas) chartInstances[2] = createHodChart(hodCanvas, hodFromEvents(occ));
53737
54226
  };
53738
54227
  graficoPanel.querySelectorAll("button[data-chart-type]").forEach((btn) => {
53739
54228
  btn.addEventListener("click", () => {
@@ -53759,11 +54248,16 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53759
54248
  buildMain();
53760
54249
  });
53761
54250
  });
54251
+ const renderCharts = () => {
54252
+ buildMain();
54253
+ buildSecondary();
54254
+ };
53762
54255
  overlay.querySelectorAll('.adm-tab[data-panel="grafico"]').forEach((tabBtn) => {
53763
- tabBtn.addEventListener("click", () => setTimeout(() => {
53764
- buildMain();
53765
- initSecondary();
53766
- }, 30));
54256
+ tabBtn.addEventListener("click", () => setTimeout(renderCharts, 30));
54257
+ });
54258
+ onEventsReady.push(() => {
54259
+ if (graficoPanel.classList.contains("is-active")) renderCharts();
54260
+ else updateNote();
53767
54261
  });
53768
54262
  }
53769
54263
  const rptPanel = overlay.querySelector('.adm-panel[data-panel="timeline"]');
@@ -53779,159 +54273,126 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53779
54273
  const _svgAck = `<svg viewBox="0 0 24 24" width="11" height="11" fill="currentColor" aria-hidden="true"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>`;
53780
54274
  const _svgSnooze = `<svg viewBox="0 0 24 24" width="11" height="11" fill="currentColor" aria-hidden="true"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67V7z"/></svg>`;
53781
54275
  const _svgEscalate = `<svg viewBox="0 0 24 24" width="11" height="11" fill="currentColor" aria-hidden="true"><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>`;
53782
- const buildOccurrenceSection = (occFirst, occLast) => {
53783
- const intervalMs = count > 1 ? (occLast - occFirst) / (count - 1) : 0;
53784
- const trigStr = alarm.triggerValue != null ? String(alarm.triggerValue) : null;
53785
- const hasTrigger = trigStr != null;
54276
+ const buildOccurrenceSection = (startMs, endMs) => {
54277
+ if (!events) {
54278
+ return { html: '<div class="adm-report-empty-hint">Carregando hist\xF3rico do alarme\u2026</div>', csvRows: [] };
54279
+ }
53786
54280
  const showDevice = !isSeparado && devices.length > 0;
54281
+ const showAlarm = groupMode === "porDispositivo" && (alarm._groupAlarmIds?.length || 0) > 1;
53787
54282
  const isActive = alarm.state === "OPEN" || alarm.state === "ESCALATED";
53788
54283
  const isClosed = alarm.state === "CLOSED";
53789
- const idxStyle = (isSingle, isLatest, isFirst) => {
53790
- if (isSingle) return "color:#7c3aed;font-weight:700";
53791
- if (isLatest) return "color:#3b82f6;font-weight:700";
53792
- if (isFirst) return "color:#ef4444;font-weight:700";
53793
- return "";
53794
- };
53795
- const csvHeader = ["#", "Data/Hora"];
53796
- if (showDevice) csvHeader.push("Dispositivo");
53797
- if (hasTrigger) csvHeader.push("Trigger");
54284
+ const occIndex = /* @__PURE__ */ new Map();
54285
+ occEvents().forEach((e, i) => occIndex.set(e, i + 1));
54286
+ const inRange = events.filter((e) => e.ts >= startMs && e.ts <= endMs).sort((a, b) => b.ts - a.ts || (occIndex.get(b) || 0) - (occIndex.get(a) || 0));
54287
+ const csvHeader = ["#", "Data/Hora", "Evento"];
54288
+ if (showAlarm) csvHeader.push("Alarme");
54289
+ if (showDevice) csvHeader.push("Dispositivo", "Name (TB)");
54290
+ csvHeader.push("Por");
53798
54291
  const rows = [csvHeader];
53799
- const tableRows = [];
53800
- if (groupMode === "porDispositivo" && alarm._alarmTypeGroups?.length) {
53801
- const groups = alarm._alarmTypeGroups;
53802
- const groupHtml = groups.map((grp, gi) => {
53803
- const gCount = grp.occurrenceCount || 1;
53804
- const gFirstMs = new Date(grp.firstOccurrence).getTime();
53805
- const gLastMs = new Date(grp.lastOccurrence).getTime();
53806
- const gInterval = gCount > 1 ? (gLastMs - gFirstMs) / (gCount - 1) : 0;
53807
- const gTrig = grp.triggerValue != null ? String(grp.triggerValue) : trigStr;
53808
- const gHasTrig = gTrig != null;
53809
- const gThead = `<thead><tr>
53810
- <th class="adm-rpt-th adm-rpt-th--idx">#</th>
53811
- <th class="adm-rpt-th">Data/Hora</th>
53812
- ${gHasTrig ? `<th class="adm-rpt-th adm-rpt-th--num">Trigger</th>` : ""}
53813
- </tr></thead>`;
53814
- const gRows = [];
53815
- const MAX = 10;
53816
- for (let i = gCount; i >= 1; i--) {
53817
- const tsMs = gFirstMs + gInterval * (i - 1);
53818
- const isSingle = gCount === 1;
53819
- const isLatest = !isSingle && i === gCount;
53820
- const isFirst = !isSingle && i === 1;
53821
- const isEst = !isSingle && !isLatest && !isFirst;
53822
- const timeStr = (isEst ? "~" : "") + fmt(tsMs);
53823
- const style = idxStyle(isSingle, isLatest, isFirst);
53824
- gRows.push(`<tr>
53825
- <td class="adm-rpt-cell adm-rpt-cell--idx"${style ? ` style="${style}"` : ""}>#${i}</td>
53826
- <td class="adm-rpt-cell">${timeStr}</td>
53827
- ${gHasTrig ? `<td class="adm-rpt-cell adm-rpt-cell--num">${escHtml2(gTrig)}</td>` : ""}
53828
- </tr>`);
53829
- const csvRow = [`#${i}`, fmt(tsMs)];
53830
- if (gHasTrig) csvRow.push(gTrig);
53831
- rows.push(csvRow);
53832
- if (gRows.length >= MAX) break;
53833
- }
53834
- const sepStyle = gi > 0 ? ' style="margin-top:12px;padding-top:12px;border-top:1px dashed #e5e7eb"' : "";
53835
- return `<div${sepStyle}>
53836
- <div class="adm-timeline-group-header">
53837
- <label class="adm-tl-cb-wrap" data-cb-wrap><input type="checkbox" class="adm-tl-row-cb" data-item-id="${alarm.id}__grp${gi}"></label>
53838
- <span class="adm-tl-group-title">${escHtml2(grp.title)}</span>
53839
- <span class="adm-timeline-group-count">${gCount}</span>
53840
- <div class="adm-tl-row-btns adm-tl-row-btns--group" data-row-btns>
53841
- ${isActive ? `<button class="adm-tl-row-btn adm-tl-row-btn--ack" data-action="acknowledge" data-item-id="${alarm.id}" title="Reconhecer">${_svgAck}</button>` : ""}
53842
- ${!isClosed ? `<button class="adm-tl-row-btn adm-tl-row-btn--snooze" data-action="snooze" data-item-id="${alarm.id}" title="Adiar">${_svgSnooze}</button>` : ""}
53843
- ${!isClosed ? `<button class="adm-tl-row-btn adm-tl-row-btn--escalate" data-action="escalate" data-item-id="${alarm.id}" title="Escalar">${_svgEscalate}</button>` : ""}
53844
- </div>
53845
- </div>
53846
- <div class="adm-rpt-table-wrap" style="margin-bottom:0">
53847
- <table class="adm-rpt-table">${gThead}<tbody>${gRows.join("")}</tbody></table>
53848
- </div>
53849
- </div>`;
53850
- }).join("");
53851
- return { html: groupHtml, csvRows: rows };
53852
- }
53853
- const deviceTokens = alarm.source ? alarm.source.split(/[,;]+/).map((s) => s.trim()).filter(Boolean) : [];
54292
+ const kindStyle = {
54293
+ raised: "color:#ef4444;font-weight:700",
54294
+ reopened: "color:#d97706;font-weight:700",
54295
+ closed: "color:#16a34a;font-weight:700",
54296
+ acknowledged: "color:var(--myio-brand-700, #7c3aed);font-weight:600",
54297
+ silenced: "color:#6b7280;font-weight:600",
54298
+ escalated: "color:#dc2626;font-weight:600",
54299
+ other: "color:#6b7280"
54300
+ };
53854
54301
  const thead = `<thead><tr>
53855
54302
  <th class="adm-rpt-th adm-rpt-th--idx">#</th>
53856
54303
  <th class="adm-rpt-th">Data/Hora</th>
54304
+ <th class="adm-rpt-th">Evento</th>
54305
+ ${showAlarm ? `<th class="adm-rpt-th">Alarme</th>` : ""}
53857
54306
  ${showDevice ? `<th class="adm-rpt-th">Dispositivo</th>` : ""}
53858
- ${hasTrigger ? `<th class="adm-rpt-th adm-rpt-th--num">Trigger</th>` : ""}
54307
+ <th class="adm-rpt-th">Por</th>
53859
54308
  <th class="adm-rpt-th adm-tl-actions-th">A\xE7\xF5es</th>
53860
54309
  </tr></thead>`;
53861
- const MAX_SHOWN = 30;
53862
- for (let i = count; i >= 1; i--) {
53863
- const tsMs = occFirst + intervalMs * (i - 1);
53864
- const isSingle = count === 1;
53865
- const isLatest = !isSingle && i === count;
53866
- const isFirst = !isSingle && i === 1;
53867
- const isEst = !isSingle && !isLatest && !isFirst;
53868
- const timeStr = (isEst ? "~" : "") + fmt(tsMs);
53869
- const device = showDevice ? deviceTokens[(i - 1) % deviceTokens.length] : "";
53870
- const style = idxStyle(isSingle, isLatest, isFirst);
53871
- tableRows.push(`<tr>
53872
- <td class="adm-rpt-cell adm-rpt-cell--idx"${style ? ` style="${style}"` : ""}>#${i}</td>
53873
- <td class="adm-rpt-cell">${timeStr}</td>
53874
- ${showDevice ? `<td class="adm-rpt-cell adm-rpt-cell--dev">${escHtml2(device)}</td>` : ""}
53875
- ${hasTrigger ? `<td class="adm-rpt-cell adm-rpt-cell--num">${escHtml2(trigStr)}</td>` : ""}
54310
+ const MAX_SHOWN = 200;
54311
+ const tableRows = inRange.slice(0, MAX_SHOWN).map((e, ri) => {
54312
+ const n = occIndex.get(e);
54313
+ const ident = showDevice ? resolveDeviceIdentity(alarm, e.device) : null;
54314
+ const actor = e.kind === "raised" ? "regra" : e.actor === "system" ? "sistema" : e.actor || "-";
54315
+ const csvRow = [n ? `#${n}` : "", fmt(e.ts), EVENT_LABEL[e.kind]];
54316
+ if (showAlarm) csvRow.push(e.title);
54317
+ if (showDevice) csvRow.push(ident.label, ident.name || e.device);
54318
+ csvRow.push(actor);
54319
+ rows.push(csvRow);
54320
+ const itemId = `${e.alarmId}__ev${ri}`;
54321
+ return `<tr>
54322
+ <td class="adm-rpt-cell adm-rpt-cell--idx">${n ? `#${n}` : ""}</td>
54323
+ <td class="adm-rpt-cell">${fmt(e.ts)}</td>
54324
+ <td class="adm-rpt-cell" style="${kindStyle[e.kind]}">${EVENT_LABEL[e.kind]}</td>
54325
+ ${showAlarm ? `<td class="adm-rpt-cell">${escHtml2(e.title)}</td>` : ""}
54326
+ ${showDevice ? `<td class="adm-rpt-cell adm-rpt-cell--dev" title="${escHtml2(ident.name || e.device)}">${escHtml2(ident.label)}</td>` : ""}
54327
+ <td class="adm-rpt-cell">${escHtml2(actor)}</td>
53876
54328
  <td class="adm-rpt-cell adm-tl-action-cell">
53877
- <label class="adm-tl-cb-wrap" data-cb-wrap><input type="checkbox" class="adm-tl-row-cb" data-item-id="${alarm.id}__row${i}"></label>
54329
+ ${e.occurrence ? `<label class="adm-tl-cb-wrap" data-cb-wrap><input type="checkbox" class="adm-tl-row-cb" data-item-id="${escHtml2(itemId)}"></label>
53878
54330
  <div class="adm-tl-row-btns" data-row-btns>
53879
- ${isActive ? `<button class="adm-tl-row-btn adm-tl-row-btn--ack" data-action="acknowledge" data-item-id="${alarm.id}" title="Reconhecer">${_svgAck}</button>` : ""}
53880
- ${!isClosed ? `<button class="adm-tl-row-btn adm-tl-row-btn--snooze" data-action="snooze" data-item-id="${alarm.id}" title="Adiar">${_svgSnooze}</button>` : ""}
53881
- ${!isClosed ? `<button class="adm-tl-row-btn adm-tl-row-btn--escalate" data-action="escalate" data-item-id="${alarm.id}" title="Escalar">${_svgEscalate}</button>` : ""}
53882
- </div>
54331
+ ${isActive ? `<button class="adm-tl-row-btn adm-tl-row-btn--ack" data-action="acknowledge" data-item-id="${escHtml2(itemId)}" title="Reconhecer">${_svgAck}</button>` : ""}
54332
+ ${!isClosed ? `<button class="adm-tl-row-btn adm-tl-row-btn--snooze" data-action="snooze" data-item-id="${escHtml2(itemId)}" title="Adiar">${_svgSnooze}</button>` : ""}
54333
+ ${!isClosed ? `<button class="adm-tl-row-btn adm-tl-row-btn--escalate" data-action="escalate" data-item-id="${escHtml2(itemId)}" title="Escalar">${_svgEscalate}</button>` : ""}
54334
+ </div>` : ""}
53883
54335
  </td>
53884
- </tr>`);
53885
- const csvRow = [`#${i}`, fmt(tsMs)];
53886
- if (showDevice) csvRow.push(device);
53887
- if (hasTrigger) csvRow.push(trigStr);
53888
- rows.push(csvRow);
53889
- if (tableRows.length >= MAX_SHOWN) break;
53890
- }
53891
- const html = `<div class="adm-rpt-table-wrap" id="admRptTableWrap" style="max-height:340px;overflow-y:auto">
54336
+ </tr>`;
54337
+ });
54338
+ const occInRange = inRange.filter((e) => e.occurrence).length;
54339
+ const colspan = 4 + (showAlarm ? 1 : 0) + (showDevice ? 1 : 0);
54340
+ const html = inRange.length ? `<div class="adm-rpt-table-wrap" id="admRptTableWrap" style="max-height:340px;overflow-y:auto">
53892
54341
  <table class="adm-rpt-table">
53893
54342
  ${thead}
53894
54343
  <tbody>${tableRows.join("")}</tbody>
53895
54344
  <tfoot><tr>
53896
- <td class="adm-rpt-cell adm-rpt-cell--total" colspan="${showDevice ? hasTrigger ? 3 : 2 : hasTrigger ? 2 : 1}">Total</td>
53897
- <td class="adm-rpt-cell adm-rpt-cell--num adm-rpt-cell--total">${count} ocorr\xEAncia${count !== 1 ? "s" : ""}</td>
54345
+ <td class="adm-rpt-cell adm-rpt-cell--total" colspan="${colspan}">Total: ${occInRange} ocorr\xEAncia${occInRange !== 1 ? "s" : ""} \xB7 ${inRange.length} evento${inRange.length !== 1 ? "s" : ""}</td>
53898
54346
  <td class="adm-rpt-cell adm-rpt-cell--total"></td>
53899
54347
  </tr></tfoot>
53900
54348
  </table>
53901
- </div>`;
54349
+ </div>` : '<div class="adm-report-empty-hint">Nenhum evento no per\xEDodo selecionado.</div>';
53902
54350
  return { html, csvRows: rows };
53903
54351
  };
53904
54352
  const buildReportTable = () => {
53905
54353
  const startInput = rptPanel.querySelector("#admRptStart");
53906
54354
  const endInput = rptPanel.querySelector("#admRptEnd");
53907
- const startMs = startInput?.value ? new Date(startInput.value).getTime() : firstMs;
53908
- const endMs = endInput?.value ? new Date(endInput.value).getTime() + 864e5 - 1 : lastMs;
53909
- const occFirst = Math.max(firstMs, startMs);
53910
- const occLast = Math.min(lastMs, endMs);
54355
+ const toLocalDay = (v) => {
54356
+ const [y, m, d] = v.split("-").map(Number);
54357
+ return new Date(y, (m || 1) - 1, d || 1).getTime();
54358
+ };
54359
+ const startMs = startInput?.value ? toLocalDay(startInput.value) : 0;
54360
+ const endMs = endInput?.value ? toLocalDay(endInput.value) + 864e5 - 1 : Date.now();
53911
54361
  const headerHtml = isSeparado ? `<div class="adm-rpt-device-header">
53912
- <span class="adm-rpt-device-name">${escHtml2(alarm.source)}</span>
54362
+ <span class="adm-rpt-device-name" title="${escHtml2(alarm.source)}">${escHtml2(resolveDeviceLabel(alarm, alarm.source) || alarm.source)}</span>
53913
54363
  <span class="adm-rpt-device-sep">\xB7</span>
53914
54364
  <span class="adm-rpt-alarm-type">${escHtml2(alarm.title)}</span>
53915
54365
  </div>` : "";
53916
- const sectionTitle = groupMode === "porDispositivo" && alarm._alarmTypeGroups?.length ? `${alarm._alarmTypeGroups.length} tipo${alarm._alarmTypeGroups.length !== 1 ? "s" : ""} \xB7 ${count} ocorr\xEAncia${count !== 1 ? "s" : ""} \xB7 ${durLabel} de janela` : `${count} ocorr\xEAncia${count !== 1 ? "s" : ""} \xB7 ${durLabel} de janela`;
53917
- const { html: occHtml, csvRows } = buildOccurrenceSection(occFirst, occLast);
54366
+ const { html: occHtml, csvRows } = buildOccurrenceSection(startMs, endMs);
53918
54367
  lastCsvRows = csvRows;
53919
- const statsHtml = count > 1 || alarm.triggerValue != null ? `<div class="adm-section">
54368
+ const occ = occEvents();
54369
+ const occN = occ.length;
54370
+ const sectionTitle = events ? `${occN} ocorr\xEAncia${occN !== 1 ? "s" : ""} \xB7 ${events.length} evento${events.length !== 1 ? "s" : ""} no hist\xF3rico` : "Hist\xF3rico do alarme";
54371
+ const reopenN = occ.filter((e) => e.kind === "reopened").length;
54372
+ const statsHtml = events && (occN > 1 || alarm.triggerValue != null) ? `<div class="adm-section">
53920
54373
  <div class="adm-section-title">Estat\xEDsticas de recorr\xEAncia</div>
53921
- ${alarm.triggerValue != null ? row("Valor do disparo", String(alarm.triggerValue)) : ""}
53922
- ${count > 1 ? row("Frequ\xEAncia m\xE9dia", avgFreq) : ""}
53923
- ${count > 1 ? row("Janela total", durLabel) : ""}
53924
- ${count > 1 ? row("Primeira ocorr\xEAncia", fmt(alarm.firstOccurrence)) : ""}
53925
- ${count > 1 ? row("\xDAltima ocorr\xEAncia", fmt(alarm.lastOccurrence)) : ""}
54374
+ ${alarm.triggerValue != null ? row("Valor do \xFAltimo disparo", String(alarm.triggerValue)) : ""}
54375
+ ${occN > 1 ? row("Reaberturas", String(reopenN)) : ""}
54376
+ ${occN > 1 ? row("Intervalo m\xE9dio entre ocorr\xEAncias", durationLabel((occ[occN - 1].ts - occ[0].ts) / (occN - 1))) : ""}
54377
+ ${occN > 1 ? row("Primeira ocorr\xEAncia", fmt(occ[0].ts)) : ""}
54378
+ ${occN > 1 ? row("\xDAltima ocorr\xEAncia", fmt(occ[occN - 1].ts)) : ""}
53926
54379
  </div>` : "";
53927
54380
  let summaryHtml = "";
53928
- if (!isSeparado && devices.length > 0) {
53929
- const devRows = devices.map((dev) => `<tr>
53930
- <td class="adm-rpt-cell">${escHtml2(dev)}</td>
53931
- <td class="adm-rpt-cell adm-rpt-cell--num">${count}</td>
53932
- <td class="adm-rpt-cell">${fmt(occFirst)}</td>
53933
- <td class="adm-rpt-cell">${fmt(occLast)}</td>
53934
- </tr>`).join("");
54381
+ if (events && !isSeparado && devices.length > 0) {
54382
+ const byDev = /* @__PURE__ */ new Map();
54383
+ occ.forEach((e) => {
54384
+ const k = devices.includes(e.device) ? e.device : devices[0];
54385
+ byDev.set(k, [...byDev.get(k) || [], e]);
54386
+ });
54387
+ const devRows = devices.map((dev) => {
54388
+ const list = byDev.get(dev) || [];
54389
+ return `<tr>
54390
+ <td class="adm-rpt-cell" title="${escHtml2(dev)}">${escHtml2(resolveDeviceLabel(alarm, dev) || dev)}</td>
54391
+ <td class="adm-rpt-cell adm-rpt-cell--num">${list.length}</td>
54392
+ <td class="adm-rpt-cell">${list.length ? fmt(list[0].ts) : "-"}</td>
54393
+ <td class="adm-rpt-cell">${list.length ? fmt(list[list.length - 1].ts) : "-"}</td>
54394
+ </tr>`;
54395
+ }).join("");
53935
54396
  summaryHtml = `
53936
54397
  <div class="adm-rpt-section-title">Sum\xE1rio por dispositivo</div>
53937
54398
  <div class="adm-rpt-table-wrap adm-rpt-table-wrap--summary">
@@ -53955,8 +54416,10 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53955
54416
  </div>
53956
54417
  ${statsHtml}
53957
54418
  ${summaryHtml}`;
53958
- csvBtn?.removeAttribute("disabled");
53959
- pdfBtn?.removeAttribute("disabled");
54419
+ if (events) {
54420
+ csvBtn?.removeAttribute("disabled");
54421
+ pdfBtn?.removeAttribute("disabled");
54422
+ }
53960
54423
  };
53961
54424
  csvBtn?.addEventListener("click", () => {
53962
54425
  if (!lastCsvRows.length) return;
@@ -53987,14 +54450,14 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
53987
54450
  tfoot td{font-weight:700;background:#ede9fe;}
53988
54451
  </style></head><body>
53989
54452
  <h2>${escHtml2(alarm.title)}</h2>
53990
- <p>${escHtml2(alarm.id)} \xB7 ${escHtml2(alarm.customerName || "")} \xB7 ${count} ocorr\xEAncia${count !== 1 ? "s" : ""} \xB7 Emitido em ${(/* @__PURE__ */ new Date()).toLocaleDateString("pt-BR")}</p>
53991
- <h3>Ocorr\xEAncias</h3>${tbl}
54453
+ <p>${escHtml2(alarm.id)} \xB7 ${escHtml2(alarm.customerName || "")} \xB7 ${occEvents().length} ocorr\xEAncia${occEvents().length !== 1 ? "s" : ""} \xB7 Emitido em ${(/* @__PURE__ */ new Date()).toLocaleDateString("pt-BR")}</p>
54454
+ <h3>Hist\xF3rico</h3>${tbl}
53992
54455
  ${sumTbl ? `<h3>Sum\xE1rio por dispositivo</h3>${sumTbl}` : ""}
53993
54456
  <script>window.onload=function(){window.print();window.close();}</script>
53994
54457
  </body></html>`);
53995
54458
  win2.document.close();
53996
54459
  });
53997
- emitBtn?.addEventListener("click", () => {
54460
+ const refreshReport = () => {
53998
54461
  buildReportTable();
53999
54462
  if (batchActive && grid) {
54000
54463
  grid.querySelectorAll("[data-cb-wrap]").forEach((el2) => {
@@ -54004,8 +54467,10 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
54004
54467
  el2.style.display = "none";
54005
54468
  });
54006
54469
  }
54007
- });
54470
+ };
54471
+ emitBtn?.addEventListener("click", refreshReport);
54008
54472
  buildReportTable();
54473
+ onEventsReady.push(refreshReport);
54009
54474
  const batchToggleBtn = rptPanel.querySelector("#admTlBatchToggle");
54010
54475
  const batchBar = rptPanel.querySelector("#admTlBatchBar");
54011
54476
  const selAllCb = rptPanel.querySelector("#admTlSelAll");
@@ -54110,17 +54575,48 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
54110
54575
  }
54111
54576
  }, tbCfg);
54112
54577
  }
54578
+ const applyEvents = () => {
54579
+ const occ = occEvents();
54580
+ const occText = events ? String(occ.length || "-") : "\u2026";
54581
+ const kpi = overlay.querySelector("#admKpiOcc");
54582
+ if (kpi) kpi.textContent = occText;
54583
+ const badge = overlay.querySelector("#admTlTabBadge");
54584
+ if (badge) badge.textContent = occText;
54585
+ const summary = overlay.querySelector("#admTlSummary");
54586
+ if (summary && events) summary.textContent = `${occ.length} ocorr\xEAncia${occ.length !== 1 ? "s" : ""}`;
54587
+ const reopenEl = overlay.querySelector("#admReopenRows");
54588
+ const reopens = occ.filter((e) => e.kind === "reopened");
54589
+ if (reopenEl) {
54590
+ reopenEl.innerHTML = reopens.length ? row("\xDAltima reabertura", fmt(reopens[reopens.length - 1].ts)) + (reopens.length > 1 ? row("Reaberturas", String(reopens.length)) : "") : "";
54591
+ }
54592
+ onEventsReady.forEach((fn) => {
54593
+ try {
54594
+ fn();
54595
+ } catch (_) {
54596
+ }
54597
+ });
54598
+ };
54599
+ loadAlarmEvents(alarm).then((ev) => {
54600
+ events = ev;
54601
+ }).catch(() => {
54602
+ events = [];
54603
+ }).then(() => {
54604
+ if (overlay.isConnected) applyEvents();
54605
+ });
54113
54606
  const drawer = overlay.querySelector(".adm-drawer");
54114
- const maximizeBtn = overlay.querySelector("#admMaximize");
54115
- const iconExpand = maximizeBtn?.querySelector(".adm-icon-expand");
54116
- const iconCompress = maximizeBtn?.querySelector(".adm-icon-compress");
54117
- maximizeBtn?.addEventListener("click", () => {
54118
- const isMax = drawer?.classList.toggle("adm-drawer--maximized");
54119
- if (iconExpand) iconExpand.style.display = isMax ? "none" : "";
54120
- if (iconCompress) iconCompress.style.display = isMax ? "" : "none";
54121
- maximizeBtn.title = isMax ? "Restaurar (Esc)" : "Maximizar";
54607
+ const chrome = createPremiumModalChrome({
54608
+ modalId: "adm-modal",
54609
+ icon: "\u{1F514}",
54610
+ title: escHtml2(alarm.title),
54611
+ modalEl: drawer,
54612
+ theme: themeMode,
54613
+ maximizedClass: "adm-drawer--maximized",
54614
+ headerRadius: "18px 18px 0 0",
54615
+ onClose: () => closeModal2(),
54616
+ footer: alarm.customerName ? { customerName: alarm.customerName } : {}
54122
54617
  });
54123
54618
  const closeModal2 = () => {
54619
+ chrome.destroy();
54124
54620
  overlay.classList.remove("adm-overlay--visible");
54125
54621
  setTimeout(() => {
54126
54622
  overlay.remove();
@@ -54137,7 +54633,34 @@ function openAlarmDetailsModal(alarm, themeMode = "light", groupMode, onAction)
54137
54633
  if (e.key === "Escape") closeModal2();
54138
54634
  };
54139
54635
  document.addEventListener("keydown", onKey);
54140
- overlay.querySelector("#admClose")?.addEventListener("click", closeModal2);
54636
+ overlay.addEventListener("click", (e) => {
54637
+ const btn = e.target.closest(".adm-copy-btn");
54638
+ if (!btn) return;
54639
+ e.stopPropagation();
54640
+ const text2 = btn.getAttribute("data-copy") || "";
54641
+ const done = () => {
54642
+ btn.classList.add("is-copied");
54643
+ btn.title = "Copiado!";
54644
+ setTimeout(() => {
54645
+ btn.classList.remove("is-copied");
54646
+ btn.title = "Copiar nome do dispositivo";
54647
+ }, 1200);
54648
+ };
54649
+ if (navigator.clipboard?.writeText) navigator.clipboard.writeText(text2).then(done).catch(() => {
54650
+ });
54651
+ else {
54652
+ const ta = document.createElement("textarea");
54653
+ ta.value = text2;
54654
+ document.body.appendChild(ta);
54655
+ ta.select();
54656
+ try {
54657
+ document.execCommand("copy");
54658
+ done();
54659
+ } catch (_) {
54660
+ }
54661
+ ta.remove();
54662
+ }
54663
+ });
54141
54664
  overlay.addEventListener("click", (e) => {
54142
54665
  if (e.target === overlay) closeModal2();
54143
54666
  });
@@ -54159,7 +54682,7 @@ function buildOccurrenceMatrix(alarm, devices) {
54159
54682
  }
54160
54683
  const deviceChip = (dev) => `<span class="adm-device-chip">
54161
54684
  <svg viewBox="0 0 24 24" width="8" height="8" fill="currentColor"><circle cx="12" cy="12" r="10"/></svg>
54162
- ${escHtml2(dev)}
54685
+ ${escHtml2(resolveDeviceLabel(alarm, dev) || dev)}
54163
54686
  </span>`;
54164
54687
  return show.map((item) => {
54165
54688
  const devIndex = (item.n - 1) % devices.length;
@@ -56357,6 +56880,46 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56357
56880
  transform: translateY(0) scale(1);
56358
56881
  }
56359
56882
 
56883
+ /* ---- Nota do gr\xE1fico (base real de ocorr\xEAncias) ---- */
56884
+ .adm-chart-note { font-size: 11px; color: #6b7280; margin: -2px 0 8px; }
56885
+ .adm-overlay[data-theme="dark"] .adm-chart-note { color: #94a3b8; }
56886
+
56887
+ /* ---- Device identity (label + name discreto + copiar) ---- */
56888
+ .adm-dev-id {
56889
+ display: inline-flex;
56890
+ align-items: baseline;
56891
+ gap: 6px;
56892
+ min-width: 0;
56893
+ flex-wrap: wrap;
56894
+ }
56895
+ .adm-dev-label { font-weight: 600; color: inherit; font-family: 'Nunito', system-ui, sans-serif; font-size: 13px; }
56896
+ .adm-dev-name {
56897
+ font-family: ui-monospace, 'SFMono-Regular', Consolas, monospace;
56898
+ font-size: 11px;
56899
+ color: #9ca3af;
56900
+ }
56901
+ .adm-copy-btn {
56902
+ display: inline-flex;
56903
+ align-items: center;
56904
+ justify-content: center;
56905
+ width: 20px;
56906
+ height: 20px;
56907
+ padding: 0;
56908
+ border: 1px solid #e5e7eb;
56909
+ border-radius: 4px;
56910
+ background: transparent;
56911
+ color: #9ca3af;
56912
+ cursor: pointer;
56913
+ align-self: center;
56914
+ transition: color 0.15s, border-color 0.15s, background 0.15s;
56915
+ }
56916
+ .adm-copy-btn:hover { color: var(--myio-brand-700, #7c3aed); border-color: var(--myio-brand-700, #7c3aed); }
56917
+ .adm-copy-btn.is-copied { color: #16a34a; border-color: #16a34a; background: #f0fdf4; }
56918
+ .adm-row-value--devices { display: flex; flex-direction: column; gap: 4px; }
56919
+ .adm-device-icon-sm svg { color: var(--myio-brand-700, #7c3aed); }
56920
+ .adm-overlay[data-theme="dark"] .adm-dev-name { color: #64748b; }
56921
+ .adm-overlay[data-theme="dark"] .adm-copy-btn { border-color: #2a2b3d; color: #64748b; }
56922
+
56360
56923
  /* ---- Maximized state ---- */
56361
56924
  .adm-drawer--maximized {
56362
56925
  width: 100% !important;
@@ -56368,7 +56931,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56368
56931
 
56369
56932
  /* ---- Header ---- */
56370
56933
  .adm-header {
56371
- padding: 18px 22px 14px;
56934
+ padding: 12px 22px;
56372
56935
  border-bottom: 1px solid #e5e7eb;
56373
56936
  flex-shrink: 0;
56374
56937
  }
@@ -56514,8 +57077,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56514
57077
  .adm-tab:hover { color: #111827; }
56515
57078
 
56516
57079
  .adm-tab.is-active {
56517
- color: #7c3aed;
56518
- border-bottom-color: #7c3aed;
57080
+ color: var(--myio-brand-700, #7c3aed);
57081
+ border-bottom-color: var(--myio-brand-700, #7c3aed);
56519
57082
  font-weight: 700;
56520
57083
  }
56521
57084
 
@@ -56527,8 +57090,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56527
57090
  height: 18px;
56528
57091
  padding: 0 4px;
56529
57092
  border-radius: 9px;
56530
- background: #ede9fe;
56531
- color: #7c3aed;
57093
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, #ffffff);
57094
+ color: var(--myio-brand-700, #7c3aed);
56532
57095
  font-size: 10px;
56533
57096
  font-weight: 700;
56534
57097
  }
@@ -56629,9 +57192,9 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56629
57192
  padding: 3px 8px;
56630
57193
  font-size: 10px;
56631
57194
  font-weight: 500;
56632
- background: rgba(139, 92, 246, 0.12);
56633
- color: #7c3aed;
56634
- border: 1px solid rgba(139, 92, 246, 0.3);
57195
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, transparent);
57196
+ color: var(--myio-brand-700, #7c3aed);
57197
+ border: 1px solid color-mix(in srgb, var(--myio-brand-700, #7c3aed) 30%, transparent);
56635
57198
  border-radius: 4px;
56636
57199
  }
56637
57200
 
@@ -56676,7 +57239,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56676
57239
  }
56677
57240
 
56678
57241
  .adm-tag-key {
56679
- color: #7c3aed;
57242
+ color: var(--myio-brand-700, #7c3aed);
56680
57243
  font-weight: 700;
56681
57244
  }
56682
57245
 
@@ -56724,15 +57287,15 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56724
57287
  width: 12px;
56725
57288
  height: 12px;
56726
57289
  border-radius: 50%;
56727
- background: #8b5cf6;
57290
+ background: var(--myio-brand-700, #8b5cf6);
56728
57291
  border: 2px solid #fff;
56729
- box-shadow: 0 0 0 2px #8b5cf6;
57292
+ box-shadow: 0 0 0 2px var(--myio-brand-700, #8b5cf6);
56730
57293
  flex-shrink: 0;
56731
57294
  }
56732
57295
 
56733
57296
  .adm-timeline-item.is-last .adm-timeline-dot { background: #3b82f6; box-shadow: 0 0 0 2px #3b82f6; }
56734
57297
  .adm-timeline-item.is-first .adm-timeline-dot { background: #ef4444; box-shadow: 0 0 0 2px #ef4444; }
56735
- .adm-timeline-item.is-single .adm-timeline-dot { background: #7c3aed; box-shadow: 0 0 0 2px #7c3aed; }
57298
+ .adm-timeline-item.is-single .adm-timeline-dot { background: var(--myio-brand-700, #7c3aed); box-shadow: 0 0 0 2px var(--myio-brand-700, #7c3aed); }
56736
57299
 
56737
57300
  .adm-timeline-num {
56738
57301
  font-size: 11px;
@@ -56818,7 +57381,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56818
57381
  gap: 6px;
56819
57382
  font-size: 10px;
56820
57383
  font-weight: 700;
56821
- color: #7c3aed;
57384
+ color: var(--myio-brand-700, #7c3aed);
56822
57385
  margin-bottom: 6px;
56823
57386
  padding: 3px 6px;
56824
57387
  background: rgba(139, 92, 246, 0.08);
@@ -56829,7 +57392,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56829
57392
  font-size: 9px;
56830
57393
  font-weight: 500;
56831
57394
  background: rgba(139, 92, 246, 0.2);
56832
- color: #7c3aed;
57395
+ color: var(--myio-brand-700, #7c3aed);
56833
57396
  padding: 1px 5px;
56834
57397
  border-radius: 8px;
56835
57398
  }
@@ -56867,14 +57430,14 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56867
57430
  transition: border-color 0.15s, color 0.15s, background 0.15s;
56868
57431
  }
56869
57432
  .adm-tl-batch-toggle:hover {
56870
- border-color: #7c3aed;
56871
- color: #7c3aed;
57433
+ border-color: var(--myio-brand-700, #7c3aed);
57434
+ color: var(--myio-brand-700, #7c3aed);
56872
57435
  background: rgba(124, 58, 237, 0.04);
56873
57436
  }
56874
57437
  .adm-tl-batch-toggle.is-active {
56875
- border-color: #7c3aed;
57438
+ border-color: var(--myio-brand-700, #7c3aed);
56876
57439
  background: rgba(124, 58, 237, 0.08);
56877
- color: #7c3aed;
57440
+ color: var(--myio-brand-700, #7c3aed);
56878
57441
  }
56879
57442
  .adm-tl-batch-bar {
56880
57443
  display: none;
@@ -56894,7 +57457,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56894
57457
  }
56895
57458
  .adm-tl-sel-count {
56896
57459
  font-size: 11px;
56897
- color: #7c3aed;
57460
+ color: var(--myio-brand-700, #7c3aed);
56898
57461
  font-weight: 600;
56899
57462
  min-width: 86px;
56900
57463
  }
@@ -56909,7 +57472,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56909
57472
  }
56910
57473
  .adm-tl-exec-btn {
56911
57474
  padding: 4px 12px;
56912
- background: #7c3aed;
57475
+ background: var(--myio-brand-700, #7c3aed);
56913
57476
  color: #fff;
56914
57477
  border: none;
56915
57478
  border-radius: 6px;
@@ -56919,7 +57482,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
56919
57482
  transition: background 0.15s;
56920
57483
  }
56921
57484
  .adm-tl-exec-btn:disabled { background: #d1d5db; cursor: not-allowed; }
56922
- .adm-tl-exec-btn:not(:disabled):hover { background: #6d28d9; }
57485
+ .adm-tl-exec-btn:not(:disabled):hover { background: var(--myio-brand-600, #6d28d9); }
56923
57486
  .adm-tl-cancel-btn {
56924
57487
  padding: 4px 10px;
56925
57488
  background: transparent;
@@ -57005,7 +57568,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57005
57568
  justify-content: center;
57006
57569
  width: 22px;
57007
57570
  height: 22px;
57008
- background: #ede9fe;
57571
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, #ffffff);
57009
57572
  border-radius: 5px;
57010
57573
  flex-shrink: 0;
57011
57574
  }
@@ -57078,7 +57641,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57078
57641
 
57079
57642
  .adm-matrix-n {
57080
57643
  font-weight: 700;
57081
- color: #7c3aed;
57644
+ color: var(--myio-brand-700, #7c3aed);
57082
57645
  }
57083
57646
 
57084
57647
  .adm-matrix-ts {
@@ -57106,8 +57669,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57106
57669
  align-items: center;
57107
57670
  gap: 4px;
57108
57671
  padding: 2px 6px;
57109
- background: #ede9fe;
57110
- color: #6d28d9;
57672
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, #ffffff);
57673
+ color: var(--myio-brand-600, #6d28d9);
57111
57674
  border-radius: 4px;
57112
57675
  font-size: 10px;
57113
57676
  font-family: 'SF Mono', Monaco, monospace;
@@ -57159,8 +57722,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57159
57722
  }
57160
57723
 
57161
57724
  .adm-report-date-input:focus {
57162
- border-color: #7c3aed;
57163
- box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.12);
57725
+ border-color: var(--myio-brand-700, #7c3aed);
57726
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, transparent);
57164
57727
  }
57165
57728
 
57166
57729
  .adm-report-emit-btn {
@@ -57168,7 +57731,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57168
57731
  align-items: center;
57169
57732
  gap: 6px;
57170
57733
  padding: 8px 16px;
57171
- background: #7c3aed;
57734
+ background: var(--myio-brand-700, #7c3aed);
57172
57735
  color: #fff;
57173
57736
  border: none;
57174
57737
  border-radius: 8px;
@@ -57179,8 +57742,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57179
57742
  align-self: flex-start;
57180
57743
  }
57181
57744
 
57182
- .adm-report-emit-btn:hover { background: #6d28d9; }
57183
- .adm-report-emit-btn:active { background: #5b21b6; }
57745
+ .adm-report-emit-btn:hover { background: var(--myio-brand-600, #6d28d9); }
57746
+ .adm-report-emit-btn:active { background: var(--myio-brand-600, #6d28d9); }
57184
57747
 
57185
57748
  .adm-report-empty-hint {
57186
57749
  font-size: 13px;
@@ -57190,7 +57753,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57190
57753
  font-style: italic;
57191
57754
  }
57192
57755
 
57193
- .adm-report-empty-hint strong { color: #7c3aed; font-style: normal; }
57756
+ .adm-report-empty-hint strong { color: var(--myio-brand-700, #7c3aed); font-style: normal; }
57194
57757
 
57195
57758
  .adm-rpt-table-wrap {
57196
57759
  overflow-x: auto;
@@ -57229,7 +57792,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57229
57792
  font-size: 11px;
57230
57793
  }
57231
57794
 
57232
- .adm-rpt-cell--num { text-align: right; font-weight: 700; color: #7c3aed; }
57795
+ .adm-rpt-cell--num { text-align: right; font-weight: 700; color: var(--myio-brand-700, #7c3aed); }
57233
57796
 
57234
57797
  .adm-rpt-cell--dev {
57235
57798
  font-family: inherit;
@@ -57244,7 +57807,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57244
57807
  .adm-rpt-cell--total {
57245
57808
  font-weight: 700;
57246
57809
  color: #111827;
57247
- background: #ede9fe;
57810
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, #ffffff);
57248
57811
  }
57249
57812
 
57250
57813
  .adm-rpt-table tbody tr:nth-child(even) td { background: #fafafa; }
@@ -57256,7 +57819,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57256
57819
  font-weight: 700;
57257
57820
  text-transform: uppercase;
57258
57821
  letter-spacing: 0.5px;
57259
- color: #7c3aed;
57822
+ color: var(--myio-brand-700, #7c3aed);
57260
57823
  margin: 12px 0 6px;
57261
57824
  }
57262
57825
  .adm-rpt-section-title:first-child { margin-top: 0; }
@@ -57289,7 +57852,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57289
57852
  .adm-rpt-device-sep { color: #9ca3af; font-size: 11px; }
57290
57853
  .adm-rpt-alarm-type {
57291
57854
  font-size: 11px;
57292
- color: #7c3aed;
57855
+ color: var(--myio-brand-700, #7c3aed);
57293
57856
  font-weight: 600;
57294
57857
  }
57295
57858
 
@@ -57370,7 +57933,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57370
57933
  }
57371
57934
 
57372
57935
  .adm-chart-btn.is-active {
57373
- background: #7c3aed;
57936
+ background: var(--myio-brand-700, #7c3aed);
57374
57937
  color: #fff;
57375
57938
  }
57376
57939
 
@@ -57470,7 +58033,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57470
58033
 
57471
58034
  .adm-dow-bar {
57472
58035
  height: 100%;
57473
- background: #7c3aed;
58036
+ background: var(--myio-brand-700, #7c3aed);
57474
58037
  border-radius: 3px;
57475
58038
  }
57476
58039
 
@@ -57502,7 +58065,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57502
58065
 
57503
58066
  .adm-hod-bar {
57504
58067
  width: 100%;
57505
- background: #7c3aed;
58068
+ background: var(--myio-brand-700, #7c3aed);
57506
58069
  border-radius: 2px 2px 0 0;
57507
58070
  min-height: 4px;
57508
58071
  }
@@ -57533,7 +58096,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57533
58096
  align-items: center;
57534
58097
  gap: 5px;
57535
58098
  padding: 7px 14px;
57536
- background: #7c3aed;
58099
+ background: var(--myio-brand-700, #7c3aed);
57537
58100
  color: #fff;
57538
58101
  border: none;
57539
58102
  border-radius: 8px;
@@ -57542,8 +58105,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57542
58105
  cursor: pointer;
57543
58106
  transition: background 0.15s;
57544
58107
  }
57545
- .adm-annot-create-btn:hover { background: #6d28d9; }
57546
- .adm-annot-create-btn:active { background: #5b21b6; }
58108
+ .adm-annot-create-btn:hover { background: var(--myio-brand-600, #6d28d9); }
58109
+ .adm-annot-create-btn:active { background: var(--myio-brand-600, #6d28d9); }
57547
58110
 
57548
58111
  .adm-annot-form-wrap { margin-bottom: 14px; }
57549
58112
 
@@ -57576,8 +58139,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57576
58139
  transition: border-color 0.15s;
57577
58140
  }
57578
58141
  .adm-annot-textarea:focus {
57579
- border-color: #7c3aed;
57580
- box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.12);
58142
+ border-color: var(--myio-brand-700, #7c3aed);
58143
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, transparent);
57581
58144
  }
57582
58145
 
57583
58146
  .adm-annot-char-count {
@@ -57664,7 +58227,7 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57664
58227
  cursor: pointer;
57665
58228
  transition: border-color 0.15s;
57666
58229
  }
57667
- .adm-annot-date-input:focus { border-color: #7c3aed; }
58230
+ .adm-annot-date-input:focus { border-color: var(--myio-brand-700, #7c3aed); }
57668
58231
 
57669
58232
  /* Form action buttons */
57670
58233
  .adm-annot-form-actions {
@@ -57685,8 +58248,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57685
58248
  }
57686
58249
  .adm-annot-form-btn--cancel { background: #e5e7eb; color: #374151; }
57687
58250
  .adm-annot-form-btn--cancel:hover { background: #d1d5db; }
57688
- .adm-annot-form-btn--save { background: #7c3aed; color: #fff; }
57689
- .adm-annot-form-btn--save:hover { background: #6d28d9; }
58251
+ .adm-annot-form-btn--save { background: var(--myio-brand-700, #7c3aed); color: #fff; }
58252
+ .adm-annot-form-btn--save:hover { background: var(--myio-brand-600, #6d28d9); }
57690
58253
  .adm-annot-form-btn--save:disabled { opacity: 0.4; cursor: not-allowed; }
57691
58254
 
57692
58255
  /* Empty state */
@@ -57786,8 +58349,8 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57786
58349
  width: 20px;
57787
58350
  height: 20px;
57788
58351
  border-radius: 50%;
57789
- background: #ede9fe;
57790
- color: #7c3aed;
58352
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, #ffffff);
58353
+ color: var(--myio-brand-700, #7c3aed);
57791
58354
  font-size: 8px;
57792
58355
  font-weight: 700;
57793
58356
  flex-shrink: 0;
@@ -57890,13 +58453,13 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57890
58453
  }
57891
58454
 
57892
58455
  .adm-overlay[data-theme="dark"] .adm-tab.is-active {
57893
- color: #a78bfa;
57894
- border-bottom-color: #a78bfa;
58456
+ color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff);
58457
+ border-bottom-color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff);
57895
58458
  }
57896
58459
 
57897
58460
  .adm-overlay[data-theme="dark"] .adm-tab-badge {
57898
- background: #2d1b69;
57899
- color: #a78bfa;
58461
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 35%, #1e1f2e);
58462
+ color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff);
57900
58463
  }
57901
58464
 
57902
58465
  .adm-overlay[data-theme="dark"] .adm-body {
@@ -57968,13 +58531,13 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57968
58531
  }
57969
58532
 
57970
58533
  .adm-overlay[data-theme="dark"] .adm-timeline-group-header {
57971
- color: #a78bfa;
57972
- background: rgba(139, 92, 246, 0.12);
58534
+ color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff);
58535
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 12%, transparent);
57973
58536
  }
57974
58537
 
57975
58538
  .adm-overlay[data-theme="dark"] .adm-timeline-group-count {
57976
- background: rgba(139, 92, 246, 0.25);
57977
- color: #a78bfa;
58539
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 25%, transparent);
58540
+ color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff);
57978
58541
  }
57979
58542
 
57980
58543
  .adm-overlay[data-theme="dark"] .adm-tl-header { border-color: #334155; }
@@ -57982,10 +58545,10 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
57982
58545
  .adm-overlay[data-theme="dark"] .adm-tl-summary { color: #94a3b8; }
57983
58546
  .adm-overlay[data-theme="dark"] .adm-tl-batch-toggle { border-color: #475569; background: #1e293b; color: #e2e8f0; }
57984
58547
  .adm-overlay[data-theme="dark"] .adm-tl-batch-toggle:hover,
57985
- .adm-overlay[data-theme="dark"] .adm-tl-batch-toggle.is-active { border-color: #a78bfa; color: #a78bfa; }
58548
+ .adm-overlay[data-theme="dark"] .adm-tl-batch-toggle.is-active { border-color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff); color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff); }
57986
58549
  .adm-overlay[data-theme="dark"] .adm-tl-batch-bar { border-color: #334155; }
57987
58550
  .adm-overlay[data-theme="dark"] .adm-tl-sel-all-label { color: #e2e8f0; }
57988
- .adm-overlay[data-theme="dark"] .adm-tl-sel-count { color: #a78bfa; }
58551
+ .adm-overlay[data-theme="dark"] .adm-tl-sel-count { color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff); }
57989
58552
  .adm-overlay[data-theme="dark"] .adm-tl-action-sel { background: #1e293b; border-color: #475569; color: #e2e8f0; }
57990
58553
  .adm-overlay[data-theme="dark"] .adm-tl-cancel-btn { border-color: #475569; color: #94a3b8; }
57991
58554
  .adm-overlay[data-theme="dark"] .adm-tl-cancel-btn:hover { color: #e2e8f0; }
@@ -58016,9 +58579,9 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
58016
58579
  }
58017
58580
 
58018
58581
  .adm-overlay[data-theme="dark"] .adm-chart-btn.is-active {
58019
- background: #2d1b69;
58020
- border-color: #6d28d9;
58021
- color: #a78bfa;
58582
+ background: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 35%, #1e1f2e);
58583
+ border-color: var(--myio-brand-600, #6d28d9);
58584
+ color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff);
58022
58585
  }
58023
58586
 
58024
58587
  .adm-overlay[data-theme="dark"] .adm-chart-btn:hover:not(.is-active) {
@@ -58082,13 +58645,13 @@ var ALARMS_NOTIFICATIONS_PANEL_STYLES = `
58082
58645
  background: #252636;
58083
58646
  }
58084
58647
 
58085
- .adm-overlay[data-theme="dark"] .adm-rpt-section-title { color: #a78bfa; }
58648
+ .adm-overlay[data-theme="dark"] .adm-rpt-section-title { color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff); }
58086
58649
  .adm-overlay[data-theme="dark"] .adm-rpt-device-header {
58087
58650
  background: rgba(139, 92, 246, 0.1);
58088
- border-color: rgba(139, 92, 246, 0.25);
58651
+ border-color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 25%, transparent);
58089
58652
  }
58090
58653
  .adm-overlay[data-theme="dark"] .adm-rpt-device-name { color: #e2e8f0; }
58091
- .adm-overlay[data-theme="dark"] .adm-rpt-alarm-type { color: #a78bfa; }
58654
+ .adm-overlay[data-theme="dark"] .adm-rpt-alarm-type { color: color-mix(in srgb, var(--myio-brand-700, #7c3aed) 50%, #ffffff); }
58092
58655
  .adm-overlay[data-theme="dark"] .adm-rpt-cell--total { background: rgba(139, 92, 246, 0.15); color: #e2e8f0; }
58093
58656
 
58094
58657
  .adm-overlay[data-theme="dark"] .adm-annot-card {
@@ -142202,425 +142765,6 @@ function createDeviceOperationalCardGridComponent(params) {
142202
142765
  };
142203
142766
  }
142204
142767
 
142205
- // src/services/alarm/AlarmApiClient.ts
142206
- var AlarmApiClient = class {
142207
- baseUrl = "";
142208
- apiKey = "";
142209
- configure(baseUrl, apiKey) {
142210
- if (baseUrl) this.baseUrl = baseUrl;
142211
- if (apiKey) this.apiKey = apiKey;
142212
- }
142213
- get headers() {
142214
- return {
142215
- "Content-Type": "application/json",
142216
- "X-API-Key": this.apiKey
142217
- };
142218
- }
142219
- async request(url, options) {
142220
- const response = await fetch(url, {
142221
- method: "GET",
142222
- headers: this.headers,
142223
- ...options
142224
- });
142225
- if (!response.ok) {
142226
- throw new Error(`Alarms API error: ${response.status} ${response.statusText}`);
142227
- }
142228
- return response.json();
142229
- }
142230
- // Some endpoints return { success: true, data: T[] } — unwrap safely
142231
- unwrapArray(raw) {
142232
- if (Array.isArray(raw)) return raw;
142233
- if (raw !== null && typeof raw === "object" && "data" in raw) {
142234
- const d = raw.data;
142235
- return Array.isArray(d) ? d : [];
142236
- }
142237
- return [];
142238
- }
142239
- // -----------------------------------------------------------------------
142240
- // Availability (existing endpoint)
142241
- // -----------------------------------------------------------------------
142242
- async getAvailability(params) {
142243
- const query = new URLSearchParams({
142244
- customerId: params.customerId,
142245
- startAt: params.startAt,
142246
- endAt: params.endAt,
142247
- includeByDevice: String(params.includeByDevice ?? true)
142248
- });
142249
- if (params.deviceType) query.set("deviceType", params.deviceType);
142250
- if (params.deviceIds) query.set("deviceIds", params.deviceIds);
142251
- const raw = await this.request(
142252
- `${this.baseUrl}/alarms/stats/availability?${query.toString()}`
142253
- );
142254
- return raw?.data ?? raw;
142255
- }
142256
- // -----------------------------------------------------------------------
142257
- // Alarm list (GET /alarms)
142258
- // -----------------------------------------------------------------------
142259
- async getAlarms(params = {}) {
142260
- const query = new URLSearchParams();
142261
- if (params.state?.length) query.set("state", params.state.join(","));
142262
- if (params.severity?.length) query.set("severity", params.severity.join(","));
142263
- if (params.alarmType) query.set("alarmType", params.alarmType);
142264
- if (params.from) query.set("from", params.from);
142265
- if (params.to) query.set("to", params.to);
142266
- if (params.customerId) query.set("customerId", params.customerId);
142267
- query.set("limit", String(params.limit ?? 100));
142268
- if (params.page && params.page > 1) query.set("page", String(params.page));
142269
- const qs = query.toString();
142270
- return this.request(
142271
- `${this.baseUrl}/alarms${qs ? `?${qs}` : ""}`
142272
- );
142273
- }
142274
- // -----------------------------------------------------------------------
142275
- // Stats (GET /api/v1/alarms/stats)
142276
- // -----------------------------------------------------------------------
142277
- async getAlarmStats(tenantId, period) {
142278
- const query = new URLSearchParams({ tenantId, period });
142279
- const raw = await this.request(
142280
- `${this.baseUrl}/alarms/stats?${query.toString()}`
142281
- );
142282
- return raw?.data ?? raw;
142283
- }
142284
- // -----------------------------------------------------------------------
142285
- // Trend (GET /api/v1/alarms/stats/trend)
142286
- // -----------------------------------------------------------------------
142287
- async getAlarmTrend(tenantId, period, groupBy) {
142288
- const query = new URLSearchParams({ tenantId, period, groupBy });
142289
- const raw = await this.request(
142290
- `${this.baseUrl}/alarms/stats/trend?${query.toString()}`
142291
- );
142292
- return this.unwrapArray(raw);
142293
- }
142294
- // -----------------------------------------------------------------------
142295
- // Top offenders (GET /api/v1/alarms/stats/top-offenders)
142296
- // -----------------------------------------------------------------------
142297
- async getTopOffenders(tenantId, limit = 5) {
142298
- const query = new URLSearchParams({ tenantId, limit: String(limit) });
142299
- const raw = await this.request(
142300
- `${this.baseUrl}/alarms/stats/top-offenders?${query.toString()}`
142301
- );
142302
- return this.unwrapArray(raw);
142303
- }
142304
- // -----------------------------------------------------------------------
142305
- // Per-device stats (GET /api/v1/alarms/stats/by-device)
142306
- // -----------------------------------------------------------------------
142307
- async getAlarmStatsByDevice(tenantId) {
142308
- const query = new URLSearchParams({ tenantId });
142309
- const raw = await this.request(
142310
- `${this.baseUrl}/alarms/stats/by-device?${query.toString()}`
142311
- );
142312
- return this.unwrapArray(raw);
142313
- }
142314
- // -----------------------------------------------------------------------
142315
- // Alarm actions (POST /alarms/:id/...)
142316
- // -----------------------------------------------------------------------
142317
- async acknowledgeAlarm(id, acknowledgedBy, note) {
142318
- await this.request(`${this.baseUrl}/alarms/${id}/ack`, {
142319
- method: "POST",
142320
- body: JSON.stringify({ acknowledgedBy, note })
142321
- });
142322
- }
142323
- async silenceAlarm(id, silencedBy, duration, reason) {
142324
- await this.request(`${this.baseUrl}/alarms/${id}/silence`, {
142325
- method: "POST",
142326
- body: JSON.stringify({ silencedBy, duration, reason })
142327
- });
142328
- }
142329
- async escalateAlarm(id, escalatedBy, reason) {
142330
- await this.request(`${this.baseUrl}/alarms/${id}/escalate`, {
142331
- method: "POST",
142332
- body: JSON.stringify({ escalatedBy, reason })
142333
- });
142334
- }
142335
- async closeAlarm(id, closedBy, resolution) {
142336
- await this.request(`${this.baseUrl}/alarms/${id}/close`, {
142337
- method: "POST",
142338
- body: JSON.stringify({ closedBy, resolution })
142339
- });
142340
- }
142341
- // -----------------------------------------------------------------------
142342
- // Batch alarm actions (POST /alarms/batch/*)
142343
- // Up to 100 IDs per call; response contains succeeded/failed arrays.
142344
- // -----------------------------------------------------------------------
142345
- async batchAcknowledge(alarmIds, acknowledgedBy) {
142346
- const res = await this.request(
142347
- `${this.baseUrl}/alarms/batch/ack`,
142348
- { method: "POST", body: JSON.stringify({ alarmIds, acknowledgedBy }) }
142349
- );
142350
- return res.data;
142351
- }
142352
- async batchSilence(alarmIds, silencedBy, duration) {
142353
- const res = await this.request(
142354
- `${this.baseUrl}/alarms/batch/silence`,
142355
- { method: "POST", body: JSON.stringify({ alarmIds, silencedBy, duration }) }
142356
- );
142357
- return res.data;
142358
- }
142359
- async batchEscalate(alarmIds, escalatedBy) {
142360
- const res = await this.request(
142361
- `${this.baseUrl}/alarms/batch/escalate`,
142362
- { method: "POST", body: JSON.stringify({ alarmIds, escalatedBy }) }
142363
- );
142364
- return res.data;
142365
- }
142366
- async batchClose(alarmIds, closedBy, resolution) {
142367
- const res = await this.request(
142368
- `${this.baseUrl}/alarms/batch/close`,
142369
- { method: "POST", body: JSON.stringify({ alarmIds, closedBy, resolution }) }
142370
- );
142371
- return res.data;
142372
- }
142373
- };
142374
-
142375
- // src/services/alarm/AlarmService.ts
142376
- function isFresh(entry, ttlMs) {
142377
- return !!entry && Date.now() - entry.timestamp < ttlMs;
142378
- }
142379
- function mapApiAlarm(api, customerMap) {
142380
- return {
142381
- id: api.id,
142382
- customerId: api.customerId ?? api.tenantId,
142383
- customerName: customerMap?.get(api.customerId ?? "") ?? "",
142384
- source: api.deviceName || api.deviceId,
142385
- severity: api.severity,
142386
- state: api.state,
142387
- title: api.title,
142388
- description: api.description ?? "",
142389
- tags: {
142390
- alarmType: api.alarmType,
142391
- deviceType: api.deviceType
142392
- },
142393
- firstOccurrence: api.raisedAt || api.raised_at || api.createdAt || void 0,
142394
- lastOccurrence: api.lastUpdatedAt || api.updatedAt || api.updated_at || void 0,
142395
- occurrenceCount: api.occurrenceCount ?? api.dispatchCount ?? 1,
142396
- acknowledgedAt: api.acknowledgedAt || void 0,
142397
- acknowledgedBy: api.acknowledgedBy,
142398
- snoozedUntil: api.silencedUntil || api.snoozedUntil || void 0,
142399
- closedAt: api.closedAt || void 0,
142400
- closedBy: api.closedBy,
142401
- closedReason: api.resolution,
142402
- triggerValue: api.metadata?.value,
142403
- centralId: api.centralId || void 0
142404
- };
142405
- }
142406
- function mapApiStats(api) {
142407
- return {
142408
- total: api.total,
142409
- bySeverity: {
142410
- CRITICAL: api.bySeverity?.CRITICAL ?? 0,
142411
- HIGH: api.bySeverity?.HIGH ?? 0,
142412
- MEDIUM: api.bySeverity?.MEDIUM ?? 0,
142413
- LOW: api.bySeverity?.LOW ?? 0,
142414
- INFO: api.bySeverity?.INFO ?? 0
142415
- },
142416
- byState: {
142417
- OPEN: api.byState?.OPEN ?? 0,
142418
- ACK: api.byState?.ACK ?? 0,
142419
- SNOOZED: api.byState?.SNOOZED ?? 0,
142420
- ESCALATED: api.byState?.ESCALATED ?? 0,
142421
- CLOSED: api.byState?.CLOSED ?? 0
142422
- },
142423
- openCritical: api.openCritical,
142424
- openHigh: api.openHigh,
142425
- last24Hours: api.last24Hours
142426
- };
142427
- }
142428
- function mapApiTrend(apiTrend) {
142429
- return (apiTrend || []).map((point) => ({
142430
- label: point.label || point.period,
142431
- timestamp: point.timestamp || new Date(point.period).getTime() || 0,
142432
- total: point.total ?? point.count,
142433
- bySeverity: point.bySeverity ? {
142434
- CRITICAL: point.bySeverity.CRITICAL ?? 0,
142435
- HIGH: point.bySeverity.HIGH ?? 0,
142436
- MEDIUM: point.bySeverity.MEDIUM ?? 0,
142437
- LOW: point.bySeverity.LOW ?? 0,
142438
- INFO: point.bySeverity.INFO ?? 0
142439
- } : void 0
142440
- }));
142441
- }
142442
- function mapApiTopOffenders(items, deviceNameMap) {
142443
- return (items || []).map((item) => ({
142444
- name: deviceNameMap.get(item.deviceId) ?? item.deviceName ?? item.deviceId,
142445
- location: item.customerName ?? "",
142446
- downtime: item.downtimeHours ?? (item.totalDowntimeMinutes ?? 0) / 60,
142447
- percentage: 0
142448
- // computed by caller if needed
142449
- }));
142450
- }
142451
- var AlarmServiceClass = class {
142452
- client = new AlarmApiClient();
142453
- cacheTtlMs = 3 * 60 * 1e3;
142454
- // 3 min default; overridable via configure()
142455
- availabilityCache = /* @__PURE__ */ new Map();
142456
- alarmsCache = /* @__PURE__ */ new Map();
142457
- statsCache = /* @__PURE__ */ new Map();
142458
- trendCache = /* @__PURE__ */ new Map();
142459
- // -----------------------------------------------------------------------
142460
- // Availability (MTBF / MTTR per device)
142461
- // -----------------------------------------------------------------------
142462
- /**
142463
- * Fetches MTBF, MTTR, and Availability metrics per device and fleet-wide.
142464
- * Results are cached for 60 seconds.
142465
- */
142466
- async getAvailability(customerId, startAt, endAt) {
142467
- if (!customerId) {
142468
- throw new Error("AlarmService.getAvailability: customerId is required");
142469
- }
142470
- const cacheKey = `avail:${customerId}:${startAt}:${endAt}`;
142471
- const cached = this.availabilityCache.get(cacheKey);
142472
- if (isFresh(cached, this.cacheTtlMs)) return cached.data;
142473
- const data = await this.client.getAvailability({
142474
- customerId,
142475
- startAt,
142476
- endAt,
142477
- includeByDevice: true
142478
- });
142479
- this.availabilityCache.set(cacheKey, { data, timestamp: Date.now() });
142480
- return data;
142481
- }
142482
- // -----------------------------------------------------------------------
142483
- // Alarm list
142484
- // -----------------------------------------------------------------------
142485
- /**
142486
- * Fetch alarm list with optional filters, auto-paginating until hasMore is false.
142487
- * Returns { data: Alarm[], summary: AlarmListSummary } with all pages merged.
142488
- * The summary comes from page 1 (reflects full dataset totals).
142489
- */
142490
- async getAlarms(params = {}, customerMap) {
142491
- const cacheKey = `alarms:${JSON.stringify(params)}`;
142492
- const cached = this.alarmsCache.get(cacheKey);
142493
- if (isFresh(cached, this.cacheTtlMs)) return cached.data;
142494
- let allData = [];
142495
- let summary;
142496
- let totalPages = 1;
142497
- let page = 1;
142498
- while (page <= totalPages) {
142499
- const response = await this.client.getAlarms({ ...params, page });
142500
- const pageData = (response.data || []).map((a) => mapApiAlarm(a, customerMap));
142501
- allData = allData.concat(pageData);
142502
- if (page === 1) {
142503
- summary = response.summary;
142504
- totalPages = response.pagination?.totalPages ?? 1;
142505
- }
142506
- page++;
142507
- }
142508
- const result = { data: allData, summary };
142509
- this.alarmsCache.set(cacheKey, { data: result, timestamp: Date.now() });
142510
- return result;
142511
- }
142512
- // -----------------------------------------------------------------------
142513
- // Alarm statistics
142514
- // -----------------------------------------------------------------------
142515
- /**
142516
- * Fetch aggregated alarm statistics for a tenant and time period.
142517
- * Returns mapped AlarmStats (from src/types/alarm.ts).
142518
- */
142519
- async getAlarmStats(tenantId, period) {
142520
- const cacheKey = `stats:${tenantId}:${period}`;
142521
- const cached = this.statsCache.get(cacheKey);
142522
- if (isFresh(cached, this.cacheTtlMs)) return cached.data;
142523
- const raw = await this.client.getAlarmStats(tenantId, period);
142524
- const data = mapApiStats(raw);
142525
- this.statsCache.set(cacheKey, { data, timestamp: Date.now() });
142526
- return data;
142527
- }
142528
- // -----------------------------------------------------------------------
142529
- // Alarm trend
142530
- // -----------------------------------------------------------------------
142531
- /**
142532
- * Fetch alarm trend data points.
142533
- * Returns mapped AlarmTrendDataPoint[] (from src/types/alarm.ts).
142534
- */
142535
- async getAlarmTrend(tenantId, period, groupBy) {
142536
- const cacheKey = `trend:${tenantId}:${period}:${groupBy}`;
142537
- const cached = this.trendCache.get(cacheKey);
142538
- if (isFresh(cached, this.cacheTtlMs)) return cached.data;
142539
- const raw = await this.client.getAlarmTrend(tenantId, period, groupBy);
142540
- const data = mapApiTrend(raw);
142541
- this.trendCache.set(cacheKey, { data, timestamp: Date.now() });
142542
- return data;
142543
- }
142544
- // -----------------------------------------------------------------------
142545
- // Top downtime / offenders
142546
- // -----------------------------------------------------------------------
142547
- /**
142548
- * Fetch top devices by alarm count / downtime.
142549
- * Returns mapped DowntimeEntry[] (from src/types/operational.ts).
142550
- *
142551
- * @param deviceNameMap - Optional map of deviceId → friendly name from ThingsBoard cache
142552
- */
142553
- async getTopDowntime(tenantId, deviceNameMap = /* @__PURE__ */ new Map(), limit = 5) {
142554
- const items = await this.client.getTopOffenders(tenantId, limit);
142555
- return mapApiTopOffenders(items, deviceNameMap);
142556
- }
142557
- // -----------------------------------------------------------------------
142558
- // Per-device alarm counts
142559
- // -----------------------------------------------------------------------
142560
- /**
142561
- * Fetch alarm counts per device.
142562
- * Returns a Map<deviceId, alarmCount>.
142563
- */
142564
- async getDeviceAlarmCounts(tenantId) {
142565
- const items = await this.client.getAlarmStatsByDevice(tenantId);
142566
- const map = /* @__PURE__ */ new Map();
142567
- (items || []).forEach((item) => map.set(item.deviceId, item.alarmCount));
142568
- return map;
142569
- }
142570
- // -----------------------------------------------------------------------
142571
- // Alarm actions
142572
- // -----------------------------------------------------------------------
142573
- async acknowledgeAlarm(id, userEmail, note) {
142574
- return this.client.acknowledgeAlarm(id, userEmail, note);
142575
- }
142576
- async silenceAlarm(id, userEmail, duration, reason) {
142577
- return this.client.silenceAlarm(id, userEmail, duration, reason);
142578
- }
142579
- async escalateAlarm(id, userEmail, reason) {
142580
- return this.client.escalateAlarm(id, userEmail, reason);
142581
- }
142582
- async closeAlarm(id, userEmail, resolution) {
142583
- return this.client.closeAlarm(id, userEmail, resolution);
142584
- }
142585
- // -----------------------------------------------------------------------
142586
- // Batch alarm actions (POST /alarms/batch/*)
142587
- // -----------------------------------------------------------------------
142588
- async batchAcknowledge(alarmIds, userEmail) {
142589
- return this.client.batchAcknowledge(alarmIds, userEmail);
142590
- }
142591
- async batchSilence(alarmIds, userEmail, duration) {
142592
- return this.client.batchSilence(alarmIds, userEmail, duration);
142593
- }
142594
- async batchEscalate(alarmIds, userEmail) {
142595
- return this.client.batchEscalate(alarmIds, userEmail);
142596
- }
142597
- async batchClose(alarmIds, userEmail, resolution) {
142598
- return this.client.batchClose(alarmIds, userEmail, resolution);
142599
- }
142600
- // -----------------------------------------------------------------------
142601
- // Configuration
142602
- // -----------------------------------------------------------------------
142603
- /**
142604
- * Set the Alarms API base URL (e.g. from MAIN_VIEW orchestrator settings).
142605
- * Clears the cache so subsequent calls use the new endpoint.
142606
- */
142607
- configure(baseUrl, cacheTtlMs, apiKey) {
142608
- this.client.configure(baseUrl ?? "", apiKey);
142609
- if (cacheTtlMs && cacheTtlMs > 0) this.cacheTtlMs = cacheTtlMs;
142610
- this.clearCache();
142611
- }
142612
- // -----------------------------------------------------------------------
142613
- // Cache management
142614
- // -----------------------------------------------------------------------
142615
- clearCache() {
142616
- this.availabilityCache.clear();
142617
- this.alarmsCache.clear();
142618
- this.statsCache.clear();
142619
- this.trendCache.clear();
142620
- }
142621
- };
142622
- var AlarmService = new AlarmServiceClass();
142623
-
142624
142768
  // src/components/AlarmsNotificationsPanel/AlarmsNotificationsPanelController.ts
142625
142769
  var AlarmsNotificationsPanelController = class {
142626
142770
  state;
@@ -143821,16 +143965,36 @@ var AlarmsNotificationsPanelView = class {
143821
143965
  * Render dashboard tab content.
143822
143966
  * Trend data is fetched asynchronously on first open and injected when ready.
143823
143967
  */
143968
+ /** KPIs do Dashboard calculados sobre uma lista de alarmes (a mesma exibida na Lista). */
143969
+ statsFromAlarms(alarms) {
143970
+ const bySeverity = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0, INFO: 0 };
143971
+ const byState = { OPEN: 0, ACK: 0, SNOOZED: 0, ESCALATED: 0, CLOSED: 0 };
143972
+ let openCritical = 0;
143973
+ let openHigh = 0;
143974
+ let last24Hours = 0;
143975
+ const since = Date.now() - 24 * 60 * 60 * 1e3;
143976
+ for (const a of alarms) {
143977
+ if (a.severity in bySeverity) bySeverity[a.severity]++;
143978
+ if (a.state in byState) byState[a.state]++;
143979
+ const open = a.state !== "CLOSED";
143980
+ if (open && a.severity === "CRITICAL") openCritical++;
143981
+ if (open && a.severity === "HIGH") openHigh++;
143982
+ const first = new Date(a.firstOccurrence || a.lastOccurrence || 0).getTime();
143983
+ if (first >= since) last24Hours++;
143984
+ }
143985
+ return { total: alarms.length, bySeverity, byState, openCritical, openHigh, last24Hours };
143986
+ }
143824
143987
  renderDashboardContent(state6) {
143825
143988
  if (!this.root) return;
143826
143989
  const container = this.root.querySelector("#dashboardContent");
143827
143990
  if (!container) return;
143991
+ const stats = state6.allAlarms.length > 0 ? this.statsFromAlarms(state6.filteredAlarms) : state6.stats;
143828
143992
  if (container.children.length > 0) {
143829
- updateDashboard(container, state6.stats, this.trendData);
143993
+ updateDashboard(container, stats, this.trendData);
143830
143994
  } else {
143831
- container.innerHTML = renderDashboard(state6.stats, this.trendData);
143995
+ container.innerHTML = renderDashboard(stats, this.trendData);
143832
143996
  }
143833
- this.emit("stats-updated", state6.stats);
143997
+ this.emit("stats-updated", stats);
143834
143998
  if (!this.trendFetched && this.params.tenantId) {
143835
143999
  this.trendFetched = true;
143836
144000
  AlarmService.getAlarmTrend(this.params.tenantId, "week", "day").then((data) => {
@@ -163286,6 +163450,11 @@ var STYLES3 = `
163286
163450
  flex-direction: column;
163287
163451
  overflow: hidden;
163288
163452
  }
163453
+ #${MODAL_ID5} .abm-card.is-maximized {
163454
+ width: 100vw !important; max-width: 100vw !important;
163455
+ height: 100vh !important; max-height: 100vh !important; border-radius: 0;
163456
+ }
163457
+ #${MODAL_ID5} .myio-premium-chrome-header .abm-view-toggle { margin-left: 0; margin-right: 6px; }
163289
163458
  #${MODAL_ID5} .abm-header {
163290
163459
  background: ${TEAL2};
163291
163460
  color: #fff;
@@ -164284,7 +164453,11 @@ async function openAlarmBundleMapModal(params) {
164284
164453
  card.className = "abm-card";
164285
164454
  overlay.appendChild(card);
164286
164455
  document.body.appendChild(overlay);
164456
+ let chrome = null;
164457
+ let maximized = false;
164287
164458
  const closeModal2 = () => {
164459
+ chrome?.destroy();
164460
+ chrome = null;
164288
164461
  overlay.remove();
164289
164462
  params.onClose?.();
164290
164463
  };
@@ -164310,12 +164483,29 @@ async function openAlarmBundleMapModal(params) {
164310
164483
  <button class="abm-close-btn" id="abm-close-btn" title="Fechar">\u2715</button>
164311
164484
  </div>
164312
164485
  <div class="abm-body">${bodyHtml}</div>
164313
- <div class="abm-footer">
164314
- <button class="abm-btn abm-btn-secondary" id="abm-close-btn-footer">Fechar</button>
164315
- </div>
164316
164486
  `;
164317
164487
  card.querySelector("#abm-close-btn")?.addEventListener("click", closeModal2);
164318
- card.querySelector("#abm-close-btn-footer")?.addEventListener("click", closeModal2);
164488
+ const toggleEl = card.querySelector(".abm-view-toggle");
164489
+ chrome?.destroy();
164490
+ chrome = createPremiumModalChrome({
164491
+ modalId: "abm-modal",
164492
+ icon: "\u{1F517}",
164493
+ title: `Mapa de Alarmes GCDR${subtitle ? ` \u2014 ${escHtml5(subtitle)}` : ""}`,
164494
+ modalEl: card,
164495
+ headerSlot: card.querySelector(".abm-header"),
164496
+ maximized,
164497
+ headerRadius: "16px 16px 0 0",
164498
+ onMaximizeChange: (m) => {
164499
+ maximized = m;
164500
+ placeToggle();
164501
+ },
164502
+ onClose: closeModal2
164503
+ });
164504
+ const placeToggle = () => {
164505
+ const actions = chrome?.header.lastElementChild;
164506
+ if (toggleEl && actions) actions.insertBefore(toggleEl, actions.firstChild);
164507
+ };
164508
+ placeToggle();
164319
164509
  }
164320
164510
  render3(`<div class="abm-loading">\u23F3 Carregando mapa de alarmes...</div>`);
164321
164511
  try {