Improved the devices map a lot

Ylian Saint-Hilaire committed Aug 29, 2017 at 14:34 UTC 7d21d41397570db3279e8917a6346fcad86bcf30
3 files changed +716 -38
package.json
+1 -1
@@ -1,6 +1,6 @@
1 {
2 "name": "meshcentral",
3 - "version": "0.0.6-r",
3 + "version": "0.0.6-t",
4 "keywords": [
5 "Remote Management",
6 "Intel AMT",
public/images/mapmarker.png
Binary files /dev/null and b/public/images/mapmarker.png differ
views/default.handlebars
+715 -37
@@ -105,10 +105,15 @@
105 <div class=h1 style=height:100%;float:left>&nbsp;</div>
106 <div id=devListToolbar class=style14 style=height:100%;float:left>
107 &nbsp;&nbsp;<input type="button" id="SelectAllButton" onclick="selectallButtonFunction();" value="Select All" />&nbsp;
108 - <input type="button" id="GroupActionButton" disabled="disabled" value="Group Action" onclick="groupActionFunction();" />&nbsp;
109 - <input id="SearchInput" type="text" style=width:120px placeholder=Search onchange="onSearchInputChanged()" onkeyup="onSearchInputChanged()" autocomplete=off onfocus="onSearchFocus(1)" onblur="onSearchFocus(0)" />&nbsp;
108 + <input type=button id=GroupActionButton disabled="disabled" value="Group Action" onclick=groupActionFunction() />&nbsp;
109 + <input id=SearchInput type=text style=width:120px placeholder=Search onchange=onSearchInputChanged() onkeyup=onSearchInputChanged() autocomplete=off onfocus=onSearchFocus(1) onblur=onSearchFocus(0) />&nbsp;
110 <input type=checkbox id=HostnameCheckBox onclick=onHostnameCheckBox() /><span title="Show device hostnames">Hostname</span>
111 </div>
112 + <div id=devMapToolbar class=style14 style=height:100%;float:left>
113 + &nbsp;&nbsp;<input type=text id=mapSearchLocation placeholder="Search Location" onfocus=onMapSearchFocus(1) onblur=onMapSearchFocus(0) />
114 + <input type=button value=Search title="Search for location" onclick=getSearchLocation() />
115 + <input type=button id=refreshmap title="Reset map view" value=Reset style=margin-left:5px onclick=refreshMap(false,true) />
116 + </div>
117 <div class="auto-style1" style="height: 100%; float: right">
118 <div style="height:100%;width:4px;float:right;background-color:#ffffff"></div>
119 <div class=h2 style="height:100%;float:right">&nbsp;</div>
@@ -145,7 +150,17 @@
150 <p></p>
151 </div>
152 <div id="xdevices" style="max-height:calc(100vh - 228px);overflow-y:auto;-webkit-overflow-scrolling:touch"></div>
148 - <div id="xdevicesmap" style="height:500px;width:100%;overflow:hidden"></div>
153 + <div id="xdevicesmap" style="height:500px;width:100%;overflow:hidden;position:relative">
154 + <div id=xmapSearchResultsDlg style="position:absolute;display:none;max-height:280px;left:5px;top:5px;max-width:250px;z-index:1000;background-color:#EEE;box-shadow:0px 0px 15px #666">
155 + <div style="width:100%;background-color:#003366;color:#FFF;border-radius:5px 5px 0 0">
156 + <div id=xmapSearchClose style=float:right;padding:5px;cursor:pointer onclick=mapCloseSearchWindow()><b>X</b></div>
157 + <div style=padding:5px>Location Results</div>
158 + <div style=width:100%;margin:6px></div>
159 + </div>
160 + <div id=xmapSearchResults style=margin:6px></div>
161 + </div>
162 + </div>
163 + <div id="xmap-info-window" style="text-shadow: 0px 0px 15px #FFF"></div>
164 </div>
165 <div id=p2 style=display:none>
166 <h1>My Account</h1>
@@ -580,6 +595,7 @@
595 var StatusStrs = ['Disconnected', 'Connecting...', 'Setup...', 'Connected', 'Intel&reg; AMT Connected'];
596 var sort = 0;
597 var searchFocus = 0;
598 + var mapSearchFocus = 0;
599 var consoleFocus = 0;
600 var showHostnames = false;
601 var meshserver = null;
@@ -600,7 +616,7 @@
616 var features = {{{features}}};
617 var serverPublicNamePort = "{{{serverDnsName}}}:{{{serverPublicPort}}}";
618 var amtScanResults = null;
603 - var xxmap = null;
619 + //var xxmap = null;
620
621 function startup() {
622 // Guard against other site's top frames (web bugs).
@@ -662,13 +678,6 @@
678 var x = '';
679 for (var c = 1; c < 27; c++) x += "<option value='" + c + "'>Ctrl-" + String.fromCharCode(64 + c) + " (" + c + ")</option>";
680 QH('specialkeylist', x);
665 -
666 - // Setup the map
667 - xxmap = new ol.Map({
668 - target: 'xdevicesmap',
669 - layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }) ],
670 - view: new ol.View({ center: ol.proj.fromLonLat([0, 0]), zoom: 1.8, minZoom: 1.8 })
671 - });
681 }
682
683 function getNodeFromId(id) {
@@ -784,6 +793,7 @@
793 onSortSelectChange();
794 onSearchInputChanged();
795 updateDevices();
796 + refreshMap(false, true);
797 if (xxcurrentView == 0) { if ('{{viewmode}}' != '') { go({{viewmode}}); } else { go(1); } }
798 if ('{{currentNode}}' != '') { gotoDevice('{{currentNode}}',{{viewmode}});}
799 break;
@@ -961,6 +971,7 @@
971 onSortSelectChange();
972 onSearchInputChanged();
973 updateDevices();
974 + updateMapMarkers();
975 break;
976 }
977 case 'removenode': {
@@ -974,6 +985,7 @@
985 }
986 nodes.splice(index, 1);
987 updateDevices();
988 + updateMapMarkers();
989 }
990 break;
991 }
@@ -1012,6 +1024,7 @@
1024 onSortSelectChange(true);
1025 drawNotifications();
1026 refreshDevice(node._id);
1027 + updateMapMarkers();
1028 }
1029 break;
1030 }
@@ -1026,6 +1039,7 @@
1039 node.conn = message.event.conn;
1040 node.pwr = message.event.pwr;
1041 updateDevices();
1042 + updateMapMarkers();
1043 refreshDevice(node._id);
1044 }
1045 break;
@@ -1113,15 +1127,27 @@
1127 return;
1128 }
1129 if (e.ctrlKey == true || e.altKey == true || e.metaKey == true) return;
1116 - var processed = 0;
1117 - if (e.key) {
1118 - if (e.key.length === 1 && searchFocus == 0) { Q('SearchInput').value = ((Q('SearchInput').value + e.key)); processed = 1; }
1119 - if (e.keyCode == 8 && searchFocus == 0) { var x = Q('SearchInput').value; Q('SearchInput').value = x.substring(0, x.length - 1); processed = 1; }
1120 - if (e.keyCode == 27) { Q('SearchInput').value = ''; processed = 1; }
1121 - } else {
1122 - if (e.charCode != 0 && searchFocus == 0) { Q('SearchInput').value = ((Q('SearchInput').value + String.fromCharCode(e.charCode))); processed = 1; }
1130 + if (Q('viewselect').value < 3) {
1131 + var processed = 0;
1132 + if (e.key) {
1133 + if (e.key.length === 1 && searchFocus == 0) { Q('SearchInput').value = ((Q('SearchInput').value + e.key)); processed = 1; }
1134 + if (e.keyCode == 8 && searchFocus == 0) { var x = Q('SearchInput').value; Q('SearchInput').value = x.substring(0, x.length - 1); processed = 1; }
1135 + if (e.keyCode == 27) { Q('SearchInput').value = ''; processed = 1; }
1136 + } else {
1137 + if (e.charCode != 0 && searchFocus == 0) { Q('SearchInput').value = ((Q('SearchInput').value + String.fromCharCode(e.charCode))); processed = 1; }
1138 + }
1139 + if (processed > 0) { if (processed == 1) { onSearchInputChanged(); } return haltEvent(e); }
1140 + }
1141 + if (Q('viewselect').value == 3) {
1142 + if (e.key) {
1143 + if (e.key.length === 1 && mapSearchFocus == 0) { Q('mapSearchLocation').value = ((Q('mapSearchLocation').value + e.key)); processed = 1; }
1144 + //if (e.keyCode == 8 && mapSearchFocus == 0) { var x = Q('mapSearchLocation').value; Q('mapSearchLocation').value = x.substring(0, x.length - 1); processed = 1; }
1145 + if (e.keyCode == 27) { Q('mapSearchLocation').value = ''; mapCloseSearchWindow(); processed = 1; }
1146 + if (e.keyCode == 13) { getSearchLocation(); }
1147 + } else {
1148 + if (e.charCode != 0 && mapSearchFocus == 0) { Q('mapSearchLocation').value = ((Q('mapSearchLocation').value + String.fromCharCode(e.charCode))); processed = 1; }
1149 + }
1150 }
1124 - if (processed > 0) { if (processed == 1) { onSearchInputChanged(); } return haltEvent(e); }
1151 }
1152
1153 function ondockeydown(e) {
@@ -1130,9 +1156,15 @@
1156 if (!xxdialogMode && xxcurrentView == 13 && e.keyCode == 116 && p13filetree != null) { haltEvent(e); return false; } // F5 Refresh on files
1157 if (xxdialogMode || xxcurrentView != 1 || e.ctrlKey == true || e.altKey == true || e.metaKey == true) return;
1158 var processed = 0;
1133 - if (e.keyCode === 8 && searchFocus == 0) { var x = Q('SearchInput').value; Q('SearchInput').value = (x.substring(0, x.length - 1)); processed = 1; }
1134 - if (e.keyCode === 27) { Q('SearchInput').value = ''; processed = 1; }
1135 - if (processed > 0) { if (processed == 1) { onSearchInputChanged(); } return haltEvent(e); }
1159 + if (Q('viewselect').value < 3) {
1160 + if (e.keyCode === 8 && searchFocus == 0) { var x = Q('SearchInput').value; Q('SearchInput').value = (x.substring(0, x.length - 1)); processed = 1; }
1161 + if (e.keyCode === 27) { Q('SearchInput').value = ''; processed = 1; }
1162 + if (processed > 0) { if (processed == 1) { onSearchInputChanged(); } return haltEvent(e); }
1163 + }
1164 + if (Q('viewselect').value == 3) {
1165 + if (e.keyCode === 8 && mapSearchFocus == 0) { var x = Q('mapSearchLocation').value; Q('mapSearchLocation').value = (x.substring(0, x.length - 1)); processed = 1; }
1166 + if (e.keyCode === 27) { Q('mapSearchLocation').value = ''; mapCloseSearchWindow(); processed = 1; }
1167 + }
1168 }
1169
1170 function ondockeyup(e) {
@@ -1140,7 +1172,8 @@
1172 if (!xxdialogMode && xxcurrentView == 12 && terminal && terminal.State == 3) return terminal.m.TermHandleKeyUp(e);
1173 if (!xxdialogMode && xxcurrentView == 13 && e.keyCode == 116 && p13filetree != null) { p13folderup(9999); haltEvent(e); return false; } // F5 Refresh on files
1174 if (xxdialogMode || xxcurrentView != 0 || e.ctrlKey == true || e.altKey == true || e.metaKey == true) return;
1143 - if ((e.keyCode === 8 && searchFocus == 0) || e.keyCode === 27) { return haltEvent(e); }
1175 + if (Q('viewselect').value < 3) { if ((e.keyCode === 8 && searchFocus == 0) || e.keyCode === 27) { return haltEvent(e); } }
1176 + if (Q('viewselect').value == 3) { if ((e.keyCode === 8 && mapSearchFocus == 0) || e.keyCode === 27) { return haltEvent(e); } }
1177 }
1178
1179 var deviceHeaderId = 0;
@@ -1151,9 +1184,10 @@
1184 QV('xdevices', view < 3);
1185 QV('xdevicesmap', view == 3);
1186 QV('devListToolbar', view < 3);
1187 + QV('devMapToolbar', view == 3);
1188 QV('devListToolbarSort', view < 3);
1189 if (view == 3) {
1156 - setTimeout( function() { xxmap.updateSize();}, 200);
1190 + setTimeout( function() { xxmap.map.updateSize();}, 200);
1191 // TODO
1192 } else {
1193 // 3 wide or list view
@@ -1546,6 +1580,7 @@
1580 function deviceHostSort(a, b) { if (a.hostl > b.hostl) return 1; if (a.hostl < b.hostl) return -1; return 0; }
1581 function onSearchInputChanged() { var x = Q('SearchInput').value.toLowerCase(); putstore("search", x); if (x == '') { for (var d in nodes) { nodes[d].v = true; } } else { for (var d in nodes) { nodes[d].v = (nodes[d].name.toLowerCase().indexOf(x) >= 0) || (nodes[d].hostl != undefined && nodes[d].hostl.toLowerCase().indexOf(x) >= 0); } } updateDevices(); }
1582 function onSearchFocus(x) { searchFocus = x; }
1583 + function onMapSearchFocus(x) { mapSearchFocus = x; }
1584 function onConsoleFocus(x) { consoleFocus = x; }
1585
1586 var contextelement = null;
@@ -1602,6 +1637,661 @@
1637 contextelement = null;
1638 }
1639
1640 + //
1641 + // DEVICES MAP
1642 + //
1643 +
1644 + // Maps code starts from here. Initialize all the variables
1645 + var xxmap = {
1646 + map: null,
1647 + contextmenu: null,
1648 + activeInteractions: [], // Save Modified features in this list
1649 + showindex: 0,
1650 + markersSource: null, // Initialize a Source Vector
1651 + markersLayer: null,
1652 + mapLayer: null, // Create a tile and use OSM source
1653 + mapView: null, // Sets the initial view
1654 + selectedNodes: {} // Save details of Node that was selected from 'Put a node' option from contextmenu
1655 + }
1656 +
1657 + // Add a feature for every Node and change style if connection status changes
1658 + function updateMapMarkers(selectedMesh) {
1659 + var boundingBox = null;
1660 + if (xxmap.map == null) loadmap();
1661 + for (var i in nodes) {
1662 + var loc = map_parseNodeLoc(nodes[i]);
1663 + if (loc) { // Draw markers for devices with locations
1664 + lat = loc[0];
1665 + lon = loc[1];
1666 + if (boundingBox == null) { boundingBox = [ lat, lon, lat, lon, 0 ]; } else { if (lat < boundingBox[0]) { boundingBox[0] = lat; } if (lon < boundingBox[1]) { boundingBox[1] = lon; } if (lat > boundingBox[2]) { boundingBox[2] = lat; } if (lon > boundingBox[3]) { boundingBox[3] = lon; } }
1667 + var feature = xxmap.markersSource.getFeatureById(nodes[i]._id);
1668 + if (nodes[i].meshid == selectedMesh || selectedMesh == undefined) {
1669 + if (feature == null || feature == undefined) { addFeature(nodes[i]); boundingBox[4] = 1; } else { updateFeature(nodes[i], feature); } // Update Feature
1670 + } else {
1671 + if (feature) { xxmap.markersSource.removeFeature(feature); } // Remove if it does not belong to that mesh
1672 + }
1673 + }
1674 + }
1675 + return boundingBox;
1676 + }
1677 +
1678 + // Show node details on hovering over a feature
1679 + var map_cm_popup = new ol.Overlay({ element: Q('xmap-info-window'), positioning: 'bottom-center', stopEvent: false });
1680 +
1681 + // Edit Marker item
1682 + var map_cm_editMarker = { text: "Modify Node location", callback: function (obj) { modifyMarkerloc(obj.data); } };
1683 +
1684 + // Save Marker item
1685 + var map_cm_saveMarker = { text: "Save Node location", callback: function (obj) { saveMarkerloc(obj.data); } };
1686 +
1687 + // Build a context menu for a feature
1688 + var map_cm_nodemenu_items = [
1689 + { text: "General information", callback: function (obj) { if (obj.data !=null) { gotoDevice(obj.data, 10); } } },
1690 + { text: "Desktop", callback: function (obj) { if (obj.data !=null) { gotoDevice(obj.data, 11); } } },
1691 + { text: "Terminal", callback: function (obj) { if (obj.data !=null) { gotoDevice(obj.data, 12); } } },
1692 + { text: "Intel&reg; AMT", callback: function (obj) { if (obj.data !=null) { gotoDevice(obj.data, 14); } } },
1693 + '-',
1694 + { text: 'Zoom-in to extent', callback: function(obj) { var coords = obj.data.getGeometry().getCoordinates(); zoomToLocation(coords, 19); } },
1695 + { text: 'Zoom-out to extent', callback: function(obj) { var coords = obj.data.getGeometry().getCoordinates(); zoomToLocation(coords, 2); } }
1696 + ];
1697 +
1698 + // Context menu for clicks other than on feature
1699 + var contextmenu_items = [
1700 + { text: 'Refresh', callback: function () { refreshMap(false, true); } },
1701 + { text: 'Zoom to fit extent', callback: function () { zoomToFitExtent(); } },
1702 + { text: 'Center map here', callback: function(obj) { xxmap.mapView.animate({ center: obj.coordinate } ); } },
1703 + { text: 'Place a node', callback: function (obj) { placeNode(obj.coordinate); } }
1704 + ];
1705 +
1706 + function stringToIntHash(str) {
1707 + var hash = 0, i;
1708 + for (i = 0; i < str.length; i++) { hash = ((hash << 5) - hash) + str.charCodeAt(i); hash |= 0; }
1709 + return hash;
1710 + };
1711 +
1712 + // Get the lat/lon from a node
1713 + function map_parseNodeLoc(node) {
1714 + if (!node.iploc) return;
1715 + var loc = node.iploc.split(',');
1716 + return [parseFloat(loc[0]) + (stringToIntHash(node._id.substring(0, 20)) / 100000000000), parseFloat(loc[1]) + (stringToIntHash(node._id.substring(20)) / 100000000000)]
1717 + }
1718 +
1719 + // Load the entire map
1720 + function loadmap() {
1721 + // Initialize a Source Vector
1722 + xxmap.markersSource = new ol.source.Vector();
1723 +
1724 + xxmap.markersLayer = new ol.layer.Vector({
1725 + source: xxmap.markersSource
1726 + });
1727 +
1728 + // Create a tile and use OSM source
1729 + xxmap.mapLayer = new ol.layer.Tile({ source: new ol.source.OSM() });
1730 +
1731 + xxmap.mapView = new ol.View({ // Set the initial view
1732 + center: ol.proj.transform([0, 0], 'EPSG:4326', 'EPSG:3857'),
1733 + zoom: 2,
1734 + minZoom: 2,
1735 + maxZoom: 20,
1736 + extent: ol.proj.transformExtent([-100000, -69.55, 100000, 69.55], 'EPSG:4326', 'EPSG:3857')
1737 + });
1738 +
1739 + xxmap.map = new ol.Map({
1740 + target: 'xdevicesmap',
1741 + layers: [xxmap.mapLayer, xxmap.markersLayer],
1742 + view: xxmap.mapView
1743 + });
1744 +
1745 + xxmap.map.addOverlay(map_cm_popup);
1746 +
1747 + // Goto information tab if a user clicks on a feature
1748 + xxmap.map.on('click', function(evt) {
1749 + var feature = xxmap.map.forEachFeatureAtPixel(evt.pixel, function(feat, layer) { return feat; });
1750 + if (feature) {
1751 + var nodeid = feature.getId();
1752 + if (nodeid != undefined) { gotoDevice(nodeid, 10); } // Goto general info tab
1753 + else { // For pointer
1754 + var nodeFeatgoto = getCorrespondingFeature(feature); gotoDevice(nodeFeatgoto.getId(), 10);
1755 + }
1756 + }
1757 + });
1758 +
1759 + // On hover feature show the name of the node. Also add pointer style
1760 + xxmap.map.on('pointermove', function(evt) {
1761 + var feature = xxmap.map.forEachFeatureAtPixel(evt.pixel, function(feat, layer) { return feat; });
1762 + if (feature) {
1763 + xxmap.map.getTargetElement().style.cursor = 'pointer';
1764 + var coord = feature.getGeometry().getCoordinates();
1765 + // map_cm_popup.setPosition(evt.coordinate);
1766 + map_cm_popup.setPosition(coord);
1767 + featid = feature.getId();
1768 + if (featid) {
1769 + QH('xmap-info-window', feature.get('name'));
1770 + } else {
1771 + var nodeFeat = getCorrespondingFeature(feature); // Return the node feature associated to pointer.
1772 + QH('xmap-info-window', nodeFeat.get('name'));
1773 + }
1774 + } else {
1775 + xxmap.map.getTargetElement().style.cursor = '';
1776 + QH('xmap-info-window', '');
1777 + }
1778 + });
1779 +
1780 + /*
1781 + // Initialize context menu for openlayers
1782 + contextmenu = new ContextMenu({
1783 + width: 160,
1784 + defaultItems: false, // defaultItems are Zoom In/Zoom Out
1785 + items: contextmenu_items
1786 + });
1787 +
1788 + // On right click open the context menu
1789 + contextmenu.on("open", function (evt) {
1790 + var feature = xxmap.map.forEachFeatureAtPixel(evt.pixel, function(ft, l){ return ft; });
1791 + xxmap.contextmenu.clear(); //Clear the context menu
1792 + if (feature) {
1793 + var featId=feature.getId();
1794 + if (featId) { // Node feature will have an id
1795 + addContextMenuItems(feature);
1796 + }
1797 + else { // If the feature is a pointer, Get its corresponding Node feature
1798 + var nodeFeature= getCorrespondingFeature(feature); //return the node feature associated to pointer.
1799 + if (nodeFeature) { addContextMenuItems(nodeFeature); }
1800 + else{ xxmap.contextmenu.extend(contextmenu_items); }
1801 + }
1802 + }
1803 + else { xxmap.contextmenu.extend(contextmenu_items); }
1804 + });
1805 + xxmap.map.addControl(xxmap.contextmenu);
1806 + */
1807 + //addMeshOptions(); // Adds Mesh names to mesh dropdown
1808 + }
1809 +
1810 + // Add feature on to Map for a Node
1811 + function addFeature(node, lat, lon){
1812 + var existingfeature = getModifiedFeature(node._id); // Check if Corresponding feature was Modified ( Modifed feature are in active interactions list)
1813 + if (existingfeature) { xxmap.markersSource.addFeature(existingfeature); } // Add that existing feature
1814 + else { // Add new feature for this node
1815 + if (!lat && !lon) { var loc = map_parseNodeLoc(node); lat = loc[0]; lon = loc[1]; }
1816 + var feature = new ol.Feature({ geometry: new ol.geom.Point(ol.proj.transform([lon, lat], 'EPSG:4326','EPSG:3857')), name: node.name, status: node.conn, lat: lat, lon: lon });
1817 + feature.setId(node._id); // Set id for the device as nodeid
1818 + feature.setStyle(markerStyle(node));
1819 + xxmap.markersSource.addFeature(feature); // Add the feature to Marker Source
1820 + }
1821 + }
1822 +
1823 + // Removing any feature from map
1824 + function removeFeature(node) {
1825 + if (node.iploc) {
1826 + var feature = xxmap.markersSource.getFeatureById(node._id);
1827 + if (feature) { xxmap.markersSource.removeFeature(feature); }
1828 + }
1829 + }
1830 +
1831 + // Update feature
1832 + function updateFeature(node, feature) {
1833 + if (node.conn != feature.get('status') ) { // Update status if changed
1834 + feature.set('status',node.conn)
1835 + feature.setStyle(markerStyle(node));
1836 + }
1837 +
1838 + // Since this is IP address location, add some fixed randomness to the location. Avoid pin pile-up.
1839 + var loc = map_parseNodeLoc(node); lat = loc[0]; lon = loc[1];
1840 +
1841 + if ((lat != feature.get('lat')) || (lon != feature.get('lon'))) { // Update lat and lon if changed
1842 + feature.set('lat', lat); feature.set('lon', lon);
1843 + var modifiedCoordinates = ol.proj.transform([parseFloat(lon), parseFloat(lat)], 'EPSG:4326','EPSG:3857');
1844 + feature.getGeometry().setCoordinates(modifiedCoordinates);
1845 + }
1846 +
1847 + if (node.name != feature.get('name') ) { feature.set('name', node.name); } // Update name
1848 + }
1849 +
1850 + // Enable dragging of a marker after edit option is clicked in context menu
1851 + function modifyMarkerloc(ft){
1852 + var featid = ft.getId();
1853 + if (featid) {
1854 + if ( !getActiveInteractions(ft)) {
1855 + var dragInteration = new ol.interaction.Modify({
1856 + features: new ol.Collection([ft]),
1857 + pixelTolerance: 10
1858 + });
1859 + xxmap.activeInteractions.push({ featureid: featid, feature:ft, interaction: dragInteration }); // Also keep track of Interactions
1860 + xxmap.map.addInteraction(dragInteration);
1861 + }
1862 + }
1863 + }
1864 +
1865 + // This will be called when save location option is clicked in context menu
1866 + function saveMarkerloc(ft){
1867 + var featid = ft.getId()
1868 + if (featid) {
1869 + var actInteraction = getActiveInteractions(ft);
1870 + if (actInteraction) { // Check if the interaction exists
1871 + xxmap.map.removeInteraction(actInteraction); //Clear Interaction for that node
1872 + removeInteraction(featid);
1873 + var coord = ft.getGeometry().getCoordinates();
1874 + var v = ol.proj.transform(coord, 'EPSG:3857', 'EPSG:4326');
1875 + meshserver.Send({ action: 'changelocation', nodeid: featid, value: v }); // Send them to server to save changes
1876 + }
1877 + }
1878 + }
1879 +
1880 + // Style the Markers
1881 + function markerStyle(node) {
1882 + var color = connStateColor(node);
1883 + var style = new ol.style.Style({
1884 + image: new ol.style.Icon({ color: color, anchor: [0.5, 1], src: 'images/mapmarker.png' })
1885 + //stroke: new ol.style.Stroke({ color: '#000', width: 20 })
1886 + //text: new ol.style.Text({ text: 'bob!', textAlign: 'right', offsetX: -10, fill: new ol.style.Fill({ color: '#000' }), stroke: new ol.style.Stroke({ color: '#fff', width: 2 }) })
1887 + });
1888 +
1889 + /*
1890 + deviceMark.setStyle(new ol.style.Style({
1891 + text: new ol.style.Text({
1892 + //font: '12px helvetica,sans-serif',
1893 + text: currentNode.name,
1894 + textAlign: 'right',
1895 + offsetX: -10,
1896 + fill: new ol.style.Fill({ color: '#000' }),
1897 + stroke: new ol.style.Stroke({ color: '#fff', width: 2 })
1898 + }),
1899 + image: new ol.style.Icon(({ color: [113, 140, 0], src: 'images/dot.png' })) }));
1900 + */
1901 +
1902 + return [ style ];
1903 + }
1904 +
1905 + // TODO: Add more connection status types. Currently we only change color if connection status changes
1906 + function connStateColor(nodeConn){
1907 + if (nodeConn.conn == 1 || nodeConn.conn == 5) { return '#00ffdd'; } // Green for connected devices
1908 + return '#C70039'; // Red if the Agent is not connected
1909 + }
1910 +
1911 + // Add save/edit option to context menu
1912 + function addContextMenuItems(feature) {
1913 + if (getActiveInteractions(feature)) { // If this feature is modified then display save option in contextmenu
1914 + map_cm_saveMarker.data = feature;
1915 + xxmap.contextmenu.push(map_cm_saveMarker);
1916 + } else {
1917 + map_cm_editMarker.data = feature;
1918 + xxmap.contextmenu.push(map_cm_editMarker);
1919 + }
1920 + map_cm_nodemenu_items.forEach(function (item){
1921 + if (item.text == 'Zoom-in to extent' || item.text == 'Zoom-out to extent') { item.data = feature; }
1922 + else { item.data = feature.getId(); }
1923 + });
1924 + xxmap.contextmenu.extend(map_cm_nodemenu_items);
1925 + }
1926 +
1927 + // Return a active Interaction if it exists in activeInteractions list
1928 + function getActiveInteractions(feature) {
1929 + var featid = feature.getId();
1930 + for (var i = 0; i < xxmap.activeInteractions.length; i++) {
1931 + if (xxmap.activeInteractions[i].featureid == featid) { return xxmap.activeInteractions[i].interaction; }
1932 + }
1933 + return false;
1934 + }
1935 +
1936 + // Return Modified feature based on Id
1937 + function getModifiedFeature(featid) {
1938 + if (featid) {
1939 + for (var i = 0; i < xxmap.activeInteractions.length; i++) {
1940 + if (xxmap.activeInteractions[i].featureid == featid) { return xxmap.activeInteractions[i].feature; }
1941 + }
1942 + }
1943 + return null;
1944 + }
1945 +
1946 + // Remove Interaction
1947 + function removeInteraction(ftid) {
1948 + var index = -1;
1949 + for (var i = 0; i < xxmap.activeInteractions.length; i++) {
1950 + if (xxmap.activeInteractions[i].featureid === ftid) { index = i; break; }
1951 + }
1952 + if (index >= 0) { xxmap.activeInteractions.splice(index, 1); }
1953 + }
1954 +
1955 + // Check if pointer coordinates are equal to features and return node feature
1956 + function getCorrespondingFeature(pointerFeat) {
1957 + var pointerCoord = pointerFeat.getGeometry().getCoordinates();
1958 + for (var i = 0; i < xxmap.activeInteractions.length ; i++) {
1959 + var modifiedFeatures = xxmap.activeInteractions[i].feature;
1960 + var fearCoord = modifiedFeatures.getGeometry().getCoordinates();
1961 + if (fearCoord[0].toFixed(5) == pointerCoord[0].toFixed(5) && fearCoord[1].toFixed(5) == pointerCoord[1].toFixed(5) ) { return modifiedFeatures; }
1962 + }
1963 + return null;
1964 + }
1965 +
1966 + // Refresh the map and clear list
1967 + function refreshMap(reset, rebound){
1968 + if (reset) {
1969 + xxmap.map.setTarget(null);
1970 + xxmap.map = null;
1971 + xxmap.markersSource = null;
1972 + xxmap.mapView = null;
1973 + xxmap.mapLayer = null;
1974 + xxmap.activeInteractions = []; // Clear Active Interaction list
1975 + }
1976 + //clearMeshOptions();
1977 + //onSelectMeshChange();
1978 + var box = updateMapMarkers();
1979 + if ((box != null) && (rebound || (box[4] == 1))) {
1980 + var clat = (box[0] + box[2]) / 2;
1981 + var clon = (box[1] + box[3]) / 2;
1982 + var cscale = Math.max(Math.abs(box[0] - box[2]), Math.abs(box[1] - box[3]));
1983 + var view = xxmap.map.getView();
1984 + view.setCenter(ol.proj.transform([clon, clat], 'EPSG:4326', 'EPSG:3857'));
1985 + var i = 360, j = -2;
1986 + while (i > cscale) { j++; i = i / 2; }
1987 + view.setZoom(j);
1988 + }
1989 + }
1990 +
1991 + // Called When Place a node option is clicked from context menu
1992 + function placeNode(coords) {
1993 + if (xxdialogMode) return;
1994 + clearSelectedNode();
1995 + var x = '<div style="float: left; margin-bottom: 6px"><label for="selectnode-search">Search</label>&nbsp&nbsp<input type="text" placeholder="Device Name" id="selectnode-search" onchange="onPNSearchInputChange()" onkeyup="onPNSearchInputChange()" autocomplete="off" style="width: 120px"></div>';
1996 + var selectMeshElement = Q("select-mesh");
1997 + var selectedIndex = selectMeshElement.selectedIndex;
1998 + var selectedMeshValue = 'all';
1999 + if (selectedIndex != 0) {
2000 + x += '<div class="selectedmesh"> Mesh: ' + selectMeshElement[selectedIndex].text + '</div>';
2001 + selectedMeshValue=selectMeshElement[selectedIndex].value;
2002 + }
2003 + x += '<div id="placenode" style="max-height:258px;overflow-y:auto;width:100%;">';
2004 + var count = 0;
2005 + var table = updatePlaceNodeTable('', selectedIndex, selectedMeshValue);
2006 + if (table != 0) { x += table; count = 1; x += '</div>'; } else { count = 0;}
2007 + if (count == 0) {
2008 + if (selectedIndex != 0) {
2009 + var noMeshNodes='<div class="flexboxdiv"><div style="margin-right:10px"><i class="fa fa-info-circle fa-3x" style="color:green" aria-hidden="true"></i></div><div style="max-width: 300px">No nodes found in Mesh '+ selectMeshElement[selectedIndex].text + '. Go to mesh <a onclick="closePNDialog(\''+selectMeshElement[selectedIndex].value+'\')" style="cursor:pointer">'+ selectMeshElement[selectedIndex].text +'</a> in My Account page to add a node.</div></div>';
2010 + setDialogMode(2, "Select a Node to place marker", 1, null, noMeshNodes, null);
2011 + } else {
2012 + var noNodesFound = '';
2013 + if (!meshExists()) {
2014 + noNodesFound = '<div class="flexboxdiv"><div style="margin-right:10px"><i class="fa fa-info-circle fa-3x" style="color:green" aria-hidden="true"></i></div><div style="max-width: 300px"> No nodes found. To create a mesh network and add devices, go to <a onclick="closePNDialog(2)" style="cursor:pointer">My Account</a> page.</div></div>';
2015 + } else {
2016 + noNodesFound = '<div class="flexboxdiv"><div style="margin-right:10px"><i class="fa fa-info-circle fa-3x" style="color:green" aria-hidden="true"></i></div><div style="max-width: 300px"> No nodes found. To add devices, go to <a onclick="closePNDialog(1)" style="cursor:pointer">My Devices</a> page.</div></div>';
2017 + }
2018 + setDialogMode(2, "Select a Node to place marker", 1, null, noNodesFound, null);
2019 + }
2020 + } else {
2021 + setDialogMode(2, "Select a Node to place marker", 18, placeNodeEx, x, coords);
2022 + }
2023 + }
2024 +
2025 + function placeNodeEx(button, coords){
2026 + for (var i in xxmap.selectedNodes) {
2027 + var node = getNodeFromId(i);
2028 + if (node) {
2029 + var feature = markersSource.getFeatureById(i);
2030 + var v = ol.proj.transform(coords, 'EPSG:3857', 'EPSG:4326');
2031 + if (button == 2) {
2032 + if (feature) {
2033 + feature.getGeometry().setCoordinates(coords);
2034 + var activeInteraction = getActiveInteractions(feature);
2035 + if (activeInteraction) {
2036 + saveMarkerloc(feature);
2037 + } else { // If this feature is not modified, then send updated coords to server.
2038 + meshserver.Send({ action: 'changelocation', nodeid: node._id, value: v }); // Send them to server to save changes
2039 + }
2040 + } else {
2041 + meshserver.Send({ action: 'changelocation', nodeid: node._id, value: v }); // This Node is not yet added to maps.
2042 + }
2043 + }
2044 + else if (button == 1) {
2045 + if (feature) {
2046 + feature.getGeometry().setCoordinates(coords);
2047 + modifyMarkerloc(feature);
2048 + } else {
2049 + if (!node.iploc) {
2050 + addFeature(node, v[0], v[1]);
2051 + var newFeature = markersSource.getFeatureById(node._id);
2052 + modifyMarkerloc(newFeature);
2053 + }
2054 + }
2055 + }
2056 + }
2057 + }
2058 + }
2059 +
2060 + // Close place node dialog
2061 + function closePNDialog(id){
2062 + dialogclose(1);
2063 + switch (id) {
2064 + case 1:
2065 + Q("showselect")[0].selected = true;
2066 + onShowSelectChange();
2067 + go(1);
2068 + break;
2069 + case 2:
2070 + go(2);
2071 + break;
2072 + default:
2073 + gotoMesh(id);
2074 + }
2075 + }
2076 +
2077 + // On input search change
2078 + function onPNSearchInputChange() {
2079 + QH('placenode', '');
2080 + var inputSearchData = Q('selectnode-search').value.toLowerCase();
2081 + var selectMeshElement=Q("select-mesh");
2082 + var selectedIndex = selectMeshElement.selectedIndex;
2083 + var selectedMeshValue=selectMeshElement[selectedIndex].value;
2084 + updatePlaceNodeTable(inputSearchData, selectedIndex, selectedMeshValue, 1);
2085 + }
2086 +
2087 + function updatePlaceNodeTable(inputSearch, selectedIndex, selectedMeshValue, innerHtmlflag) {
2088 + var x = '<table id="placenode-table" class="selectnode-table"><tbody>';
2089 + var count = 0;
2090 + for (var i in nodes) {
2091 + if (nodes[i].mtype == 2) {
2092 + if ((nodes[i].meshid == selectedMeshValue || selectedIndex == 0) && ((nodes[i].name.toLowerCase().indexOf(inputSearch) >= 0 || inputSearch == '') || (nodes[i].hostl != undefined && nodes[i].hostl.toLowerCase().indexOf(inputSearch) >= 0))) {
2093 + count++;
2094 + x+='<tr id="'+ nodes[i]._id +'-rowid" onclick=selectNodetoplace(\''+ nodes[i]._id +'\') onmouseover="changeRadioImg(\''+ nodes[i]._id +'!#!mouseover'+ '\')" onmouseout="changeRadioImg(\''+ nodes[i]._id +'!#!mouseout'+ '\')">';
2095 + x+='<td style="width: 30px; border-radius: 4px; text-align: center;"><i class="fa fa-square-o fa-lg selectnode-checkbox " id="'+ nodes[i]._id +'-img' +'" style="border-color: #7CFC00;" aria-hidden="true"></i></td>';
2096 + x+='<td class="selectnode-td">'+ nodes[i].name + '</td>';
2097 + x+='</tr>';
2098 + }
2099 + }
2100 + }
2101 + x+='</tbody></table>';
2102 + if (innerHtmlflag) {
2103 + if (count == 0) { QH('placenode', '<div class="flexboxdiv"><div style="margin: 0px 10px 0px 4px"><i class="fa fa-exclamation-circle fa-2x" style="color:red" aria-hidden="true"></i></div><div style="max-width: 300px"><b>No Nodes found with this Search Criteria.</b></div></div>'); } else { QH('placenode', x);}
2104 + }else {
2105 + if (count == 0) { return 0; }
2106 + else { return x; }
2107 + }
2108 + }
2109 +
2110 + function selectNodetoplace(id){
2111 + var imgeElement = Q(id + '-img');
2112 + if (xxmap.selectedNodes[id]) {
2113 + imgeElement.classList.remove('fa-check-square-o');
2114 + imgeElement.classList.add('fa-square');
2115 + delete xxmap.selectedNodes[id];
2116 + CheckedNodesforPN();
2117 + } else {
2118 + imgeElement.classList.remove('fa-square');
2119 + imgeElement.classList.add('fa-check-square-o');
2120 + xxmap.selectedNodes[id] = { color: Q(id+'-rowid').style.backgroundColor};
2121 + QE('idx_dlgPlaceandSave', true);
2122 + QE('idx_dlgPlaceButton', true);
2123 + }
2124 + }
2125 +
2126 + function CheckedNodesforPN() {
2127 + var c = 0;
2128 + for (var i in xxmap.selectedNodes) { if (xxmap.selectedNodes[i]) { c++; } }
2129 + if (c == 0) {
2130 + QE('idx_dlgPlaceandSave', false);
2131 + QE('idx_dlgPlaceButton', false);
2132 + }
2133 + }
2134 +
2135 + // Set selected node to null
2136 + function clearSelectedNode() {
2137 + xxmap.selectedNodes = {};
2138 + QE('idx_dlgPlaceandSave', false);
2139 + QE('idx_dlgPlaceButton', false);
2140 + }
2141 +
2142 + // Change radio button on mouseover and out for 'select a node to place dialog'
2143 + function changeRadioImg(name) {
2144 + var index = name.indexOf("mouseout");
2145 + var element = name.split('!#!');
2146 + if (!xxmap.selectedNodes[element[0]]) {
2147 + var imgeElement= Q(element[0]+'-img');
2148 + if (index > -1) {
2149 + imgeElement.classList.remove('fa-square');
2150 + imgeElement.classList.add('fa-square-o');
2151 + } else {
2152 + imgeElement.classList.remove('fa-square-o');
2153 + imgeElement.classList.add('fa-square');
2154 + }
2155 + }
2156 + }
2157 +
2158 + // Add option for available meshes in mesh Dropdown
2159 + function addMeshOptions(addMeshid, meshName) {
2160 + /*
2161 + var meshOptions = Q('select-mesh');
2162 + if (addMeshid && meshName) {
2163 + var option = document.createElement('option');
2164 + option.value =addMeshid;
2165 + option.text = meshName;
2166 + meshOptions.add(option); // Add specific option
2167 + }
2168 + else {
2169 + for (var i in meshes) { // Add all options
2170 + var option = document.createElement('option');
2171 + option.value = i;
2172 + option.text = meshes[i].name;
2173 + meshOptions.add(option);
2174 + }
2175 + }
2176 + */
2177 + }
2178 +
2179 + // Remove/Modify options in Mesh dropdown (if modMeshname is defined then Modify else Remove)
2180 + function meshOptionRmvMod(delMeshid, modMeshname){
2181 + /*
2182 + var meshOptions = Q('select-mesh');
2183 + if (delMeshid) {
2184 + var index=-1;
2185 + for (var i = 1; i < meshOptions.options.length; i++) {
2186 + if (meshOptions[i].value === delMeshid) { index=i; }
2187 + }
2188 + if (index > 0) {
2189 + if (modMeshname) {
2190 + meshOptions[index].innerHTML=modMeshname; // If Mesh name is Modified
2191 + }
2192 + else { meshOptions.remove(index); }
2193 + }
2194 + }
2195 + */
2196 + }
2197 +
2198 + //Check if there is any mesh created
2199 + function meshExists() {
2200 + for (var i in meshes) { if (meshes[i]) { return true; } }
2201 + return false;
2202 + }
2203 +
2204 + // Reset Mesh dropdown option to 'All' when a current view mesh is deleted.
2205 + function setMeshView(emeshid) {
2206 + var selectMeshElement=Q("select-mesh");
2207 + var selectedIndex = selectMeshElement.selectedIndex;
2208 + if (selectMeshElement[selectedIndex].value == emeshid) { selectMeshElement[0].selected = true; onSelectMeshChange(); }
2209 + }
2210 +
2211 + // Clear all mesh options except 'All'
2212 + function clearMeshOptions() {
2213 + /*
2214 + var meshOptions=Q('select-mesh');
2215 + for(var i = meshOptions.options.length - 1 ; i > 0 ; i--) { meshOptions.remove(i); }
2216 + */
2217 + }
2218 +
2219 + // Make a http get call- Replace this with AJAX get if jquery is used
2220 + function getSearchLocation() {
2221 + try {
2222 + var searchdata = Q('mapSearchLocation').value.trim();
2223 + if (searchdata.length > 0) {
2224 + var xmlhttp = new XMLHttpRequest(); // Compatible with Chrome, Opera, Safari, IE7+, Firefox.
2225 + xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { formatSearchData(xmlhttp.responseText); } }
2226 + xmlhttp.open("GET", 'https://nominatim.openstreetmap.org/search?q=' + searchdata + '&format=json', true); // Get request
2227 + xmlhttp.send();
2228 + }
2229 + } catch (e) {}
2230 + }
2231 +
2232 + // Format data recieved from nominatim API and display it on content window
2233 + function formatSearchData(data) {
2234 + try {
2235 + QH('xmapSearchResults','');
2236 + var dataInfo = JSON.parse(data), count = 0, x = '<div style="overflow-y:auto;width:100%;max-height:240px">';
2237 + for (var i = 0; i < dataInfo.length; i++) {
2238 + if (dataInfo[i].display_name && dataInfo[i].boundingbox[0] && dataInfo[i].boundingbox[1] && dataInfo[i].boundingbox[2] && dataInfo[i].boundingbox[3]) {
2239 + count++;
2240 + var color = (i % 2 == 0)?'F5F5F5':'EBEBEB';
2241 + x += '<div style=cursor:pointer;padding:5px;background-color:#' + color + ' onclick=mapGotoSelectedLocation(this)><div>' + dataInfo[i].display_name + '</div><div style=display:none>' + dataInfo[i].boundingbox[0] + '!#!' + dataInfo[i].boundingbox[1] + '!#!' + dataInfo[i].boundingbox[2] + '!#!' + dataInfo[i].boundingbox[3] + '</div></div>';
2242 + }
2243 + }
2244 + x += '</div>';
2245 + if (count == 1) {
2246 + // If only one result is returned then zoom to that location
2247 + var extent = [ parseFloat(dataInfo[0].boundingbox[2]), parseFloat(dataInfo[0].boundingbox[0]), parseFloat(dataInfo[0].boundingbox[3]), parseFloat(dataInfo[0].boundingbox[1]) ];
2248 + zoomToExtent(extent);
2249 + } else {
2250 + if (count == 0) { x = '<div style=width:200px>No location found.<div>'; }
2251 + QV('xmapSearchResultsDlg', true);
2252 + }
2253 + QH('xmapSearchResults', x);
2254 + }
2255 + catch (e) {}
2256 + }
2257 +
2258 + // Zoom into the bounding box
2259 + function mapGotoSelectedLocation(obj) {
2260 + var objchildren = obj.children;
2261 + var boundingBox = objchildren[1].innerHTML.split('!#!');
2262 + var extent = [parseFloat(boundingBox[2]), parseFloat(boundingBox[0]), parseFloat(boundingBox[3]), parseFloat(boundingBox[1])];
2263 + //Q('search-location').value = objchildren[0].innerHTML;
2264 + zoomToExtent(extent);
2265 + mapCloseSearchWindow();
2266 + }
2267 +
2268 + // Close the search window
2269 + function mapCloseSearchWindow() {
2270 + QH('xmapSearchResults', '');
2271 + QV('xmapSearchResultsDlg', false);
2272 + }
2273 +
2274 + // Zoom to specific cordinates
2275 + function zoomToLocation(coordinates, zoomVal) {
2276 + var view = xxmap.map.getView();
2277 + view.setCenter(coordinates);
2278 + view.setZoom(zoomVal);
2279 + }
2280 +
2281 + function zoomToFitExtent() {
2282 + var features = xxmap.markersSource.getFeatures();
2283 + if (features.length > 0) {
2284 + var extent = xxmap.markersSource.getExtent();
2285 + xxmap.map.getView().fit(extent, xxmap.map.getSize());
2286 + }
2287 + }
2288 +
2289 + function zoomToExtent(extent){
2290 + var boundingExtent = ol.proj.transformExtent(extent, ol.proj.get('EPSG:4326'), ol.proj.get('EPSG:3857'));
2291 + xxmap.map.getView().fit(boundingExtent, xxmap.map.getSize());
2292 + }
2293 +
2294 +
2295 //
2296 // MY DEVICE
2297 //
@@ -1943,18 +2633,7 @@
2633
2634 // Setup the device mark layer
2635 var deviceMark = new ol.Feature({ geometry: new ol.geom.Point(ol.proj.fromLonLat([lng, lat])) });
1946 - //deviceMark.setStyle(new ol.style.Style({ image: new ol.style.Icon(({ color: [113, 140, 0], src: 'images/dot.png' })) }));
1947 - deviceMark.setStyle(new ol.style.Style({
1948 -
1949 - text: new ol.style.Text({
1950 - //font: '12px helvetica,sans-serif',
1951 - text: currentNode.name,
1952 - textAlign: 'right',
1953 - offsetX: -10,
1954 - fill: new ol.style.Fill({ color: '#000' }),
1955 - stroke: new ol.style.Stroke({ color: '#fff', width: 2 })
1956 - }),
1957 - image: new ol.style.Icon(({ color: [113, 140, 0], src: 'images/dot.png' })) }));
2636 + deviceMark.setStyle(markerStyle(currentNode));
2637 var vectorSource = new ol.source.Vector({ features: [deviceMark] });
2638 var vectorLayer = new ol.layer.Vector({ source: vectorSource });
2639
@@ -2135,7 +2814,6 @@
2814 }
2815
2816 function applyDesktopSettings() {
2138 - console.log(desktopsettings);
2817 d7desktopmode.value = desktopsettings.encoding;
2818 d7showfocus.checked = desktopsettings.showfocus;
2819 d7showcursor.checked = desktopsettings.showmouse;