From ad6fa24a65585127d73392a56f94c9a7915bf259 Mon Sep 17 00:00:00 2001 From: Paul Philion Date: Mon, 19 Aug 2024 18:54:14 +0000 Subject: [PATCH] bringing latest into allconnect branch --- .env | 2 +- README.md | 103 ++++++++++++- package.json | 4 +- pm2-running-services.png | Bin 0 -> 17898 bytes src/vis/MeasurementMap.tsx | 308 +++++++++++++++++++++---------------- src/vis/Vis.tsx | 29 ++++ 6 files changed, 312 insertions(+), 134 deletions(-) create mode 100644 pm2-running-services.png diff --git a/.env b/.env index 97887ac..842cf89 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ PUBLIC_URL=. -PORT=3002 \ No newline at end of file +PORT=3002 diff --git a/README.md b/README.md index cde142d..ccba729 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,108 @@ Visualizations of coverage and performance analysis for Community Cellular Netwo Now hosted on https://coverage.seattlecommunitynetwork.org/ -# Testing & Deployment + +## Initial Setup +To install this service, the fist time, you will need to: + +1. Required tools and versions: + 1. Install `node` and `npm` according to the directions at https://nodejs.org/en/download/package-manager + 2. Install `pm2` using: `npm install pm2 -g` (as per https://www.npmjs.com/package/pm2#installing-pm2) +2. Clone the service: `https://github.com/Local-Connectivity-Lab/ccn-coverage-vis` +2. Configure: + 1. `cd cd ccn-coverage-vis` + 1. Edit `src/utils/config.ts` and set the correct URL for your API host (if you're testing or you're deploying to a new URL). +4. Deploy as below. +5. When starting the ccn-coverage-vis service the first time, use: + ``` + pm2 start --name "Vis Server" npm -- run start + ``` + This will register ccn-coverage-vis with [PM2](https://pm2.keymetrics.io/docs/usage/quick-start/). + + +## Deploying +Once the service has been setup (as above), it can be deployed using the following process: +1. Login to the coverage-host +2. Pull the lastest version from github +3. Restart the service + +The shell commands are: +``` +ssh coverage-host +cd ccn-coverage-vis +git pull +npm install +npm run build +pm2 restart Vis Server +``` + +## Troubleshooting & Recovery +When a problem occurs, there are several checks to determine where the failure is: +1. Check HTTP errors in the browser +1. Login to the coverage-host +2. Confirm ccn-coverage-vis is operating as expected +3. Confirm nginx is operating as expected + +### Checking HTTP errors in the browser +First, open your browser and go to: https://coverage.seattlecommunitynetwork.org/ + +Is it working? + +If not, open up the browser **Web Developer Tools**, usually under the menu Tools > Developer Tools > Web Developer Tools. + +With this panel open at the bottom of the screen select the **Network** tab and refresh the browser page. + +Look in the first column, Status: +* `200`: OK, everything is good. +* `502`: Error with the backend services (behind nginx) +* `500` errors: problem with nxginx. Look in `/var/log/nginx/error.log` for details. +* `400` errors: problem with the service. Check the service logs and nginx logs. +* Timeout or unreachable error: Something is broken in the network between your web browser and the coverage-vis host. + + +### Checking ccn-coverage-vis with pm2 +Next, confirm ccn-coverage-vis is operating as expected. To do this, you will need to be able to log into the server hosting the coverage service. + +Use `pm2 list` to confirm the "Vis Server" is **online** +``` +ssh coverage-host +pm2 list +``` +![Online services under PM2](pm2-running-services.png "Online services under PM2") + +If the "Vis Server" is not online as expected, restart it with: +``` +pm2 restart Vis Server +``` + + +### Checking nginx +If there appear problems with nginx, then check that the + +Check service operation: +``` +systemctl status nginx +``` + +Check nginx logs: +``` +sudo tail /var/log/nginx/error.log +``` + +Sources of errors might include nginx configuration in `/etc/nginx/conf.d/01-ccn-coverage.conf` + +If you need to restart nginx, use: +``` +sudo systemctl restart nginx +``` + +### Clean Recovery +If nothing else works, the last option is a clean reinstall of the service. The process is: +* Remove the `ccn-coverage-vis` directory. +* Re-install as per **Initial Setup**. + + +## Testing Changes to the main branch are automically built and deployed to: https://seattlecommunitynetwork.org/ccn-coverage-vis/ diff --git a/package.json b/package.json index 79723c8..ee25fd8 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,8 @@ "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", - "format": "prettier --write --config ./prettierrc.json 'src/**/*.{ts,tsx}'", - "format-check": "prettier --check --config ./prettierrc.json 'src/**/*.{ts,tsx}'" + "format": "prettier --write --config ./prettierrc.json src/**/*.{ts,tsx}", + "format-check": "prettier --check --config ./prettierrc.json src/**/*.{ts,tsx}" }, "eslintConfig": { "extends": [ diff --git a/pm2-running-services.png b/pm2-running-services.png new file mode 100644 index 0000000000000000000000000000000000000000..7ff8f500a6023c67abf0ef928a91b436a62bf0ae GIT binary patch literal 17898 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

Ezdg;BnHnH9O@zBo?&Z` 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 literal 0 HcmV?d00001 diff --git a/src/vis/MeasurementMap.tsx b/src/vis/MeasurementMap.tsx index 40866f1..ed821a4 100644 --- a/src/vis/MeasurementMap.tsx +++ b/src/vis/MeasurementMap.tsx @@ -1,22 +1,22 @@ -import React, { useEffect, useState } from 'react'; -import { MapType } from './MapSelectionRadio'; -import { API_URL } from '../utils/config'; -import { SCRAPER_URL } from '../utils/config'; -import * as L from 'leaflet'; -import * as d3 from 'd3'; -import * as parser from 'parse-address'; +import React, { useEffect, useState } from "react"; +import { MapType } from "./MapSelectionRadio"; +import { API_URL } from "../utils/config"; +import { SCRAPER_URL } from "../utils/config"; +import * as L from "leaflet"; +import * as d3 from "d3"; +import * as parser from "parse-address"; import { siteMarker, siteSmallMarker, isSiteArray, -} from '../leaflet-component/site-marker'; -import getBounds from '../utils/get-bounds'; -import MapLegend from './MapLegend'; -import fetchToJson from '../utils/fetch-to-json'; -import Loading from '../Loading'; -import axios from 'axios'; -import { GeoSearchControl, OpenStreetMapProvider } from 'leaflet-geosearch'; -import 'leaflet-geosearch/dist/geosearch.css'; +} from "../leaflet-component/site-marker"; +import getBounds from "../utils/get-bounds"; +import MapLegend from "./MapLegend"; +import fetchToJson from "../utils/fetch-to-json"; +import Loading from "../Loading"; +import axios from "axios"; +import { GeoSearchControl, OpenStreetMapProvider } from "leaflet-geosearch"; +import "leaflet-geosearch/dist/geosearch.css"; // Updated with details from: https://stadiamaps.com/stamen/onboarding/migrate/ const ATTRIBUTION = @@ -26,7 +26,7 @@ const ATTRIBUTION = '© OpenStreetMap contributors'; const URL = `https://tiles.stadiamaps.com/tiles/stamen_toner_lite/{z}/{x}/{y}${ - devicePixelRatio > 1 ? '@2x' : '' + devicePixelRatio > 1 ? "@2x" : "" }.png`; const BIN_SIZE_SHIFT = 0; @@ -34,7 +34,7 @@ const DEFAULT_ZOOM = 10; const LEGEND_WIDTH = 25; function cts(p: Cell): string { - return p.x + ',' + p.y; + return p.x + "," + p.y; } // print results of user search event to console @@ -43,7 +43,7 @@ function searchEventHandler(result: any): void { console.log(result); var addressLabel = result.location.label; console.log(addressLabel); - // var parser = require("parse-address"); + // var parser = require("parse-address"); var parsedAddr = parser.parseLocation(addressLabel); console.log("hello 1"); console.log(parsedAddr); @@ -65,48 +65,81 @@ function searchEventHandler(result: any): void { var postcode = result.location.raw.address.postcode; var state = result.location.raw.address.state; - var url = SCRAPER_URL // server "http://127.0.0.1:8000/" - + "?state=" + state - + "&cityname=" + city - + "&primary=" + parsedAddr.number - + "&street_number=" + parsedAddr.street - + "&st=" + parsedAddr.type - + "&post_direction=" + post_direction - + "&zip_5=" + postcode; - //+ "&zip_9=" + "3207"; + var url = + SCRAPER_URL + // server "http://127.0.0.1:8000/" + "?state=" + + state + + "&cityname=" + + city + + "&primary=" + + parsedAddr.number + + "&street_number=" + + parsedAddr.street + + "&st=" + + parsedAddr.type + + "&post_direction=" + + post_direction + + "&zip_5=" + + postcode; + //+ "&zip_9=" + "3207"; console.log(url); - const xhr = new XMLHttpRequest(); //xhr.open('GET', "http://127.0.0.1:8000/?state=va&cityname=arlington&primary=3109&street_number=9th&st=St&post_direction=N&zip_5=22201&zip_9=2024"); - xhr.open('GET', url); - xhr.onload = function() { - console.log("200 check"); - console.log(xhr.status); - if (xhr.status === 200) { - //result.marker.setPopupContent(xhr.responseText); - result.marker.setPopupContent(organizePopup(xhr.responseText, postcode, lon, lat)); - } + xhr.open("GET", url); + xhr.onload = function () { + console.log("200 check"); + console.log(xhr.status); + if (xhr.status === 200) { + //result.marker.setPopupContent(xhr.responseText); + result.marker.setPopupContent( + organizePopup(xhr.responseText, postcode, lon, lat) + ); + } }; xhr.send(); } - - // organize popup content. called from searchEvenHandler -function organizePopup(apiText: any, postcode: any, lon: any, lat: any): string{ +function organizePopup( + apiText: any, + postcode: any, + lon: any, + lat: any +): string { let dict = JSON.parse(apiText); console.log(dict); - if(Object.keys(dict).length == 0) { - return "No prices could be found." + "Visit " + "FCC National Broadband Map." + " to see providers in your area, and " - + "allconnect.com" + " to see provider rates in your area."; + if (Object.keys(dict).length === 0) { + return ( + "No prices could be found." + + "Visit " + + "FCC National Broadband Map." + + " to see providers in your area, and " + + "allconnect.com" + + " to see provider rates in your area." + ); } - let returnString = "" - + "" + "" - + "" - + "" - + "" - + "" + ""; + let returnString = + "
" + "Provider" + "" + "Speeds" + "" + "Rate" + "
" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; if ("message" in dict) { return dict["message"]; @@ -116,26 +149,43 @@ function organizePopup(apiText: any, postcode: any, lon: any, lat: any): string{ // for loop that goes over each key in dictionary for (let key in dict) { - /*returnString = returnString + "" + key + "" + ": Available speeds up to " + dict2[key]["Available speeds"] + /*returnString = returnString + "" + key + "" + ": Available speeds up to " + dict2[key]["Available speeds"] + ", Starting at " + dict2[key]["Starting at"] + "
" + "
";*/ let keyName = key; if (keyName.endsWith(" Internet")) { keyName = keyName.substring(0, keyName.length - 9); console.log(keyName); } - returnString += "" - + "" - + "" - + "" - + ""; - + returnString += + "" + + "" + + "" + + "" + + ""; } - + returnString = returnString + "
" + + "Provider" + + "" + + "Speeds" + + "" + + "Rate" + + "
" + keyName + "" + dict[key]["Available speeds"] + "" + dict[key]["Starting at"] + "
" + + keyName + + "" + + dict[key]["Available speeds"] + + "" + + dict[key]["Starting at"] + + "
"; - returnString = returnString + "

"+ "Disclaimer: The table above shows a general estimate of the rates and providers in your area, using information from these sources: " - + "
" + "allconnect.com" - + "
" + "FCC National Broadband Map." - + "

"; + returnString = + returnString + + "

" + + "Disclaimer: The table above shows a general estimate of the rates and providers in your area, using information from these sources: " + + "
" + + "allconnect.com" + + "
" + + "FCC National Broadband Map." + + "

"; return returnString; } @@ -146,10 +196,10 @@ function resultFormat(result: any): string { } export const UNITS = { - dbm: 'dBm', - ping: 'ms', - download_speed: 'Mbps', - upload_speed: 'Mbps', + dbm: "dBm", + ping: "ms", + download_speed: "Mbps", + upload_speed: "Mbps", } as const; export const MULTIPLIERS = { @@ -160,10 +210,10 @@ export const MULTIPLIERS = { } as const; export const MAP_TYPE_CONVERT = { - dbm: 'Signal Strength', - ping: 'Ping', - download_speed: 'Download Speed', - upload_speed: 'Upload Speed', + dbm: "Signal Strength", + ping: "Ping", + download_speed: "Download Speed", + upload_speed: "Upload Speed", } as const; interface MapProps { @@ -224,8 +274,8 @@ const MeasurementMap = ({ useEffect(() => { (async () => { - const dataRange = await fetchToJson(API_URL + '/api/dataRange'); - const _map = L.map('map-id').setView(dataRange.center, DEFAULT_ZOOM); + const dataRange = await fetchToJson(API_URL + "/api/dataRange"); + const _map = L.map("map-id").setView(dataRange.center, DEFAULT_ZOOM); const _bounds = getBounds({ ...dataRange, map: _map, width, height }); L.tileLayer(URL, { @@ -248,27 +298,25 @@ const MeasurementMap = ({ provider: new OpenStreetMapProvider({ params: { addressdetails: 1, - } - + countrycodes: "us", // limit to USA + }, }), - style: 'bar', // optional: bar|button - default button + style: "bar", // optional: bar|button - default button showPopup: true, marker: { icon: new L.Icon.Default(), - draggable:false, + draggable: false, }, popupFormat: resultFormat, - //showMarker: false, + //showMarker: false, }); _map.addControl(search); - _map.on('geosearch/showlocation', searchEventHandler); + _map.on("geosearch/showlocation", searchEventHandler); /*var marker = L.marker(search.location) .bindPopup("hello") .addTo(_map);*/ - - })(); }, [width, height]); @@ -279,11 +327,11 @@ const MeasurementMap = ({ } const _siteSummary = await fetchToJson( API_URL + - '/api/sitesSummary?' + + "/api/sitesSummary?" + new URLSearchParams([ - ['timeFrom', timeFrom.toISOString()], - ['timeTo', timeTo.toISOString()], - ]), + ["timeFrom", timeFrom.toISOString()], + ["timeTo", timeTo.toISOString()], + ]) ); setSiteSummary(_siteSummary); })(); @@ -294,9 +342,9 @@ const MeasurementMap = ({ // TODO: MOVE TO UTILS; const greenIcon = new L.Icon({ iconUrl: - 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png', + "https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png", shadowUrl: - 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', + "https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png", iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], @@ -304,9 +352,9 @@ const MeasurementMap = ({ }); const goldIcon = new L.Icon({ iconUrl: - 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-gold.png', + "https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-gold.png", shadowUrl: - 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', + "https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png", iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], @@ -314,9 +362,9 @@ const MeasurementMap = ({ }); const redIcon = new L.Icon({ iconUrl: - 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png', + "https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png", shadowUrl: - 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', + "https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png", iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], @@ -327,35 +375,35 @@ const MeasurementMap = ({ const _markers = new Map(); const _sites: Site[] = allSites || []; if (!isSiteArray(_sites)) { - throw new Error('data has incorrect type'); + throw new Error("data has incorrect type"); } blayer.clearLayers(); for (let site of _sites) { if (site.boundary) { - L.polygon(site.boundary, { color: site.color ?? 'black' }).addTo( - blayer, + L.polygon(site.boundary, { color: site.color ?? "black" }).addTo( + blayer ); console.log(site.boundary); } _markers.set( site.name, - siteMarker(site, siteSummary[site.name], map).addTo(slayer), + siteMarker(site, siteSummary[site.name], map).addTo(slayer) ); } _markers.forEach((marker, site) => { - if (selectedSites.some(s => s.label === site)) { + if (selectedSites.some((s) => s.label === site)) { marker.setOpacity(1); } else { marker.setOpacity(0.5); } - if (allSites.some(s => s.name === site && s.status === 'active')) { + if (allSites.some((s) => s.name === site && s.status === "active")) { marker.setIcon(greenIcon); } else if ( - allSites.some(s => s.name === site && s.status === 'confirmed') + allSites.some((s) => s.name === site && s.status === "confirmed") ) { marker.setIcon(goldIcon); } else if ( - allSites.some(s => s.name === site && s.status === 'in-conversation') + allSites.some((s) => s.name === site && s.status === "in-conversation") ) { marker.setIcon(redIcon); } @@ -368,10 +416,10 @@ const MeasurementMap = ({ setMarkerData([]); return; } - const markerRes = await axios.get(API_URL + '/api/markers', { + const markerRes = await axios.get(API_URL + "/api/markers", { params: { - sites: selectedSites.map(ss => ss.label).join(','), - devices: selectedDevices.map(ss => ss.label).join(','), + sites: selectedSites.map((ss) => ss.label).join(","), + devices: selectedDevices.map((ss) => ss.label).join(","), timeFrom: timeFrom.toISOString(), timeTo: timeTo.toISOString(), }, @@ -384,12 +432,12 @@ const MeasurementMap = ({ if (!map || !markerData || !llayer) return; llayer.clearLayers(); const _markers = new Map(); - markerData.forEach(m => - _markers.set(m.mid, siteSmallMarker(m).addTo(llayer)), + markerData.forEach((m) => + _markers.set(m.mid, siteSmallMarker(m).addTo(llayer)) ); const smallIcon = new L.Icon({ iconUrl: - 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-grey.png', + "https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-grey.png", // shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png', iconSize: [20, 35], iconAnchor: [12, 35], @@ -411,20 +459,20 @@ const MeasurementMap = ({ setBins( await fetchToJson( API_URL + - '/api/data?' + + "/api/data?" + new URLSearchParams([ - ['width', bounds.width + ''], - ['height', bounds.height + ''], - ['left', bounds.left + ''], - ['top', bounds.top + ''], - ['binSizeShift', BIN_SIZE_SHIFT + ''], - ['zoom', DEFAULT_ZOOM + ''], - ['selectedSites', selectedSites.map(ss => ss.label).join(',')], - ['mapType', mapType], - ['timeFrom', timeFrom.toISOString()], - ['timeTo', timeTo.toISOString()], - ]), - ), + ["width", bounds.width + ""], + ["height", bounds.height + ""], + ["left", bounds.left + ""], + ["top", bounds.top + ""], + ["binSizeShift", BIN_SIZE_SHIFT + ""], + ["zoom", DEFAULT_ZOOM + ""], + ["selectedSites", selectedSites.map((ss) => ss.label).join(",")], + ["mapType", mapType], + ["timeFrom", timeFrom.toISOString()], + ["timeTo", timeTo.toISOString()], + ]) + ) ); })(); }, [ @@ -444,15 +492,15 @@ const MeasurementMap = ({ setLoading(true); (async () => { const colorDomain = [ - d3.max(bins, d => d[1] * MULTIPLIERS[mapType]) ?? 1, - d3.min(bins, d => d[1] * MULTIPLIERS[mapType]) ?? 0, + d3.max(bins, (d) => d[1] * MULTIPLIERS[mapType]) ?? 1, + d3.min(bins, (d) => d[1] * MULTIPLIERS[mapType]) ?? 0, ]; const colorScale = d3.scaleSequential(colorDomain, d3.interpolateViridis); setCDomain(colorDomain); layer.clearLayers(); - bins.forEach(p => { + bins.forEach((p) => { const idx = p[0]; const bin = Number(p[1]); if (bin) { @@ -462,7 +510,7 @@ const MeasurementMap = ({ const sw = map.unproject([x, y], DEFAULT_ZOOM); const ne = map.unproject( [x + (1 << BIN_SIZE_SHIFT), y + (1 << BIN_SIZE_SHIFT)], - DEFAULT_ZOOM, + DEFAULT_ZOOM ); L.rectangle(L.latLngBounds(sw, ne), { @@ -471,10 +519,10 @@ const MeasurementMap = ({ stroke: false, }) .bindTooltip(`${bin.toFixed(2)} ${UNITS[mapType]}`, { - direction: 'top', + direction: "top", }) .addTo(layer) - .on('click', e => { + .on("click", (e) => { const cs = cells; const c = cts({ x: x, y: y }); if (cs.has(c)) { @@ -507,7 +555,7 @@ const MeasurementMap = ({ mlayer.clearLayers(); var binSum: number = 0; var binNum: number = 0; - bins.forEach(p => { + bins.forEach((p) => { const idx = p[0]; const bin = Number(p[1]); if (bin) { @@ -517,19 +565,19 @@ const MeasurementMap = ({ if (cells.has(c)) { const ct = map.unproject( [x + (1 << BIN_SIZE_SHIFT) / 2, y + (1 << BIN_SIZE_SHIFT) / 2], - DEFAULT_ZOOM, + DEFAULT_ZOOM ); binSum += bin; binNum += 1; L.circle(L.latLng(ct), { - fillColor: '#FF0000', + fillColor: "#FF0000", fillOpacity: 0.75, radius: 24, stroke: false, }) - .bindTooltip(`${bin.toFixed(2)}`, { direction: 'top' }) + .bindTooltip(`${bin.toFixed(2)}`, { direction: "top" }) .addTo(mlayer) - .on('click', e => { + .on("click", (e) => { const cs = cells; if (cs.has(c)) { cs.delete(c); @@ -559,12 +607,12 @@ const MeasurementMap = ({ ]); return ( -
+
-
+
+ + { + setDisplayOptions( + solveDisplayOptions(displayOptions, 'displayGraph', true), + ); + }} + > + + + +