diff --git a/README.md b/README.md
index 48b897a..9eefcbb 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,8 @@ Successfully built this on the Raspberry Pi (Raspbian Buster) and a desktop PC (
  - Measuring tool for measure distances between points
  - Map stays focused on the same point across page refreshes
  - Clicking gateway or data point markers pops up information about them
+ - Toggle visibility of markers of each device on map
+ - Select age of markers to show on map
 
 ### TODO
 
@@ -40,19 +42,20 @@ Make sure you have your application set up on The Things Network with the integr
 ### Install dependencies
 
 ```
-sudo apt-get install libffi-dev libssl-dev
+sudo apt-get install -y libffi-dev libssl-dev
 ```
 
 ### Install docker and docker-compose
 
 ```
 curl -sSL https://get.docker.com | sh
-sudo pip install docker-compose
+sudo apt-get install -y python3 python3-pip
+sudo pip3 install docker-compose
 ```
 
 ### Add user to docker group
 
-```sudo usermod -a -G docker YOUR_USER```
+```sudo usermod -a -G docker $USER```
 
 Then log out and back in again.
 
diff --git a/flask_app/Dockerfile b/flask_app/Dockerfile
index b9820d6..b66018c 100644
--- a/flask_app/Dockerfile
+++ b/flask_app/Dockerfile
@@ -4,10 +4,13 @@ RUN mkdir -pv /home/project/flask_app
 RUN mkdir -pv /var/ttn_tracker/ssl_certs
 
 WORKDIR /home/project/flask_app
-COPY requirements.txt /home/project/flask_app
+
 RUN pip install --upgrade pip
+
+COPY requirements.txt /home/project/flask_app
 RUN pip install --no-cache-dir -r requirements.txt
 
-COPY . /home/project/flask_app
-
+COPY ssl-certs-generate.sh /home/project/flask_app/ssl-certs-generate.sh
 RUN /bin/bash /home/project/flask_app/ssl-certs-generate.sh
+
+COPY . /home/project/flask_app
diff --git a/flask_app/app.py b/flask_app/app.py
index 0795523..20b57e9 100644
--- a/flask_app/app.py
+++ b/flask_app/app.py
@@ -9,6 +9,12 @@ from math import sqrt
 import os
 import requests
 from apscheduler.schedulers.background import BackgroundScheduler
+from dateutil.parser import parser
+from flask import Flask
+from flask import jsonify
+from flask import render_template
+from flask_sqlalchemy import SQLAlchemy
+
 from config import app_key
 from config import application
 from config import bing_api_key
@@ -19,11 +25,6 @@ from config import path_db
 from config import refresh_period_seconds
 from config import start_lat
 from config import start_lon
-from dateutil.parser import parser
-from flask import Flask
-from flask import jsonify
-from flask import render_template
-from flask_sqlalchemy import SQLAlchemy
 
 logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
 logger = logging.getLogger(__name__)
@@ -90,6 +91,7 @@ def main_page():
     get_new_data()
     return render_template('map.html',
                            bing_api_key=bing_api_key,
+                           devices=devices,
                            gateway_locations=gateway_locations,
                            location_data=Location.query.all(),
                            refresh_period_seconds=refresh_period_seconds,
diff --git a/flask_app/requirements.txt b/flask_app/requirements.txt
index 60e9b0d..b3aee60 100644
--- a/flask_app/requirements.txt
+++ b/flask_app/requirements.txt
@@ -1,6 +1,6 @@
-apscheduler
-python-dateutil
-gunicorn
-Flask==1.1.1
-flask_sqlalchemy
-requests
+apscheduler==3.6.3
+python-dateutil==2.8.1
+gunicorn==20.0.4
+Flask==1.1.2
+flask_sqlalchemy==2.4.4
+requests==2.25.1
diff --git a/flask_app/templates/map.html b/flask_app/templates/map.html
index 26b1be7..0b9bf69 100644
--- a/flask_app/templates/map.html
+++ b/flask_app/templates/map.html
@@ -58,8 +58,7 @@
       var default_zoom = true;
 
       b = JSON.parse(localStorage.getItem('bounds'));
-      if (b == null)
-      {
+      if (b == null) {
         map = L.map('mapid', {layers: [esri_Map]}).setView([{{start_lat}}, {{start_lon}}], 15);
       }
       else {
@@ -78,7 +77,6 @@
       //disable inertia because it is irritating and slow
       map.options.inertia=false;
 
-
       var measureControl = L.control.measure({
         activeColor: '#FF0000',
         completedColor: '#FF8000',
@@ -112,6 +110,53 @@
       var gateway = L.marker([{{each_gateway[1]}}, {{each_gateway[2]}}]).bindPopup('Gateway: {{each_gateway[0]}}<br />Lat/Lon: {{each_gateway[1]}}, {{each_gateway[2]}}').addTo(map);
       {% endfor %}
 
+      var markers = {};
+      {% for each_device in devices %}
+      markers['{{each_device}}'] = new L.FeatureGroup();
+      map.addLayer(markers['{{each_device}}']);
+      {% endfor %}
+
+      var legend_age = L.control({position: 'topright'});
+      legend_age.onAdd = function(map) {
+        var div = L.DomUtil.create('div', 'info legend_age');
+        div.innerHTML = '<select>' +
+            '<option value="7day">Past 7 Days</option>' +
+            '<option value="1day">Past 1 Day</option>' +
+            '<option value="6hour">Past 6 Hours</option>' +
+            '<option value="1hour">Past 1 Hour</option>' +
+            '<option value="30min">Past 30 Min</option>' +
+            '</select>';
+        div.firstChild.onmousedown = div.firstChild.ondblclick = L.DomEvent.stopPropagation;
+        return div;
+      };
+      legend_age.addTo(map);
+
+      function toggleFunction(element) {
+        if (element.checked) {
+          map.addLayer(markers[element.value]);
+        } else {
+          map.removeLayer(markers[element.value]);
+        }
+      }
+
+      var legend_devices = L.control({position: 'topright'});
+      legend_devices.onAdd = function(map) {
+        var div = L.DomUtil.create('div');
+        div.innerHTML = `
+        <div class="leaflet-control-layers leaflet-control-layers-expanded">
+          <form>
+            {% for each_device in devices -%}
+            <div>
+              <input class="leaflet-control-layers-overlays" id="command"
+                onclick=toggleFunction(this) type="checkbox" value="{{each_device}}" checked>{{each_device}}</input>
+            </div>
+            {%- endfor %}
+          </form>
+        </div>`;
+        return div;
+      };
+      legend_devices.addTo(map);
+
       function add_new_markers(past_seconds) {
         var url = '/dsf673bh_past/' + past_seconds;
         $.getJSON(url,
@@ -121,13 +166,36 @@
                 var node = L.circleMarker([parseFloat(data[i]["latitude"]), parseFloat(data[i]["longitude"])], {
                   color: 'red',
                   radius: 5
-                }).bindPopup('Node: ' + data[i]["device_id"] + '<br />' + data[i]["datetime"] + '<br />Lat/Lon: ' + data[i]["latitude"] + ', ' + data[i]["longitude"] + '<br />Altitude: ' + data[i]["altitude"] + ' m, hdop: ' + data[i]["hdop"]).addTo(map);
+                });
+                node.bindPopup('Node: ' + data[i]["device_id"] + '<br />' + data[i]["datetime"] + '<br />Lat/Lon: ' + data[i]["latitude"] + ', ' + data[i]["longitude"] + '<br />Altitude: ' + data[i]["altitude"] + ' m, hdop: ' + data[i]["hdop"]);
+                markers[data[i]["device_id"]].addLayer(node);
               }
             }
           }
         );
       }
 
+      $('select').change(function(){
+        for (var key in markers) {
+          map.removeLayer(markers[key]);
+          markers[key] = new L.FeatureGroup();
+          map.addLayer(markers[key]);
+        }
+        if ($(this).val() === "all") {
+          add_new_markers(0);
+        } else if ($(this).val() === "7day") {
+          add_new_markers(604800);
+        } else if ($(this).val() === "1day") {
+          add_new_markers(86400);
+        } else if ($(this).val() === "6hour") {
+          add_new_markers(21600);
+        } else if ($(this).val() === "1hour") {
+          add_new_markers(3600);
+        } else if ($(this).val() === "30min") {
+          add_new_markers(1800);
+        }
+      });
+
       // Repeat function for getLastData()
       function repeat_add_new_markers(past_seconds) {
         setInterval(function () {