web-mojo 2.2.9 → 2.2.10

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/admin.es.js CHANGED
@@ -3,14 +3,14 @@ import { V as View, M as MOJOUtils, r as rest } from "./chunks/Rest-ChN4Ntac.js"
3
3
  import "./chunks/WebSocketClient-bLYhu2Wv.js";
4
4
  import { D as Dialog$1 } from "./chunks/Dialog-Cu_Dx46k.js";
5
5
  import { W } from "./chunks/Dialog-Cu_Dx46k.js";
6
- import { M as MetricsChart, c as MetricsMiniChartWidget, P as PieChart } from "./chunks/MetricsMiniChartWidget-Cg5Hyx28.js";
6
+ import { M as MetricsChart, c as MetricsMiniChartWidget, P as PieChart } from "./chunks/MetricsMiniChartWidget-DL5stA6A.js";
7
7
  import { aj as MemberList, T as TableView, B as IncidentEventList, ap as PushDeviceList, ai as LogList, c as TabView, b as TablePage, M as Member, ak as MemberForms, ay as GeoLocatedIP, az as GeoLocatedIPList, aB as TicketList, J as IncidentList, a0 as IncidentStats, V as IncidentHistoryList, U as IncidentHistory, H as Incident, C as ChatView, K as IncidentForms, I as IncidentEvent, G as IncidentEventForms, aD as TicketNoteList, aC as TicketNote, aA as Ticket, aE as TicketForms, aF as TicketCategories, W as RuleSet, a2 as MatchByOptions, a1 as BundleByOptions, _ as RuleList, X as RuleSetList, k as EmailDomainForms, j as EmailDomainList, E as EmailDomain, n as MailboxForms, m as MailboxList, l as Mailbox, s as EmailTemplate, u as EmailTemplateForms, t as EmailTemplateList, o as SentMessage, q as SentMessageList, av as PushDeliveryList, aw as PushConfigForms, at as PushConfigList, ax as PushTemplateForms, ar as PushTemplateList, a6 as Job, ac as JobEventList, aa as JobLogList, a8 as JobForms, a7 as JobList, af as JobRunnerList, ad as JobsEngineStats, ag as JobRunnerForms, ae as JobRunner, ah as Log, al as MetricsPermission, an as MetricsForms, am as MetricsPermissionList, x as FileManagerForms, w as FileManagerList, y as File, A as FileForms, z as FileList, i as S3BucketForms, h as S3BucketList } from "./chunks/ChatView-DfhhZKoN.js";
8
8
  import DataView from "./chunks/DataView-i7kpJjVQ.js";
9
9
  import { F as FormView, a as applyFileDropMixin } from "./chunks/FormView-B_90L1RY.js";
10
10
  import { MapView } from "./map.es.js";
11
11
  import { M as Model, C as Collection } from "./chunks/Collection-G5_fJcpB.js";
12
12
  import { L as LightboxGallery, P as PDFViewer } from "./chunks/PDFViewer--jlqnuVw.js";
13
- import { B, a, V, b, c, d } from "./chunks/version-CHVtoVR0.js";
13
+ import { B, a, V, b, c, d } from "./chunks/version-DOHckOGK.js";
14
14
  class AdminHeaderView extends View {
15
15
  constructor(options = {}) {
16
16
  super({
@@ -2249,6 +2249,795 @@ class GeoLocatedIPTablePage extends TablePage {
2249
2249
  }
2250
2250
  }
2251
2251
  }
2252
+ class MapLibreView extends View {
2253
+ constructor(options = {}) {
2254
+ super({
2255
+ className: "maplibre-view",
2256
+ ...options
2257
+ });
2258
+ this.markers = options.markers || [];
2259
+ this.center = options.center || null;
2260
+ this.zoom = options.zoom || 13;
2261
+ this.height = options.height || 400;
2262
+ this.pitch = options.pitch || 0;
2263
+ this.bearing = options.bearing || 0;
2264
+ this.mapStyle = options.style || "streets";
2265
+ this.showNavigationControl = options.showNavigationControl !== false;
2266
+ this.autoFitBounds = options.autoFitBounds !== false;
2267
+ this.map = null;
2268
+ this.mapMarkers = [];
2269
+ this.pendingMarkers = [...this.markers];
2270
+ this.lineSources = options.lineSources || [];
2271
+ this.pendingLineData = /* @__PURE__ */ new Map();
2272
+ this.template = `
2273
+ <div class="maplibre-container">
2274
+ <div id="maplibre-{{id}}" style="height: {{height}}px; width: 100%; border-radius: 0.375rem; border: 1px solid #dee2e6;"></div>
2275
+ </div>
2276
+ `;
2277
+ }
2278
+ async onAfterRender() {
2279
+ await this.loadMapLibre();
2280
+ await this.initializeMap();
2281
+ }
2282
+ async loadMapLibre() {
2283
+ if (window.maplibregl) return;
2284
+ const link = document.createElement("link");
2285
+ link.rel = "stylesheet";
2286
+ link.href = "https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.css";
2287
+ document.head.appendChild(link);
2288
+ return new Promise((resolve, reject) => {
2289
+ const script = document.createElement("script");
2290
+ script.src = "https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.js";
2291
+ script.onload = resolve;
2292
+ script.onerror = reject;
2293
+ document.head.appendChild(script);
2294
+ });
2295
+ }
2296
+ getMapStyle() {
2297
+ const styles = {
2298
+ streets: "https://demotiles.maplibre.org/style.json",
2299
+ dark: {
2300
+ version: 8,
2301
+ sources: {
2302
+ osm: {
2303
+ type: "raster",
2304
+ tiles: ["https://a.tile.openstreetmap.org/{z}/{x}/{y}.png"],
2305
+ tileSize: 256,
2306
+ attribution: "© OpenStreetMap contributors"
2307
+ }
2308
+ },
2309
+ layers: [{
2310
+ id: "osm",
2311
+ type: "raster",
2312
+ source: "osm"
2313
+ }],
2314
+ glyphs: "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf"
2315
+ },
2316
+ light: {
2317
+ version: 8,
2318
+ sources: {
2319
+ osm: {
2320
+ type: "raster",
2321
+ tiles: ["https://a.tile.openstreetmap.org/{z}/{x}/{y}.png"],
2322
+ tileSize: 256,
2323
+ attribution: "© OpenStreetMap contributors"
2324
+ }
2325
+ },
2326
+ layers: [{
2327
+ id: "osm",
2328
+ type: "raster",
2329
+ source: "osm"
2330
+ }],
2331
+ glyphs: "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf"
2332
+ },
2333
+ satellite: {
2334
+ version: 8,
2335
+ sources: {
2336
+ satellite: {
2337
+ type: "raster",
2338
+ tiles: ["https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"],
2339
+ tileSize: 256,
2340
+ attribution: "© Esri"
2341
+ }
2342
+ },
2343
+ layers: [{
2344
+ id: "satellite",
2345
+ type: "raster",
2346
+ source: "satellite"
2347
+ }],
2348
+ glyphs: "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf"
2349
+ },
2350
+ terrain: {
2351
+ version: 8,
2352
+ sources: {
2353
+ terrain: {
2354
+ type: "raster",
2355
+ tiles: ["https://a.tile.opentopomap.org/{z}/{x}/{y}.png"],
2356
+ tileSize: 256,
2357
+ attribution: "© OpenTopoMap contributors"
2358
+ }
2359
+ },
2360
+ layers: [{
2361
+ id: "terrain",
2362
+ type: "raster",
2363
+ source: "terrain"
2364
+ }],
2365
+ glyphs: "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf"
2366
+ }
2367
+ };
2368
+ return styles[this.mapStyle] || styles.streets;
2369
+ }
2370
+ async initializeMap() {
2371
+ const mapElement = this.element.querySelector(`#maplibre-${this.id}`);
2372
+ if (!mapElement || !window.maplibregl) return;
2373
+ let mapCenter = this.center;
2374
+ if (!mapCenter && this.markers.length > 0) {
2375
+ mapCenter = [this.markers[0].lng, this.markers[0].lat];
2376
+ }
2377
+ if (!mapCenter) {
2378
+ mapCenter = [0, 0];
2379
+ }
2380
+ this.map = new window.maplibregl.Map({
2381
+ container: mapElement,
2382
+ style: this.getMapStyle(),
2383
+ center: mapCenter,
2384
+ zoom: this.zoom,
2385
+ pitch: this.pitch,
2386
+ bearing: this.bearing
2387
+ });
2388
+ if (this.showNavigationControl) {
2389
+ this.map.addControl(new window.maplibregl.NavigationControl(), "top-right");
2390
+ }
2391
+ this.map.on("load", () => {
2392
+ const initialMarkers = this.pendingMarkers.length ? this.pendingMarkers : this.markers;
2393
+ this.updateMarkers(initialMarkers);
2394
+ this.lineSources.forEach((source) => this.addLineSource(source));
2395
+ this.pendingLineData.forEach((source) => this.addLineSource(source));
2396
+ this.pendingLineData.clear();
2397
+ });
2398
+ }
2399
+ addMarkers(markers) {
2400
+ if (!this.map || !Array.isArray(markers)) {
2401
+ this.pendingMarkers = Array.isArray(markers) ? markers : [];
2402
+ return;
2403
+ }
2404
+ markers.forEach((markerData) => {
2405
+ const { lng, lat, popup, color, icon, size } = markerData;
2406
+ if (!lng || !lat) return;
2407
+ const el = document.createElement("div");
2408
+ el.className = "maplibre-marker";
2409
+ const markerSize = Number(size) || 30;
2410
+ el.style.width = `${markerSize}px`;
2411
+ el.style.height = `${markerSize}px`;
2412
+ el.style.borderRadius = "50%";
2413
+ el.style.backgroundColor = color || "#3b82f6";
2414
+ el.style.border = "3px solid white";
2415
+ el.style.boxShadow = "0 2px 4px rgba(0,0,0,0.3)";
2416
+ el.style.cursor = "pointer";
2417
+ el.style.display = "flex";
2418
+ el.style.alignItems = "center";
2419
+ el.style.justifyContent = "center";
2420
+ if (icon) {
2421
+ el.innerHTML = `<i class="${icon}" style="color: white;"></i>`;
2422
+ }
2423
+ const marker = new window.maplibregl.Marker({ element: el }).setLngLat([lng, lat]).addTo(this.map);
2424
+ if (popup) {
2425
+ const mapPopup = new window.maplibregl.Popup({ offset: 25 }).setHTML(popup);
2426
+ marker.setPopup(mapPopup);
2427
+ }
2428
+ this.mapMarkers.push(marker);
2429
+ });
2430
+ }
2431
+ fitBounds() {
2432
+ if (!this.map || this.markers.length === 0) return;
2433
+ const bounds = new window.maplibregl.LngLatBounds();
2434
+ this.markers.forEach((marker) => {
2435
+ bounds.extend([marker.lng, marker.lat]);
2436
+ });
2437
+ this.map.fitBounds(bounds, {
2438
+ padding: 50,
2439
+ maxZoom: 15
2440
+ });
2441
+ }
2442
+ updateMarkers(newMarkers) {
2443
+ const markers = Array.isArray(newMarkers) ? newMarkers : [];
2444
+ this.markers = markers;
2445
+ this.pendingMarkers = markers;
2446
+ if (!this.map) return;
2447
+ this.clearMarkers();
2448
+ this.addMarkers(markers);
2449
+ if (this.autoFitBounds && markers.length > 1) {
2450
+ this.fitBounds();
2451
+ } else if (markers.length === 1 && this.map) {
2452
+ this.map.flyTo({
2453
+ center: [markers[0].lng, markers[0].lat],
2454
+ zoom: this.zoom
2455
+ });
2456
+ }
2457
+ }
2458
+ clearMarkers() {
2459
+ this.mapMarkers.forEach((marker) => {
2460
+ marker.remove();
2461
+ });
2462
+ this.mapMarkers = [];
2463
+ }
2464
+ addLineSource({ id, data, paint, layout }) {
2465
+ if (!id || !data) return;
2466
+ const sourceId = `${id}-source`;
2467
+ if (!this.map) {
2468
+ this.pendingLineData.set(id, { id, data, paint, layout });
2469
+ return;
2470
+ }
2471
+ if (this.map.getLayer(id)) {
2472
+ this.map.removeLayer(id);
2473
+ }
2474
+ if (this.map.getSource(sourceId)) {
2475
+ this.map.removeSource(sourceId);
2476
+ }
2477
+ this.map.addSource(sourceId, {
2478
+ type: "geojson",
2479
+ data
2480
+ });
2481
+ this.map.addLayer({
2482
+ id,
2483
+ type: "line",
2484
+ source: sourceId,
2485
+ paint: {
2486
+ "line-color": "#3b82f6",
2487
+ "line-width": 2,
2488
+ "line-opacity": 0.6,
2489
+ ...paint
2490
+ },
2491
+ layout: {
2492
+ "line-cap": "round",
2493
+ "line-join": "round",
2494
+ ...layout
2495
+ }
2496
+ });
2497
+ }
2498
+ updateLineSource(id, { data, paint, layout } = {}) {
2499
+ if (!id || !data) return;
2500
+ const sourceId = `${id}-source`;
2501
+ if (!this.map) {
2502
+ this.pendingLineData.set(id, { id, data, paint, layout });
2503
+ return;
2504
+ }
2505
+ if (this.map.getSource(sourceId)) {
2506
+ this.map.getSource(sourceId).setData(data);
2507
+ if (paint || layout) {
2508
+ if (paint) {
2509
+ Object.entries(paint).forEach(([key, value]) => {
2510
+ this.map.setPaintProperty(id, key, value);
2511
+ });
2512
+ }
2513
+ if (layout) {
2514
+ Object.entries(layout).forEach(([key, value]) => {
2515
+ this.map.setLayoutProperty(id, key, value);
2516
+ });
2517
+ }
2518
+ }
2519
+ } else {
2520
+ this.addLineSource({ id, data, paint, layout });
2521
+ }
2522
+ }
2523
+ setView(lng, lat, zoom = null) {
2524
+ if (!this.map) return;
2525
+ this.map.flyTo({
2526
+ center: [lng, lat],
2527
+ zoom: zoom || this.zoom
2528
+ });
2529
+ }
2530
+ setZoom(zoom) {
2531
+ if (!this.map) return;
2532
+ this.map.setZoom(zoom);
2533
+ }
2534
+ setPitch(pitch) {
2535
+ if (!this.map) return;
2536
+ this.map.setPitch(pitch);
2537
+ }
2538
+ setBearing(bearing) {
2539
+ if (!this.map) return;
2540
+ this.map.setBearing(bearing);
2541
+ }
2542
+ async onBeforeDestroy() {
2543
+ if (this.map) {
2544
+ this.map.remove();
2545
+ this.map = null;
2546
+ }
2547
+ await super.onBeforeDestroy();
2548
+ }
2549
+ }
2550
+ const COUNTRY_CENTROIDS = {
2551
+ "AD": { name: "Andorra", lat: 42.54859834854764, lng: 1.5802243611232873 },
2552
+ "AE": { name: "United Arab Emirates", lat: 24.18250292309135, lng: 54.27920525789581 },
2553
+ "AF": { name: "Afghanistan", lat: 34.13402601376932, lng: 66.59216131095278 },
2554
+ "AG": { name: "Antigua and Barbuda", lat: 17.07146759372967, lng: -61.78530823226373 },
2555
+ "AI": { name: "Anguilla", lat: 18.222874004219086, lng: -63.06008343771806 },
2556
+ "AL": { name: "Albania", lat: 41.14165894891656, lng: 20.061082767269493 },
2557
+ "AM": { name: "Armenia", lat: 40.17841274230679, lng: 45.05490831965259 },
2558
+ "AO": { name: "Angola", lat: -12.167424062667942, lng: 17.651768783079 },
2559
+ "AQ": { name: "Antarctica", lat: -77.16987521415838, lng: -177.56451613408842 },
2560
+ "AR": { name: "Argentina", lat: -35.697270518120085, lng: -64.53238503843076 },
2561
+ "AS": { name: "American Samoa", lat: -14.305711987770538, lng: -170.7007316174498 },
2562
+ "AT": { name: "Austria", lat: 47.631858269895794, lng: 13.797778364631036 },
2563
+ "AU": { name: "Australia", lat: -25.697337673983082, lng: 134.02277170916162 },
2564
+ "AW": { name: "Aruba", lat: 12.515625722992898, lng: -69.97564014284046 },
2565
+ "AZ": { name: "Azerbaijan", lat: 40.3920509942049, lng: 48.634592670644324 },
2566
+ "BA": { name: "Bosnia and Herzegovina", lat: 44.14415356126429, lng: 17.83467240787538 },
2567
+ "BB": { name: "Barbados", lat: 13.183219369337529, lng: -59.557383949150285 },
2568
+ "BD": { name: "Bangladesh", lat: 23.673728665121, lng: 90.43212562608613 },
2569
+ "BE": { name: "Belgium", lat: 50.6182138854095, lng: 4.675010154696485 },
2570
+ "BF": { name: "Burkina Faso", lat: 12.108709036312737, lng: -1.6932816211842325 },
2571
+ "BG": { name: "Bulgaria", lat: 42.82043677302438, lng: 25.251739122561908 },
2572
+ "BH": { name: "Bahrain", lat: 26.04798501537066, lng: 50.540695402276775 },
2573
+ "BI": { name: "Burundi", lat: -3.261251993278643, lng: 29.88518227845293 },
2574
+ "BJ": { name: "Benin", lat: 9.503013199615893, lng: 2.305714528830206 },
2575
+ "BL": { name: "Saint Barthelemy", lat: 17.90561691241738, lng: -62.83051610005156 },
2576
+ "BM": { name: "Bermuda", lat: 32.315067430740726, lng: -64.7458500599169 },
2577
+ "BN": { name: "Brunei Darussalam", lat: 4.543205889917609, lng: 114.6430958360464 },
2578
+ "BO": { name: "Bolivia", lat: -16.7312488393574, lng: -64.45209597511206 },
2579
+ "BQ": { name: "Bonaire", lat: 12.180844982440338, lng: -68.29350445958761 },
2580
+ "BQ": { name: "Saba", lat: 17.632512616389718, lng: -63.23739481909494 },
2581
+ "BQ": { name: "Saint Eustatius", lat: 17.4919042294197, lng: -62.978230589445026 },
2582
+ "BR": { name: "Brazil", lat: -11.524630416426652, lng: -54.355206608256424 },
2583
+ "BS": { name: "Bahamas", lat: 24.72162633646784, lng: -78.07275370060313 },
2584
+ "BT": { name: "Bhutan", lat: 27.42163933959824, lng: 90.46716647173861 },
2585
+ "BV": { name: "Bouvet Island", lat: -54.42316679395248, lng: 3.411969465057627 },
2586
+ "BW": { name: "Botswana", lat: -22.236609002062902, lng: 23.85779956995608 },
2587
+ "BY": { name: "Belarus", lat: 53.46791374543163, lng: 27.964252054715104 },
2588
+ "BZ": { name: "Belize", lat: 17.24252476647155, lng: -88.68273510023441 },
2589
+ "CA": { name: "Canada", lat: 57.550480044655636, lng: -98.41680517868062 },
2590
+ "CC": { name: "Cocos Islands", lat: -12.171249450199545, lng: 96.83688767323002 },
2591
+ "CD": { name: "Congo DRC", lat: -3.338629596207896, lng: 23.419827574282188 },
2592
+ "CF": { name: "Central African Republic", lat: 6.331390033944319, lng: 20.520743419397256 },
2593
+ "CG": { name: "Congo", lat: -0.7294391595233845, lng: 14.879732849491393 },
2594
+ "CH": { name: "Switzerland", lat: 46.73678128684938, lng: 8.286928794895285 },
2595
+ "CI": { name: "Côte d'Ivoire", lat: 7.536779279421307, lng: -5.571710194917734 },
2596
+ "CK": { name: "Cook Islands", lat: -21.222613253399842, lng: -159.78768870952257 },
2597
+ "CL": { name: "Chile", lat: -37.82938283049967, lng: -70.76863431739216 },
2598
+ "CM": { name: "Cameroon", lat: 6.294168487480992, lng: 12.948474142398263 },
2599
+ "CN": { name: "China", lat: 38.07325481105728, lng: 104.69113855849604 },
2600
+ "CO": { name: "Colombia", lat: 4.187753877352739, lng: -72.6445066243485 },
2601
+ "CR": { name: "Costa Rica", lat: 9.863467407406214, lng: -84.14673625701816 },
2602
+ "CU": { name: "Cuba", lat: 21.476176522869448, lng: -79.69817857618705 },
2603
+ "CV": { name: "Cabo Verde", lat: 15.076411518651643, lng: -23.63401005900474 },
2604
+ "CW": { name: "Curacao", lat: 12.199996647939832, lng: -68.96939768599042 },
2605
+ "CX": { name: "Christmas Island", lat: -10.446440802183416, lng: 105.70209512200549 },
2606
+ "CY": { name: "Cyprus", lat: 35.11700416345239, lng: 33.375346009199205 },
2607
+ "CZ": { name: "Czech Republic", lat: 49.74917370930982, lng: 15.383273292023533 },
2608
+ "DE": { name: "Germany", lat: 51.08304539800482, lng: 10.426171427430804 },
2609
+ "DJ": { name: "Djibouti", lat: 11.750235727618804, lng: 42.613496898789506 },
2610
+ "DK": { name: "Denmark", lat: 56.00118817971057, lng: 9.378670542409406 },
2611
+ "DM": { name: "Dominica", lat: 15.429269860940513, lng: -61.360471946942994 },
2612
+ "DO": { name: "Dominican Republic", lat: 18.77954818522993, lng: -70.43495198520012 },
2613
+ "DZ": { name: "Algeria", lat: 28.350969744889056, lng: 2.6558464719769135 },
2614
+ "EC": { name: "Ecuador", lat: -1.5642721388853116, lng: -78.4630326109714 },
2615
+ "EE": { name: "Estonia", lat: 58.648108311231034, lng: 25.916870250633806 },
2616
+ "EG": { name: "Egypt", lat: 26.60517034450628, lng: 30.240135435012338 },
2617
+ "ER": { name: "Eritrea", lat: 15.005533147667684, lng: 39.2672401449901 },
2618
+ "ES": { name: "Spain", lat: 40.365008336683836, lng: -3.6516251409956983 },
2619
+ "ES": { name: "Canarias", lat: 28.297665106525546, lng: -16.53799441021647 },
2620
+ "ET": { name: "Ethiopia", lat: 8.729389557048396, lng: 39.914902886544276 },
2621
+ "FI": { name: "Finland", lat: 65.01578959749911, lng: 25.65738433454702 },
2622
+ "FJ": { name: "Fiji", lat: -17.822470952336204, lng: 177.98144613732626 },
2623
+ "FK": { name: "Falkland Islands", lat: -51.75901312766726, lng: -58.746646363799854 },
2624
+ "FM": { name: "Micronesia", lat: 6.8789448129255435, lng: 158.2291899444527 },
2625
+ "FO": { name: "Faroe Islands", lat: 62.130896281495346, lng: -6.9811060913122835 },
2626
+ "FR": { name: "France", lat: 46.6423682169416, lng: 2.1940236627886227 },
2627
+ "GA": { name: "Gabon", lat: -0.628448459921234, lng: 11.839410898545754 },
2628
+ "GB": { name: "United Kingdom", lat: 53.97844735080214, lng: -2.852943909329258 },
2629
+ "GD": { name: "Grenada", lat: 12.112926656338907, lng: -61.67937937204098 },
2630
+ "GE": { name: "Georgia", lat: 42.17986277737226, lng: 43.378866534112234 },
2631
+ "GF": { name: "French Guiana", lat: 3.857429742497078, lng: -53.32232307156624 },
2632
+ "GG": { name: "Guernsey", lat: 49.45870771378872, lng: -2.576392582891568 },
2633
+ "GH": { name: "Ghana", lat: 7.94530467243628, lng: -1.219233362526581 },
2634
+ "GI": { name: "Gibraltar", lat: 36.14022671336082, lng: -5.345549484594358 },
2635
+ "GL": { name: "Greenland", lat: 74.16847218965994, lng: -42.07567788066985 },
2636
+ "GM": { name: "Gambia", lat: 13.428617959189328, lng: -15.383380385869662 },
2637
+ "GN": { name: "Guinea", lat: 10.255986541378112, lng: -10.986948848040218 },
2638
+ "GP": { name: "Guadeloupe", lat: 16.24420002705553, lng: -61.54382262282755 },
2639
+ "GQ": { name: "Equatorial Guinea", lat: 1.5954643936590733, lng: 10.425456672353823 },
2640
+ "GR": { name: "Greece", lat: 39.42012261727978, lng: 23.110368936161876 },
2641
+ "GS": { name: "South Georgia and South Sandwich Islands", lat: -54.37666443862139, lng: -36.77509575898928 },
2642
+ "GT": { name: "Guatemala", lat: 15.820878515352684, lng: -90.31219349119617 },
2643
+ "GU": { name: "Guam", lat: 13.445430479945276, lng: 144.78024458298802 },
2644
+ "GW": { name: "Guinea-Bissau", lat: 11.980075324820504, lng: -14.980186756910847 },
2645
+ "GY": { name: "Guyana", lat: 4.68211981385056, lng: -58.91352612754667 },
2646
+ "HM": { name: "Heard Island and McDonald Islands", lat: -53.084170035513736, lng: 73.49298560844045 },
2647
+ "HN": { name: "Honduras", lat: 14.740370695750006, lng: -86.49251679006962 },
2648
+ "HR": { name: "Croatia", lat: 44.91192100856702, lng: 16.625761129583374 },
2649
+ "HT": { name: "Haiti", lat: 18.883520486983567, lng: -72.89291379842 },
2650
+ "HU": { name: "Hungary", lat: 47.22527332486294, lng: 19.39620048366142 },
2651
+ "ID": { name: "Indonesia", lat: 0.15591979959037652, lng: 113.96538246847302 },
2652
+ "IE": { name: "Ireland", lat: 53.30489539816495, lng: -8.241128545554096 },
2653
+ "IL": { name: "Israel", lat: 31.513542220043195, lng: 35.027923472437024 },
2654
+ "IM": { name: "Isle of Man", lat: 54.22855301008011, lng: -4.532995055468449 },
2655
+ "IN": { name: "India", lat: 23.586300567746722, lng: 81.17300408530181 },
2656
+ "IO": { name: "British Indian Ocean Territory", lat: -7.323548444385743, lng: 72.43501618476016 },
2657
+ "IQ": { name: "Iraq", lat: 33.105075667527906, lng: 43.832529181056884 },
2658
+ "IR": { name: "Iran", lat: 32.906023742890056, lng: 54.237077001065444 },
2659
+ "IS": { name: "Iceland", lat: 65.12360920205514, lng: -19.05682967106099 },
2660
+ "IT": { name: "Italy", lat: 42.98201127614267, lng: 12.763657166123137 },
2661
+ "JE": { name: "Jersey", lat: 49.215396925724306, lng: -2.1291601162653575 },
2662
+ "JM": { name: "Jamaica", lat: 18.12207889341651, lng: -77.30358894542778 },
2663
+ "JO": { name: "Jordan", lat: 31.387064884449156, lng: 36.95728884547246 },
2664
+ "JP": { name: "Japan", lat: 36.76738832597829, lng: 137.46934234351835 },
2665
+ "KE": { name: "Kenya", lat: 0.6899182318376179, lng: 37.95309411262371 },
2666
+ "KG": { name: "Kyrgyzstan", lat: 41.35698905438358, lng: 74.17532903468319 },
2667
+ "KH": { name: "Cambodia", lat: 12.699186865507775, lng: 105.03973078680701 },
2668
+ "KI": { name: "Kiribati", lat: 1.8676673749241066, lng: -157.39024189323504 },
2669
+ "KM": { name: "Comoros", lat: -11.658861474508491, lng: 43.34826587429403 },
2670
+ "KN": { name: "Saint Kitts and Nevis", lat: 17.314736299587768, lng: -62.74560385895787 },
2671
+ "KP": { name: "North Korea", lat: 40.19198091470839, lng: 127.3379805653744 },
2672
+ "KR": { name: "South Korea", lat: 36.402386712544114, lng: 127.76224551357649 },
2673
+ "KW": { name: "Kuwait", lat: 29.281360965443092, lng: 47.56311109320184 },
2674
+ "KY": { name: "Cayman Islands", lat: 19.311231805620288, lng: -81.25203208977878 },
2675
+ "KZ": { name: "Kazakhstan", lat: 47.641465195176835, lng: 66.3759193479301 },
2676
+ "LA": { name: "Laos", lat: 18.117282736873282, lng: 103.76375899026448 },
2677
+ "LB": { name: "Lebanon", lat: 33.91160170722086, lng: 35.89651946324749 },
2678
+ "LC": { name: "Saint Lucia", lat: 13.895749185129906, lng: -60.9689510935251 },
2679
+ "LI": { name: "Liechtenstein", lat: 47.14627562133036, lng: 9.547674672376376 },
2680
+ "LK": { name: "Sri Lanka", lat: 7.696630939329944, lng: 80.66931169770622 },
2681
+ "LR": { name: "Liberia", lat: 6.52012979398834, lng: -9.258988935497618 },
2682
+ "LS": { name: "Lesotho", lat: -29.60168116924817, lng: 28.24475317864227 },
2683
+ "LT": { name: "Lithuania", lat: 55.29437393417175, lng: 23.946021605013534 },
2684
+ "LU": { name: "Luxembourg", lat: 49.77523454542369, lng: 6.103230338458876 },
2685
+ "LV": { name: "Latvia", lat: 56.813853047554154, lng: 24.693671325654403 },
2686
+ "LY": { name: "Libya", lat: 27.202915771690794, lng: 17.91133392454237 },
2687
+ "MA": { name: "Morocco", lat: 28.687598134979325, lng: -8.817212587250811 },
2688
+ "MC": { name: "Monaco", lat: 43.74798224995656, lng: 7.412820873271196 },
2689
+ "MD": { name: "Moldova", lat: 47.0725674580696, lng: 28.391111865941348 },
2690
+ "ME": { name: "Montenegro", lat: 42.73694835210454, lng: 19.29505087156758 },
2691
+ "MF": { name: "Saint Martin", lat: 18.078012113224464, lng: -63.06678525361946 },
2692
+ "MG": { name: "Madagascar", lat: -19.04163612493041, lng: 46.68493466832544 },
2693
+ "MH": { name: "Marshall Islands", lat: 7.307929900994344, lng: 168.72016025351076 },
2694
+ "MK": { name: "North Macedonia", lat: 41.59402890143112, lng: 21.70998923872772 },
2695
+ "ML": { name: "Mali", lat: 17.168146208584837, lng: -4.346399841781153 },
2696
+ "MM": { name: "Myanmar", lat: 19.901227931399873, lng: 97.08892285397344 },
2697
+ "MN": { name: "Mongolia", lat: 47.08644454604851, lng: 103.3987360327455 },
2698
+ "MP": { name: "Northern Mariana Islands", lat: 15.178063516432115, lng: 145.74119737203134 },
2699
+ "MQ": { name: "Martinique", lat: 14.642697353597645, lng: -61.01432380875083 },
2700
+ "MR": { name: "Mauritania", lat: 20.466731212820022, lng: -10.495079045035716 },
2701
+ "MS": { name: "Montserrat", lat: 16.735363391460357, lng: -62.18693281256255 },
2702
+ "MT": { name: "Malta", lat: 35.890522650899314, lng: 14.441922442508494 },
2703
+ "MU": { name: "Mauritius", lat: -20.28142317475198, lng: 57.56415671066546 },
2704
+ "MV": { name: "Maldives", lat: -0.6065577168009924, lng: 73.10076245140479 },
2705
+ "MW": { name: "Malawi", lat: -13.128986464184024, lng: 34.23441182298881 },
2706
+ "MX": { name: "Mexico", lat: 23.87436068093592, lng: -101.55399731630118 },
2707
+ "MY": { name: "Malaysia", lat: 3.6716608556387857, lng: 114.63330303992869 },
2708
+ "MZ": { name: "Mozambique", lat: -17.525230309488748, lng: 35.208577031290176 },
2709
+ "NA": { name: "Namibia", lat: -21.90858163281473, lng: 18.16451345845268 },
2710
+ "NC": { name: "New Caledonia", lat: -21.33003372660827, lng: 165.50767040438612 },
2711
+ "NE": { name: "Niger", lat: 17.08105392407292, lng: 8.86863247002646 },
2712
+ "NF": { name: "Norfolk Island", lat: -29.037654445046282, lng: 167.95259597483337 },
2713
+ "NG": { name: "Nigeria", lat: 9.61029352034213, lng: 8.147714845256194 },
2714
+ "NI": { name: "Nicaragua", lat: 12.893566631930554, lng: -85.016088327669 },
2715
+ "NL": { name: "Netherlands", lat: 52.134054128923886, lng: 5.554136426128487 },
2716
+ "NO": { name: "Norway", lat: 64.97775882947745, lng: 16.670259272390894 },
2717
+ "NP": { name: "Nepal", lat: 28.300920699755657, lng: 84.1338898313567 },
2718
+ "NR": { name: "Nauru", lat: -0.5221021440668993, lng: 166.92937633139178 },
2719
+ "NU": { name: "Niue", lat: -19.05230921680393, lng: -169.86878106699083 },
2720
+ "NZ": { name: "New Zealand", lat: -43.82765432544426, lng: 170.69035541428696 },
2721
+ "OM": { name: "Oman", lat: 20.7242833183209, lng: 55.841088119829 },
2722
+ "PA": { name: "Panama", lat: 8.439536749576892, lng: -80.14428761482796 },
2723
+ "PE": { name: "Peru", lat: -8.522717984240291, lng: -74.11416196781884 },
2724
+ "PF": { name: "French Polynesia", lat: -17.674684080120677, lng: -149.40041671099763 },
2725
+ "PG": { name: "Papua New Guinea", lat: -7.156912819152135, lng: 144.8348942994695 },
2726
+ "PH": { name: "Philippines", lat: 15.586542251094242, lng: 121.82208941416745 },
2727
+ "PK": { name: "Pakistan", lat: 30.116188371410882, lng: 69.08835090769651 },
2728
+ "PL": { name: "Poland", lat: 52.06848055692473, lng: 19.43573279234468 },
2729
+ "PM": { name: "Saint Pierre and Miquelon", lat: 46.95153913615198, lng: -56.32465353437558 },
2730
+ "PN": { name: "Pitcairn", lat: -24.366121747565458, lng: -128.3149848627581 },
2731
+ "PR": { name: "Puerto Rico", lat: 18.216224086610914, lng: -66.49425339593509 },
2732
+ "PS": { name: "Palestinian Territory", lat: 31.930818736453883, lng: 35.24251184154588 },
2733
+ "PT": { name: "Portugal", lat: 39.67529214702138, lng: -7.933662183874006 },
2734
+ "PW": { name: "Palau", lat: 7.534775852547396, lng: 134.57965086721052 },
2735
+ "PY": { name: "Paraguay", lat: -23.42190559259428, lng: -58.38906357428651 },
2736
+ "QA": { name: "Qatar", lat: 25.318528486425386, lng: 51.19794918743203 },
2737
+ "RE": { name: "Réunion", lat: -21.119825460665105, lng: 55.54393506194689 },
2738
+ "RO": { name: "Romania", lat: 45.82454894397586, lng: 25.094158201563292 },
2739
+ "RS": { name: "Serbia", lat: 44.02679870131969, lng: 20.85677444395745 },
2740
+ "RU": { name: "Russian Federation", lat: 59.039434214106194, lng: 98.6704990698032 },
2741
+ "RW": { name: "Rwanda", lat: -2.014687460047154, lng: 29.919439681764082 },
2742
+ "SA": { name: "Saudi Arabia", lat: 24.136038144757897, lng: 44.600958178225596 },
2743
+ "SB": { name: "Solomon Islands", lat: -9.613104734596515, lng: 160.16475795033884 },
2744
+ "SC": { name: "Seychelles", lat: -4.660002318822744, lng: 55.47250789595527 },
2745
+ "SD": { name: "Sudan", lat: 15.67060230984256, lng: 29.951458283594064 },
2746
+ "SE": { name: "Sweden", lat: 62.73420986108448, lng: 17.062431988004956 },
2747
+ "SG": { name: "Singapore", lat: 1.3528251890006349, lng: 103.81025757634053 },
2748
+ "SH": { name: "Saint Helena", lat: -15.962963318340398, lng: -5.717391620813109 },
2749
+ "SI": { name: "Slovenia", lat: 46.13759229564504, lng: 14.890636899973781 },
2750
+ "SJ": { name: "Svalbard", lat: 78.57318936469626, lng: 16.036378851505283 },
2751
+ "SK": { name: "Slovakia", lat: 48.69808390520484, lng: 19.581015362490966 },
2752
+ "SL": { name: "Sierra Leone", lat: 8.561330384750587, lng: -11.78656695731115 },
2753
+ "SM": { name: "San Marino", lat: 43.942820729097896, lng: 12.461278349581722 },
2754
+ "SN": { name: "Senegal", lat: 14.228861491763402, lng: -14.610875368352305 },
2755
+ "SO": { name: "Somalia", lat: 6.524534573103924, lng: 45.40037867243972 },
2756
+ "SR": { name: "Suriname", lat: 4.098723595920171, lng: -55.855514311561286 },
2757
+ "SS": { name: "South Sudan", lat: 7.657782041763295, lng: 30.3851856901788 },
2758
+ "ST": { name: "Sao Tome and Principe", lat: 0.22744704294793774, lng: 6.606158796857607 },
2759
+ "SV": { name: "El Salvador", lat: 13.758041517055418, lng: -88.85911489238985 },
2760
+ "SX": { name: "Sint Maarten", lat: 18.03942608463078, lng: -63.06883135915303 },
2761
+ "SY": { name: "Syria", lat: 35.09751106058316, lng: 38.5117323139514 },
2762
+ "SZ": { name: "Eswatini", lat: -26.562540935608702, lng: 31.510685746082007 },
2763
+ "TC": { name: "Turks and Caicos Islands", lat: 21.799865427483745, lng: -71.74058946811508 },
2764
+ "TD": { name: "Chad", lat: 15.283493546654503, lng: 18.427113900363025 },
2765
+ "TF": { name: "Juan De Nova Island", lat: -17.06449193630804, lng: 42.74374761089645 },
2766
+ "TF": { name: "French Southern Territories", lat: -49.26329687105643, lng: 69.54686984724839 },
2767
+ "TF": { name: "Glorioso Islands", lat: -11.566224871643417, lng: 47.290948081543384 },
2768
+ "TG": { name: "Togo", lat: 8.660743037717811, lng: 0.8990857571109684 },
2769
+ "TH": { name: "Thailand", lat: 13.66222784745538, lng: 101.08675116214552 },
2770
+ "TJ": { name: "Tajikistan", lat: 38.56933138382972, lng: 70.94215281065698 },
2771
+ "TK": { name: "Tokelau", lat: -9.195174767256544, lng: -171.85265950722743 },
2772
+ "TL": { name: "Timor-Leste", lat: -8.809894630601962, lng: 125.95024049562659 },
2773
+ "TM": { name: "Turkmenistan", lat: 39.06069118001429, lng: 58.4577357627054 },
2774
+ "TN": { name: "Tunisia", lat: 34.08636179565723, lng: 9.65587551697984 },
2775
+ "TO": { name: "Tonga", lat: -21.15927212823853, lng: -175.20415878511247 },
2776
+ "TR": { name: "Turkey", lat: 38.93207363123396, lng: 35.56886764076691 },
2777
+ "TT": { name: "Trinidad and Tobago", lat: 10.415515638298093, lng: -61.37236579976247 },
2778
+ "TV": { name: "Tuvalu", lat: -8.514701506447222, lng: 179.217833977593 },
2779
+ "TZ": { name: "Tanzania", lat: -6.355794440041147, lng: 34.81832206060381 },
2780
+ "UA": { name: "Ukraine", lat: 48.657532515563794, lng: 31.27377208442636 },
2781
+ "UG": { name: "Uganda", lat: 1.2821729218416205, lng: 32.34371768463123 },
2782
+ "UM": { name: "United States Minor Outlying Islands", lat: 19.302045812215958, lng: 166.63800339749642 },
2783
+ "US": { name: "United States", lat: 38.8208089190304, lng: -96.33161660829639 },
2784
+ "UY": { name: "Uruguay", lat: -32.78195043831093, lng: -56.01919523458085 },
2785
+ "UZ": { name: "Uzbekistan", lat: 41.4879065244633, lng: 63.8548297593985 },
2786
+ "VA": { name: "Vatican City", lat: 41.90402351316735, lng: 12.451312917026133 },
2787
+ "VC": { name: "Saint Vincent and the Grenadines", lat: 13.254808122970651, lng: -61.193766354393034 },
2788
+ "VE": { name: "Venezuela", lat: 7.148324760507107, lng: -66.36492135985132 },
2789
+ "VG": { name: "British Virgin Islands", lat: 18.42195819619707, lng: -64.62406519257699 },
2790
+ "VI": { name: "US Virgin Islands", lat: 17.738009708772523, lng: -64.76155341409797 },
2791
+ "VN": { name: "Vietnam", lat: 16.517347170839393, lng: 105.91338832758704 },
2792
+ "VU": { name: "Vanuatu", lat: -15.189132121699332, lng: 166.84912735669738 },
2793
+ "WF": { name: "Wallis and Futuna", lat: -14.283442307826677, lng: -178.12735555777184 },
2794
+ "WS": { name: "Samoa", lat: -13.634252953274622, lng: -172.44107655740137 },
2795
+ "YE": { name: "Yemen", lat: 16.001392616307445, lng: 47.46815793206386 },
2796
+ "YT": { name: "Mayotte", lat: -12.824468416301052, lng: 45.128142327031064 },
2797
+ "ZA": { name: "South Africa", lat: -28.55361930679731, lng: 24.75252746489084 },
2798
+ "ZM": { name: "Zambia", lat: -13.162832953186246, lng: 27.75521363430896 },
2799
+ "ZW": { name: "Zimbabwe", lat: -18.92700121981475, lng: 29.717829640720844 }
2800
+ };
2801
+ class MetricsCountryMapView extends View {
2802
+ constructor(options = {}) {
2803
+ const mapHeight = options.height || 320;
2804
+ super({
2805
+ className: "metrics-country-map-view",
2806
+ ...options
2807
+ });
2808
+ this.endpoint = options.endpoint || "/api/metrics/fetch";
2809
+ this.account = options.account || "global";
2810
+ this.category = options.category || null;
2811
+ this.slugs = options.slugs || null;
2812
+ this.granularity = options.granularity || "days";
2813
+ this.maxCountries = options.maxCountries || 12;
2814
+ this.metricLabel = options.metricLabel || "Events";
2815
+ this.height = mapHeight;
2816
+ this.mapStyle = options.mapStyle || "dark";
2817
+ this.mapOptions = options.mapOptions || {};
2818
+ this.showRoutes = options.showRoutes !== false;
2819
+ this.routeOrigin = options.routeOrigin || { lng: -77.346, lat: 38.958, name: "Reston, VA" };
2820
+ this._refreshing = false;
2821
+ }
2822
+ async getTemplate() {
2823
+ return `
2824
+ <div class="metrics-country-map">
2825
+ <div class="map-container mb-3" data-container="${this.id}-map" style="height:${this.height}px"></div>
2826
+ <div class="map-legend small" data-region="legend"></div>
2827
+ </div>
2828
+ `;
2829
+ }
2830
+ async onInit() {
2831
+ this.statusEl = document.createElement("div");
2832
+ this.statusEl.className = "text-muted small px-3 pb-2";
2833
+ this.element.appendChild(this.statusEl);
2834
+ this.mapView = new MapLibreView({
2835
+ containerId: `${this.id}-map`,
2836
+ height: this.height,
2837
+ style: this.mapStyle,
2838
+ zoom: this.mapOptions.zoom ?? 1.3,
2839
+ center: this.mapOptions.center || [10, 20],
2840
+ pitch: this.mapOptions.pitch ?? 20,
2841
+ bearing: this.mapOptions.bearing ?? 0,
2842
+ showNavigationControl: this.mapOptions.showNavigationControl ?? true,
2843
+ autoFitBounds: this.mapOptions.autoFitBounds ?? false
2844
+ });
2845
+ this.addChild(this.mapView);
2846
+ await this.refresh();
2847
+ }
2848
+ async refresh() {
2849
+ if (this._refreshing) return;
2850
+ this._refreshing = true;
2851
+ this.setStatus("Loading hotspots…");
2852
+ try {
2853
+ const metrics = await this.fetchMetrics();
2854
+ await this.applyMetrics(metrics);
2855
+ this.setStatus("");
2856
+ } catch (error) {
2857
+ console.error("MetricsCountryMapView refresh error", error);
2858
+ this.setStatus("Unable to load country metrics.");
2859
+ } finally {
2860
+ this._refreshing = false;
2861
+ }
2862
+ }
2863
+ async fetchMetrics() {
2864
+ const rest2 = this.getApp()?.rest;
2865
+ if (!rest2) {
2866
+ throw new Error("REST client unavailable");
2867
+ }
2868
+ const params = {
2869
+ account: this.account,
2870
+ granularity: this.granularity,
2871
+ with_labels: true
2872
+ };
2873
+ if (this.category) params.category = this.category;
2874
+ if (this.slugs) {
2875
+ const slugsArray = Array.isArray(this.slugs) ? this.slugs : [this.slugs];
2876
+ params["slugs[]"] = slugsArray;
2877
+ }
2878
+ const response = await rest2.GET(this.endpoint, params);
2879
+ if (!response.success || !response.data?.status) {
2880
+ throw new Error(response.data?.error || "Metrics API error");
2881
+ }
2882
+ return response.data.data;
2883
+ }
2884
+ async applyMetrics(data) {
2885
+ const metrics = data?.data || {};
2886
+ const labels = data?.labels || [];
2887
+ const entries = [];
2888
+ Object.entries(metrics).forEach(([iso, series]) => {
2889
+ const total = series.reduce((sum, value) => sum + (Number(value) || 0), 0);
2890
+ if (!total) return;
2891
+ const centroid = COUNTRY_CENTROIDS[iso.toUpperCase()];
2892
+ if (!centroid) return;
2893
+ entries.push({
2894
+ code: iso.toUpperCase(),
2895
+ total,
2896
+ values: series,
2897
+ centroid
2898
+ });
2899
+ });
2900
+ if (!entries.length) {
2901
+ this.mapView.updateMarkers([]);
2902
+ this.renderLegend([]);
2903
+ this.setStatus("No country data available for the selected range.");
2904
+ return;
2905
+ }
2906
+ entries.sort((a2, b2) => b2.total - a2.total);
2907
+ const topEntries = entries.slice(0, this.maxCountries);
2908
+ const maxValue = topEntries[0]?.total || 1;
2909
+ const markers = topEntries.map((entry) => {
2910
+ const intensity = entry.total / maxValue;
2911
+ const markerSize = Math.round(18 + intensity * 24);
2912
+ return {
2913
+ lng: entry.centroid.lng,
2914
+ lat: entry.centroid.lat,
2915
+ size: markerSize,
2916
+ color: this.getMarkerColor(intensity),
2917
+ popup: `
2918
+ <div class="text-center">
2919
+ <strong>${entry.centroid.name}</strong><br/>
2920
+ <span class="text-muted">${entry.total.toLocaleString()} ${this.metricLabel}</span>
2921
+ </div>
2922
+ `
2923
+ };
2924
+ });
2925
+ if (this.showRoutes && this.routeOrigin?.lng && this.routeOrigin?.lat) {
2926
+ markers.push({
2927
+ lng: this.routeOrigin.lng,
2928
+ lat: this.routeOrigin.lat,
2929
+ size: 26,
2930
+ color: "#0d6efd",
2931
+ icon: "bi bi-broadcast-pin",
2932
+ popup: `
2933
+ <div class="text-center">
2934
+ <strong>${this.routeOrigin.name || "Operations Hub"}</strong><br/>
2935
+ <span class="text-muted">Origin</span>
2936
+ </div>
2937
+ `
2938
+ });
2939
+ }
2940
+ this.mapView.updateMarkers(markers);
2941
+ this.renderLegend(topEntries, labels);
2942
+ if (this.showRoutes) {
2943
+ this.renderRoutes(topEntries, maxValue);
2944
+ }
2945
+ }
2946
+ getMarkerColor(intensity) {
2947
+ const start = [32, 201, 151];
2948
+ const end = [255, 193, 7];
2949
+ const mix = start.map(
2950
+ (value, idx) => Math.round(value + (end[idx] - value) * intensity)
2951
+ );
2952
+ return `rgba(${mix[0]}, ${mix[1]}, ${mix[2]}, 0.9)`;
2953
+ }
2954
+ renderLegend(entries) {
2955
+ const legendEl = this.element.querySelector('[data-region="legend"]');
2956
+ if (!legendEl) return;
2957
+ if (!entries.length) {
2958
+ legendEl.innerHTML = "";
2959
+ return;
2960
+ }
2961
+ const maxValue = entries[0]?.total || 1;
2962
+ const rows = entries.map((entry) => {
2963
+ const percent = (entry.total / maxValue * 100).toFixed(0);
2964
+ return `
2965
+ <div class="d-flex justify-content-between align-items-center py-1 border-top">
2966
+ <div>
2967
+ <span class="fw-semibold">${entry.centroid.name}</span>
2968
+ <span class="text-muted ms-1">(${entry.code})</span>
2969
+ </div>
2970
+ <div class="text-end">
2971
+ <div class="fw-semibold">${entry.total.toLocaleString()}</div>
2972
+ <small class="text-muted">${percent}% of top</small>
2973
+ </div>
2974
+ </div>
2975
+ `;
2976
+ }).join("");
2977
+ legendEl.innerHTML = rows;
2978
+ }
2979
+ renderRoutes(entries, maxValue) {
2980
+ const origin = this.routeOrigin || null;
2981
+ if (!origin || !origin.lng || !origin.lat || !this.mapView) return;
2982
+ const features = entries.map((entry) => ({
2983
+ type: "Feature",
2984
+ geometry: {
2985
+ type: "LineString",
2986
+ coordinates: [
2987
+ [origin.lng, origin.lat],
2988
+ [entry.centroid.lng, entry.centroid.lat]
2989
+ ]
2990
+ },
2991
+ properties: {
2992
+ total: entry.total,
2993
+ intensity: entry.total / maxValue
2994
+ }
2995
+ }));
2996
+ const geojson = {
2997
+ type: "FeatureCollection",
2998
+ features
2999
+ };
3000
+ const paint = {
3001
+ "line-color": [
3002
+ "interpolate",
3003
+ ["linear"],
3004
+ ["get", "intensity"],
3005
+ 0,
3006
+ "rgba(32, 201, 151, 0.2)",
3007
+ 1,
3008
+ "rgba(255, 193, 7, 0.85)"
3009
+ ],
3010
+ "line-width": [
3011
+ "interpolate",
3012
+ ["linear"],
3013
+ ["get", "intensity"],
3014
+ 0,
3015
+ 1,
3016
+ 1,
3017
+ 5
3018
+ ],
3019
+ "line-opacity": [
3020
+ "interpolate",
3021
+ ["linear"],
3022
+ ["get", "intensity"],
3023
+ 0,
3024
+ 0.2,
3025
+ 1,
3026
+ 0.9
3027
+ ]
3028
+ };
3029
+ this.mapView.lineSources = this.mapView.lineSources.filter((src) => src.id !== `${this.id}-routes`);
3030
+ this.mapView.updateLineSource(`${this.id}-routes`, {
3031
+ data: geojson,
3032
+ paint
3033
+ });
3034
+ }
3035
+ setStatus(message) {
3036
+ if (!this.statusEl) return;
3037
+ this.statusEl.textContent = message || "";
3038
+ this.statusEl.style.display = message ? "block" : "none";
3039
+ }
3040
+ }
2252
3041
  class IncidentDashboardHeader extends View {
2253
3042
  constructor(options = {}) {
2254
3043
  super({
@@ -2367,6 +3156,23 @@ class IncidentDashboardPage extends Page {
2367
3156
  <div class="col-xl-6 col-lg-12" data-container="incidents-widget"></div>
2368
3157
  </div>
2369
3158
 
3159
+
3160
+ <div class="row g-4 mt-1">
3161
+ <div class="col-12">
3162
+ <div class="card shadow-sm h-100">
3163
+ <div class="card-header border-0 bg-transparent d-flex align-items-center justify-content-between">
3164
+ <div>
3165
+ <h6 class="mb-0 text-uppercase small text-muted">Global Event Hotspots</h6>
3166
+ <span class="text-muted small">Clusters sized by total events</span>
3167
+ </div>
3168
+ <span class="badge bg-info-subtle text-info">Interactive</span>
3169
+ </div>
3170
+ <div class="card-body p-0" data-container="events-country-map"></div>
3171
+ </div>
3172
+ </div>
3173
+ </div>
3174
+
3175
+
2370
3176
  <div class="row g-4 mt-1">
2371
3177
  <div class="col-lg-6">
2372
3178
  <div class="card shadow-sm h-100">
@@ -2489,11 +3295,12 @@ class IncidentDashboardPage extends Page {
2489
3295
  endpoint: "/api/metrics/fetch",
2490
3296
  account: "incident",
2491
3297
  category: "incident_events_by_country",
2492
- granularity: "hours",
2493
- chartType: "bar",
3298
+ granularity: "days",
3299
+ chartType: "line",
2494
3300
  showDateRange: false,
2495
3301
  showMetricsFilter: false,
2496
3302
  height: 220,
3303
+ maxDatasets: 10,
2497
3304
  colors: [
2498
3305
  "rgba(32, 201, 151, 0.85)"
2499
3306
  ],
@@ -2510,11 +3317,12 @@ class IncidentDashboardPage extends Page {
2510
3317
  endpoint: "/api/metrics/fetch",
2511
3318
  account: "incident",
2512
3319
  category: "incident_by_country",
2513
- granularity: "hours",
2514
- chartType: "bar",
3320
+ granularity: "days",
3321
+ chartType: "line",
2515
3322
  showDateRange: false,
2516
3323
  showMetricsFilter: false,
2517
3324
  height: 220,
3325
+ maxDatasets: 10,
2518
3326
  colors: [
2519
3327
  "rgba(255, 193, 7, 0.85)"
2520
3328
  ],
@@ -2526,6 +3334,16 @@ class IncidentDashboardPage extends Page {
2526
3334
  containerId: "incidents-by-country-chart"
2527
3335
  });
2528
3336
  this.addChild(this.incidentsByCountryChart);
3337
+ this.eventsCountryMap = new MetricsCountryMapView({
3338
+ containerId: "events-country-map",
3339
+ category: "incident_events_by_country",
3340
+ account: "incident",
3341
+ maxCountries: 15,
3342
+ metricLabel: "Events",
3343
+ height: 360,
3344
+ mapStyle: "dark"
3345
+ });
3346
+ this.addChild(this.eventsCountryMap);
2529
3347
  const myTicketsCollection = new TicketList({
2530
3348
  params: {
2531
3349
  status: "new"
@@ -2570,6 +3388,7 @@ class IncidentDashboardPage extends Page {
2570
3388
  this.incidentsWidget?.refresh(),
2571
3389
  this.eventsByCountryChart?.refresh(),
2572
3390
  this.incidentsByCountryChart?.refresh(),
3391
+ this.eventsCountryMap?.refresh(),
2573
3392
  this.myTicketsTable?.collection?.fetch(),
2574
3393
  this.highPriorityIncidentsTable?.collection?.fetch()
2575
3394
  ].filter(Boolean);