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 +972 -782
- package/dist/index.d.cts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +972 -782
- package/dist/myio-js-library.umd.js +973 -783
- package/dist/myio-js-library.umd.min.js +1 -1
- package/package.json +1 -1
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.
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
53111
53549
|
}
|
|
53112
|
-
|
|
53113
|
-
|
|
53114
|
-
|
|
53115
|
-
|
|
53116
|
-
|
|
53117
|
-
|
|
53118
|
-
|
|
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
|
|
53122
|
-
|
|
53123
|
-
|
|
53124
|
-
return
|
|
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
|
|
53127
|
-
const
|
|
53128
|
-
|
|
53129
|
-
|
|
53130
|
-
|
|
53131
|
-
|
|
53132
|
-
|
|
53133
|
-
|
|
53134
|
-
const
|
|
53135
|
-
let
|
|
53136
|
-
|
|
53137
|
-
|
|
53138
|
-
|
|
53139
|
-
|
|
53140
|
-
|
|
53141
|
-
|
|
53142
|
-
|
|
53143
|
-
|
|
53144
|
-
|
|
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
|
-
|
|
53196
|
-
}
|
|
53678
|
+
}
|
|
53679
|
+
return { label, name: name && name !== label ? name : null };
|
|
53197
53680
|
}
|
|
53198
|
-
function
|
|
53199
|
-
|
|
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
|
|
53207
|
-
const
|
|
53208
|
-
const
|
|
53209
|
-
const
|
|
53210
|
-
|
|
53211
|
-
|
|
53212
|
-
|
|
53213
|
-
|
|
53214
|
-
|
|
53215
|
-
|
|
53216
|
-
|
|
53217
|
-
|
|
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: "#
|
|
53308
|
-
borderColor:
|
|
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: "#
|
|
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
|
|
53416
|
-
const
|
|
53417
|
-
const
|
|
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="
|
|
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">${
|
|
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
|
|
53435
|
-
const
|
|
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"
|
|
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}"
|
|
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">${
|
|
53515
|
-
<div class="adm-kpi-label"
|
|
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
|
|
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("
|
|
53540
|
-
|
|
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
|
|
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"
|
|
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(
|
|
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">
|
|
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
|
-
|
|
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
|
|
54211
|
+
const buildSecondary = () => {
|
|
54212
|
+
if (!events) return;
|
|
54213
|
+
const occ = occEvents();
|
|
53733
54214
|
const dowCanvas = graficoPanel.querySelector("#admDowChart");
|
|
53734
|
-
if (
|
|
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 (
|
|
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
|
-
|
|
53765
|
-
|
|
53766
|
-
|
|
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 = (
|
|
53783
|
-
|
|
53784
|
-
|
|
53785
|
-
|
|
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
|
|
53790
|
-
|
|
53791
|
-
|
|
53792
|
-
|
|
53793
|
-
|
|
53794
|
-
|
|
53795
|
-
|
|
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
|
|
53800
|
-
|
|
53801
|
-
|
|
53802
|
-
|
|
53803
|
-
|
|
53804
|
-
|
|
53805
|
-
|
|
53806
|
-
|
|
53807
|
-
|
|
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
|
-
|
|
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 =
|
|
53862
|
-
|
|
53863
|
-
const
|
|
53864
|
-
const
|
|
53865
|
-
const
|
|
53866
|
-
const
|
|
53867
|
-
|
|
53868
|
-
|
|
53869
|
-
|
|
53870
|
-
|
|
53871
|
-
|
|
53872
|
-
|
|
53873
|
-
<td class="adm-rpt-cell">${
|
|
53874
|
-
|
|
53875
|
-
|
|
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
|
-
|
|
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="${
|
|
53880
|
-
${!isClosed ? `<button class="adm-tl-row-btn adm-tl-row-btn--snooze" data-action="snooze" data-item-id="${
|
|
53881
|
-
${!isClosed ? `<button class="adm-tl-row-btn adm-tl-row-btn--escalate" data-action="escalate" data-item-id="${
|
|
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
|
-
|
|
53886
|
-
|
|
53887
|
-
|
|
53888
|
-
|
|
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="${
|
|
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
|
|
53908
|
-
|
|
53909
|
-
|
|
53910
|
-
|
|
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
|
|
53917
|
-
const { html: occHtml, csvRows } = buildOccurrenceSection(occFirst, occLast);
|
|
54366
|
+
const { html: occHtml, csvRows } = buildOccurrenceSection(startMs, endMs);
|
|
53918
54367
|
lastCsvRows = csvRows;
|
|
53919
|
-
const
|
|
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
|
-
${
|
|
53923
|
-
${
|
|
53924
|
-
${
|
|
53925
|
-
${
|
|
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
|
|
53930
|
-
|
|
53931
|
-
|
|
53932
|
-
|
|
53933
|
-
|
|
53934
|
-
|
|
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
|
-
|
|
53959
|
-
|
|
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 ${
|
|
53991
|
-
<h3>
|
|
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
|
-
|
|
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
|
|
54115
|
-
|
|
54116
|
-
|
|
54117
|
-
|
|
54118
|
-
|
|
54119
|
-
|
|
54120
|
-
|
|
54121
|
-
|
|
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.
|
|
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:
|
|
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: #
|
|
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:
|
|
56633
|
-
color: #7c3aed;
|
|
56634
|
-
border: 1px solid
|
|
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: #
|
|
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: #
|
|
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
|
|
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: #
|
|
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: #
|
|
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: #
|
|
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
|
|
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: #
|
|
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: #
|
|
57894
|
-
border-bottom-color: #
|
|
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: #
|
|
57899
|
-
color: #
|
|
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: #
|
|
57972
|
-
background:
|
|
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:
|
|
57977
|
-
color: #
|
|
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: #
|
|
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: #
|
|
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: #
|
|
58020
|
-
border-color: #6d28d9;
|
|
58021
|
-
color: #
|
|
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: #
|
|
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:
|
|
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: #
|
|
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,
|
|
143993
|
+
updateDashboard(container, stats, this.trendData);
|
|
143830
143994
|
} else {
|
|
143831
|
-
container.innerHTML = renderDashboard(
|
|
143995
|
+
container.innerHTML = renderDashboard(stats, this.trendData);
|
|
143832
143996
|
}
|
|
143833
|
-
this.emit("stats-updated",
|
|
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("
|
|
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 {
|