Here's a quick overview and most of the code I have stripped out that is relevant. It was a big learning curve so my code might be poorly written. When I have time I'll make it better.      My actual setup is an AWS server using Docker containers. I have the following containers running.  Flask - Publishing the web pages  Grafana - Displaying historical data for all Meraki devices and other network devices.  InflxDb - Storing the data for above  Python3 - For my main code  Busybox - Used for sharing data between containers  =========================================================================  Flask code. The URL for the map will be http://<your server>/map  getOrganizationDeviceStatuses_with_location.json is from your python code generated from script 2  You'll need a sample index.html for your home or remove the route to it.  I've stripped out loads of code as I do a lot more than just maps. I do health reporting too but that would be too hard to explain how it all hangs together. One day I might write a doco.  There's probably imports not needed for a simple map in the python code but you'll have to work some stuff out. You'll need to setup directory structures and have an understanding of python and html etc to get it working but it'll give you an idea on how it works.  =========================================================================  from flask import Flask, render_template, request, jsonify, send_file, send_from_directory  import json  import os  import datetime  import pytz  import csv  app = Flask(__name__)  @app.route('/')  def home():  return render_template('index.html')  @app.route('/map')  def map_page():  data_url = "http://<your server>/shared_data/getOrganizationDeviceStatuses_with_location.json"  return render_template('map.html', data_url=data_url)       Python code - 2 scripts I use as I do other stuff with the first API results. can be combined into one script. i collect data every 5 minutes in a loop.  JSON_store is the place I store the JSON data in the busybox container  ==============  SCRIPT 1 - This part grabs the devices from the ORG and the Device statuses and writes to a JSON store  ==============  response = dashboard.organizations.getOrganizationDevices(org_id, total_pages='all')  with open(JSON_store + "getOrganizationDevices.json", "w") as f:  json.dump(response, f)  response = dashboard.organizations.getOrganizationDevicesStatuses(org_id, total_pages='all')  with open(JSON_store + "getOrganizationDevicesStatuses.json", "w") as f:  json.dump(response, f)  ===========  SCRIPT 2  ===========  import json  # Read the first JSON file  with open('json_files/getOrganizationDevices.json', 'r') as file:  devices = json.load(file)  # Read the second JSON file  with open('json_files/getOrganizationDevicesStatuses.json', 'r') as file:  statuses = json.load(file)  # Create a dictionary to store the device status with location  devices_with_location = {}  # Iterate over the devices  for device in devices:  name = device['name']  if 'nomad' in name.lower() or 'lab' in name.lower():  continue # Skip this device if it contains "nomad" or "lab" in the name # <<< Filtering out unwanted networks    lat = device['lat']  lng = device['lng']  # Find the corresponding status for the device  for status in statuses:  if status['name'] == name:  device_status = status['status']  break  else:  device_status = None  # Store the device status with location  devices_with_location[name] = {  'lat': lat,  'lng': lng,  'status': device_status  }  print("Write getOrganizationDeviceStatuses_with_location.json")  # Write the result to a new JSON file  with open('json_files/getOrganizationDeviceStatuses_with_location.json', 'w') as file: # <<<<<< This is the final JSON the webpage calls  json.dump(devices_with_location, file)     ===========  Webpage to display the map - It's a bit messy and needs tidying up as it's my first draft but does the job for now. Sometimes Meraki comes back with a device status of 'null' which is strange and I need to work on hadling that better  ===========  <!DOCTYPE html>  <html>  <head>  <title>Full Screen Map</title>  <style>  html, body {  margin: 0;  padding: 0;  height: 100%;  }  #map {  position: absolute;  top: 0;  left: 12%; /* Adjusted the left position to accommodate the marker names */  width: 88%;  height: 100%;  }  #marker-names {  position: absolute;  top: 0;  left: 0;  width: 20%;  height: 100%;  background-color: rgb(10, 15, 22);  color: white;  overflow-y: auto;  }  #marker-names p {  margin: 0;  padding: 5px 0;  }  #problem-devices {  font-weight: bold;  margin-bottom: 10px;  }  .circle {  display: inline-block;  width: 12px;  height: 12px;  border-radius: 50%;  margin-right: 5px;  }  #refresh-timer {  position: absolute;  top: 50%;  left: 50%;  transform: translate(-50%, -50%);  color: white;  font-family: "Roboto", sans-serif;  font-size: 50px;  }  #timer-svg {  width: 150px;  height: 150px;  }  #timer-text {  font-size: 48px;  fill: white;  text-anchor: middle;  font-family: "Roboto", sans-serif;  font-weight: bold;  letter-spacing: 2px;  }  </style>  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/leaflet.css" />  </head>  <body>  <div id="map"></div>  <div id="marker-names">  <div id="problem-devices" style="font-size: 20px;">Refreshing data...</div>  <svg id="timer-svg" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">  <circle id="timer-bg" cx="100" cy="100" r="90" fill="none" stroke="rgba(255, 255, 255, 0.1)" stroke-width="20" />  <circle id="timer-progress" cx="100" cy="100" r="90" fill="none" stroke="green" stroke-width="20" stroke-dasharray="565.48" stroke-dashoffset="0" />  <text id="timer-text" x="100" y="100">60</text>  </svg>  </div>  <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/leaflet.js"></script>  <script>  var map = L.map('map').setView([-30.5, 145], 5);  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {}).addTo(map);  map.on('zoomend', function() {  if (map.getZoom() === 4) {  map.setZoom(4.5);  }  });  function refreshPage() {  location.reload();  }  function startRefreshTimer() {  var count = 60; // Refresh countdown in seconds  var startTime = null;  var frameRate = 5; // Desired frame rate (5 frames per second)  var timerText = document.getElementById('timer-text');  var timerProgress = document.getElementById('timer-progress');  function animateTimer(timestamp) {  if (!startTime) startTime = timestamp;  var elapsedTime = timestamp - startTime;  count = Math.max(60 - Math.floor(elapsedTime / 1000), 0);  timerText.textContent = count;  // Update the progress circle  var circumference = 2 * Math.PI * 90;  var dashOffset = circumference - (count / 60) * circumference;  timerProgress.style.strokeDashoffset = dashOffset;  if (count > 0) {  var timePerFrame = 1000 / frameRate;  var timeSinceLastFrame = elapsedTime % timePerFrame;  if (timeSinceLastFrame >= timePerFrame - 1 || timeSinceLastFrame === 0) {  requestAnimationFrame(animateTimer);  } else {  setTimeout(function() {  requestAnimationFrame(animateTimer);  }, timePerFrame - timeSinceLastFrame);  }  } else {  timerText.textContent = '0';  refreshPage();  }  }  requestAnimationFrame(animateTimer);  }    fetch('http://<server IP>/shared_data/getOrganizationDeviceStatuses_with_location.json') # Your server IP  .then(response => response.json())  .then(data => {  var redMarkers = [];  var offlineMarkerNames = [];  var alertingMarkerNames = [];  Object.entries(data).forEach(([key, value]) => {  if (value.status === 'null') {  return; // Ignore when status is 'null'  }  var markerColor;  var markerRadius = 3;  if (value.status === 'online') {  markerColor = 'rgb(0, 200, 0)'; // Slightly brighter green color  } else if (value.status === 'alerting') {  markerColor = 'yellow';  markerRadius *= 2.5;  alertingMarkerNames.push(key); // Add to alerting marker names  } else if (value.status === 'dormant') {  markerColor = 'white';  markerRadius *= 2;  } else {  markerColor = 'red';  markerRadius *= 2.5;  offlineMarkerNames.push(key); // Add to offline marker names  }  if (value.status !== 'null') {  var marker = L.circleMarker([value.lat, value.lng], { radius: markerRadius, color: markerColor });  marker.bindPopup(`<b>${key}</b><br>Status: ${value.status}`);  map.addLayer(marker);  if (markerColor === 'red' || markerColor === 'yellow') {  redMarkers.push(marker);  }  }  });    redMarkers.forEach(marker => {  marker.bringToFront();  });  var flashingMarkers = redMarkers.filter(marker => marker.options.color === 'yellow' || marker.options.color === 'red');  var pulseInterval = setInterval(function() {  flashingMarkers.forEach(marker => {  var radius = marker.options.radius;  var targetRadius1, targetRadius2, targetRadius3, targetRadius4;  if (marker.options.color === 'yellow') {  targetRadius1 = radius * 1; // Increase the radius by 5% for yellow markers  targetRadius2 = radius * 0.1; // Increase the radius by 10% for yellow markers  targetRadius3 = radius * 0.25; // Increase the radius by 15% for yellow markers  targetRadius4 = radius * 0.5; // Reduce the radius by 10% for yellow markers  } else {  targetRadius1 = radius * 1; // Increase the radius by 5% for red markers  targetRadius2 = radius * 0.1; // Increase the radius by 10% for red markers  targetRadius3 = radius * 0.25; // Increase the radius by 15% for red markers  targetRadius4 = radius * 0.5; // Reduce the radius by 10% for red markers  }  marker.setStyle({ radius: targetRadius1 });  setTimeout(function() {  marker.setStyle({ radius: targetRadius2 });  setTimeout(function() {  marker.setStyle({ radius: targetRadius3 });  setTimeout(function() {  marker.setStyle({ radius: targetRadius4 });  setTimeout(function() {  marker.setStyle({ radius: radius }); // Reset the radius to its original value  }, 200); // Wait for 400 milliseconds before resetting the radius  }, 200); // Wait for 400 milliseconds before reducing the radius  }, 200); // Wait for 400 milliseconds before increasing the radius  }, 250); // Wait for 500 milliseconds before increasing the radius  });  }, 1000); // Repeat the pulse animation every 2000 milliseconds (2 seconds)       var markerNamesElement = document.getElementById('marker-names');  var offlineNamesHTML = offlineMarkerNames.map(name => `<p style="color: white; font-family: 'Arial', sans-serif; font-size: 14px;"><span class="circle" style="background-color: red;"></span>${name}</p>`).join('');  var alertingNamesHTML = alertingMarkerNames.map(name => `<p style="color: white; font-family: 'Arial', sans-serif; font-size: 14px;"><span class="circle" style="background-color: yellow;"></span>${name}</p>`).join('');  var problemDevicesElement = document.getElementById('problem-devices');  problemDevicesElement.innerHTML = "<table style='font-family: Roboto, sans-serif;'>" +  "<tr><td><span style='font-size: smaller; color: white;'>Dormant</span> <span style='display: inline-block; width: 12px; height: 12px; background-color: white; border-radius: 50%;'></span></td><td><span style='font-size: smaller; color: white;'>Alerting</span> <span style='display: inline-block; width: 12px; height: 12px; background-color: yellow; border-radius: 50%;'></span></td></tr>" +  "<tr><td><span style='font-size: smaller; color: white;'>Online</span> <span style='display: inline-block; width: 12px; height: 12px; background-color: green; border-radius: 50%;'></span></td><td><span style='font-size: smaller; color: white;'>Offline</span> <span style='display: inline-block; width: 12px; height: 12px; background-color: red; border-radius: 50%;'></span></td></tr>" +  "</table>" +  "<hr>";  markerNamesElement.innerHTML += alertingNamesHTML;  markerNamesElement.innerHTML += offlineNamesHTML;  // Start the refresh timer  startRefreshTimer();  })  .catch(error => console.error('Error:', error));  </script>  </body>  </html>             
						
					
					... View more