summaryrefslogtreecommitdiffstats
path: root/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src
diff options
context:
space:
mode:
Diffstat (limited to 'sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src')
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/doc/mwtnTopology.builinfo.md39
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/doc/mwtnTopology.fs.md78
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/images/mwtnTopology.pngbin0 -> 3447 bytes
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology-custom.css102
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology.controller.js4877
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology.module.js57
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology.rest209
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology.services.js1020
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/accordeonHeader.tpl.html13
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/clocksGrid.tpl.html1
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/ethConnectionsGrid.tpl.html1
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/ethernetView.tpl.html18
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/frame.tpl.html34
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/ieee1588View.tpl.html18
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/linkDetails.tpl.html110
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/links.tpl.html2
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/linksGrid.tpl.html1
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/maps.tpl.html59
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/networkElementsGrid.tpl.html1
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/nodes.tpl.html2
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/physicalView.tpl.html18
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/portsGrid.tpl.html1
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/ptpLinksGrid.tpl.html1
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/siteDetails.tpl.html77
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/siteGrid.tpl.html9
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/siteLinkGrid.tpl.html8
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/sitePathGrid.tpl.html1
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/siteView.tpl.html31
-rwxr-xr-xsdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/topology.tpl.html2
29 files changed, 6790 insertions, 0 deletions
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/doc/mwtnTopology.builinfo.md b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/doc/mwtnTopology.builinfo.md
new file mode 100755
index 00000000..594b8e26
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/doc/mwtnTopology.builinfo.md
@@ -0,0 +1,39 @@
+# Buildinfo
+
+Ausgehend vom Verzeichnis:
+SDN-Projects-Boron/code/ux
+
+ll => pom.xml muss vorhanden sein.
+
+## manuelles Builden
+
+Die Schritte 1, 5 und 6 sind während des Buildens notwendig, die anderen Schritte sind zu Kontrollzwecken und zum starten / Stoppen vom Karaf Server
+
+1. Für den Compilevorgang der eigenen Komponenten:
+ - `cd ~/SDN-Projects-Boron/code`
+ - `mvn clean install -DskipTests`
+ - `cp -R ~/.m2/repository/com/highstreet $ODL_KARAF_HOME/system/com`
+2. Zum beenden von Karaf:
+ - ctrl+d in der Shell, auf der Karaf läuft.
+ - `sudo $ODL_KARAF_HOME/bin/client -u karaf "shutdown --force"`, wenn man nicht in der passenden Shell ist
+3. Starten von Karaf:
+ - `cd $ODL_KARAF_HOME`
+ - `./bin/karaf`
+4. Zum gucken, welche Module installiert sind (von highstreet, grep):
+ - `bundle:list | grep mwtn`
+5. Bundles deinstallieren (Funktioniert nur innerhalb der Karaf Shell)
+ - `bundle:uninstall "ONF :: Wireless :: mwtnCommons-bundle" "ONF :: Wireless :: mwtnTopology-bundle"`
+6. (geänderte) Bundles installieren (Funktioniert nur innerhalb der Karaf Shell)
+ - `bundle:install -s mvn:com.highstreet.technologies.odl.dlux/mwtnCommons-bundle/0.4.0-SNAPSHOT mvn:com.highstreet.technologies.odl.dlux/mwtnTopology-bundle/0.4.0-SNAPSHOT`
+
+## GIT:
+
+Abrufen:
+- `cd ~/SDN-Projects-Boron/code/ux/mwtnTopology/mwtnTopology-module/src/main/resources`
+- `git pull`
+
+Einchecken:
+- `git add doc/*.md`
+- `git status` (Kontrolle der einzucheckenden Dateien)
+- `git commit` (Ist der eincheckvorgang an sich (lokaler GitServer))
+- `git push` (Übertragen aller Commits zum zentralen Server)
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/doc/mwtnTopology.fs.md b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/doc/mwtnTopology.fs.md
new file mode 100755
index 00000000..861306c2
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/doc/mwtnTopology.fs.md
@@ -0,0 +1,78 @@
+# mwtnTopology - Functional Specification
+
+## Beschreibung
+
+Das mwtnTopology Control zeichnet eine Karte mit Hilfe eines austauschbaren Kartendienstes
+Dabei wird die Satellitendarstellung verwendet (wenn möglich), sonst wird die Kartendarstellung gewählt.
+Über dem Kartenlayer soll ein Vectorlayer mit Informationen zu Mobilfunkmasten und zugehörigen Richtfunkstrecken dargestellt werden.
+Die Karte soll mittels Mausbedienung Scroll und Zoombar sein, Klicks auf einen Mast oder eine Richtfunkstrecke stellen weitere Informationen in einem Popup zur Verfügung.
+
+## Anforderungen
+
+* Die Webseite / der Webserver hat *keinen* Zugriff auf das öffentliche Internet. Online-Kartendienste wie Google, Bing oder OpenStreetMap sind damit nicht erreichbar.
+* Der Server stellt eine API bereit, an welcher die Kartenkacheln als Bitmaps (png/jpeg) bereitgestellt werden.
+* Der darzustellende Bereich wird angegeben durch
+ - Ein Latitude/Longitude Koordinatenpaar (Mittelpunkt) und eine Zoomstufe in % wobei 0% der "Weltraumansicht" und 100% der "Sandkrümel auf der Straße" entspricht.
+ Die benötigten Kacheln werden anhand der Zoomstufe und der Größe des Kartenausschnitts in Bildschirmpixel berechnet.
+ - Zwei Latitude/Longitude Koordinatenpaare, welche die obere rechte Ecke und die untere linke Ecke des darzustellenden Kartenausschnitts darstellen.
+ Die benötigte Zoomstufe wird anhand der Koordinatenpaare und der Größe des Kartenausschnitts in Bildschirmpixel berechnet.
+ + 2 Mögliche SubModi: "Fill" und "Cut"
+* Die Zoomstufen als %-Angaben werden auf die vom Kartenprovider verwendeten ganzzahligen Zoomstufen gemappt.
+ - Nicht jeder Kartenprovider kann für alle Zoomstufen Bitmaps liefern. Gibt es eine Möglichkeit die maximale Provider-Zoomstufe für eine Koordinate abzufragen?
+* Der Client cached die Bildkacheln, um die Serverlast zu reduzieren.
+ - Localstorage?
+
+## Offene Fragen
+
+* Soll die Karte immer eingenordet sein oder ist eine Kartenrotation erforderlich?
+* Welche Karten-UI-Steuerelemente müssen angezeigt werden
+ - +/- Button / Zoom-Slider
+ - 2D-Rotations-Gizmo / Kompass
+ - Geokoordinate Zentrieren
+ - Kartenscale (20m|---------------|)
+ - Copyright-Angaben der verwendeten Kartendaten
+ - Ein/Ausblenden des Vector-Overlays
+* Kann ein eigener Tile Render Server (wie z.B. Maperitive) verwendet werden?
+* Transparente, vorberechnete Kacheln des VectorLayers verfügbar? (Kacheln erlauben keine Hover-Informationen einzelner Richtfunktstrecken)
+
+## Berechnungsformeln
+
+- [OSM: Slippy map tilenames](http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames)
+
+The first part of the URL specifies the tile server, and perhaps other parameters which might influence the style.
+
+Generally several subdomains (server names) are provided to get around browser limitations on the number of simultaneous HTTP connections to each host.
+Browser-based applications can thus request multiple tiles from multiple subdomains faster than from one subdomain.
+For example, OSM, OpenCycleMap and CloudMade servers have three subdomains (a.tile, b.tile, c.tile), MapQuest has four (otile1, otile2, otile3, otile4),
+all pointing to the same CDN. That all comes before the /zoom/x/y.png tail.
+
+Example:
+http://[abc].tile.openstreetmap.org/zoom/x/y.png
+
+```js
+function long2tile(lon,zoom) { return (Math.floor((lon+180)/360*Math.pow(2,zoom))); }
+function lat2tile(lat,zoom) { return (Math.floor((1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2 *Math.pow(2,zoom))); }
+
+var zoom = 9;
+var top_tile = lat2tile(north_edge, zoom); // eg.lat2tile(34.422, 9);
+var left_tile = lon2tile(west_edge, zoom);
+var bottom_tile = lat2tile(south_edge, zoom);
+var right_tile = lon2tile(east_edge, zoom);
+var width = Math.abs(left_tile - right_tile) + 1;
+var height = Math.abs(top_tile - bottom_tile) + 1;
+
+// total tiles
+var total_tiles = width * height; // -> eg. 377
+```
+
+## Links
+
+- [OpenStreetMap DE:Tile usage policy](http://wiki.openstreetmap.org/wiki/DE:Tile_usage_policy)
+- [OpenStreetMap DE:Karte in Webseite einbinden](http://wiki.openstreetmap.org/wiki/DE:Karte_in_Webseite_einbinden)
+- [OpenStreetMap DE:Maperitive](http://wiki.openstreetmap.org/wiki/DE:Maperitive)
+- [Maperitive, EN:Hauptwebseite](http://maperitive.net/)
+- [Google Maps Javascript API + Key anfordern](https://developers.google.com/maps/documentation/javascript/?hl=de)
+- [Google Maps Javascript Erste Schritte](https://developers.google.com/maps/documentation/javascript/tutorial?hl=de)
+- [Google Developer Console](https://console.developers.google.com/)
+- [GoogleMap AngularJS Directive](https://rawgit.com/allenhwkim/angularjs-google-maps/master/build/docs/index.html)
+- [Sample asnyc script load into head element](http://just-run.it/#/ByvA_ClTe/0)
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/images/mwtnTopology.png b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/images/mwtnTopology.png
new file mode 100755
index 00000000..7ddbeb2e
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/images/mwtnTopology.png
Binary files differ
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology-custom.css b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology-custom.css
new file mode 100755
index 00000000..d04a7645
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology-custom.css
@@ -0,0 +1,102 @@
+/**
+ * Add your application related css here
+ */
+
+.owl p span {
+ color: #393939;
+}
+
+.angular-google-map-container {
+ height: 1200px;
+}
+
+.mwtnTopologyGrid {
+ height: 412px;
+ background-color: white;
+}
+
+.mwtnTopologyGrid span {
+ color: #393939;
+}
+
+#mwtnTopology .nav-tabs {
+ border-bottom: 1px solid #ddd;
+}
+#mwtnTopology .nav-tabs > li {
+ float: left;
+ margin-bottom: -1px;
+}
+#mwtnTopology .nav-tabs > li > a {
+ margin-right: 2px;
+ line-height: 1.42857143;
+ border: 1px solid transparent;
+ border-radius: 4px 4px 0 0;
+
+ color: #555;
+ cursor: default;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ border-bottom-color: transparent;
+}
+#mwtnTopology .nav-tabs > li > a:hover {
+ color: #fff;
+ cursor: default;
+ background-color: #428bca;
+ border: 1px solid #428bca;
+ border-color: #eee #eee #ddd;
+ border-bottom-color: transparent;
+
+}
+#mwtnTopology .nav-tabs > li.active > a,
+#mwtnTopology .nav-tabs > li.active > a:hover,
+#mwtnTopology .nav-tabs > li.active > a:focus {
+ color: #fff;
+ cursor: default;
+ background-color: #428bca;
+ border: 1px solid #428bca;
+ border-bottom-color: transparent;
+}
+#mwtnTopology .nav-tabs.nav-justified {
+ width: 100%;
+ border-bottom: 0;
+}
+#mwtnTopology .nav-tabs.nav-justified > li {
+ float: none;
+}
+#mwtnTopology .nav-tabs.nav-justified > li > a {
+ margin-bottom: 5px;
+ text-align: center;
+}
+#mwtnTopology .nav-tabs.nav-justified > .dropdown .dropdown-menu {
+ top: auto;
+ left: auto;
+}
+@media (min-width: 768px) {
+ #mwtnTopology .nav-tabs.nav-justified > li {
+ display: table-cell;
+ width: 1%;
+ }
+ #mwtnTopology .nav-tabs.nav-justified > li > a {
+ margin-bottom: 0;
+ }
+}
+#mwtnTopology .nav-tabs.nav-justified > li > a {
+ margin-right: 0;
+ border-radius: 4px;
+}
+#mwtnTopology .nav-tabs.nav-justified > .active > a,
+#mwtnTopology .nav-tabs.nav-justified > .active > a:hover,
+#mwtnTopology .nav-tabs.nav-justified > .active > a:focus {
+ border: 1px solid #ddd;
+}
+@media (min-width: 768px) {
+ #mwtnTopology .nav-tabs.nav-justified > li > a {
+ border-bottom: 1px solid #ddd;
+ border-radius: 4px 4px 0 0;
+ }
+ #mwtnTopology .nav-tabs.nav-justified > .active > a,
+ #mwtnTopology .nav-tabs.nav-justified > .active > a:hover,
+ #mwtnTopology .nav-tabs.nav-justified > .active > a:focus {
+ border-bottom-color: #fff;
+ }
+}
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology.controller.js b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology.controller.js
new file mode 100755
index 00000000..f8aa303a
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology.controller.js
@@ -0,0 +1,4877 @@
+/*
+ * Copyright (c) 2016 highstreet technologies GmbH and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+if (typeof (Number.prototype.toRad) === "undefined") {
+ Number.prototype.toRadians = function () {
+ return this * Math.PI / 180;
+ }
+}
+
+if (Number.prototype.toDegrees === undefined) {
+ Number.prototype.toDegrees = function () {
+ return this * 180 / Math.PI;
+ };
+}
+
+// If array is empty, undefined is returned. If not empty, the first element
+// that evaluates to false is returned. If no elements evaluate to false, the
+// last element in the array is returned.
+Array.prototype.and = function (defaultValue) {
+ for (var i = 0, len = this.length - 1; i < len && this[i]; i++);
+ return this.length ? this[i] : defaultValue;
+};
+
+// If array is empty, undefined is returned. If not empty, the first element
+// that evaluates to true is returned. If no elements evaluate to true, the
+// last element in the array is returned.
+Array.prototype.or = function () {
+ for (var i = 0, len = this.length - 1; i < len && !this[i]; i++);
+ return this[i];
+};
+
+var eventsFabric = function () {
+ var topics = {};
+ var hOP = topics.hasOwnProperty;
+
+ return {
+ subscribe: function (topic, listener) {
+ // Create the topic's object if not yet created
+ if (!hOP.call(topics, topic)) topics[topic] = [];
+
+ // Add the listener to queue
+ var index = topics[topic].push(listener) - 1;
+
+ // Provide handle back for removal of topic
+ return {
+ remove: function () {
+ delete topics[topic][index];
+ }
+ };
+ },
+ publish: function (topic, info) {
+ // If the topic doesn't exist, or there's no listeners in queue, just leave
+ if (!hOP.call(topics, topic)) return;
+
+ // Cycle through topics queue, fire!
+ topics[topic].forEach(function (item) {
+ item(info != undefined ? info : {});
+ });
+ }
+ };
+};
+
+var lastOpenedInfoWindow = null;
+
+/*********************************************************************************************/
+
+/** @typedef {{ id: string, siteA: string, siteZ: string, siteNameA: string, siteNameZ: string, airInterfaceLinks: {id: string, siteLink: string, radio: string, polarization: string }[]}} SiteLinkDetails */
+
+define(['app/mwtnCommons/bower_components/lodash/dist/lodash',
+ 'app/mwtnCommons/bower_components/cytoscape/dist/cytoscape',
+ 'app/mwtnTopology/mwtnTopology.module',
+ 'app/mwtnTopology/mwtnTopology.services',
+ 'app/mwtnCommons/mwtnCommons.services'
+], function (_, cytoscape, mwtnTopologyApp) {
+
+ // module.exports = function () {
+ // const mwtnTopologyApp = require('app/mwtnTopology/mwtnTopology.module');
+ // const mwtnTopologyService = require('app/mwtnTopology/mwtnTopology.services');
+ // const cytoscape = require('cytoscape');
+
+ // remove '_' from global scope but use it here as '_'.
+ _.noConflict();
+
+ /********************************************* Sites *************************/
+
+ mwtnTopologyApp.directive('mwtnTopologyMap', ["$timeout", "$q", "$mwtnTopology", function ($timeout, $q, $mwtnTopology) {
+ return {
+ restrict: 'E',
+ replace: true,
+ template: '<div style="position:realtive;width:100%;height:700px" ng-transclude=""></div>',
+ controller: function () {
+
+ },
+ transclude: true,
+ scope: {
+ manualMapBounds: "=?",
+ onBoundsChanged: "&?"
+ },
+ link: function (scope, element, attrs, ctrl) {
+
+ var mapApiReadyDefer = $q.defer();
+ var disbaleNotifications = true;
+ //var includeCenter = false;
+
+ var initialMapOptions = {
+ center: new google.maps.LatLng(0, 0),
+ mapTypeId: google.maps.MapTypeId.HYBRID,
+ panControl: false,
+ rotateControl: false,
+ streetViewControl: false,
+ tilt: 0,
+ zoom: 2,
+ minZoom: 2
+ };
+
+ // Wait at least one digest cycle before init the google map.
+ $timeout(function () {
+ ctrl.map = new google.maps.Map(element[0], initialMapOptions);
+
+ // Wait until google maps is fully intialized to attach nessessary event handler.
+ waitUntilGoogleMapsIsReady();
+ });
+
+ element.on("$destroy", function () {
+ if (scope.waitUntilGoogleMapsIsReadyTimeoutId) {
+ $timeout.cancel(scope.waitUntilGoogleMapsIsReadyTimeoutId);
+ }
+ if (scope.onDragEndListener) {
+ google.maps.event.removeListener(scope.onDragEndListener);
+ delete (scope.onDragEndListener);
+ }
+ if (scope.onZoomChangedListener) {
+ google.maps.event.removeListener(scope.onZoomChangedListener);
+ delete (scope.onZoomChangedListener);
+ }
+
+ // window.removeEventListener('resize', onBoundsChanged);
+
+ });
+
+ scope.$watch("manualMapBounds", function (newBounds, oldBounds) {
+ mapApiReadyDefer.promise.then(function () {
+
+ // if this was an uri change from an not user originated action just return;
+ if (newBounds.internal) return;
+
+ // values must exist and at least one must be different or initial load (new === old)
+ if (newBounds.bottom != null && newBounds.left != null && newBounds.top != null && newBounds.right != null) {
+ fitBounds(ctrl.map, new google.maps.LatLngBounds(
+ new google.maps.LatLng(newBounds.bottom, newBounds.left),
+ new google.maps.LatLng(newBounds.top, newBounds.right)));
+ } else if (newBounds.lat != null && newBounds.lng != null && newBounds.zoom != null && (newBounds.lat != oldBounds.lat || newBounds.lng != oldBounds.lng || newBounds.zoom != oldBounds.zoom || newBounds === oldBounds)) {
+ disbaleNotifications = true;
+ google.maps.event.addListenerOnce(ctrl.map, 'bounds_changed', function (event) {
+ disbaleNotifications = false;
+ onBoundsChanged(false);
+
+ });
+ ctrl.map.setCenter({ lat: newBounds.lat, lng: newBounds.lng });
+ ctrl.map.setZoom(newBounds.zoom);
+
+ }
+ console.log("manualMapBounds", newBounds);
+ });
+ });
+
+ /**
+ * An asynchronous function that is called continuously every 100 milliseconds until the google maps control has finished its first complete draw.
+ * Then it sets up all event handlers and reports its bounds.
+ */
+ function waitUntilGoogleMapsIsReady() {
+ var getBoundsApi = ctrl.map.getBounds();
+ if (getBoundsApi) {
+ delete (scope.waitUntilGoogleMapsIsReadyTimeoutId);
+ // Change to dragend https://developers.google.com/maps/documentation/javascript/events?hl=de
+ scope.onDragEndListener = ctrl.map.addListener('dragend', function () {
+ onBoundsChanged(true);
+ });
+ scope.onZoomChangedListener = ctrl.map.addListener('zoom_changed', function () {
+ onBoundsChanged(true);
+ });
+ // window.addEventListener('resize', function () {
+ // onBoundsChanged(false);
+ // });
+
+ disbaleNotifications = false;
+ mapApiReadyDefer.resolve();
+ } else {
+ scope.waitUntilGoogleMapsIsReadyTimeoutId = $timeout(waitUntilGoogleMapsIsReady, 100);
+ }
+ }
+
+ /**
+ * Gets the current bound and zoom level of the map control and reports it to its parent component.
+ * Hint: do not call directly use fitBounds helper function since there is a google bug in the fit bounds function.
+ */
+ function onBoundsChanged(userOriginated) {
+ if (!disbaleNotifications && scope.onBoundsChanged && angular.isFunction(scope.onBoundsChanged)) {
+
+ var getBoundsApi = ctrl.map.getBounds();
+ var northEastApi = getBoundsApi.getNorthEast();
+ var southWestApi = getBoundsApi.getSouthWest();
+ var center = ctrl.map.getCenter();
+
+ var data = {
+ top: northEastApi.lat(),
+ right: northEastApi.lng(),
+ bottom: southWestApi.lat(),
+ left: southWestApi.lng(),
+ lat: center.lat(),
+ lng: center.lng(),
+ zoom: ctrl.map.getZoom()
+ };
+
+ console.log("onBoundsChanged", data);
+
+ scope.onBoundsChanged({
+ data: data,
+ userOriginated: userOriginated === true
+ });
+ }
+ }
+
+ // bug: the original google code will fit bounds will add some extra space, this function will fix it
+ function fitBounds(map, bounds) {
+ var oldBounds = map.getBounds();
+
+ // ensure there is any change
+ if (oldBounds.toUrlValue() == bounds.toUrlValue()) {
+ return;
+ }
+
+ disbaleNotifications = true;
+ google.maps.event.addListenerOnce(map, 'bounds_changed', function (event) {
+ // bugfix: this is a fix for the https://issuetracker.google.com/issues/35820423
+ // the map does sometime zoom out if it gets its one bounds
+
+ // var newSpan = map.getBounds().toSpan(); // the span of the map set by Google fitBounds (always larger by what we ask)
+ // var askedSpan = bounds.toSpan(); // the span of what we asked for
+ // var latRatio = (newSpan.lat() / askedSpan.lat()) - 1; // the % of increase on the latitude
+ // var lngRatio = (newSpan.lng() / askedSpan.lng()) - 1; // the % of increase on the longitude
+ // // if the % of increase is too big (> to a threshold) we zoom in
+ // if (Math.min(latRatio, lngRatio) > 0.46) {
+ // // 0.46 is the threshold value for zoming in. It has been established empirically by trying different values.
+ // this.setZoom(this.getZoom() + 1);
+ // }
+ disbaleNotifications = false;
+ onBoundsChanged(false);
+
+ });
+ map.fitBounds(bounds); // does the job asynchronously
+ }
+ }
+ };
+ }]);
+
+ mwtnTopologyApp.directive('mwtnTopologyMapSites', ['$compile', '$rootScope', function ($compile, $rootScope) {
+
+ return {
+ restrict: 'E',
+ require: '^mwtnTopologyMap',
+ scope: {
+ sites: "=",
+ selectedSite: "=",
+ api: "="
+ },
+ link: function (scope, element, attrs, mwtnTopologyMapController) {
+ // todo: shouild come from a service
+ var normalIcon = {
+ path: google.maps.SymbolPath.CIRCLE,
+ scale: 10,
+ strokeColor: '#00ccff',
+ strokeOpacity: 0.8,
+ strokeWeight: 2,
+ fillColor: '#00ccff',
+ fillOpacity: 0.35,
+ zIndex: 2
+ };
+
+ var highlightIcon = {
+ path: google.maps.SymbolPath.CIRCLE,
+ scale: 10,
+ strokeColor: '#eaae3c',
+ strokeOpacity: 0.8,
+ strokeWeight: 3,
+ fillColor: '#eaae3c',
+ fillOpacity: 0.65,
+ zIndex: 2
+ };
+
+ var selectedIcon = {
+ path: google.maps.SymbolPath.CIRCLE,
+ scale: 10,
+ strokeColor: '#00FD30',
+ strokeOpacity: 0.8,
+ strokeWeight: 2,
+ fillColor: '#00FD30',
+ fillOpacity: 0.35,
+ zIndex: 2
+ };
+
+ scope.displayedSites = {};
+ scope.knownSites = {};
+
+ scope.$watch("selectedSite", function (newSiteId, oldSiteId) {
+
+ if (oldSiteId && scope.displayedSites[oldSiteId]) {
+ scope.displayedSites[oldSiteId].setOptions({
+ icon: normalIcon
+ });
+ }
+
+ if (newSiteId && scope.displayedSites[newSiteId]) {
+ scope.displayedSites[newSiteId].setOptions({
+ icon: selectedIcon
+ });
+ }
+
+ });
+
+ if (!scope.api) return;
+
+ /**
+ * Updates the google map markers
+ * @param addedSiteIds {string[]} The ids of the sites which are added to the scope.displayedSites dictionary.
+ * @param removedFromVisibleIds {string[]} The ids of sites which are removed from the visible bounding rectangle.
+ * @param movedFromVisibleToKnownIds {string[]} The ids of sites which are moved from the visible bounding rectangle to the extended bounding rectangle.
+ * @param removedFromKnownIds {string[]} The ids of sites which are removed from the extended bounding rectangle.
+ * @param movedFromKnownToVisibleIds {string[]} The ids of sites which are moved from the extended bounding rectangle to the visible bounding rectangle.
+ */
+ scope.api.updateSites = function (addedSiteIds, removedFromVisibleIds, movedFromVisibleToKnownIds, removedFromKnownIds, movedFromKnownToVisibleIds) {
+ if (!addedSiteIds) addedSiteIds = [];
+ if (!removedFromVisibleIds) removedFromVisibleIds = [];
+ if (!movedFromVisibleToKnownIds) movedFromVisibleToKnownIds = [];
+ if (!removedFromKnownIds) removedFromKnownIds = [];
+ if (!movedFromKnownToVisibleIds) movedFromKnownToVisibleIds = [];
+
+ removedFromVisibleIds.forEach(function (siteId) {
+ var marker = scope.displayedSites[siteId];
+ if (marker) {
+ marker.setMap(null);
+ //delete marker;
+ delete scope.displayedSites[siteId];
+ }
+ });
+
+ removedFromKnownIds.forEach(function (siteId) {
+ var marker = scope.knownSites[siteId];
+ if (marker) {
+ marker.setMap(null);
+ //delete marker;
+ delete scope.knownSites[siteId];
+ }
+ });
+
+ movedFromVisibleToKnownIds.forEach(function (siteId) {
+ var marker = scope.displayedSites[siteId];
+ if (marker) {
+ marker.setVisible(false);
+ scope.knownSites[siteId] = marker;
+ delete scope.displayedSites[siteId];
+ }
+ });
+
+ movedFromKnownToVisibleIds.forEach(function (siteId) {
+ var marker = scope.knownSites[siteId];
+ if (marker) {
+ marker.setVisible(true);
+ scope.displayedSites[siteId] = marker;
+ delete scope.knownSites[siteId];
+ }
+ });
+
+ addedSiteIds.forEach(function (newSiteId) {
+ var newSite = scope.sites[newSiteId];
+ var newSiteNormalIcon = Object.assign({}, normalIcon, {
+ fillColor: (newSite.type === 'data-center') ? "#ffff00": "#00ccff",
+ strokeColor: (newSite.type === 'data-center') ? "#ffff00" : "#00ccff"
+ });
+ var newSiteHighlightIcon = Object.assign({}, highlightIcon, {
+ fillColor: (newSite.type === 'data-center') ? "#ffff00": "#00ccff",
+ strokeColor: (newSite.type === 'data-center') ? "#ffff00" : "#00ccff"
+ });
+ if (newSite && !scope.displayedSites[newSiteId]) {
+
+ // create the marker
+ var marker = new google.maps.Marker({
+ map: mwtnTopologyMapController.map,
+ position: newSite.location,
+ title: newSite.name,
+ icon: newSiteNormalIcon
+ });
+
+ // add event listeners to the marker
+ marker.addListener('click', function () {
+ // cloase all already opened windows
+ if (lastOpenedInfoWindow) {
+ lastOpenedInfoWindow.close();
+ lastOpenedInfoWindow = null;
+ }
+
+ // compile the content
+ var infoWindowTemplate = '<mwtn-topology-site-details site-id="siteId" />';
+ var infoWindowScope = $rootScope.$new();
+ infoWindowScope['siteId'] = newSiteId;
+ var infoWindowContent = $compile(infoWindowTemplate)(infoWindowScope)[0];
+
+ // create the info window
+ var infowindow = new google.maps.InfoWindow({
+ content: infoWindowContent,
+ disableAutoPan: true
+ });
+
+ // open the window and keek a refenrece to close it if new window opens
+ infowindow.open(mwtnTopologyMapController.map, marker);
+ lastOpenedInfoWindow = infowindow;
+
+ // remove the reference if the windows is closed
+ infowindow.addListener('closeclick', function () {
+ lastOpenedInfoWindow = null;
+ });
+ });
+
+ marker.addListener('mouseover', function () {
+ marker.setOptions({ icon: newSiteHighlightIcon });
+ });
+
+ marker.addListener('mouseout', function () {
+ marker.setOptions({ icon: newSiteNormalIcon });
+ });
+
+ // store marker
+ scope.displayedSites[newSiteId] = marker;
+ }
+ });
+ };
+
+ }
+ }
+ }]);
+
+ mwtnTopologyApp.directive('mwtnTopologyMapSiteLinks', ['$compile', '$rootScope', function ($compile, $rootScope) {
+ return {
+ restrict: 'E',
+ require: '^mwtnTopologyMap',
+ scope: {
+ selectedSiteLink: "=",
+ siteLinks: "=",
+ api: "="
+ },
+ link: function (scope, element, attrs, mwtnTopologyMapController) {
+
+ var normalColor = "#FF0000";
+ var selectedColor = "#00FD30";
+
+ scope.$watch("selectedSiteLink", function (newSiteLinkId, oldSiteLinkId) {
+
+ if (oldSiteLinkId && scope.displayedSiteLinks[oldSiteLinkId]) {
+ scope.displayedSiteLinks[oldSiteLinkId].setOptions({
+ strokeColor: normalColor,
+ });
+ }
+
+ if (newSiteLinkId && scope.displayedSiteLinks[newSiteLinkId]) {
+ scope.displayedSiteLinks[newSiteLinkId].setOptions({
+ strokeColor: selectedColor,
+ });
+ }
+
+ });
+
+ scope.displayedSiteLinks = {};
+
+ /**
+ * Updates the google map Polylines
+ * @param addedSiteLinkIds {string[]} The ids of sites links which are added to the scope.displayedSiteLinks dictionary and the google map.
+ * @param removedSiteLinkIds {string[]} The ids of sites links which are removed from the visible bounding rectangle within the google map.
+ */
+ scope.api.updateSiteLinks = function (addedSiteLinkIds, removedSiteLinkIds) {
+ if (!addedSiteLinkIds) addedSiteLinkIds = [];
+ if (!removedSiteLinkIds) removedSiteLinkIds = [];
+
+ removedSiteLinkIds.forEach(function (siteLinkId) {
+ var polyline = scope.displayedSiteLinks[siteLinkId];
+ if (polyline) {
+ polyline.setMap(null);
+ //delete polyline;
+ delete scope.displayedSiteLinks[siteLinkId];
+ }
+ });
+
+ addedSiteLinkIds.forEach(function (siteLinkId) {
+ var siteLink = scope.siteLinks[siteLinkId];
+ console.log(siteLink);
+
+ if (siteLink && !scope.displayedSiteLinks[siteLinkId]) {
+
+ // create the poly line
+ var polyline = new google.maps.Polyline({
+ map: mwtnTopologyMapController.map,
+ path: [siteLink.siteA.location, siteLink.siteZ.location],
+ strokeColor: siteLink.type === 'traffic' ? '#00ccff': '#ffff00',
+ strokeOpacity: 0.8,
+ strokeWeight: siteLink.type === 'traffic' ? 4 : 2,
+ zIndex: 1,
+ geodesic: true
+ });
+ console.log(siteLink.type, polyline.strokeColor);
+
+ // add event listeners to the polyline
+ polyline.addListener('click', function (event) {
+ // cloase all already opened windows
+ if (lastOpenedInfoWindow) {
+ lastOpenedInfoWindow.close();
+ lastOpenedInfoWindow = null;
+ }
+
+ // compile the content
+ var infoWindowTemplate = '<mwtn-topology-link-details link-id="linkId"></mwtn-topology-link-details>';
+ var infoWindowScope = $rootScope.$new();
+ infoWindowScope['linkId'] = siteLinkId;
+ var infoWindowContent = $compile(infoWindowTemplate)(infoWindowScope)[0];
+
+ // create the info window
+ var infowindow = new google.maps.InfoWindow({
+ content: infoWindowContent,
+ disableAutoPan: true
+ });
+
+ // open the window and keek a refenrece to close it if new window opens
+ // adjust and show the info window
+ infowindow.setPosition(event.latLng);
+ infowindow.open(mwtnTopologyMapController.map);
+ lastOpenedInfoWindow = infowindow;
+
+ // remove the reference if the windows is closed
+ infowindow.addListener('closeclick', function () {
+ lastOpenedInfoWindow = null;
+ });
+
+ });
+
+ polyline.addListener('mouseover', function () {
+ polyline.setOptions({ strokeWeight: 6 });
+ });
+
+ polyline.addListener('mouseout', function () {
+ polyline.setOptions({ strokeWeight: 3 });
+ });
+
+ // store the ployline
+ scope.displayedSiteLinks[siteLinkId] = polyline;
+ }
+ });
+ };
+
+ }
+ };
+ }]);
+
+ mwtnTopologyApp.controller('mwtnTopologySiteDetailsController', ['$scope', '$timeout', '$mwtnTopology', function ($scope, $timeout, $mwtnTopology) {
+ var vm = this;
+ vm.site = null;
+
+ $scope.$watch(function () { return vm.siteId }, function (newSiteId, oldSiteId) {
+ if (!newSiteId) return;
+
+ vm.status = {
+ message: 'Searching...',
+ type: 'warning',
+ isWorking: true
+ };
+
+ $mwtnTopology.getSiteDetailsBySiteId(newSiteId).then(function (siteDetails) {
+ vm.site = siteDetails;
+
+ vm.status = {
+ message: undefined,
+ type: 'success',
+ isWorking: false
+ };
+ }, function (err) {
+ vm.status = {
+ message: err,
+ type: 'error',
+ isWorking: false
+ };
+ });
+ });
+
+ }]);
+
+ mwtnTopologyApp.directive('mwtnTopologySiteDetails', function () {
+ return {
+ scope: { siteId: "=" },
+ restrict: 'E',
+ controller: 'mwtnTopologySiteDetailsController',
+ controllerAs: 'vm',
+ bindToController: true,
+ templateUrl: 'src/app/mwtnTopology/templates/siteDetails.tpl.html'
+ };
+ });
+
+ mwtnTopologyApp.controller('mwtnTopologyLinkDetailsController', ['$scope', '$timeout', '$mwtnTopology', function ($scope, $timeout, $mwtnTopology) {
+ /** @type {{ link: SiteLinkDetails } & {[key: string]: any}} */
+ var vm = this;
+ vm.site = null;
+ vm.link = null;
+
+ $scope.$watch(function () { return vm.linkId }, function (newLinkId, oldLinkId) {
+ if (!newLinkId) return;
+
+ vm.status = {
+ message: 'Searching...',
+ type: 'warning',
+ isWorking: true
+ };
+
+ // getLinkDetailsByLinkId
+ $mwtnTopology.getLinkDetailsByLinkId(newLinkId).then(
+ /** @param {SiteLinkDetails} linkDetails*/
+ function (linkDetails) {
+ vm.link = linkDetails;
+
+ vm.status = {
+ message: undefined,
+ type: 'success',
+ isWorking: false
+ };
+ }, function (err) {
+ vm.status = {
+ message: err,
+ type: 'error',
+ isWorking: false
+ };
+ });
+ });
+
+ }]);
+
+ mwtnTopologyApp.directive('mwtnTopologyLinkDetails', function () {
+ return {
+ scope: { linkId: "=" },
+ restrict: 'E',
+ controller: 'mwtnTopologyLinkDetailsController',
+ controllerAs: 'vm',
+ bindToController: true,
+ templateUrl: 'src/app/mwtnTopology/templates/linkDetails.tpl.html'
+ };
+ });
+
+ mwtnTopologyApp.controller('mwtnTopologyFrameController', ['$scope', '$rootScope', '$timeout', '$state', '$window', '$mwtnTopology', function ($scope, $rootScope, $timeout, $state, $window, $mwtnTopology) {
+ var vm = this;
+
+ $rootScope.section_logo = 'src/app/mwtnTopology/images/mwtnTopology.png'; // Add your topbar logo location here such as 'assets/images/logo_topology.gif'
+
+ /** @type {{ [tabName: string] : { [parameterName : string] : any } }} */
+ var tabParameters = {};
+
+ var tabs;
+ (function (tabs) {
+ tabs[tabs["site"] = 0] = "site";
+ tabs[tabs["physical"] = 1] = "physical";
+ tabs[tabs["ethernet"] = 2] = "ethernet";
+ tabs[tabs["ieee1588"] = 3] = "ieee1588";
+ })(tabs || (tabs = {}));
+
+ $scope.$watch(function () { return vm.activeTab; }, function (newVal, oldVal, scope) {
+ if (newVal == null) return;
+
+ var newName = tabs[newVal];
+
+ if (oldVal != null && tabs[oldVal]) {
+ var oldName = tabs[oldVal];
+ tabParameters[oldName] = $state.params;
+ }
+
+ var newParameters = Object.keys($state.params).reduce(function (acc, cur, ind, arr) { acc[cur] = null; return acc; }, {});
+ Object.assign(newParameters, tabParameters[newName] || {}, { tab: newName, internal: false });
+
+ $state.go('main.mwtnTopology', newParameters, { notify: false });
+ console.log("activeTab: ", newName);
+ });
+
+ $scope.$watch(function () { return $state.params.tab; }, function (newVal, oldVal, scope) {
+ if (newVal == oldVal) return;
+ if (newVal && !$state.params.internal) {
+ vm.activeTab = tabs[newVal || "site"];
+ console.log("navigationTab: ", vm.activeTab);
+ }
+ });
+
+ // hide all tabs until the google api is fully loaded.
+ // initialize with true is the promise is resolved === 1
+ vm.googleIsReady = $mwtnTopology.googleMapsApiPromise.$$state.status === 1;
+
+ if (!vm.googleIsReady) $mwtnTopology.googleMapsApiPromise.then(function () {
+ $timeout(function () {
+ vm.googleIsReady = true;
+ });
+ });
+
+ }]);
+
+ mwtnTopologyApp.directive('mwtnTopologyFrame', function () {
+ return {
+ restrict: 'E',
+ controller: 'mwtnTopologyFrameController',
+ controllerAs: 'vm',
+ templateUrl: 'src/app/mwtnTopology/templates/frame.tpl.html'
+ };
+ });
+
+ mwtnTopologyApp.controller('mwtnTopologySiteViewController', ['$scope', '$q', '$timeout', '$state', '$window', '$mwtnTopology', function ($scope, $q, $timeout, $state, $window, $mwtnTopology) {
+ var vm = this;
+ // Determines if a database request loop should be canceld.
+ var cancelRequested = false;
+ // Represents a defer which is resolved, if a cancel was requested and fulfilled.
+ var cancelDefer = $q.defer();
+ // Represents the new bounding rectangle who was set with the last user action to change the map bounds.
+ // The value is saved until the cancel operation is fulfilled.
+ /** @type {{top: number, right: number, bottom: number, left: number, zoom: number}} */
+ var waitForCancelMapBoundAndZoom;
+ // While getting all sites collect all site link ids which are available through visible and known sites.
+ // This dictionary will be cleared each time a new map bound is changed and a new database request loop is started.
+ var collectedSiteLinkIds = {};
+
+ vm.status = {
+ // Determines the bounds of the map which is manually set by the code. These are the initial values.
+ manualMapBounds: {
+ top: !Number.isNaN(+$state.params.top) ? +$state.params.top : undefined,
+ bottom: !Number.isNaN(+$state.params.bottom) ? +$state.params.bottom : undefined,
+ left: !Number.isNaN(+$state.params.left) ? +$state.params.left : undefined,
+ right: !Number.isNaN(+$state.params.right) ? +$state.params.right : undefined,
+ zoom: !Number.isNaN(+$state.params.zoom) ? +$state.params.zoom : undefined,
+ lat: !Number.isNaN(+$state.params.lat) ? +$state.params.lat : undefined,
+ lng: !Number.isNaN(+$state.params.lng) ? +$state.params.lng : undefined
+ },
+
+ // Determines if the accordeon group element for SiteMap, SiteGrid or SiteLinkGrid is open.
+ siteMapIsOpen: true,
+ siteGridIsOpen: true,
+ siteLinkGridIsOpen: true,
+ sitePathGridIsOpen: false,
+
+ // Determines if a processing is running.
+ processing: false,
+ totalSitesInBoundingBox: 0,
+ loadedSitesInBoundingBox: 0,
+ maximumCountOfVisibleSites: 300,
+
+ // selectet Elements
+ site: null,
+ siteLink: null,
+ sitePath: null
+ };
+
+ // Contains the known and visible sites as well as known site links as dictionaries.
+ vm.knownSites = {};
+ vm.visibleSites = {};
+ vm.knownSiteLinks = {};
+
+ // Api-Object that will be filled with update methods from the mwtnTopologyMapSites Component.
+ vm.mapSitesComponentApi = {};
+ // Api-Object that will be filled with update methods from the mwtnTopologyMapSiteLinks Component.
+ vm.mapSiteLinksComponentApi = {};
+
+ //addedSiteLinkIds, removedSiteLinkIds
+
+ initializeMapBounds();
+
+ function initializeMapBounds() {
+
+ vm.status.manualMapBounds = {
+ top: !Number.isNaN(+$state.params.top) ? +$state.params.top : undefined,
+ bottom: !Number.isNaN(+$state.params.bottom) ? +$state.params.bottom : undefined,
+ left: !Number.isNaN(+$state.params.left) ? +$state.params.left : undefined,
+ right: !Number.isNaN(+$state.params.right) ? +$state.params.right : undefined,
+ zoom: !Number.isNaN(+$state.params.zoom) ? +$state.params.zoom : undefined,
+ lat: !Number.isNaN(+$state.params.lat) ? +$state.params.lat : undefined,
+ lng: !Number.isNaN(+$state.params.lng) ? +$state.params.lng : undefined
+ };
+
+ // Calculate the initial map control bound, if we have no initial bounds
+ if ((vm.status.manualMapBounds.top == null && vm.status.manualMapBounds.bottom == null && vm.status.manualMapBounds.right == null && vm.status.manualMapBounds.left == null && vm.status.manualMapBounds.lat == null && vm.status.manualMapBounds.lng == null && $state.params.site == null && $state.params.siteLink == null && $state.params.sitePath == null)) {
+ $mwtnTopology.getOuterBoundingRectangleForSites().then(function (bounds) {
+ // Ensures that the new value is set within a digest cycle.
+ vm.status.manualMapBounds = bounds;
+ });
+ }
+ }
+
+ $scope.$watch(function () { return $state.params.site; }, function (newVal, oldVal, scope) {
+ if (newVal) {
+ $mwtnTopology.getSiteDetailsBySiteId(newVal).then(function (siteDetails) {
+ if (siteDetails) {
+ // just update the location of the browser if the location has changed
+ $state.go('main.mwtnTopology', {
+ tab: "site",
+ lat: siteDetails.location.lat,
+ lng: siteDetails.location.lng,
+ zoom: 19,
+ top: null,
+ bottom: null,
+ right: null,
+ left: null,
+ site: $state.params.site,
+ siteLink: null,
+ sitePath: null,
+ internal: false
+ }, {
+ notify: false
+ });
+ };
+ }, function (err) {
+
+ });
+ }
+ });
+
+ $scope.$watch(function () { return $state.params.siteLink; }, function (newVal, oldVal, scope) {
+ if (newVal) {
+ $mwtnTopology.getLinkDetailsByLinkId(newVal).then(function (link) {
+ if (link) {
+
+ var top = link.siteA.location.lat >= link.siteZ.location.lat ? link.siteA.location.lat : link.siteZ.location.lat;
+ var bottom = link.siteA.location.lat >= link.siteZ.location.lat ? link.siteZ.location.lat : link.siteA.location.lat;
+
+ // todo: this code is not able to handle links which overlap the -100|180 ° borderline
+ var left = link.siteA.location.lng <= link.siteZ.location.lng ? link.siteA.location.lng : link.siteZ.location.lng;
+ var right = link.siteA.location.lng <= link.siteZ.location.lng ? link.siteZ.location.lng : link.siteA.location.lng;
+ // just update the location of the browser if the location has changed
+ $state.go('main.mwtnTopology', {
+ tab: "site",
+ lat: null,
+ lng: null,
+ zoom: $state.params.zoom,
+ top: top,
+ bottom: bottom,
+ right: right,
+ left: left,
+ site: null,
+ siteLink: $state.params.siteLink,
+ sitePath: null,
+ internal: false
+ }, {
+ notify: false
+ });
+ };
+ }, function (err) {
+
+ });
+ }
+ });
+
+ $scope.$watchCollection(function () { return [vm.status.siteMapIsOpen, vm.status.siteGridIsOpen, vm.status.siteLinkGridIsOpen, vm.status.sitePathGridIsOpen] }, function (newVal, oldVal) {
+ if (newVal[1] || newVal[2] || newVal[3]) {
+ $timeout(function () {
+ $window.dispatchEvent(new Event("resize"));
+ });
+ }
+ });
+
+ $scope.$watchCollection(function () { return [+$state.params.top, +$state.params.bottom, +$state.params.left, +$state.params.right, +$state.params.lat, +$state.params.lng, +$state.params.zoom]; }, function (newVal, oldVal, scope) {
+
+ if (oldVal == null || oldVal === newVal) return; // ignore initial value
+
+ if (!vm.status.siteMapIsOpen) {
+ console.log("open Site Map")
+ vm.status.siteMapIsOpen = true;
+ return;
+ }
+
+ vm.status.manualMapBounds = {
+ top: !Number.isNaN(newVal[0]) ? newVal[0] : undefined,
+ bottom: !Number.isNaN(newVal[1]) ? newVal[1] : undefined,
+ left: !Number.isNaN(newVal[2]) ? newVal[2] : undefined,
+ right: !Number.isNaN(newVal[3]) ? newVal[3] : undefined,
+ zoom: !Number.isNaN(newVal[6]) ? newVal[6] : undefined,
+ internal: !!$state.params.internal
+ };
+
+ if (!Number.isNaN(newVal[4]) && !Number.isNaN(newVal[5])) {
+ vm.status.manualMapBounds.lat = newVal[4];
+ vm.status.manualMapBounds.lng = newVal[5];
+ } else {
+ vm.status.manualMapBounds.lat = undefined;
+ vm.status.manualMapBounds.lng = undefined;
+ }
+
+ if (!!$state.params.internal) $state.params.internal = false;
+
+ });
+
+ $scope.$watchCollection(function () { return vm.status.siteMapIsOpen }, function (newVal, oldVal, scope) {
+ if (oldVal == newVal) return; // ignore initial value
+ if (newVal) {
+ // Map (re)-opend
+ initializeMapBounds();
+ } else {
+ // Map closed
+ vm.status.loadedSitesInBoundingBox = 0;
+ vm.status.totalSitesInBoundingBox = 0;
+ vm.status.manualMapBounds = {};
+ vm.knownSites = {};
+ vm.visibleSites = {};
+ vm.knownSiteLinks = {};
+ };
+ // tell all sub components the visuability of the map view
+ $scope.$broadcast('mapViewVisuability', newVal);
+ });
+
+ /* callback and helper methods */
+ /**
+ * A callback function which is called by the map control of the user changed the bounds of the map.
+ * @param mapBoundsAndZoom {{top: number, right: number, bottom: number, left: number, zoom: number}} The new bounds and the new zoom level of the map control.
+ */
+ vm.mapBoundsChanged = function (mapBoundsAndZoom, userOriginated) {
+
+ // just update the location of the browser if the location has changed
+ $state.go('main.mwtnTopology', {
+ tab: "site",
+ lat: mapBoundsAndZoom.lat,
+ lng: mapBoundsAndZoom.lng,
+ zoom: mapBoundsAndZoom.zoom,
+ top: null,
+ bottom: null,
+ right: null,
+ left: null,
+ site: !userOriginated ? $state.params.site : null,
+ siteLink: !userOriginated ? $state.params.siteLink : null,
+ sitePath: !userOriginated ? $state.params.sitePath : null,
+ internal: true
+ }, {
+ notify: false
+ });
+ console.log("handleBoundsChanged", mapBoundsAndZoom);
+
+ if (vm.status.siteMapIsOpen) refreshAllSites(mapBoundsAndZoom);
+ };
+
+ /**
+ * Refreshes all visible elements in the given view port
+ * @param mapBoundsAndZoom {{top: number, right: number, bottom: number, left: number, zoom: number}} The new bounds and the new zoom level of the map control.
+ */
+ function refreshAllSites(mapBoundsAndZoom) {
+
+ // Datenbankabfragen werden werden in kleine Chunks zerlegt.
+ // Jeder Chunk enthält maximal ${chunkSize} (100) Sites und die Anzahl aller Sites für die spezifische Abfrage in der Antwort.
+ // Sind noch nicht alle Chunks von der Datenbank angefordert worden, wird zuerst der nächste Chunk angefordert.
+ // Der aktuell zurückgegebene Chunk wird aber erst verarbeitet, bevor in einer weiteren Callback-Situation der nächste Datenbank-Chunk abgeholt wird.
+ // Dadurch werden kontinuierlich alle Sites auch auf der Map ergänzt, sobald sie verarbeitet wurden und es kommt zu keiner Verzögerung im Benutzerinterface.
+ // Im Maximum werden aber ${vm.status.maximumCountOfVisibleSites} (2500) Sites abgerufen, um die Google Map nicht zu überlasten.
+
+ // Fallunterscheidung: gibt es bereits eine aktive Datenbankabfrage zu den Sites?
+ // => Nein: Starte eine neue Datenbankabfrage
+ // => Ja: Breche die aktuelle Verarbeitung ab, aktualisiere den Status und starte dann eine neue Datenbankabfrage.
+ if (vm.status.processing) {
+ waitForCancelMapBoundAndZoom = mapBoundsAndZoom;
+
+ if (cancelRequested) {
+ console.log("Cancel is already requested.");
+ return;
+ }
+
+ // Request a database loop cancel.
+ cancelRequested = true;
+ console.log("New Cancel requested.")
+
+ // Wait until the current database loos was canceled.
+ cancelDefer.promise.then(function () {
+ // The database loop is canceled. Create a new defer for the next possible cancel request.
+ console.log("Cancel fulfilled.");
+ cancelDefer = $q.defer();
+ // Restart the database loop with the new bounding rectangle.
+ beginProcessing(waitForCancelMapBoundAndZoom);
+ });
+
+ // Do not start a database loop here - so leave the method.
+ return;
+ }
+
+ // Start a new database loop here.
+ beginProcessing(mapBoundsAndZoom);
+
+ /**
+ * The private function which begins a new database processing loop.
+ * @param mapBoundsAndZoom {{top: number, right: number, bottom: number, left: number, zoom: number}} The new bounds and the new zoom level of the map control.
+ */
+ function beginProcessing(mapBoundsAndZoom) {
+ // Set the processing flag to true.
+ vm.status.processing = true;
+
+ var chunkSize = 100;
+ var chunkSiteStartIndex = 0;
+
+ // If a site link cannot convert its siteA or siteZ (id) Parameter to a real site object these ids will collected.
+ // After a subsequent database call to get these sites the queued site links will be converted.
+ var additionalSiteIds = [];
+ var queuedSiteLinksToConvert = [];
+
+ // Entferne Sites, welche aufgrund des neuen Bounds leicht außer Sicht sind von den sichtbaren Sites.
+ // Hinzufügen von Sites aus den leicht außer Sicht Bereich zum sichtbaren Bereich.
+ var movedOrRemovedSites = refreshOutOfSightSites(mapBoundsAndZoom);
+ var removedFromVisibleIds = movedOrRemovedSites.removedFromVisibleIds;
+ var movedFromVisibleToKnownIds = movedOrRemovedSites.movedFromVisibleToKnownIds;
+ var removedFromKnownIds = movedOrRemovedSites.removedFromKnownIds;
+ var movedFromKnownToVisibleIds = movedOrRemovedSites.movedFromKnownToVisibleIds;
+ var removedSiteLinkIds = movedOrRemovedSites.removedSiteLinkIds;
+
+ // While getting all sites collect all site link ids which are available through visible and known sites.
+ collectedSiteLinkIds = {};
+
+ doSiteRequestLoop();
+
+ /* internal function within beginProcessing */
+ /**
+ * Starts a new database request loop to get all sites.
+ */
+ function doSiteRequestLoop() {
+ requestNextSiteChunk(mapBoundsAndZoom, chunkSize, chunkSiteStartIndex).then(
+ /**
+ * @param result {{addedSiteIds: string[], total: number}} An array with the ids of the sites which are added to the vm.visibleSites dictionary.
+ */
+ function (result) {
+ // update the frontend before continue with the next database request.
+ vm.mapSitesComponentApi.updateSites && vm.mapSitesComponentApi.updateSites(result.addedSiteIds, removedFromVisibleIds, movedFromVisibleToKnownIds, removedFromKnownIds, movedFromKnownToVisibleIds);
+ removedSiteLinkIds && removedSiteLinkIds.length && vm.mapSiteLinksComponentApi.updateSiteLinks && vm.mapSiteLinksComponentApi.updateSiteLinks([], removedSiteLinkIds);
+ // to not re-remove and re-move already removed and moved sites clear the arrays for any subsequent database requests.
+ removedFromVisibleIds = [];
+ movedFromVisibleToKnownIds = [];
+ removedFromKnownIds = [];
+ movedFromKnownToVisibleIds = [];
+ removedSiteLinkIds = [];
+
+ if (cancelRequested) {
+ // reset the cancelRequested flag and resolve the cancel request.
+ cancelRequested = false;
+ cancelDefer.resolve();
+ return;
+ }
+
+ // Enforce angular to process a new digest cycle.
+ $timeout(function () {
+ vm.status.totalSitesInBoundingBox = result.total;
+ vm.status.loadedSitesInBoundingBox = Math.min(chunkSiteStartIndex + chunkSize, result.total);
+ if (chunkSiteStartIndex + chunkSize >= result.total || chunkSiteStartIndex + chunkSize >= vm.status.maximumCountOfVisibleSites) {
+ // This was the last database request to get sites.
+ // Remove all known site link ids from the collectedSiteLinkIds dictionary.
+ var knownSiteLinkIds = Object.keys(vm.knownSiteLinks);
+ knownSiteLinkIds.forEach(function (knownSiteLinkId) {
+ if (collectedSiteLinkIds[knownSiteLinkId]) {
+ delete collectedSiteLinkIds[knownSiteLinkId];
+ }
+ });
+
+ // Start the request loop for site links now.
+ doSiteLinkRequestLoop(Object.keys(collectedSiteLinkIds));
+ } else {
+ // There are more data, request the database again.
+ chunkSiteStartIndex += chunkSize;
+
+ doSiteRequestLoop();
+ }
+ });
+
+ }, processError);
+ }
+
+ function endProcessing() {
+ vm.status.processing = false;
+
+ $timeout(function () {
+ vm.status.site = $state.params.site;
+ vm.status.siteLink = $state.params.siteLink;
+ });
+ }
+
+ /**
+ * Starts a new database request loop to get all site links.
+ */
+ function doSiteLinkRequestLoop(siteLinkIds) {
+ var requestedSiteLinkIds = siteLinkIds.splice(0, chunkSize);
+
+ $mwtnTopology.getSiteLinksByIds(requestedSiteLinkIds).then(
+ /**
+ * Processes the database result.
+ * The returned site links doesn't contain site objects but site ids.
+ * In a subsequent step these ids will be converted into real site objects.
+ * @param result {{id: string, siteA: string, siteZ: string}[]} The database result.
+ */
+ function (result) {
+ // Subsequent convert.
+ // All site links which siteA and siteZ site ids are known could be added to vm.knownSiteLinks.
+ // The rest has to saved until all requests are finished.
+ // Another database call will get the additional sites to convert these site links.
+ var addedSiteLinkIds = result.reduce(function (accumulator, currentSiteLink) {
+ var siteA = vm.visibleSites[currentSiteLink.siteA] || vm.knownSites[currentSiteLink.siteA];
+ var siteZ = vm.visibleSites[currentSiteLink.siteZ] || vm.knownSites[currentSiteLink.siteZ];
+ if (siteA && siteZ) {
+ vm.knownSiteLinks[currentSiteLink.id] = {
+ id: currentSiteLink.id,
+ siteA: siteA,
+ siteZ: siteZ,
+ type: currentSiteLink.type
+ };
+ accumulator.push(currentSiteLink.id);
+ } else {
+ // Theoretically, only siteA or siteZ can be unknown. I still check both separately.
+ var isPushed = false;
+ if (!siteA) {
+ additionalSiteIds.push(currentSiteLink.siteA);
+ queuedSiteLinksToConvert.push(currentSiteLink);
+ isPushed = true;
+ }
+ if (!siteZ) {
+ additionalSiteIds.push(currentSiteLink.siteZ);
+ isPushed || queuedSiteLinksToConvert.push(currentSiteLink);
+ }
+ }
+ return accumulator;
+ }, []);
+
+ // update the frontend before continue with the next database request.
+ vm.mapSiteLinksComponentApi.updateSiteLinks && vm.mapSiteLinksComponentApi.updateSiteLinks(addedSiteLinkIds);
+
+ if (cancelRequested) {
+ // reset the cancelRequested flag and resolve the cancel request.
+ cancelRequested = false;
+ cancelDefer.resolve();
+ return;
+ }
+
+ // Enforce angular to process a new digest cycle. (And to let angular time to redraw the map.)
+ $timeout(function () {
+ if (siteLinkIds.length === 0) {
+ if (additionalSiteIds.length === 0) {
+ endProcessing();
+ return;
+ }
+
+ // Only get a maximum of chunkSize additional sites.
+ // Site links more than that will not be drawed in the map due to performance reasons.
+ // If the user zooms in the additional site links will be requested again.
+ $mwtnTopology.getSitesByIds(additionalSiteIds.splice(0, chunkSize)).then(
+ /**
+ * Processes the database result to gain new sites within the bounding box of the database request.
+ * @param result {{total: number, sites: {id: string, name: string, location: {lat: number, lng: number}, amslGround: number, references: {siteLinks: string[]}}[]}} The database result.
+ */
+ function (result) {
+ var addedSiteLinkIds = queuedSiteLinksToConvert.reduce(function (accumulator, currentSiteLink) {
+ var siteA = vm.visibleSites[currentSiteLink.siteA] || vm.knownSites[currentSiteLink.siteA] || result.sites.find(
+ function (site) {
+ return site.id === currentSiteLink.siteA
+ });
+ var siteZ = vm.visibleSites[currentSiteLink.siteZ] || vm.knownSites[currentSiteLink.siteZ] || result.sites.find(
+ function (site) {
+ return site.id === currentSiteLink.siteZ
+ });
+ if (siteA && siteZ) {
+ vm.knownSiteLinks[currentSiteLink.id] = {
+ id: currentSiteLink.id,
+ siteA: siteA,
+ siteZ: siteZ,
+ type: currentSiteLink.type
+ };
+ accumulator.push(currentSiteLink.id);
+ }
+ return accumulator;
+ }, []);
+ // update the frontend the last time until the user changed the map position.
+ vm.mapSiteLinksComponentApi.updateSiteLinks && vm.mapSiteLinksComponentApi.updateSiteLinks(addedSiteLinkIds);
+
+ // all done - puh.
+ endProcessing();
+ }, processError);
+
+ } else {
+ // There are more data, request the database again.
+ doSiteLinkRequestLoop(siteLinkIds);
+ }
+ });
+
+ }, processError);
+ }
+
+ }
+ };
+
+ /**
+ * Recalculates the targets site container for Sites within knownSites and visibleSites.
+ * @param mapBoundsAndZoom {{top: number, right: number, bottom: number, left: number, zoom: number}} The new bounds and the new zoom level of the map control.
+ */
+ function refreshOutOfSightSites(mapBoundsAndZoom) {
+ var additionalWidth = (mapBoundsAndZoom.right - mapBoundsAndZoom.left) / 4;
+ var additionalHeight = (mapBoundsAndZoom.top - mapBoundsAndZoom.bottom) / 4;
+ var slightlyTop = mapBoundsAndZoom.top + additionalHeight;
+ var slightlyRight = mapBoundsAndZoom.right + additionalWidth;
+ var slightlyBottom = mapBoundsAndZoom.bottom - additionalHeight;
+ var slightlyLeft = mapBoundsAndZoom.left - additionalWidth;
+
+ var removedFromVisibleIds = [];
+ var movedFromVisibleToKnownIds = [];
+ var removedFromKnownIds = [];
+ var movedFromKnownToVisibleIds = [];
+ var removedSiteLinkIds = [];
+
+ var visibleSiteKeys = Object.keys(vm.visibleSites);
+ var knownSiteKeys = Object.keys(vm.knownSites);
+ var knownSiteLinkKeys = Object.keys(vm.knownSiteLinks);
+
+ visibleSiteKeys.forEach(function (siteId) {
+ /** @var {{id: string, name: string, location: {lat: number, lng: number}, amslGround: number, references: {siteLinks: string[]}} site */
+ var site = vm.visibleSites[siteId];
+
+ if (site.location.lat > slightlyTop || site.location.lng > slightlyRight || site.location.lat < slightlyBottom || site.location.lng < slightlyLeft) {
+ // This site is completly out of sight - so remove it and add the id to the removedFromVisibleIds list.
+ delete vm.visibleSites[siteId];
+ removedFromVisibleIds.push(siteId);
+ return;
+ }
+
+ if (site.location.lat > mapBoundsAndZoom.top || site.location.lng > mapBoundsAndZoom.right || site.location.lat < mapBoundsAndZoom.bottom || site.location.lng < mapBoundsAndZoom.left) {
+ // This site is moved from the visible map bounds to the extended map bounds and will only hide but stay in the google object.
+ vm.knownSites[siteId] = site;
+ delete vm.visibleSites[siteId];
+ movedFromVisibleToKnownIds.push(siteId);
+ return;
+ }
+ });
+
+ knownSiteKeys.forEach(function (siteId) {
+ /** @type {{id: string, name: string, location: {lat: number, lng: number}, amslGround: number, references: {siteLinks: string[]}} site */
+ var site = vm.knownSites[siteId];
+
+ if (site.location.lat > slightlyTop || site.location.lng > slightlyRight || site.location.lat < slightlyBottom || site.location.lng < slightlyLeft) {
+ // This site is completly out of sight - so remove it and add the id to the removedFromKnownIds list.
+ delete vm.knownSites[siteId];
+ removedFromKnownIds.push(siteId);
+ return;
+ }
+
+ if (site.location.lat <= mapBoundsAndZoom.top && site.location.lng <= mapBoundsAndZoom.right && site.location.lat >= mapBoundsAndZoom.bottom && site.location.lng >= mapBoundsAndZoom.left) {
+ // This site is moved from the extended map bounds to the visible map bounds. Therefore the hidden google maps object should set visible again.
+ vm.visibleSites[siteId] = site;
+ delete vm.knownSites[siteId];
+ movedFromKnownToVisibleIds.push(siteId);
+ return;
+ }
+ });
+
+ knownSiteLinkKeys.forEach(function (siteLinkId) {
+ /** @type {{id: string, siteA: {id: string, name: string, location: {lat: number, lng: number}, amslGround: number, references: {siteLinks: string[]}}, siteZ: {id: string, name: string, location: {lat: number, lng: number}, amslGround: number, references: {siteLinks: string[]}}}} siteLink */
+ var siteLink = vm.knownSiteLinks[siteLinkId];
+ var siteA = siteLink.siteA;
+ var siteZ = siteLink.siteZ;
+
+ if ((siteA.location.lat > slightlyTop || siteA.location.lng > slightlyRight || siteA.location.lat < slightlyBottom || siteA.location.lng < slightlyLeft) &&
+ (siteZ.location.lat > slightlyTop || siteZ.location.lng > slightlyRight || siteZ.location.lat < slightlyBottom || siteZ.location.lng < slightlyLeft)) {
+ // This site link is completly out of sight - so remove it and add the id to the removedSiteLinkIds list.
+ delete vm.knownSiteLinks[siteLinkId];
+ removedSiteLinkIds.push(siteLinkId);
+ }
+ });
+
+ return {
+ removedFromVisibleIds: removedFromVisibleIds,
+ movedFromVisibleToKnownIds: movedFromVisibleToKnownIds,
+ removedFromKnownIds: removedFromKnownIds,
+ movedFromKnownToVisibleIds: movedFromKnownToVisibleIds,
+ removedSiteLinkIds: removedSiteLinkIds
+ };
+ }
+
+ /**
+ * Requests a chunk of sites from the database.
+ * @param mapBoundsAndZoom {{top: number, right: number, bottom: number, left: number, zoom: number}} The bounds and the zoom level of the map control to request the next chunk for.
+ * @param chunkSize {number} The chunk size of a single database request. To big values will block the user interface, to small values will lead in to many database requests.
+ * @param chunkSiteStartIndex {number} The index of the first site returned by the database.
+ */
+ function requestNextSiteChunk(mapBoundsAndZoom, chunkSize, chunkSiteStartIndex) {
+ var requestNextSiteChunkDefer = $q.defer();
+ var addedSiteIds = [];
+
+ // Request the next chunk.
+ $mwtnTopology.getSitesInBoundingBox(mapBoundsAndZoom, chunkSize, chunkSiteStartIndex).then(
+ /**
+ * Processes the database result to gain new sites within the bounding box of the database request.
+ * @param result {{chunkSize: number, chunkSiteStartIndex: number, total: number, sites: {id: string, name: string, location: {lat: number, lng: number}, amslGround: number, references: {siteLinks: string[]}}[]}} The database result.
+ */
+ function (result) {
+ result.sites.forEach(function (site) {
+ // The map bounds may have changed since the start of the database request.
+ // Therefore check if the site is within the bounding rectangle.
+
+ if (!$mwtnTopology.isInBounds(mapBoundsAndZoom, site.location)) {
+ return;
+ }
+
+ // add all site link ids to the selectedSiteLinkIds dictionary.
+ site.references.siteLinks.forEach(function (siteLinkId) {
+ collectedSiteLinkIds[siteLinkId] = true;
+ });
+
+ // check, if the site is within the knownSites dictionary.
+ if (vm.knownSites[site.id]) {
+ delete vm.knownSites[site.id];
+ }
+
+ // check if the site is within the visibleSites dictionary.
+ if (vm.visibleSites[site.id]) {
+ // Override the site (refresh)
+ vm.visibleSites[site.id] = site;
+ } else {
+ // Add the site to the dictionary and remember the siteId.
+ vm.visibleSites[site.id] = site;
+ addedSiteIds.push(site.id);
+ }
+ });
+
+ requestNextSiteChunkDefer.resolve({
+ addedSiteIds: addedSiteIds,
+ total: result.total
+ });
+ }, requestNextSiteChunkDefer.reject);
+
+ return requestNextSiteChunkDefer.promise;
+ }
+
+ /**
+ * Handles error messages by writing the information to the javascript console.
+ * Resets the processing flag.
+ * @param error Information about the error.
+ */
+ function processError(error) {
+ // Reset the processing flag.
+ vm.status.processing = false;
+ // Write the error information to the console.
+ console.error(error);
+ }
+
+ }]);
+
+ mwtnTopologyApp.directive('mwtnTopologySiteView', function () {
+ return {
+ restrict: 'E',
+ controller: 'mwtnTopologySiteViewController',
+ controllerAs: 'vm',
+ bindToController: true,
+ templateUrl: 'src/app/mwtnTopology/templates/siteView.tpl.html',
+ scope: {
+ initialMapBounds: "="
+ }
+ };
+ });
+
+ mwtnTopologyApp.controller('mwtnTopologySiteGridController', ['$scope', '$timeout', '$state', '$mwtnCommons', '$mwtnTopology', 'uiGridConstants', function ($scope, $timeout, $state, $mwtnCommons, $mwtnTopology, uiGridConstants) {
+ var vm = this;
+
+ // The page number to show in the grid.
+ var paginationPage = 1;
+ // The page size.
+ var paginationPageSize = 100;
+ // The grid column object with current sorting informations.
+ var sortColumn = null;
+ // The grid column object with current sorting informations.
+ var gridFilters = [];
+ // caches all sites at the current grid page
+ var sitesAtCurrentPageCache = {};
+
+ vm.showAllSites = false;
+
+ vm.onNavigateToSite = function (row) {
+ var site = sitesAtCurrentPageCache[row.entity.id];
+ $state.go("main.mwtnTopology", {
+ tab: "site",
+ lat: site.location.lat,
+ lng: site.location.lng,
+ zoom: 19,
+ site: site.id,
+ internal: false
+ }, { notify: false });
+ };
+
+ // see http://ui-grid.info/docs/#/tutorial/317_custom_templates
+ var buttonCellTemplate = '<div class="ui-grid-cell-contents tooltip-uigrid" title="TOOLTIP"><i ng-click="grid.appScope.vm.onNavigateToSite(row)" ng-class="{\'fa\':true, \'fa-map-marker\': true, \'{{COL_FIELD}}\':true}" aria-hidden="true"></i></div>';
+
+ vm.gridOptions = Object.assign({}, $mwtnCommons.gridOptions, {
+ showGridFooter: false, // disable the grid footer because the pagination component sets its own.
+ paginationPageSizes: [10, 25, 50, 100],
+ paginationPageSize: paginationPageSize,
+ useExternalPagination: true,
+ useExternalFiltering: true,
+ useExternalSorting: true,
+ totalItems: 0,
+ columnDefs: [{
+ field: "id",
+ type: "string",
+ displayName: "Id"
+ },
+ {
+ field: "name",
+ type: "string",
+ displayName: "Name"
+ },
+ {
+ field: "location",
+ type: "string",
+ displayName: "Location"
+ },
+ {
+ field: "amslGround",
+ type: "string",
+ displayName: "AmslGround"
+ },
+ {
+ field: "countLinks",
+ type: "number",
+ displayName: "Count (Links)"
+ },
+ {
+ field: "countNetworkElements",
+ type: "number",
+ displayName: "Count (Network elements)"
+ },
+ {
+ field: "buttons",
+ type: "string",
+ displayName: "",
+ width: 40,
+ enableFiltering: false,
+ enableSorting: false,
+ cellTemplate: buttonCellTemplate,
+ pinnedRight: true
+ }
+ ],
+ data: [],
+ onRegisterApi: function (gridApi) {
+ // http://ui-grid.info/docs/#/api/ui.grid.core.api:PublicApi
+ vm.gridApi = gridApi;
+
+ vm.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
+ // Save the current sort column for later use.
+ sortColumn = (!sortColumns || sortColumns.length === 0)
+ ? null
+ : sortColumns[0];
+ loadPage();
+ });
+
+ vm.gridApi.pagination.on.paginationChanged($scope, function (newPage, newPageSize) {
+ // Save the pagination informations for later use.
+ paginationPage = newPage;
+ paginationPageSize = newPageSize;
+ loadPage();
+ });
+
+ vm.gridApi.core.on.filterChanged($scope, function () {
+ // Save the all filters for later use.
+ var filters = [];
+ this.grid.columns.forEach(function (col, ind) {
+ if (col.filters[0] && col.filters[0].term) {
+ filters.push({ field: col.field, term: col.filters[0].term });
+ }
+ });
+ gridFilters = filters;
+ loadPage();
+ });
+
+ loadPage();
+ }
+ });
+
+ $scope.$on("mapViewVisuability", function (event, data) {
+ vm.showAllSites = !data;
+ });
+
+ $scope.$watch("visibleSites", function (newVisibleSites, oldVisibleSites) {
+ console.log("watch: visibleSites");
+ if (!vm.showAllSites) loadPage();
+ }, true); // deep watch, maybe find a better solution; e.g. with an api object like the site in the map.
+
+ $scope.$watch(function () { return vm.showAllSites; }, loadPage);
+
+ function loadPage() {
+ if (vm.showAllSites) {
+ loadRemotePage();
+ } else {
+ loadLocalPage();
+ }
+ }
+
+ /**
+ * Calculates the page content of the grid and sets the values to the gridOprions.data object.
+ */
+ function loadLocalPage() {
+ var siteIds = Object.keys($scope.visibleSites);
+
+ var tempData = siteIds.filter(function (siteId, ind, arr) {
+ var site = $scope.visibleSites[siteId];
+ return gridFilters.map(function (filter) {
+ switch (filter.field) {
+ case "countLinks":
+ return (site.references.siteLinks ? site.references.siteLinks.length : 0) == +filter.term;
+ default:
+ return site[filter.field].contains(filter.term);
+ }
+ }).and(true);
+ }).map(function (siteId) {
+ var site = $scope.visibleSites[siteId];
+ var orderBy;
+
+ if (!sortColumn || !sortColumn.sort.direction) {
+ return {
+ id: siteId
+ }
+ }
+
+ switch (sortColumn.field) {
+ case "countLinks":
+ orderBy = site.references.siteLinks ? site.references.siteLinks.length : 0;
+ break;
+ case "countNetworkElements":
+ orderBy = site.references.networkElements ? site.references.networkElements.length : 0;
+ break;
+ case "buttons":
+ orderBy = siteId;
+ break;
+ default:
+ orderBy = site[sortColumn.field];
+ break;
+ }
+
+ return {
+ id: siteId,
+ orderBy: orderBy
+ };
+ });
+
+ if (sortColumn && sortColumn.sort.direction) {
+ tempData.sort(function (left, right) {
+ if (left === right || left.orderBy === right.orderBy) {
+ return 0;
+ }
+ if (left.orderBy > right.orderBy) {
+ return (sortColumn.sort.direction === uiGridConstants.ASC) ? 1 : -1;
+ }
+ return (sortColumn.sort.direction === uiGridConstants.ASC) ? -1 : 1;
+ });
+ }
+
+ var maxPageNumber = Math.max(1, Math.ceil(tempData.length / paginationPageSize));
+ var currentPage = Math.min(maxPageNumber, paginationPage);
+ var orderedSitesAtCurrentPage = tempData.slice((currentPage - 1) * paginationPageSize, currentPage * paginationPageSize);
+
+ sitesAtCurrentPageCache = {};
+ var orderedData = [];
+
+ orderedSitesAtCurrentPage.forEach(function (orderedSite) {
+ var site = $scope.visibleSites[orderedSite.id];
+ sitesAtCurrentPageCache[site.id] = site;
+ orderedData.push({
+ id: orderedSite.id,
+ name: site.name,
+ location: site.location.lat.toLocaleString("en-US", {
+ minimumFractionDigits: 4
+ }) + ", " + site.location.lng.toLocaleString("en-US", {
+ minimumFractionDigits: 4
+ }),
+ amslGround: site.amslGround,
+ countLinks: site.references.siteLinks ? site.references.siteLinks.length : 0,
+ countNetworkElements: site.references.networkElements ? site.references.networkElements.length : 0
+ });
+ });
+
+ $timeout(function () {
+ vm.gridOptions.data = orderedData;
+ vm.gridOptions.totalItems = tempData.length;
+ });
+ }
+
+ /**
+ * Loads the page content for the grid and sets the values to the gridOprions.data object.
+ */
+ function loadRemotePage() {
+
+ $mwtnTopology.getSites((sortColumn && sortColumn.field), sortColumn && ((sortColumn.sort.direction && sortColumn.sort.direction === uiGridConstants.ASC) ? 'asc' : 'desc'), gridFilters, paginationPageSize, (paginationPage - 1) * paginationPageSize).then(function (result) {
+ sitesAtCurrentPageCache = result.sites.reduce(function (acc, cur, ind, arr) {
+ acc[cur.id] = cur;
+ return acc;
+ }, {});
+ vm.gridOptions.data = result.sites.map(function (site) {
+ return {
+ id: site.id,
+ name: site.name,
+ location: site.location.lat.toLocaleString("en-US", {
+ minimumFractionDigits: 4
+ }) + ", " + site.location.lng.toLocaleString("en-US", {
+ minimumFractionDigits: 4
+ }),
+ amslGround: site.amslGround,
+ countLinks: site.references.siteLinks ? site.references.siteLinks.length : 0,
+ countNetworkElements: site.references.networkElements ? site.references.networkElements.length : 0
+ }
+ });
+ vm.gridOptions.totalItems = result.total;
+ });
+ }
+ }]);
+
+ mwtnTopologyApp.directive('mwtnTopologySiteGrid', function () {
+ return {
+ restrict: 'E',
+ replace: false,
+ controller: 'mwtnTopologySiteGridController',
+ controllerAs: 'vm',
+ scope: {
+ visibleSites: "="
+ },
+ templateUrl: 'src/app/mwtnTopology/templates/siteGrid.tpl.html'
+ };
+ });
+
+ mwtnTopologyApp.controller('mwtnTopologySiteLinkGridController', ['$scope', '$timeout', '$state', '$mwtnCommons', '$mwtnTopology', 'uiGridConstants', function ($scope, $timeout, $state, $mwtnCommons, $mwtnTopology, uiGridConstants) {
+ var vm = this;
+
+ // The page number to show in the grid.
+ var paginationPage = 1;
+ // The page size.
+ var paginationPageSize = 100;
+ // The grid column object with current sorting informations.
+ var sortColumn = null;
+ // The grid column object with current sorting informations.
+ var gridFilters = [];
+
+ var linksAtCurrentPageCache = {};
+
+ vm.showAllLinks = false;
+
+ vm.onNavigateToLink = function (row) {
+ var link = linksAtCurrentPageCache[row.entity.id];
+
+ var top = link.siteA.location.lat >= link.siteZ.location.lat ? link.siteA.location.lat : link.siteZ.location.lat;
+ var bottom = link.siteA.location.lat >= link.siteZ.location.lat ? link.siteZ.location.lat : link.siteA.location.lat;
+
+ // todo: this code is not able to handle links which overlap the -100|180 ° borderline
+ var left = link.siteA.location.lng <= link.siteZ.location.lng ? link.siteA.location.lng : link.siteZ.location.lng;
+ var right = link.siteA.location.lng <= link.siteZ.location.lng ? link.siteZ.location.lng : link.siteA.location.lng;
+
+ $state.go("main.mwtnTopology", {
+ tab: "site",
+ top: top,
+ bottom: bottom,
+ left: left,
+ right: right,
+ siteLink: link.id,
+ internal: false
+ }, { notify: false });
+ console.log(link);
+ };
+
+ // see http://ui-grid.info/docs/#/tutorial/317_custom_templates
+ var buttonCellTemplate = '<div class="ui-grid-cell-contents tooltip-uigrid" title="TOOLTIP"><i ng-click="grid.appScope.vm.onNavigateToLink(row)" ng-class="{\'fa\':true, \'fa-map-marker\': true, \'{{COL_FIELD}}\':true}" aria-hidden="true"></i></div>';
+
+ vm.gridOptions = Object.assign({}, $mwtnCommons.gridOptions, {
+ // enableFiltering: false,
+ showGridFooter: false, // disable the grid footer because the pagination component sets its own.
+ paginationPageSizes: [10, 25, 50, 100],
+ paginationPageSize: paginationPageSize,
+ useExternalPagination: true,
+ useExternalFiltering: true,
+ useExternalSorting: true,
+ totalItems: 0,
+ columnDefs: [{
+ field: "id",
+ type: "string",
+ displayName: "Id"
+ },
+ {
+ field: "name",
+ type: "string",
+ displayName: "Name"
+ },
+ {
+ field: "siteIdA",
+ type: "string",
+ displayName: "SiteA"
+ },
+ {
+ field: "siteIdZ",
+ type: "string",
+ displayName: "SiteZ"
+ },
+ {
+ field: "buttons",
+ type: "string",
+ displayName: "",
+ width: 40,
+ enableFiltering: false,
+ enableSorting: false,
+ cellTemplate: buttonCellTemplate,
+ pinnedRight: true
+ }
+ ],
+ data: [],
+ onRegisterApi: function (gridApi) {
+ // http://ui-grid.info/docs/#/api/ui.grid.core.api:PublicApi
+ vm.gridApi = gridApi;
+
+ vm.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
+ // save the current sort column for later use.
+ sortColumn = (!sortColumns || sortColumns.length === 0) ?
+ null :
+ sortColumns[0];
+
+ loadPage();
+ });
+
+ vm.gridApi.pagination.on.paginationChanged($scope, function (newPage, newPageSize) {
+ paginationPage = newPage;
+ paginationPageSize = newPageSize;
+ loadPage();
+ });
+
+ vm.gridApi.core.on.filterChanged($scope, function () {
+ // Save the all filters for later use.
+ var filters = [];
+ this.grid.columns.forEach(function (col, ind) {
+ if (col.filters[0] && col.filters[0].term) {
+ filters.push({ field: col.field, term: col.filters[0].term });
+ }
+ });
+ gridFilters = filters;
+ loadPage();
+ });
+
+ loadPage();
+ }
+ });
+
+ $scope.$on("mapViewVisuability", function (event, data) {
+ vm.showAllLinks = !data;
+ });
+
+ $scope.$watch("knownSiteLinks", function (newKnownSiteLinks, oldKnownSiteLinks) {
+ console.log("watch: knownSiteLinks");
+ loadPage();
+ }, true); // deep watch, maybe find a better solution; e.g. with an api object like the site in the map.
+
+ $scope.$watch(function () { return vm.showAllLinks; }, loadPage);
+
+ function loadPage() {
+ if (vm.showAllLinks) {
+ loadRemotePage();
+ } else {
+ loadLocalPage();
+ }
+ }
+
+ /**
+ * Calculates the page content of the grid and sets the values to the gridOprions.data object.
+ */
+ function loadLocalPage() {
+ var linkIds = Object.keys($scope.knownSiteLinks);
+
+ var tempData = linkIds.filter(function (linkId, ind, arr) {
+ var link = $scope.knownSiteLinks[linkId];
+ return gridFilters.map(function (filter) {
+ switch (filter.field) {
+ case 'siteIdA':
+ return link.siteA.id.contains(filter.term);
+ case 'siteIdZ':
+ return link.siteZ.id.contains(filter.term);
+ default:
+ return link[filter.field].contains(filter.term);
+ }
+ }).and(true);
+ }).map(function (linkId) {
+ var link = $scope.knownSiteLinks[linkId];
+ var orderBy;
+
+ if (!sortColumn || !sortColumn.sort.direction) {
+ return {
+ id: linkId
+ }
+ }
+
+ switch (sortColumn.field) {
+ case 'siteIdA':
+ orderBy = link.siteA.id;
+ break;
+ case 'siteIdZ':
+ orderBy = link.siteZ.id;
+ break;
+ default:
+ orderBy = link[sortColumn.field];
+ break;
+ }
+
+ return {
+ id: linkId,
+ orderBy: orderBy
+ };
+ });
+
+ if (sortColumn && sortColumn.sort.direction) {
+ tempData.sort(function (left, right) {
+ if (left === right || left.orderBy === right.orderBy) {
+ return 0;
+ }
+ if (left.orderBy > right.orderBy) {
+ return (sortColumn.sort.direction === uiGridConstants.ASC) ? 1 : -1;
+ }
+ return (sortColumn.sort.direction === uiGridConstants.ASC) ? -1 : 1;
+ });
+ }
+
+ var maxPageNumber = Math.max(1, Math.ceil(tempData.length / paginationPageSize));
+ var currentPage = Math.min(maxPageNumber, paginationPage);
+ var orderedSitesAtCurrentPage = tempData.slice((currentPage - 1) * paginationPageSize, currentPage * paginationPageSize);
+
+ linksAtCurrentPageCache = {};
+ var orderedData = [];
+
+ orderedSitesAtCurrentPage.forEach(function (orderdLink) {
+ var link = $scope.knownSiteLinks[orderdLink.id];
+ linksAtCurrentPageCache[link.id] = link;
+ orderedData.push({
+ id: link.id,
+ name: link.name,
+ siteIdA: link.siteA.id,
+ siteIdZ: link.siteZ.id,
+ type: link.type
+ });
+ });
+
+ $timeout(function () {
+ vm.gridOptions.data = orderedData;
+ vm.gridOptions.totalItems = tempData.length;
+ });
+ }
+
+ /**
+ * Loads the page content for the grid and sets the values to the gridOprions.data object.
+ */
+ function loadRemotePage() {
+ linksAtCurrentPageCache = {};
+
+ // the links for one page
+ $mwtnTopology.getLinks((sortColumn && sortColumn.field), sortColumn && ((sortColumn.sort.direction && sortColumn.sort.direction === uiGridConstants.ASC) ? 'asc' : 'desc'), gridFilters, paginationPageSize, (paginationPage - 1) * paginationPageSize).then(function (result) {
+ // get all site id s
+ var siteIds = {};
+ result.links.forEach(function (link) {
+ siteIds[link.siteA] = link.siteA;
+ siteIds[link.siteZ] = link.siteZ;
+ });
+
+ // load all sites
+ $mwtnTopology.getSitesByIds(Object.keys(siteIds)).then(function (sitesResult) {
+ var sites = sitesResult.sites.reduce(function (acc, cur, ind, arr) {
+ acc[cur.id] = cur;
+ return acc;
+ }, {});
+ result.links.forEach(function (link) {
+ linksAtCurrentPageCache[link.id] = {
+ id: link.id,
+ name: link.name,
+ siteA: sites[link.siteA],
+ siteZ: sites[link.siteZ],
+ type: link.type
+ };
+ });
+ vm.gridOptions.data = result.links.map(function (link) {
+ return {
+ id: link.id,
+ name: link.name,
+ siteIdA: link.siteA,
+ siteIdZ: link.siteZ,
+ type: link.type
+ }
+ });
+ vm.gridOptions.totalItems = result.total;
+
+ });
+ });
+ }
+
+
+ }]);
+
+ mwtnTopologyApp.directive('mwtnTopologySiteLinkGrid', function () {
+ return {
+ restrict: 'E',
+ replace: false,
+ controller: 'mwtnTopologySiteLinkGridController',
+ controllerAs: 'vm',
+ templateUrl: 'src/app/mwtnTopology/templates/siteLinkGrid.tpl.html',
+ scope: {
+ knownSiteLinks: "="
+ },
+ };
+ });
+
+ mwtnTopologyApp.controller('mwtnTopologySitePathGridController', ['$scope', function ($scope) {
+ var vm = this;
+
+ vm.showAllPaths = false;
+
+ $scope.$on("mapViewVisuability", function (event, data) {
+ vm.showAllPaths = !data;
+ });
+
+ }]);
+
+ mwtnTopologyApp.directive('mwtnTopologySitePathGrid', function () {
+ return {
+ restrict: 'E',
+ replace: false,
+ controller: 'mwtnTopologySitePathGridController',
+ controllerAs: 'vm',
+ templateUrl: 'src/app/mwtnTopology/templates/sitePathGrid.tpl.html'
+ };
+ });
+
+ /********************************************* Physical ***********************************/
+
+ mwtnTopologyApp.controller('mwtnTopologyPhysicalViewController', ['$scope', '$q', '$timeout', '$state', '$window', '$mwtnTopology', function ($scope, $q, $timeout, $state, $window, $mwtnTopology) {
+ var vm = this;
+ vm.status = {
+ graphIsOpen: false,
+ networkElementsIsOpen: false,
+ LinksIsOpen: false
+ };
+
+ $scope.$watchCollection(function () { return [vm.status.graphIsOpen, vm.status.networkElementsIsOpen, vm.status.LinksIsOpen] }, function (newVal, oldVal) {
+ if (newVal[1] || newVal[2]) {
+ $timeout(function () {
+ $window.dispatchEvent(new Event("resize"));
+ });
+ }
+ });
+
+ }]);
+
+ mwtnTopologyApp.directive('mwtnTopologyPhysicalView', function () {
+ return {
+ restrict: 'E',
+ controller: 'mwtnTopologyPhysicalViewController',
+ controllerAs: 'vm',
+ bindToController: true,
+ templateUrl: 'src/app/mwtnTopology/templates/physicalView.tpl.html',
+ scope: {
+
+ }
+ };
+ });
+
+ /**
+ * Typedefinitions for the mwtnTopologyPhysicalPathData service.
+ * @typedef { { 'site' | 'device' | 'port' } } NodeLayerType
+ * @typedef { { x: number, y: number } } PositionType
+ * @typedef { { id: string, label: string, parent: string, grentparent: string, active: string, latitude: number, longitude: number } } NodeDataVo
+ * @typedef { { data: NodeDataVo, position: PositionType } } NodeVo
+ * @typedef { { id: string, label: string, parent: string, type: string, layer: NodeLayerType, active: boolean, latitude?: number, longitude?: number } } NodeData
+ * @typedef { { data: NodeData, position: PositionType } } Node
+ * @typedef { { id: string, source: string, target: string, label: string , lentgh: string, azimuthAZ: string , azimuthZA: string , layer: string , active: string } } EdgeData
+ * @typedef { { data: EdgeData } Edge
+ */
+ mwtnTopologyApp.factory("mwtnTopologyPhysicalPathData", ['$q','$mwtnTopology',
+ /** @param $q { ng.IQService } */
+ function ($q, $mwtnTopology) {
+ var colors = {
+ root: '#f54',
+ port: '#377',
+ device: '#252',
+ site: '#525',
+ edge: '#49a',
+ white: '#eed',
+ grey: '#555',
+ selected: '#ff0'
+ };
+
+ var styles = [
+ {
+ selector: 'node',
+ css: {
+ 'content': 'data(label)',
+ 'text-valign': 'center',
+ 'text-halign': 'center',
+ 'background-color': '#666666',
+ 'border-color': '#000000',
+ 'border-width': '1px',
+ 'color': '#ffffff'
+ }
+ },
+ {
+ selector: 'node[layer = "MWPS"]',
+ css: {
+ 'content': 'data(label)',
+ 'text-valign': 'center',
+ 'text-halign': 'center',
+ 'background-color': '#316ac5',
+ 'border-color': '#000000',
+ 'border-width': '1px',
+ 'color': '#ffffff'
+ }
+ },
+ {
+ selector: '$node > node',
+ css: {
+ 'shape': 'roundrectangle',
+ 'padding-top': '10px',
+ 'padding-left': '10px',
+ 'padding-bottom': '10px',
+ 'padding-right': '10px',
+ 'text-valign': 'top',
+ 'text-halign': 'center',
+ 'background-color': '#eeeeee',
+ 'color': '#444444',
+ 'border-color': '#888888'
+ }
+ },
+ {
+ selector: 'node[type = "site"]',
+ css: {
+ 'shape': 'roundrectangle',
+ 'padding-top': '10px',
+ 'padding-left': '10px',
+ 'padding-bottom': '10px',
+ 'padding-right': '10px',
+ 'text-valign': 'center',
+ 'text-halign': 'center',
+ 'background-color': '#fefefe',
+ 'color': '#444444',
+ 'border-color': '#888888',
+ 'font-weight': 'bold'
+ }
+ },
+ {
+ selector: 'node[type = "device"][active = "true"]',
+ css: {
+ 'background-color': '#316ac5',
+ 'background-opacity': '0.3',
+ 'border-color': '#316ac5',
+ 'border-width': '2px',
+ 'color': '#444444'
+ }
+ },
+ {
+ selector: 'node[type = "port"][active = "true"]',
+ css: {
+ 'background-opacity': '1.0',
+ }
+ },
+ {
+ selector: 'node[active = "false"]',
+ css: {
+ 'background-opacity': '0.3',
+ 'border-opacity': '0.5'
+ }
+ },
+
+ {
+ selector: 'edge',
+ css: {
+ 'content': 'data(id)',
+ 'target-arrow-shape': 'triangle',
+ 'line-color': '#666666',
+ 'color': '#444444'
+ }
+ },
+ {
+ selector: 'edge[active = "false"]',
+ css: {
+ 'line-color': '#cccccc',
+ 'text-opacity': '0.9'
+ }
+ },
+ {
+ selector: 'edge[layer = "MWPS"]',
+ css: {
+ 'content': 'data(id)',
+ 'target-arrow-shape': 'triangle',
+ 'width': '5px',
+ 'line-color': '#316ac5',
+ 'color': '#444444'
+ }
+ },
+ {
+ selector: 'edge[layer = "MWPS"][active = "false"]',
+ css: {
+ 'line-color': '#C0D1EC',
+ 'text-opacity': '0.9'
+ }
+ },
+ {
+ selector: ':selected',
+ css: {
+ 'background-color': 'black',
+ 'line-color': 'black',
+ 'target-arrow-color': 'black',
+ 'source-arrow-color': 'black'
+ }
+ }
+ ];
+
+ var events = eventsFabric();
+
+ /** Heplerfunction to retrive all elements from the database and convert to the structure needed */
+ function getElements() {
+ var res = $q.defer();
+
+ $q.all([
+ $mwtnTopology.getAllNodes(),
+ $mwtnTopology.getAllEdges()
+ ]).then(function (results) {
+ res.resolve({ nodes: results[0], edges: results[1] });
+ });
+
+ return res.promise;
+ }
+
+ var result = {
+ colors: colors,
+ getElements: getElements,
+ styles: styles,
+ events: events
+ };
+
+ var someMethodChangingTheElements = function () {
+ // @Martin: hier kannst Du die Elements ändern, anschließend mußt Du das Ereignis veröffentlichen
+ // das Ereigniss wird in der Directive aufgefangen und die Grig wird neu gezeichnet
+
+ // Hinweis: Die Reihenfolge muss so bleiben und du kannst NUR result.elements ändern.
+
+ events.publish("elementsChanged", {
+ elements: result.elements
+ });
+ };
+
+ return result;
+ }]);
+
+ mwtnTopologyApp.directive("mwtnTopologyPhysicalPathGraph", ["mwtnTopologyPhysicalPathData", "$mwtnTopology", "$mwtnCommons", function (pathGraphData, $mwtnTopology, $mwtnCommons) {
+
+ return {
+ restrict: 'E',
+ replace: true,
+ template: '<div style="height:750px; width: 100%;"></div>',
+ controller: function () {
+
+ },
+ scope: {
+
+ },
+ link: function (scope, element, attrs, ctrl) {
+
+ var cy = cytoscape({
+ container: element[0],
+
+ boxSelectionEnabled: false,
+ autounselectify: true,
+
+ style: pathGraphData.styles,
+ elements: [],
+ layout: {
+ name: 'preset',
+ padding: 5
+ }
+ });
+
+ cy.viewport({
+ zoom: 0.50,
+ pan: { x: 100, y: 50 }
+ });
+
+ pathGraphData.getElements().then(function (elements) {
+ cy.json({
+ elements: elements
+ });
+
+ // disable drag & drop
+ cy.nodes().ungrabify();
+ });
+
+ var filterActiveMountPoints = function (mountpoints) {
+ return mountpoints.filter(function (mountpoint) {
+ if (!mountpoint) return false;
+ // console.warn(mountpoint['node-id'], mountpoint['netconf-node-topology:connection-status']);
+ return mountpoint['netconf-node-topology:connection-status'] === 'connected';
+ }).map(function (mountpoint) {
+ return mountpoint['node-id'];
+ });
+ };
+
+ var setDevicesActive = function (nodeIds) {
+ // console.warn(nodeIds);
+ cy.nodes().filter(function (node) {
+ node.data('active', 'false');
+ return node.data('type') === 'device' && nodeIds.contains(node.data('id'));
+ }).map(function (node) {
+ node.data('active', 'true');
+ });
+ };
+
+ var setAllDevicesInactive = function () {
+ cy.nodes().map(function (node) {
+ node.data('active', 'false');
+ });
+ };
+
+ var setPortAndEdgedActive = function () {
+ cy.edges().map(function (edge) {
+ var active = 'true';
+ edge.connectedNodes().map(function (port) {
+ // console.log(' node', JSON.stringify(edge.data()));
+ var parent = cy.getElementById(port.data('parent'));
+ if (parent.data('active') === 'false') {
+ port.data('active', 'false');
+ edge.data('active', 'false');
+ } else {
+ port.data('active', 'true');
+ }
+ });
+ });
+ };
+
+ var setCss = function () {
+ var width = 5 / cy.zoom();
+ var stroke = 1 / cy.zoom();
+ cy.edges().css('width', width);
+ cy.nodes().css('border-width', stroke);
+ };
+
+ var init = function () {
+ $mwtnCommons.getMountPoints().then(function (mountpoints) {
+ var filtered = filterActiveMountPoints(mountpoints);
+ setDevicesActive(filtered);
+ setPortAndEdgedActive();
+ }, function (error) {
+ setAllDevicesInactive();
+ setPortAndEdgedActive();
+ });
+ };
+
+ init();
+ setCss();
+
+ var orderLtps = function () {
+ var done = [];
+ var x = 0;
+ var y = 0;
+ var row = 0;
+ var offset = 100;
+ var selector = "[type = 'device']";
+ var devices = cy.nodes(selector).sort(function (a, b) {
+ if (a.children().length > b.children().length) return -1;
+ if (a.children().length < b.children().length) return 1;
+ return 0;
+ }).map(function (device) {
+ device.children().filter(function (ltp) {
+ return done.indexOf(ltp.id()) === -1;
+ }).map(function (ltp) {
+ ltp.position({ x: x, y: y });
+ y = y + offset;
+ var remeberX = x;
+ ltp.connectedEdges().map(function (edge) {
+ edge.connectedNodes().sort(function (a, b) {
+ if (a.data("parent") === device.id()) return -1;
+ return 1;
+ }).map(function (node) {
+ // x = remeberX + 0 * offset;
+ node.position({ x: x, y: y });
+ x = x + 3 * offset;
+ y = y + offset;
+ done.push(node.id());
+ });
+ y = y - 2 * offset;
+
+ });
+ x = remeberX;
+ done.push(ltp.id());
+ });
+ y = row * 6 * offset;
+ x = x + 6 * offset;
+
+ if (x > 4000) {
+ row = row + 1;
+ x = 0;
+ y = row * 6 * offset;
+ }
+ return device;
+ });
+ };
+
+ var dragedNodes = [];
+ // add an event handler for 'tabdrag' for all ports
+ cy.on('drag', 'node[type = "port"]', function (event) {
+ var id = event.target.data().id;
+ dragedNodes.indexOf(id) <= -1 && dragedNodes.push(id);
+ });
+
+ cy.on('tap', function (event) {
+ if (event.target !== cy) {
+ console.info('click', JSON.stringify(event.target.data()));
+ } else {
+ orderLtps();
+ // cy.zoom(0.1);
+ cy.fit();
+ cy.center();
+ }
+ });
+
+ cy.on('zoom', function (event) {
+ setCss();
+ });
+
+ // global keyboard event handler
+ function handleKey(e) {
+ if (!e.ctrlKey && !e.commandKey) return;
+ switch (e.which) {
+ case 69:
+ dragedNodes = [];
+ cy.nodes().grabify();
+ e.preventDefault();
+ return false;
+ break;
+ case 83:
+ cy.nodes().ungrabify();
+ e.preventDefault();
+ var modifiedNodes = dragedNodes.map(id => ({ id: id, position: cy.nodes().getElementById(id).position() }));
+ $mwtnTopology.saveChangedNodes(modifiedNodes);
+ console.log("dragedNodes", modifiedNodes);
+ return false;
+ break;
+ // default:
+ // console.log(e.which);
+ // e.preventDefault();
+ // return false;
+ // break;
+ }
+ }
+
+ // register global keyboard event handler
+ window.addEventListener('keydown', handleKey, false);
+
+ scope.$on('$destroy', function () {
+ // un-register global keyboard event handler
+ window.removeEventListener('keydown', handleKey, false);
+ });
+
+ }
+ }
+ }]);
+
+ mwtnTopologyApp.controller("mwtnTopologyNetworkElementsGridController", ['$scope', '$timeout', '$state', '$mwtnCommons', '$mwtnTopology', 'uiGridConstants', 'mwtnTopologyPhysicalPathData', function ($scope, $timeout, $state, $mwtnCommons, $mwtnTopology, uiGridConstants, mwtnTopologyPhysicalPathData) {
+ var vm = this;
+
+ // The page number to show in the grid.
+ var paginationPage = 1;
+ // The page size.
+ var paginationPageSize = 100;
+ // The grid column object with current sorting informations.
+ var sortColumn = null;
+ // The grid column object with current sorting informations.
+ var gridFilters = [];
+ // caches all sites at the current grid page
+
+ vm.gridOptions = Object.assign({}, $mwtnCommons.gridOptions, {
+ showGridFooter: false, // disable the grid footer because the pagination component sets its own.
+ paginationPageSizes: [10, 25, 50, 100],
+ paginationPageSize: paginationPageSize,
+ useExternalPagination: true,
+ useExternalFiltering: true,
+ useExternalSorting: true,
+ totalItems: 0,
+ columnDefs: [{
+ field: "id",
+ type: "string",
+ displayName: "Id"
+ },
+ {
+ field: "layer",
+ type: "string",
+ displayName: "Layer",
+ width: 100,
+ },
+ {
+ field: "active",
+ type: "string",
+ displayName: "Active",
+ width: 100,
+ },
+ {
+ field: "latitude",
+ type: "string",
+ displayName: "Latitude",
+ visible: false
+ },
+ {
+ field: "longitude",
+ type: "string",
+ displayName: "Longitude",
+ visible: false
+ },
+ {
+ field: "installed", // capacity in Mbit/s
+ type: "number",
+ displayName: "Installed [Mbit/s]",
+ className: "number",
+ width: 150
+ },
+ {
+ field: "configured", // capacity in Mbit/s
+ type: "number",
+ displayName: "Configured [Mbit/s]",
+ className: "number",
+ width: 150
+ },
+ {
+ field: "effective", // capacity in Mbit/s
+ type: "number",
+ displayName: "Effective [Mbit/s]",
+ className: "number",
+ width: 150
+ }
+ ],
+ data: [],
+ onRegisterApi: function (gridApi) {
+ // http://ui-grid.info/docs/#/api/ui.grid.core.api:PublicApi
+ vm.gridApi = gridApi;
+
+ vm.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
+ // Save the current sort column for later use.
+ sortColumn = (!sortColumns || sortColumns.length === 0) ?
+ null :
+ sortColumns[0];
+ loadPage();
+ });
+
+ vm.gridApi.pagination.on.paginationChanged($scope, function (newPage, newPageSize) {
+ // Save the pagination informations for later use.
+ paginationPage = newPage;
+ paginationPageSize = newPageSize;
+ loadPage();
+ });
+
+ vm.gridApi.core.on.filterChanged($scope, function () {
+ // Save the all filters for later use.
+ var filters = [];
+ this.grid.columns.forEach(function (col, ind) {
+ if (col.filters[0] && col.filters[0].term) {
+ filters.push({
+ field: col.field,
+ term: col.filters[0].term
+ });
+ }
+ });
+ gridFilters = filters;
+ loadPage();
+ });
+
+ loadPage();
+ }
+ });
+
+ /**
+ * Calculates the page content of the grid and sets the values to the gridOprions.data object.
+ */
+ function loadPage() {
+
+ // extract all ports
+ var ports = mwtnTopologyPhysicalPathData.elements.nodes.filter(function (node, ind, arr) {
+ return node && node.data && node.data.type === 'port';
+ }).reduce(function (acc, cur, ind, arr) {
+ if (cur.data) acc[cur.data.id] = cur.data;
+ return acc;
+ }, {});
+
+ // get all port ids
+ var portIds = Object.keys(ports);
+
+ // apply the grid filters
+ var tempData = portIds.filter(function (portId, ind, arr) {
+ var port = ports[portId];
+ return gridFilters.map(function (filter) {
+ switch (filter.field) {
+ case "active":
+ return port[filter.field].toString().contains(filter.term);
+ default:
+ return port[filter.field].contains(filter.term);
+ }
+ }).and(true);
+ }).map(function (portId) {
+ var port = ports[portId];
+ var orderBy;
+
+ if (!sortColumn || !sortColumn.sort.direction) {
+ return {
+ id: port.id
+ }
+ }
+
+ switch (sortColumn.field) {
+ case "active":
+ orderBy = port[sortColumn.field] ? 1 : 0;
+ break;
+ default:
+ orderBy = port[sortColumn.field];
+ break;
+ }
+
+ return {
+ id: port.id,
+ orderBy: orderBy
+ };
+ });
+
+ if (sortColumn && sortColumn.sort.direction) {
+ tempData.sort(function (left, right) {
+ if (left === right || left.orderBy === right.orderBy) {
+ return 0;
+ }
+ if (left.orderBy > right.orderBy) {
+ return (sortColumn.sort.direction === uiGridConstants.ASC) ? 1 : -1;
+ }
+ return (sortColumn.sort.direction === uiGridConstants.ASC) ? -1 : 1;
+ });
+ }
+
+ var maxPageNumber = Math.max(1, Math.ceil(tempData.length / paginationPageSize));
+ var currentPage = Math.min(maxPageNumber, paginationPage);
+ var orderedPortsAtCurrentPage = tempData.slice((currentPage - 1) * paginationPageSize, currentPage * paginationPageSize);
+
+ portsAtCurrentPageCache = {};
+ var orderedData = [];
+
+ orderedPortsAtCurrentPage.forEach(function (orderedPort) {
+ var port = ports[orderedPort.id];
+ portsAtCurrentPageCache[port.id] = port;
+ orderedData.push({
+ id: orderedPort.id,
+ layer: port.layer,
+ active: port.active,
+ latitude: port.latitude.toLocaleString("en-US", {
+ minimumFractionDigits: 4
+ }),
+ longitude: port.longitude.toLocaleString("en-US", {
+ minimumFractionDigits: 4
+ }),
+ installed: 0,
+ configured: 0,
+ effective: 0
+ });
+ });
+
+ $timeout(function () {
+ vm.gridOptions.data = orderedData;
+ vm.gridOptions.totalItems = tempData.length;
+ });
+ }
+
+ // subscribe to the elementsChanged event to reload the grid page if the data in the service has chenged
+ mwtnTopologyPhysicalPathData.events.subscribe("elementsChanged", function (data) {
+ loadPage();
+ });
+
+ }]);
+
+ mwtnTopologyApp.directive("mwtnTopologyNetworkElementsGrid", [function () {
+ return {
+ restrict: 'E',
+ replace: false,
+ controller: 'mwtnTopologyNetworkElementsGridController',
+ controllerAs: 'vm',
+ scope: {
+
+ },
+ templateUrl: 'src/app/mwtnTopology/templates/networkElementsGrid.tpl.html'
+ };
+ }]);
+
+ mwtnTopologyApp.controller("mwtnTopologyLinksGridController", ['$scope', '$timeout', '$state', '$mwtnCommons', '$mwtnTopology', 'uiGridConstants', 'mwtnTopologyPhysicalPathData', function ($scope, $timeout, $state, $mwtnCommons, $mwtnTopology, uiGridConstants, mwtnTopologyPhysicalPathData) {
+ var vm = this;
+
+ // The page number to show in the grid.
+ var paginationPage = 1;
+ // The page size.
+ var paginationPageSize = 100;
+ // The grid column object with current sorting informations.
+ var sortColumn = null;
+ // The grid column object with current sorting informations.
+ var gridFilters = [];
+ // caches all sites at the current grid page
+
+ vm.gridOptions = Object.assign({}, $mwtnCommons.gridOptions, {
+ showGridFooter: false, // disable the grid footer because the pagination component sets its own.
+ paginationPageSizes: [10, 25, 50, 100],
+ paginationPageSize: paginationPageSize,
+ useExternalPagination: true,
+ useExternalFiltering: true,
+ useExternalSorting: true,
+ totalItems: 0,
+ columnDefs: [{
+ field: "id",
+ type: "string",
+ displayName: "Id"
+ },
+ {
+ field: "source",
+ type: "string",
+ displayName: "PortA"
+ },
+ {
+ field: "target",
+ type: "string",
+ displayName: "PortZ"
+ },
+ {
+ field: "layer",
+ type: "string",
+ displayName: "Layer"
+ },
+ {
+ field: "length",
+ type: "string",
+ displayName: "Length"
+ },
+ {
+ field: "azimuthAZ",
+ type: "string",
+ displayName: "azimuthAZ"
+ },
+ {
+ field: "azimuthZA",
+ type: "string",
+ displayName: "azimuthZA"
+ }
+ ],
+ data: [],
+ onRegisterApi: function (gridApi) {
+ // http://ui-grid.info/docs/#/api/ui.grid.core.api:PublicApi
+ vm.gridApi = gridApi;
+
+ vm.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
+ // Save the current sort column for later use.
+ sortColumn = (!sortColumns || sortColumns.length === 0) ?
+ null :
+ sortColumns[0];
+ loadPage();
+ });
+
+ vm.gridApi.pagination.on.paginationChanged($scope, function (newPage, newPageSize) {
+ // Save the pagination informations for later use.
+ paginationPage = newPage;
+ paginationPageSize = newPageSize;
+ loadPage();
+ });
+
+ vm.gridApi.core.on.filterChanged($scope, function () {
+ // Save the all filters for later use.
+ var filters = [];
+ this.grid.columns.forEach(function (col, ind) {
+ if (col.filters[0] && col.filters[0].term) {
+ filters.push({
+ field: col.field,
+ term: col.filters[0].term
+ });
+ }
+ });
+ gridFilters = filters;
+ loadPage();
+ });
+
+ loadPage();
+ }
+ });
+
+ /**
+ * Calculates the page content of the grid and sets the values to the gridOprions.data object.
+ */
+ function loadPage() {
+ // extract all ports
+ var ports = mwtnTopologyPhysicalPathData.elements.nodes.filter(function (node, ind, arr) {
+ return node && node.data && node.data.type === 'port';
+ }).reduce(function (acc, cur, ind, arr) {
+ if (cur.data) acc[cur.data.id] = cur.data;
+ return acc;
+ }, {});
+
+ // extract all links
+ var links = mwtnTopologyPhysicalPathData.elements.edges.filter(function (node, ind, arr) {
+ return true; // node && node.data && node.data.type === 'port';
+ }).reduce(function (acc, cur, ind, arr) {
+ if (cur.data) {
+
+ if (cur.data.layer === 'MWPS') {
+ var portA = ports[cur.data.source];
+ var portZ = ports[cur.data.target];
+
+ // calculate length
+ cur.data.length = (portA && portZ) ? $mwtnTopology.getDistance(portA.latitude, portA.longitude, portZ.latitude, portZ.longitude) : 0;
+ cur.data.azimuthAZ = (portA && portZ) ? $mwtnTopology.bearing(portA.latitude, portA.longitude, portZ.latitude, portZ.longitude) : 0;
+ cur.data.azimuthZA = (portA && portZ) ? $mwtnTopology.bearing(portZ.latitude, portZ.longitude, portA.latitude, portA.longitude) : 0;
+
+ } else {
+ cur.data.length = 0;
+ cur.data.azimuthAZ = 0;
+ cur.data.azimuthZA = 0;
+ }
+ acc[cur.data.id] = cur.data;
+ }
+ return acc;
+ }, {});
+
+ // get all link ids
+ var linkIds = Object.keys(links);
+
+ // apply the grid filters
+ var tempData = linkIds.filter(function (linkId, ind, arr) {
+ var link = links[linkId];
+ return gridFilters.map(function (filter) {
+ switch (filter.field) {
+ case "length":
+ case "azimuthAZ":
+ case "azimuthZA":
+ return true;
+ default:
+ return link[filter.field].contains(filter.term);
+ }
+ }).and(true);
+ }).map(function (linkId) {
+ var link = links[linkId];
+ var orderBy;
+
+ if (!sortColumn || !sortColumn.sort.direction) {
+ return {
+ id: link.id
+ }
+ }
+
+ switch (sortColumn.field) {
+ default:
+ orderBy = link[sortColumn.field];
+ break;
+ }
+
+ return {
+ id: link.id,
+ orderBy: orderBy
+ };
+ });
+
+ if (sortColumn && sortColumn.sort.direction) {
+ tempData.sort(function (left, right) {
+ if (left === right || left.orderBy === right.orderBy) {
+ return 0;
+ }
+ if (left.orderBy > right.orderBy) {
+ return (sortColumn.sort.direction === uiGridConstants.ASC) ? 1 : -1;
+ }
+ return (sortColumn.sort.direction === uiGridConstants.ASC) ? -1 : 1;
+ });
+ }
+
+ var maxPageNumber = Math.max(1, Math.ceil(tempData.length / paginationPageSize));
+ var currentPage = Math.min(maxPageNumber, paginationPage);
+ var orderedLinksAtCurrentPage = tempData.slice((currentPage - 1) * paginationPageSize, currentPage * paginationPageSize);
+
+ linksAtCurrentPageCache = {};
+ var orderedData = [];
+
+ orderedLinksAtCurrentPage.forEach(function (orderedLink) {
+ var link = links[orderedLink.id];
+ linksAtCurrentPageCache[link.id] = link;
+ orderedData.push({
+ id: orderedLink.id,
+ source: link.source,
+ target: link.target,
+ layer: link.layer,
+ length: link.length,
+ azimuthAZ: link.azimuthAZ.toLocaleString("en-US", {
+ minimumFractionDigits: 4
+ }),
+ azimuthZA: link.azimuthZA.toLocaleString("en-US", {
+ minimumFractionDigits: 4
+ }),
+ });
+ });
+
+ $timeout(function () {
+ vm.gridOptions.data = orderedData;
+ vm.gridOptions.totalItems = tempData.length;
+ });
+ }
+
+ // subscribe to the elementsChanged event to reload the grid page if the data in the service has chenged
+ var subscription = mwtnTopologyPhysicalPathData.events.subscribe("elementsChanged", function (data) {
+ loadPage();
+ // to unsubscribe call subscription.remove();
+ });
+
+ }]);
+
+ mwtnTopologyApp.directive("mwtnTopologyLinksGrid", [function () {
+ return {
+ restrict: 'E',
+ replace: false,
+ controller: 'mwtnTopologyLinksGridController',
+ controllerAs: 'vm',
+ scope: {
+
+ },
+ templateUrl: 'src/app/mwtnTopology/templates/linksGrid.tpl.html'
+ };
+ }]);
+
+ /********************************************* Ethernet ***********************************/
+
+ mwtnTopologyApp.controller('mwtnTopologyEthernetViewController', ['$scope', '$q', '$timeout', '$state', '$window', '$mwtnTopology', function ($scope, $q, $timeout, $state, $window, $mwtnTopology) {
+ var vm = this;
+ vm.status = {
+ topologyIsOpen: false,
+ portsOpen: false,
+ ethConnectionsIsOpen: false
+ }
+
+ $scope.$watchCollection(function () { return [vm.status.topologyIsOpen, vm.status.portsOpen, vm.status.ethConnectionsIsOpen] }, function (newVal, oldVal) {
+ if (newVal[1] || newVal[2]) {
+ $timeout(function () {
+ $window.dispatchEvent(new Event("resize"));
+ });
+ }
+ });
+
+ }]);
+
+ mwtnTopologyApp.directive('mwtnTopologyEthernetView', function () {
+ return {
+ restrict: 'E',
+ controller: 'mwtnTopologyEthernetViewController',
+ controllerAs: 'vm',
+ bindToController: true,
+ templateUrl: 'src/app/mwtnTopology/templates/ethernetView.tpl.html',
+ scope: {
+
+ }
+ };
+ });
+
+ mwtnTopologyApp.factory("mwtnTopologyEthernetPathData", function () {
+ var colors = {
+ root: '#f54',
+ port: '#377',
+ device: '#252',
+ site: '#525',
+ edge: '#49a',
+ white: '#eed',
+ grey: '#555',
+ selected: '#ff0'
+ };
+
+ var styles = [
+ {
+ selector: 'node',
+ css: {
+ 'content': 'data(label)',
+ 'text-valign': 'center',
+ 'text-halign': 'center',
+ 'background-color': '#eeeeee',
+ 'border-color': '#000000',
+ 'border-width': '1px',
+ 'color': '#000000'
+ }
+ },
+ {
+ selector: 'node[type = "label"]',
+ css: {
+ 'content': 'data(label)',
+ 'border-width': '0px',
+ 'background-color': '#ffffff',
+ 'font-size': '50px',
+ 'text-valign': 'bottom',
+ 'text-halign': 'right',
+
+ }
+ },
+
+ {
+ selector: 'node[layer = "MWPS"]',
+ css: {
+ 'content': 'data(label)',
+ 'text-valign': 'center',
+ 'text-halign': 'center',
+ 'background-color': '#316ac5',
+ 'border-color': '#000000',
+ 'border-width': '1px',
+ 'color': '#ffffff'
+ }
+ },
+ {
+ selector: 'node[layer = "ETH-TTP"]',
+ css: {
+ 'content': 'data(label)',
+ 'text-valign': 'center',
+ 'text-halign': 'center',
+ 'background-color': '#008800',
+ 'border-color': '#004400',
+ 'border-width': '1px',
+ 'color': '#ffffff'
+ }
+ },
+ {
+ selector: '$node > node',
+ css: {
+ 'shape': 'roundrectangle',
+ 'padding-top': '10px',
+ 'padding-left': '10px',
+ 'padding-bottom': '10px',
+ 'padding-right': '10px',
+ 'text-valign': 'top',
+ 'text-halign': 'center',
+ 'background-color': '#eeeeee',
+ 'color': '#444444',
+ 'border-color': '#888888'
+ }
+ },
+ {
+ selector: 'node[type = "site"]',
+ css: {
+ 'shape': 'roundrectangle',
+ 'padding-top': '10px',
+ 'padding-left': '10px',
+ 'padding-bottom': '10px',
+ 'padding-right': '10px',
+ 'text-valign': 'center',
+ 'text-halign': 'center',
+ 'background-color': '#fefefe',
+ 'color': '#444444',
+ 'border-color': '#888888',
+ 'font-weight': 'bold'
+ }
+ },
+ {
+ selector: 'node[type = "device"]',
+ css: {
+ 'background-color': '#eeeeee',
+ 'background-opacity': '0.1',
+ 'border-color': '#888888',
+ 'border-width': '2px',
+ 'color': '#444444'
+ }
+ },
+ {
+ selector: 'node[type = "device"][active = "true"]',
+ css: {
+ 'background-color': '#316ac5',
+ 'background-opacity': '0.1',
+ 'border-color': '#316ac5',
+ 'border-width': '2px',
+ 'color': '#444444'
+ }
+ },
+ {
+ selector: 'node[type = "port"][active = "true"]',
+ css: {
+ 'background-opacity': '1.0',
+ }
+ },
+ {
+ selector: 'node[path = "working"]',
+ css: {
+ 'background-color': '#FFA500',
+ 'border-color': '#FFA500',
+ 'border-width': '2px',
+ 'color': '#444444'
+ }
+ },
+ {
+ selector: 'node[path = "protection"]',
+ css: {
+ 'background-color': '#ffffff',
+ 'border-color': '#FFA500',
+ 'border-width': '2px',
+ 'color': '#444444'
+ }
+ },
+ {
+ selector: '$node > node[path = "working"]',
+ css: {
+ 'background-color': '#FFA500',
+ 'border-color': '#FFA500',
+ 'border-width': '2px',
+ 'color': '#444444'
+ }
+ },
+ {
+ selector: '$node > node[path = "protection"]',
+ css: {
+ 'border-color': '#FFA500',
+ 'border-width': '2px',
+ 'color': '#444444'
+ }
+ },
+ {
+ selector: 'node[active = "false"]',
+ css: {
+ 'background-opacity': '0.3',
+ 'border-opacity': '0.5'
+ }
+ },
+ {
+ selector: 'edge',
+ css: {
+ 'content': 'data(id)',
+ 'target-arrow-shape': 'triangle',
+ 'line-color': '#666666',
+ 'color': '#444444'
+ }
+ },
+ {
+ selector: 'edge[active = "false"]',
+ css: {
+ 'line-color': '#cccccc',
+ 'text-opacity': '0.9'
+ }
+ },
+ {
+ selector: 'edge[layer = "ETC"]',
+ css: {
+ 'content': 'data(id)',
+ 'target-arrow-shape': 'triangle',
+ 'width': '3px',
+ 'line-color': '#316ac5',
+ 'color': '#444444'
+ }
+ },
+ {
+ selector: 'edge[layer = "ETH"]',
+ css: {
+ 'content': '',
+ 'target-arrow-shape': 'triangle',
+ 'width': '2px',
+ 'line-color': '#FFA500',
+ 'color': '#000000'
+ }
+ }, {
+ selector: 'edge[layer = "ETC"][active = "false"]',
+ css: {
+ 'line-color': '#C0D1EC',
+ 'text-opacity': '0.9'
+ }
+ },
+
+ {
+ selector: 'edge[layer = "ETH"][path = "false"]',
+ css: {
+ 'opacity': '0.0'
+ }
+ },
+ {
+ selector: 'edge[path = "working"]',
+ css: {
+ 'line-color': '#FFA500',
+ 'width': '5px',
+ 'opacity': '1.0'
+ }
+ },
+ {
+ selector: 'edge[path = "protection"]',
+ css: {
+ 'line-color': '#FFA500',
+ 'line-style': 'dashed',
+ 'width': '3px',
+ 'opacity': '1.0'
+ }
+ },
+ {
+ selector: ':selected',
+ css: {
+ 'background-color': 'black',
+ 'line-color': 'black',
+ 'target-arrow-color': 'black',
+ 'source-arrow-color': 'black'
+ }
+ }
+ ];
+
+ var elements = {
+ nodes: [
+ { data: { id: 'label', label : '' , type: 'label' }, position: { x: -259, y: -167 } },
+
+ { data: { id: 'north', label : 'north' , type: 'site', latitude: 50.734916, longitude: 7.137636 } },
+ { data: { id: 'north-east', label : 'north-east' , type: 'site', latitude: 50.733028, longitude: 7.151086 } },
+ { data: { id: 'north-west', label : 'north-west' , type: 'site', latitude: 50.730230, longitude: 7.126017 } },
+ { data: { id: 'east', label : 'east' , type: 'site', latitude: 50.725672, longitude: 7.158488 } },
+ { data: { id: 'west', label : 'west' , type: 'site', latitude: 50.721914, longitude: 7.120521 } },
+ { data: { id: 'south-east', label : 'south-east' , type: 'site', latitude: 50.717158, longitude: 7.155506 } },
+ { data: { id: 'south-west', label : 'south-west' , type: 'site', latitude: 50.714359, longitude: 7.130437 } },
+ { data: { id: 'south', label : 'south' , type: 'site', latitude: 50.712472, longitude: 7.143887 } },
+
+ { data: { id: 'ADVA-Y', label : 'ADVA-Y' , parent : 'north-east', type: 'device', active: 'true' , latitude: 50.733028, longitude: 7.151086 } },
+ { data: { id: 'ADVA-Z', label : 'ADVA-Z' , parent : 'south', type: 'device', active: 'true' , latitude: 50.712472, longitude: 7.143887 } },
+ { data: { id: 'Aviat-A', label : 'Aviat-A' , parent : 'north-east', type: 'device', active: 'true' , latitude: 50.733028, longitude: 7.151086 } },
+ { data: { id: 'Aviat-Z', label : 'Aviat-Z' , parent : 'east', type: 'device', active: 'true' , latitude: 50.725672, longitude: 7.158488 } },
+ { data: { id: 'Ceragon-A', label : 'Ceragon-A' , parent : 'north-west', type: 'device', active: 'true' , latitude: 50.730230, longitude: 7.126017 } },
+ { data: { id: 'Ceragon-Z', label : 'Ceragon-Z' , parent : 'west', type: 'device', active: 'true' , latitude: 50.721914, longitude: 7.120521 } },
+ { data: { id: 'DragonWave-A', label : 'DragonWave-A' , parent : 'south-west', type: 'device', active: 'true' , latitude: 50.714359, longitude: 7.130437 } },
+ { data: { id: 'DragonWave-Z', label : 'DragonWave-Z' , parent : 'south', type: 'device', active: 'true' , latitude: 50.712472, longitude: 7.143887 } },
+ { data: { id: 'ELVA-1-A', label : 'ELVA-1-A' , parent : 'north', type: 'device', active: 'true' , latitude: 50.734916, longitude: 7.137636 } },
+ { data: { id: 'ELVA-1-Z', label : 'ELVA-1-Z' , parent : 'south-west', type: 'device', active: 'true' , latitude: 50.714359, longitude: 7.130437 } },
+ { data: { id: 'Ericsson-A', label : 'Ericsson-A' , parent : 'north-east', type: 'device', active: 'true' , latitude: 50.733028, longitude: 7.151086 } },
+ { data: { id: 'Ericsson-Z', label : 'Ericsson-Z' , parent : 'east', type: 'device', active: 'true' , latitude: 50.725672, longitude: 7.158488 } },
+ { data: { id: 'Fujitsu-A', label : 'Fujitsu-A' , parent : 'east', type: 'device', active: 'true' , latitude: 50.725672, longitude: 7.158488 } },
+ { data: { id: 'Fujitsu-Z', label : 'Fujitsu-Z' , parent : 'south-east', type: 'device', active: 'true' , latitude: 50.717158, longitude: 7.155506 } },
+ { data: { id: 'Huawei-A', label : 'Huawei-A' , parent : 'south-west', type: 'device', active: 'true' , latitude: 50.714359, longitude: 7.130437 } },
+ { data: { id: 'Huawei-Z', label : 'Huawei-Z' , parent : 'south', type: 'device', active: 'true' , latitude: 50.712472, longitude: 7.143887 } },
+ { data: { id: 'Intracom-A', label : 'Intracom-A' , parent : 'south', type: 'device', active: 'true' , latitude: 50.712472, longitude: 7.143887 } },
+ { data: { id: 'Intracom-Z', label : 'Intracom-Z' , parent : 'south-east', type: 'device', active: 'true' , latitude: 50.717158, longitude: 7.155506 } },
+ { data: { id: 'NEC-A', label : 'NEC-A' , parent : 'north', type: 'device', active: 'true' , latitude: 50.734916, longitude: 7.137636 } },
+ { data: { id: 'NEC-Z', label : 'NEC-Z' , parent : 'north-east', type: 'device', active: 'true' , latitude: 50.733028, longitude: 7.151086 } },
+ { data: { id: 'Nokia-A', label : 'Nokia-A' , parent : 'west', type: 'device', active: 'fasel' , latitude: 50.721914, longitude: 7.120521 } },
+ { data: { id: 'Nokia-Z', label : 'Nokia-Z' , parent : 'south-west', type: 'device', active: 'true' , latitude: 50.714359, longitude: 7.130437 } },
+ { data: { id: 'SIAE-A', label : 'SIAE-A' , parent : 'south', type: 'device', active: 'true' , latitude: 50.712472, longitude: 7.143887 } },
+ { data: { id: 'SIAE-Z', label : 'SIAE-Z' , parent : 'south-east', type: 'device', active: 'true' , latitude: 50.717158, longitude: 7.155506 } },
+ { data: { id: 'ZTE-A', label : 'ZTE-A' , parent : 'north-west', type: 'device', active: 'true' , latitude: 50.730230, longitude: 7.126017 } },
+ { data: { id: 'ZTE-Z', label : 'ZTE-Z' , parent : 'north', type: 'device', active: 'true' , latitude: 50.734916, longitude: 7.137636 } },
+
+ { data: { id: 'Aviat-Z#5', label : '#5' , parent : 'Aviat-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.725672, longitude: 7.158488 }, position: { x: 1984, y: 390 } },
+ { data: { id: 'Aviat-Z#6', label : '#6' , parent : 'Aviat-Z' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.725672, longitude: 7.158488 }, position: { x: 1984, y: 321 } },
+ { data: { id: 'Ericsson-Z#5', label : '#5' , parent : 'Ericsson-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.725672, longitude: 7.158488 }, position: { x: 1777, y: 393 } },
+ { data: { id: 'Ericsson-Z#6', label : '#6' , parent : 'Ericsson-Z' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.725672, longitude: 7.158488 }, position: { x: 1765, y: 325 } },
+ { data: { id: 'Fujitsu-A#5', label : '#5' , parent : 'Fujitsu-A' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.725672, longitude: 7.158488 }, position: { x: 1859, y: 567 } },
+ { data: { id: 'Fujitsu-A#6', label : '#6' , parent : 'Fujitsu-A' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.725672, longitude: 7.158488 }, position: { x: 1859, y: 647 } },
+ { data: { id: 'ELVA-1-A#2', label : '#2' , parent : 'ELVA-1-A' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.734916, longitude: 7.137636 }, position: { x: 895, y: 150 } },
+ { data: { id: 'ELVA-1-A#6', label : '#6' , parent : 'ELVA-1-A' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.734916, longitude: 7.137636 }, position: { x: 815, y: 150 } },
+ { data: { id: 'NEC-A#3', label : '#3' , parent : 'NEC-A' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.734916, longitude: 7.137636 }, position: { x: 955, y: -52 } },
+ { data: { id: 'NEC-A#6', label : '#6' , parent : 'NEC-A' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.734916, longitude: 7.137636 }, position: { x: 1035, y: -52 } },
+ { data: { id: 'ZTE-Z#4', label : '#4' , parent : 'ZTE-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.734916, longitude: 7.137636 }, position: { x: 707, y: -67 } },
+ { data: { id: 'ZTE-Z#5', label : '#5' , parent : 'ZTE-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.734916, longitude: 7.137636 }, position: { x: 747, y: -27 } },
+ { data: { id: 'ZTE-Z#6', label : '#6' , parent : 'ZTE-Z' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.734916, longitude: 7.137636 }, position: { x: 667, y: -27 } },
+ // { data: { id: 'ADVA-Y#1', label : '#1' , parent : 'ADVA-Y' , type:'port', layer:'ETY', active:'true', latitude:50.733028, longitude:7.151086}, position: { x: 1392, y: 229 } },
+ { data: { id: 'ADVA-Y#2', label : '#2' , parent : 'ADVA-Y' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1449, y: 172 } },
+ { data: { id: 'ADVA-Y#3', label : '#3' , parent : 'ADVA-Y' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1392, y: 172 } },
+ { data: { id: 'ADVA-Y#4', label : '#4' , parent : 'ADVA-Y' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1449, y: 229 } },
+ { data: { id: 'Aviat-A#5', label : '#5' , parent : 'Aviat-A' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1654, y: -47 } },
+ { data: { id: 'Aviat-A#6', label : '#6' , parent : 'Aviat-A' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1654, y: 22 } },
+ { data: { id: 'Ericsson-A#4', label : '#4' , parent : 'Ericsson-A' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1605, y: 229 } },
+ { data: { id: 'Ericsson-A#5', label : '#5' , parent : 'Ericsson-A' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1605, y: 172 } },
+ { data: { id: 'Ericsson-A#6', label : '#6' , parent : 'Ericsson-A' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1664, y: 226 } },
+ // { data: { id: 'NEC-Z#3', label : '#3' , parent : 'NEC-Z' , type:'port', layer:'ETY', active:'true', latitude:50.733028, longitude:7.151086}, position: { x: 1392, y: 16 } },
+ { data: { id: 'NEC-Z#4', label : '#4' , parent : 'NEC-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1449, y: 16 } },
+ { data: { id: 'NEC-Z#2', label : '#2' , parent : 'NEC-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1449, y: -41 } },
+ { data: { id: 'NEC-Z#6', label : '#6' , parent : 'NEC-Z' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1382, y: -23 } },
+ { data: { id: 'Ceragon-A#5', label : '#5' , parent : 'Ceragon-A' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.730230, longitude: 7.126017 }, position: { x: 195, y: 312 } },
+ { data: { id: 'Ceragon-A#6', label : '#6' , parent : 'Ceragon-A' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.730230, longitude: 7.126017 }, position: { x: 136, y: 366 } },
+ { data: { id: 'ZTE-A#5', label : '#5' , parent : 'ZTE-A' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.730230, longitude: 7.126017 }, position: { x: 345, y: 108 } },
+ { data: { id: 'ZTE-A#6', label : '#6' , parent : 'ZTE-A' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.730230, longitude: 7.126017 }, position: { x: 408, y: 99 } },
+ { data: { id: 'ADVA-Z#1', label : '#1' , parent : 'ADVA-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.712472, longitude: 7.143887 }, position: { x: 1139, y: 1001 } },
+ // { data: { id: 'ADVA-Z#2', label : '#2' , parent : 'ADVA-Z' , type:'port', layer:'ETY', active:'true', latitude:50.712472, longitude:7.143887}, position: { x: 1196, y: 944 } },
+ // { data: { id: 'ADVA-Z#3', label : '#3' , parent : 'ADVA-Z' , type:'port', layer:'ETY', active:'true', latitude:50.712472, longitude:7.143887}, position: { x: 1139, y: 944 } },
+ { data: { id: 'ADVA-Z#4', label : '#4' , parent : 'ADVA-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.712472, longitude: 7.143887 }, position: { x: 1196, y: 1001 } },
+ { data: { id: 'DragonWave-Z#2', label : '#2' , parent : 'DragonWave-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.712472, longitude: 7.143887 }, position: { x: 1133, y: 1252 } },
+ { data: { id: 'DragonWave-Z#6', label : '#6' , parent : 'DragonWave-Z' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.712472, longitude: 7.143887 }, position: { x: 1053, y: 1252 } },
+ { data: { id: 'Huawei-Z#3', label : '#3' , parent : 'Huawei-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.712472, longitude: 7.143887 }, position: { x: 1046, y: 1094 } },
+ // { data: { id: 'Huawei-Z#4', label : '#4' , parent : 'Huawei-Z' , type:'port', layer:'ETY', active:'true', latitude:50.712472, longitude:7.143887}, position: { x: 989, y: 1094 } },
+ { data: { id: 'Huawei-Z#5', label : '#5' , parent : 'Huawei-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.712472, longitude: 7.143887 }, position: { x: 1058, y: 1123 } },
+ { data: { id: 'Huawei-Z#6', label : '#6' , parent : 'Huawei-Z' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.712472, longitude: 7.143887 }, position: { x: 978, y: 1123 } },
+ { data: { id: 'Intracom-A#2', label : '#2' , parent : 'Intracom-A' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.712472, longitude: 7.143887 }, position: { x: 1203, y: 1252 } },
+ { data: { id: 'Intracom-A#6', label : '#6' , parent : 'Intracom-A' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.712472, longitude: 7.143887 }, position: { x: 1283, y: 1252 } },
+ { data: { id: 'SIAE-A#4', label : '#4' , parent : 'SIAE-A' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.712472, longitude: 7.143887 }, position: { x: 1289, y: 1094 } },
+ { data: { id: 'SIAE-A#5', label : '#5' , parent : 'SIAE-A' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.712472, longitude: 7.143887 }, position: { x: 1278, y: 1123 } },
+ { data: { id: 'SIAE-A#6', label : '#6' , parent : 'SIAE-A' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.712472, longitude: 7.143887 }, position: { x: 1346, y: 1094 } },
+ { data: { id: 'Fujitsu-Z#5', label : '#5' , parent : 'Fujitsu-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.717158, longitude: 7.155506 }, position: { x: 1855, y: 821 } },
+ { data: { id: 'Fujitsu-Z#6', label : '#6' , parent : 'Fujitsu-Z' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.717158, longitude: 7.155506 }, position: { x: 1855, y: 741 } },
+ { data: { id: 'Intracom-Z#5', label : '#5' , parent : 'Intracom-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.717158, longitude: 7.155506 }, position: { x: 1755, y: 998 } },
+ { data: { id: 'Intracom-Z#6', label : '#6' , parent : 'Intracom-Z' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.717158, longitude: 7.155506 }, position: { x: 1714, y: 1018 } },
+ { data: { id: 'SIAE-Z#4', label : '#4' , parent : 'SIAE-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.717158, longitude: 7.155506 }, position: { x: 1590, y: 784 } },
+ { data: { id: 'SIAE-Z#5', label : '#5' , parent : 'SIAE-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.717158, longitude: 7.155506 }, position: { x: 1647, y: 784 } },
+ { data: { id: 'SIAE-Z#6', label : '#6' , parent : 'SIAE-Z' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.717158, longitude: 7.155506 }, position: { x: 1588, y: 838 } },
+ { data: { id: 'DragonWave-A#2', label : '#2' , parent : 'DragonWave-A' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.714359, longitude: 7.130437 }, position: { x: 455, y: 1178 } },
+ { data: { id: 'DragonWave-A#6', label : '#6' , parent : 'DragonWave-A' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.714359, longitude: 7.130437 }, position: { x: 535, y: 1178 } },
+ { data: { id: 'ELVA-1-Z#2', label : '#2' , parent : 'ELVA-1-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.714359, longitude: 7.130437 }, position: { x: 455, y: 878 } },
+ { data: { id: 'ELVA-1-Z#6', label : '#6' , parent : 'ELVA-1-Z' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.714359, longitude: 7.130437 }, position: { x: 535, y: 878 } },
+ { data: { id: 'Huawei-A#5', label : '#5' , parent : 'Huawei-A' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.714359, longitude: 7.130437 }, position: { x: 605, y: 1028 } },
+ { data: { id: 'Huawei-A#6', label : '#6' , parent : 'Huawei-A' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.714359, longitude: 7.130437 }, position: { x: 685, y: 1028 } },
+ { data: { id: 'Nokia-Z33', label : '33' , parent : 'Nokia-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.714359, longitude: 7.130437 }, position: { x: 385, y: 1028 } },
+ { data: { id: 'Nokia-Z#6', label : '#6' , parent : 'Nokia-Z' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.714359, longitude: 7.130437 }, position: { x: 305, y: 1028 } },
+ { data: { id: 'Ceragon-Z#4', label : '#4' , parent : 'Ceragon-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.721914, longitude: 7.120521 }, position: { x: -159, y: 547 } },
+ { data: { id: 'Ceragon-Z#5', label : '#5' , parent : 'Ceragon-Z' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.721914, longitude: 7.120521 }, position: { x: -159, y: 604 } },
+ { data: { id: 'Ceragon-Z#6', label : '#6' , parent : 'Ceragon-Z' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.721914, longitude: 7.120521 }, position: { x: -130, y: 536 } },
+ // { data: { id: 'Nokia-A13', label : '13' , parent : 'Nokia-A' , type:'port', layer:'ETY', active:'true', latitude:50.721914, longitude:7.120521}, position: { x: 40, y: 801 } },
+ // { data: { id: 'Nokia-A34', label : '34' , parent : 'Nokia-A' , type:'port', layer:'ETY', active:'true', latitude:50.721914, longitude:7.120521}, position: { x: 28, y: 772 } },
+ { data: { id: 'Nokia-A11', label : '11' , parent : 'Nokia-A' , type: 'port', layer: 'ETY', active: 'true', latitude: 50.721914, longitude: 7.120521 }, position: { x: -29, y: 772 } },
+ { data: { id: 'Nokia-A#6', label : '#6' , parent : 'Nokia-A' , type: 'port', layer: 'ETC', active: 'true', latitude: 50.721914, longitude: 7.120521 }, position: { x: 28, y: 829 } },
+
+ { data: { id: 'Spirent#1', label : '#1' , parent : 'Spirent' , type: 'host', layer: 'ETH-TTP', active: 'true', service: 'service13' }, position: { x: 707, y: -167 } },
+ { data: { id: 'Spirent#2', label : '#2' , parent : 'Spirent' , type: 'host', layer: 'ETH-TTP', active: 'true', service: 'service24' }, position: { x: -259, y: 547 } },
+ { data: { id: 'Spirent#3', label : '#3' , parent : 'Spirent' , type: 'host', layer: 'ETH-TTP', active: 'true', service: 'service13' }, position: { x: 1292, y: 172 } },
+ { data: { id: 'Spirent#4', label : '#4' , parent : 'Spirent' , type: 'host', layer: 'ETH-TTP', active: 'true', service: 'service24' }, position: { x: 1490, y: 784 } },
+ { data: { id: 'Spirent#5', label : '#5' , parent : 'Spirent' , type: 'host', layer: 'ETH-TTP', active: 'true', service: 'service56' }, position: { x: 1754, y: -47 } },
+ { data: { id: 'Spirent#6', label : '#6' , parent : 'Spirent' , type: 'host', layer: 'ETH-TTP', active: 'true', service: 'service56' }, position: { x: 455, y: 1278 } },
+ { data: { id: 'Spirent#7', label : '#7' , parent : 'Spirent' , type: 'host', layer: 'ETH-TTP', active: 'true', service: 'service78' }, position: { x: 895, y: 250 } },
+ { data: { id: 'Spirent#8', label : '#8' , parent : 'Spirent' , type: 'host', layer: 'ETH-TTP', active: 'true', service: 'service78' }, position: { x: 455, y: 778 } },
+
+ ],
+ edges: [
+
+ { data: { id: '21', source: 'Aviat-A#6', target: 'Aviat-Z#6', label: '21', layer: 'ETC' , active: 'true' } },
+ { data: { id: '31', source: 'Ceragon-A#6', target: 'Ceragon-Z#6', label: '31', layer: 'ETC' , active: 'true' } },
+ { data: { id: '41', source: 'DragonWave-A#6', target: 'DragonWave-Z#6', label: '41', layer: 'ETC' , active: 'true' } },
+ { data: { id: '121', source: 'ELVA-1-A#6', target: 'ELVA-1-Z#6', label: '121' , layer: 'ETC' , active: 'true' } },
+ { data: { id: 'ERI1', source: 'Ericsson-A#6', target: 'Ericsson-Z#6', label: 'ERI1', layer: 'ETC' , active: 'true' } },
+ { data: { id: '61', source: 'Fujitsu-A#6', target: 'Fujitsu-Z#6', label: '61', layer: 'ETC' , active: 'true' } },
+ { data: { id: '71', source: 'Huawei-A#6', target: 'Huawei-Z#6', label: '71', layer: 'ETC' , active: 'true' } },
+ { data: { id: '131', source: 'Intracom-A#6', target: 'Intracom-Z#6', label: '131', layer: 'ETC' , active: 'true' } },
+ { data: { id: '81', source: 'NEC-A#6', target: 'NEC-Z#6', label: '81', layer: 'ETC' , active: 'true' } },
+ { data: { id: '91', source: 'Nokia-A#6', target: 'Nokia-Z#6', label: '91', layer: 'ETC' , active: 'true' } },
+ { data: { id: '101', source: 'SIAE-A#6', target: 'SIAE-Z#6', label: '101', layer: 'ETC' , active: 'true' } },
+ { data: { id: '111', source: 'ZTE-A#6', target: 'ZTE-Z#6', label: '111', layer: 'ETC' , active: 'true' } },
+
+ // { data: { id: 'ETY01', source: 'ADVA-A#1', target: 'Nokia-A34', label: 'ADVA-A#1-Nokia-A34' , layer: 'ETY' , active: 'true' } },
+ // { data: { id: 'ETY02', source: 'ADVA-A#2', target: 'ZTE-A#4', label: 'ADVA-A#2-ZTE-A#4' , layer: 'ETY' , active: 'false' } },
+ // { data: { id: 'ETY03', source: 'ADVA-B#1', target: 'Nokia-A13', label: 'ADVA-B#1-Nokia-A13' , layer: 'ETY' , active: 'true' } },
+ // { data: { id: 'ETY04', source: 'ADVA-B#2', target: 'ZTE-A#3', label: 'ADVA-B#2-ZTE-A#3' , layer: 'ETY' , active: 'true' } },
+ // { data: { id: 'ETY05', source: 'ADVA-Y#1', target: 'Huawei-Z#4', label: 'ADVA-Y#1-Huawei-Z#4' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY06', source: 'ADVA-Y#2', target: 'NEC-Z#4', label: 'ADVA-Y#2-NEC-Z#4' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY07', source: 'ADVA-Y#3', target: 'Spirent#3', label: 'ADVA-Y#3-Spirent#3' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY08', source: 'ADVA-Y#4', target: 'Ericsson-A#4', label: 'ADVA-Y#4-Ericsson-A#4' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY09', source: 'ADVA-Z#1', target: 'Huawei-Z#3', label: 'ADVA-Z#1-Huawei-Z#3' , layer: 'ETY' , active: 'true' } },
+ // { data: { id: 'ETY10', source: 'ADVA-Z#2', target: 'NEC-Z#3', label: 'ADVA-Z#2-NEC-Z#3' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY11', source: 'ADVA-Z#4', target: 'SIAE-A#4', label: 'ADVA-Z#4-SIAE-A#4' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY12', source: 'Aviat-A#5', target: 'Spirent#5', label: 'Aviat-A#5-Spirent#5' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY13', source: 'Aviat-Z#5', target: 'Fujitsu-A#5', label: 'Aviat-Z#5-Fujitsu-A#5' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY14', source: 'Ceragon-A#5', target: 'ZTE-A#5', label: 'Ceragon-A#5-ZTE-A#5' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY15', source: 'Ceragon-Z#4', target: 'Spirent#2', label: 'Ceragon-Z#4-Spirent#2' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY16', source: 'Ceragon-Z#5', target: 'Nokia-A11', label: 'Ceragon-Z#5-Nokia-A11' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY17', source: 'DragonWave-A#2', target: 'Spirent#6', label: 'DragonWave-A#2-Spirent#6' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY18', source: 'DragonWave-Z#2', target: 'Intracom-A#2', label: 'DragonWave-Z#2-Intracom-A#2' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY19', source: 'ELVA-1-A#2', target: 'Spirent#7', label: 'ELVA-1-A#2-Spirent#7' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY20', source: 'ELVA-1-Z#2', target: 'Spirent#8', label: 'ELVA-1-Z#2-Spirent#8' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY21', source: 'Ericsson-A#5', target: 'NEC-Z#2', label: 'Ericsson-A#5-NEC-Z#2' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY22', source: 'Ericsson-Z#5', target: 'SIAE-Z#5', label: 'Ericsson-Z#5-SIAE-Z#5' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY23', source: 'Fujitsu-Z#5', target: 'Intracom-Z#5', label: 'Fujitsu-Z#5-Intracom-Z#5' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY24', source: 'Huawei-A#5', target: 'Nokia-Z33', label: 'Huawei-A#5-Nokia-Z33' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY25', source: 'Huawei-Z#5', target: 'SIAE-A#5', label: 'Huawei-Z#5-SIAE-A#5' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY26', source: 'NEC-A#3', target: 'ZTE-Z#5', label: 'NEC-A#3-ZTE-Z#5' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY27', source: 'SIAE-Z#4', target: 'Spirent#4', label: 'SIAE-Z#4-Spirent#4' , layer: 'ETY' , active: 'true' } },
+ { data: { id: 'ETY28', source: 'ZTE-Z#4', target: 'Spirent#1', label: 'Spirent#1-ZTE-Z#4' , layer: 'ETY' , active: 'true' } },
+
+ {  data:  {  id:  'ADVA-Y#2-ETH-13<->ADVA-Y#3-ETH-13',  source:  'ADVA-Y#2',  target:  'ADVA-Y#3',  label:  'service13' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service13' ,  rule:  'protection'  }  },
+ {  data:  {  id:  'ADVA-Y#2-ETH-24<->ADVA-Y#4-ETH-24',  source:  'ADVA-Y#2',  target:  'ADVA-Y#4',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'protection'  }  },
+ {  data:  {  id:  'ADVA-Y#3-ETH-13<->ADVA-Y#4-ETH-13',  source:  'ADVA-Y#3',  target:  'ADVA-Y#4',  label:  'service13' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service13' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Aviat-A#5-ETH-56<->Aviat-A#6-ETH-56',  source:  'Aviat-A#5',  target:  'Aviat-A#6',  label:  'service56' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service56' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Aviat-Z#5-ETH-56<->Aviat-Z#6-ETH-56',  source:  'Aviat-Z#5',  target:  'Aviat-Z#6',  label:  'service56' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service56' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Ceragon-A#5-ETH-13<->Ceragon-A#6-ETH-13',  source:  'Ceragon-A#5',  target:  'Ceragon-A#6',  label:  'service13' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service13' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Ceragon-A#5-ETH-24<->Ceragon-A#6-ETH-24',  source:  'Ceragon-A#5',  target:  'Ceragon-A#6',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'protection'  }  },
+ {  data:  {  id:  'Ceragon-Z#4-ETH-24<->Ceragon-Z#5-ETH-24',  source:  'Ceragon-Z#4',  target:  'Ceragon-Z#5',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Ceragon-Z#4-ETH-24<->Ceragon-Z#6-ETH-24',  source:  'Ceragon-Z#4',  target:  'Ceragon-Z#6',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'protection'  }  },
+ {  data:  {  id:  'Ceragon-Z#5-ETH-13<->Ceragon-Z#6-ETH-13',  source:  'Ceragon-Z#5',  target:  'Ceragon-Z#6',  label:  'service13' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service13' ,  rule:  'working'  }  },
+ {  data:  {  id:  'DragonWave-A#2-ETH-56<->DragonWave-A#6-ETH-56',  source:  'DragonWave-A#2',  target:  'DragonWave-A#6',  label:  'service56' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service56' ,  rule:  'working'  }  },
+ {  data:  {  id:  'DragonWave-Z#2-ETH-56<->DragonWave-Z#6-ETH-56',  source:  'DragonWave-Z#2',  target:  'DragonWave-Z#6',  label:  'service56' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service56' ,  rule:  'working'  }  },
+ {  data:  {  id:  'ELVA-1-A#2-ETH-78<->ELVA-1-A#6-ETH-78',  source:  'ELVA-1-A#2',  target:  'ELVA-1-A#6',  label:  'service78' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service78' ,  rule:  'working'  }  },
+ {  data:  {  id:  'ELVA-1-Z#2-ETH-78<->ELVA-1-Z#6-ETH-78',  source:  'ELVA-1-Z#2',  target:  'ELVA-1-Z#6',  label:  'service78' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service78' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Ericsson-A#4-ETH-13<->Ericsson-A#6-ETH-13',  source:  'Ericsson-A#4',  target:  'Ericsson-A#6',  label:  'service13' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service13' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Ericsson-A#4-ETH-24<->Ericsson-A#6-ETH-24',  source:  'Ericsson-A#4',  target:  'Ericsson-A#6',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'protection'  }  },
+ {  data:  {  id:  'Ericsson-Z#5-ETH-13<->Ericsson-Z#6-ETH-13',  source:  'Ericsson-Z#5',  target:  'Ericsson-Z#6',  label:  'service13' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service13' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Ericsson-Z#5-ETH-24<->Ericsson-Z#6-ETH-24',  source:  'Ericsson-Z#5',  target:  'Ericsson-Z#6',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'protection'  }  },
+ {  data:  {  id:  'Fujitsu-A#5-ETH-56<->Fujitsu-A#6-ETH-56',  source:  'Fujitsu-A#5',  target:  'Fujitsu-A#6',  label:  'service56' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service56' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Fujitsu-Z#5-ETH-56<->Fujitsu-Z#6-ETH-56',  source:  'Fujitsu-Z#5',  target:  'Fujitsu-Z#6',  label:  'service56' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service56' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Huawei-A#5-ETH-13<->Huawei-A#6-ETH-13',  source:  'Huawei-A#5',  target:  'Huawei-A#6',  label:  'service13' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service13' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Huawei-A#5-ETH-24<->Huawei-A#6-ETH-24',  source:  'Huawei-A#5',  target:  'Huawei-A#6',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Huawei-Z#5-ETH-13<->Huawei-Z#6-ETH-13',  source:  'Huawei-Z#5',  target:  'Huawei-Z#6',  label:  'service13' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service13' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Huawei-Z#5-ETH-24<->Huawei-Z#6-ETH-24',  source:  'Huawei-Z#5',  target:  'Huawei-Z#6',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Intracom-A#2-ETH-56<->Intracom-A#6-ETH-56',  source:  'Intracom-A#2',  target:  'Intracom-A#6',  label:  'service56' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service56' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Intracom-Z#5-ETH-56<->Intracom-Z#6-ETH-56',  source:  'Intracom-Z#5',  target:  'Intracom-Z#6',  label:  'service56' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service56' ,  rule:  'working'  }  },
+ {  data:  {  id:  'NEC-A#3-ETH-13<->NEC-A#6-ETH-13',  source:  'NEC-A#3',  target:  'NEC-A#6',  label:  'service13' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service13' ,  rule:  'protection'  }  },
+ {  data:  {  id:  'NEC-A#3-ETH-24<->NEC-A#6-ETH-24',  source:  'NEC-A#3',  target:  'NEC-A#6',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'protection'  }  },
+ {  data:  {  id:  'NEC-Z#4-ETH-13<->NEC-Z#6-ETH-13',  source:  'NEC-Z#4',  target:  'NEC-Z#6',  label:  'service13' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service13' ,  rule:  'protection'  }  },
+ {  data:  {  id:  'NEC-Z#4-ETH-24<->NEC-Z#6-ETH-24',  source:  'NEC-Z#4',  target:  'NEC-Z#6',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'protection'  }  },
+ {  data:  {  id:  'Nokia-A11-ETH-13<->Nokia-A#6-ETH-13',  source:  'Nokia-A11',  target:  'Nokia-A#6',  label:  'service13' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service13' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Nokia-A11-ETH-24<->Nokia-A#6-ETH-24',  source:  'Nokia-A11',  target:  'Nokia-A#6',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Nokia-Z33-ETH-13<->Nokia-Z#6-ETH-13',  source:  'Nokia-Z33',  target:  'Nokia-Z#6',  label:  'service13' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service13' ,  rule:  'working'  }  },
+ {  data:  {  id:  'Nokia-Z33-ETH-24<->Nokia-Z#6-ETH-24',  source:  'Nokia-Z33',  target:  'Nokia-Z#6',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'working'  }  },
+ {  data:  {  id:  'SIAE-A#5-ETH-13<->SIAE-A#6-ETH-13',  source:  'SIAE-A#5',  target:  'SIAE-A#6',  label:  'service13' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service13' ,  rule:  'working'  }  },
+ {  data:  {  id:  'SIAE-A#5-ETH-24<->SIAE-A#6-ETH-24',  source:  'SIAE-A#5',  target:  'SIAE-A#6',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'working'  }  },
+ {  data:  {  id:  'SIAE-Z#4-ETH-24<->SIAE-Z#6-ETH-24',  source:  'SIAE-Z#4',  target:  'SIAE-Z#6',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'working'  }  },
+ {  data:  {  id:  'SIAE-Z#4-ETH-24<->SIAE-Z#5-ETH-24',  source:  'SIAE-Z#4',  target:  'SIAE-Z#5',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'protection'  }  },
+ {  data:  {  id:  'SIAE-Z#5-ETH-13<->SIAE-Z#6-ETH-13',  source:  'SIAE-Z#5',  target:  'SIAE-Z#6',  label:  'service13' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service13' ,  rule:  'working'  }  },
+ {  data:  {  id:  'ZTE-A#5-ETH-13<->ZTE-A#6-ETH-13',  source:  'ZTE-A#5',  target:  'ZTE-A#6',  label:  'service13' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service13' ,  rule:  'working'  }  },
+ {  data:  {  id:  'ZTE-A#5-ETH-24<->ZTE-A#6-ETH-24',  source:  'ZTE-A#5',  target:  'ZTE-A#6',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'protection'  }  },
+ {  data:  {  id:  'ZTE-A#5-ETH-24<->ZTE-A#6-ETH-24',  source:  'ZTE-A#5',  target:  'ZTE-A#6',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'protection'  }  },
+ {  data:  {  id:  'ZTE-Z#4-ETH-13<->ZTE-Z#6-ETH-13',  source:  'ZTE-Z#4',  target:  'ZTE-Z#6',  label:  'service13' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service13' ,  rule:  'working'  }  },
+ {  data:  {  id:  'ZTE-Z#4-ETH-13<->ZTE-Z#5-ETH-13',  source:  'ZTE-Z#4',  target:  'ZTE-Z#5',  label:  'service13' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service13' ,  rule:  'protection'  }  },
+ {  data:  {  id:  'ZTE-Z#5-ETH-24<->ZTE-Z#6-ETH-24',  source:  'ZTE-Z#5',  target:  'ZTE-Z#6',  label:  'service24' ,  layer:  'ETH' ,  active:  'true' ,  path:  'false'  ,  service:  'service24' ,  rule:  'protection'  }  },
+ ]
+ };
+
+ var events = eventsFabric();
+
+ var result = {
+ colors: colors,
+ elements: elements,
+ styles: styles,
+ events: events
+ };
+
+ var someMethodChangingTheElements = function () {
+ // @Martin: hier kannst Du die Elements ändern, anschließend mußt Du das Ereignis veröffentlichen
+ // das Ereigniss wird in der Directive aufgefangen und die Grig wird neu gezeichnet
+
+ // Hinweis: Die Reihenfolge muss so bleiben und du kannst NUR result.elements ändern.
+
+ events.publish("elementsChanged", {
+ elements: result.elements
+ });
+ };
+
+ return result;
+ });
+
+ mwtnTopologyApp.directive("mwtnTopologyEthernetPathGraph", ["mwtnTopologyEthernetPathData", '$mwtnCommons', function (mwtnTopologyEthernetPathData, $mwtnCommons) {
+
+ return {
+ restrict: 'E',
+ replace: true,
+ template: '<div id="cy" style="height: 750px; width: 100%;"></div>',
+ controller: function () {
+
+ },
+ scope: {
+
+ },
+ link: function (scope, element, attrs, ctrl) {
+
+ var cy = cytoscape({
+ container: element[0],
+
+ boxSelectionEnabled: false,
+ autounselectify: true,
+
+ style: mwtnTopologyEthernetPathData.styles,
+ elements: mwtnTopologyEthernetPathData.elements,
+ layout: {
+ name: 'preset',
+ padding: 5
+ }
+ });
+
+ // @ Martin: Hier wird das Ereignis aus dem Service aboniert.
+ // Es ist möglich mehrere Ereignisse zu definieren.
+ mwtnTopologyEthernetPathData.events.subscribe("elementsChanged", function (data) {
+
+ // @Martin: cy aktualisiert sich mit Hilfe der Referenz auf die Elemente aus dem Service
+ cy.json({
+ elements: mwtnTopologyEthernetPathData.elements // oder data.elements
+ });
+
+ });
+
+ // pathGraphData.events.subscribe("styleChanged", function () {
+ // cy.json({
+ // style: mwtnTopologyEthernetPathData.styles
+ // });
+ // });
+
+ cy.viewport({
+ zoom: 0.5,
+ pan: { x: 150, y: 100 }
+ });
+
+ var clearService = function () {
+ var lable = cy.getElementById('label');
+ lable.data('label', '');
+
+ var pathState = ['working', 'protection', 'hidden'];
+ pathState.map(function (state) {
+ var selector = "[path = '" + state + "']";
+ cy.elements(selector).map(function (element) {
+ element.data('path', 'false');
+ });
+ });
+ };
+
+ var highlightService = function (service) {
+ var lable = cy.getElementById('label');
+ lable.data('label', service);
+
+ var selector = "[service = '" + service + "']";
+ // start and end service node (host, traffic analyser)
+ cy.nodes(selector).map(function (node) {
+ // console.log(node.id());
+ node.data('path', 'working');
+ });
+
+ ['protection', 'working'].map(function (state) {
+ selector = "[service = '" + service + "'][rule = '" + state + "']";
+ cy.edges(selector).map(function (edge) {
+ edge.connectedNodes().map(function (node) {
+ node.data('path', edge.data('rule'));
+ });
+ });
+ return state;
+ }).reverse().map(function (state) {
+ selector = "[path = '" + state + "']";
+ cy.nodes(selector).connectedEdges().filter(function (edge) {
+ return edge.data('service') === service || edge.data('layer') !== "ETH";
+ }).map(function (edge) {
+ edge.data('path', state);
+ });
+ });
+ };
+ var filterActiveMountPoints = function (mountpoints) {
+ return mountpoints.filter(function (mountpoint) {
+ if (!mountpoint) return false;
+ // console.warn(mountpoint['node-id'], mountpoint['netconf-node-topology:connection-status']);
+ return mountpoint['netconf-node-topology:connection-status'] === 'connected';
+ }).map(function (mountpoint) {
+ return mountpoint['node-id'];
+ });
+ };
+
+ var setDevicesActive = function (nodeIds) {
+ // console.warn(nodeIds);
+ cy.nodes().filter(function (node) {
+ node.data('active', 'false');
+ return node.data('type') === 'device' && nodeIds.contains(node.data('id'));
+ }).map(function (node) {
+ node.data('active', 'true');
+ });
+ };
+
+ var setAllDevicesInactive = function () {
+ cy.nodes().map(function (node) {
+ node.data('active', 'false');
+ });
+ };
+
+ var setPortAndEdgedActive = function () {
+ cy.edges().map(function (edge) {
+ var active = 'true';
+ edge.connectedNodes().map(function (port) {
+ // console.log(' node', JSON.stringify(edge.data()));
+ var parent = cy.getElementById(port.data('parent'));
+ if (parent.data('active') === 'false') {
+ port.data('active', 'false');
+ edge.data('active', 'false');
+ } else {
+ port.data('active', 'true');
+ }
+ });
+ });
+ };
+
+ var init = function () {
+ var timerName = 'init ethernet';
+ console.time(timerName);
+ $mwtnCommons.getMountPoints().then(function (mountpoints) {
+ var filtered = filterActiveMountPoints(mountpoints);
+ setDevicesActive(filtered);
+ setPortAndEdgedActive();
+ console.timeEnd(timerName);
+ }, function (error) {
+ setAllDevicesInactive();
+ setPortAndEdgedActive();
+ console.timeEnd(timerName);
+ });
+
+ };
+ init();
+
+ cy.on('tap', function (event) {
+ clearService();
+ if (event.target !== cy) {
+ if (event.target.data('service')) {
+ highlightService(event.target.data('service'));
+ }
+ } else {
+ init();
+ }
+ });
+ }
+ }
+ }]);
+
+ mwtnTopologyApp.controller("mwtnTopologyPortsGridController", ['$scope', '$timeout', '$state', '$mwtnCommons', '$mwtnTopology', 'uiGridConstants', 'mwtnTopologyEthernetPathData', function ($scope, $timeout, $state, $mwtnCommons, $mwtnTopology, uiGridConstants, mwtnTopologyEthernetPathData) {
+ var vm = this;
+
+ // The page number to show in the grid.
+ var paginationPage = 1;
+ // The page size.
+ var paginationPageSize = 100;
+ // The grid column object with current sorting informations.
+ var sortColumn = null;
+ // The grid column object with current sorting informations.
+ var gridFilters = [];
+ // caches all sites at the current grid page
+
+ vm.gridOptions = Object.assign({}, $mwtnCommons.gridOptions, {
+ showGridFooter: false, // disable the grid footer because the pagination component sets its own.
+ paginationPageSizes: [10, 25, 50, 100],
+ paginationPageSize: paginationPageSize,
+ useExternalPagination: true,
+ useExternalFiltering: true,
+ useExternalSorting: true,
+ totalItems: 0,
+ columnDefs: [{
+ field: "id",
+ type: "string",
+ displayName: "Id"
+ },
+ {
+ field: "layer",
+ type: "string",
+ displayName: "Layer"
+ },
+ {
+ field: "active",
+ type: "string",
+ displayName: "Active"
+ }
+ ],
+ data: [],
+ onRegisterApi: function (gridApi) {
+ // http://ui-grid.info/docs/#/api/ui.grid.core.api:PublicApi
+ vm.gridApi = gridApi;
+
+ vm.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
+ // Save the current sort column for later use.
+ sortColumn = (!sortColumns || sortColumns.length === 0) ?
+ null :
+ sortColumns[0];
+ loadPage();
+ });
+
+ vm.gridApi.pagination.on.paginationChanged($scope, function (newPage, newPageSize) {
+ // Save the pagination informations for later use.
+ paginationPage = newPage;
+ paginationPageSize = newPageSize;
+ loadPage();
+ });
+
+ vm.gridApi.core.on.filterChanged($scope, function () {
+ // Save the all filters for later use.
+ var filters = [];
+ this.grid.columns.forEach(function (col, ind) {
+ if (col.filters[0] && col.filters[0].term) {
+ filters.push({
+ field: col.field,
+ term: col.filters[0].term
+ });
+ }
+ });
+ gridFilters = filters;
+ loadPage();
+ });
+
+ loadPage();
+ }
+ });
+
+ /**
+ * Calculates the page content of the grid and sets the values to the gridOprions.data object.
+ */
+ function loadPage() {
+
+ // extract all ports
+ var ports = mwtnTopologyEthernetPathData.elements.nodes.filter(function (node, ind, arr) {
+ return node && node.data && node.data.type === 'port';
+ }).reduce(function (acc, cur, ind, arr) {
+ if (cur.data) acc[cur.data.id] = cur.data;
+ return acc;
+ }, {});
+
+ // get all port ids
+ var portIds = Object.keys(ports);
+
+ // apply the grid filters
+ var tempData = portIds.filter(function (portId, ind, arr) {
+ var port = ports[portId];
+ return gridFilters.map(function (filter) {
+ switch (filter.field) {
+ case "active":
+ return port[filter.field].toString().contains(filter.term);
+ default:
+ return port[filter.field].contains(filter.term);
+ }
+ }).and(true);
+ }).map(function (portId) {
+ var port = ports[portId];
+ var orderBy;
+
+ if (!sortColumn || !sortColumn.sort.direction) {
+ return {
+ id: port.id
+ }
+ }
+
+ switch (sortColumn.field) {
+ case "active":
+ orderBy = port[sortColumn.field] ? 1 : 0;
+ break;
+ default:
+ orderBy = port[sortColumn.field];
+ break;
+ }
+
+ return {
+ id: port.id,
+ orderBy: orderBy
+ };
+ });
+
+ if (sortColumn && sortColumn.sort.direction) {
+ tempData.sort(function (left, right) {
+ if (left === right || left.orderBy === right.orderBy) {
+ return 0;
+ }
+ if (left.orderBy > right.orderBy) {
+ return (sortColumn.sort.direction === uiGridConstants.ASC) ? 1 : -1;
+ }
+ return (sortColumn.sort.direction === uiGridConstants.ASC) ? -1 : 1;
+ });
+ }
+
+ var maxPageNumber = Math.max(1, Math.ceil(tempData.length / paginationPageSize));
+ var currentPage = Math.min(maxPageNumber, paginationPage);
+ var orderedPortsAtCurrentPage = tempData.slice((currentPage - 1) * paginationPageSize, currentPage * paginationPageSize);
+
+ portsAtCurrentPageCache = {};
+ var orderedData = [];
+
+ orderedPortsAtCurrentPage.forEach(function (orderedPort) {
+ var port = ports[orderedPort.id];
+ portsAtCurrentPageCache[port.id] = port;
+ orderedData.push({
+ id: orderedPort.id,
+ layer: port.layer,
+ active: port.active
+ });
+ });
+
+ $timeout(function () {
+ vm.gridOptions.data = orderedData;
+ vm.gridOptions.totalItems = tempData.length;
+ });
+ }
+
+ // subscribe to the elementsChanged event to reload the grid page if the data in the service has chenged
+ mwtnTopologyEthernetPathData.events.subscribe("elementsChanged", function (data) {
+ loadPage();
+ });
+
+ }]);
+
+ mwtnTopologyApp.directive("mwtnTopologyPortsGrid", [function () {
+ return {
+ restrict: 'E',
+ replace: false,
+ controller: 'mwtnTopologyPortsGridController',
+ controllerAs: 'vm',
+ scope: {
+
+ },
+ templateUrl: 'src/app/mwtnTopology/templates/portsGrid.tpl.html'
+ };
+ }]);
+
+ mwtnTopologyApp.controller("mwtnTopologyEthConnectionsGridController", ['$scope', '$timeout', '$state', '$mwtnCommons', '$mwtnTopology', 'uiGridConstants', 'mwtnTopologyEthernetPathData', function ($scope, $timeout, $state, $mwtnCommons, $mwtnTopology, uiGridConstants, mwtnTopologyEthernetPathData) {
+ var vm = this;
+
+ // The page number to show in the grid.
+ var paginationPage = 1;
+ // The page size.
+ var paginationPageSize = 100;
+ // The grid column object with current sorting informations.
+ var sortColumn = null;
+ // The grid column object with current sorting informations.
+ var gridFilters = [];
+ // caches all sites at the current grid page
+
+ vm.gridOptions = Object.assign({}, $mwtnCommons.gridOptions, {
+ showGridFooter: false, // disable the grid footer because the pagination component sets its own.
+ paginationPageSizes: [10, 25, 50, 100],
+ paginationPageSize: paginationPageSize,
+ useExternalPagination: true,
+ useExternalFiltering: true,
+ useExternalSorting: true,
+ totalItems: 0,
+ columnDefs: [{
+ field: "id",
+ type: "string",
+ displayName: "Id",
+ width: 400
+ },
+ {
+ field: "source",
+ type: "string",
+ displayName: "PortA",
+ width: 200
+ },
+ {
+ field: "target",
+ type: "string",
+ displayName: "PortZ",
+ width: 200
+ },
+ {
+ field: "layer",
+ type: "string",
+ displayName: "Layer",
+ width: 80
+ },
+ {
+ field: "service",
+ type: "string",
+ displayName: "Service",
+ width: 150
+ },
+ {
+ field: "rule",
+ type: "string",
+ displayName: "Rule",
+ width: 150
+ }
+ ],
+ data: [],
+ onRegisterApi: function (gridApi) {
+ // http://ui-grid.info/docs/#/api/ui.grid.core.api:PublicApi
+ vm.gridApi = gridApi;
+
+ vm.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
+ // Save the current sort column for later use.
+ sortColumn = (!sortColumns || sortColumns.length === 0) ?
+ null :
+ sortColumns[0];
+ loadPage();
+ });
+
+ vm.gridApi.pagination.on.paginationChanged($scope, function (newPage, newPageSize) {
+ // Save the pagination informations for later use.
+ paginationPage = newPage;
+ paginationPageSize = newPageSize;
+ loadPage();
+ });
+
+ vm.gridApi.core.on.filterChanged($scope, function () {
+ // Save the all filters for later use.
+ var filters = [];
+ this.grid.columns.forEach(function (col, ind) {
+ if (col.filters[0] && col.filters[0].term) {
+ filters.push({
+ field: col.field,
+ term: col.filters[0].term
+ });
+ }
+ });
+ gridFilters = filters;
+ loadPage();
+ });
+
+ loadPage();
+ }
+ });
+
+ /**
+ * Calculates the page content of the grid and sets the values to the gridOprions.data object.
+ */
+ function loadPage() {
+
+ // extract all links
+ var links = mwtnTopologyEthernetPathData.elements.edges.filter(function (link, ind, arr) {
+ // return true; // node && node.data && node.data.type === 'port';
+ return link.data.layer === 'ETH';
+ }).reduce(function (acc, cur, ind, arr) {
+ if (cur.data) {
+ acc[cur.data.id] = cur.data;
+ }
+ return acc;
+ }, {});
+
+ // get all relevant link ids
+ var linkIds = Object.keys(links);
+
+ // apply the grid filters
+ var tempData = linkIds.filter(function (linkId, ind, arr) {
+ var link = links[linkId];
+ return gridFilters.map(function (filter) {
+ switch (filter.field) {
+ default:
+ return link[filter.field].contains(filter.term);
+ }
+ }).and(true);
+ }).map(function (linkId) {
+ var link = links[linkId];
+ var orderBy;
+
+ if (!sortColumn || !sortColumn.sort.direction) {
+ return {
+ id: link.id
+ }
+ }
+
+ switch (sortColumn.field) {
+ default:
+ orderBy = link[sortColumn.field];
+ break;
+ }
+
+ return {
+ id: link.id,
+ orderBy: orderBy
+ };
+ });
+
+ if (sortColumn && sortColumn.sort.direction) {
+ tempData.sort(function (left, right) {
+ if (left === right || left.orderBy === right.orderBy) {
+ return 0;
+ }
+ if (left.orderBy > right.orderBy) {
+ return (sortColumn.sort.direction === uiGridConstants.ASC) ? 1 : -1;
+ }
+ return (sortColumn.sort.direction === uiGridConstants.ASC) ? -1 : 1;
+ });
+ }
+
+ var maxPageNumber = Math.max(1, Math.ceil(tempData.length / paginationPageSize));
+ var currentPage = Math.min(maxPageNumber, paginationPage);
+ var orderedLinksAtCurrentPage = tempData.slice((currentPage - 1) * paginationPageSize, currentPage * paginationPageSize);
+
+ linksAtCurrentPageCache = {};
+ var orderedData = [];
+
+ orderedLinksAtCurrentPage.forEach(function (orderedLink) {
+ var link = links[orderedLink.id];
+ linksAtCurrentPageCache[link.id] = link;
+ orderedData.push({
+ id: orderedLink.id,
+ source: link.source,
+ target: link.target,
+ layer: link.layer,
+ service: link.service,
+ rule: link.rule
+ });
+ });
+
+ $timeout(function () {
+ vm.gridOptions.data = orderedData;
+ vm.gridOptions.totalItems = tempData.length;
+ });
+ }
+
+ // subscribe to the elementsChanged event to reload the grid page if the data in the service has chenged
+ var subscription = mwtnTopologyEthernetPathData.events.subscribe("elementsChanged", function (data) {
+ loadPage();
+ // to unsubscribe call subscription.remove();
+ });
+
+ }]);
+
+ mwtnTopologyApp.directive("mwtnTopologyEthConnectionsGrid", [function () {
+ return {
+ restrict: 'E',
+ replace: false,
+ controller: 'mwtnTopologyEthConnectionsGridController',
+ controllerAs: 'vm',
+ scope: {
+
+ },
+ templateUrl: 'src/app/mwtnTopology/templates/ethConnectionsGrid.tpl.html'
+ };
+ }]);
+
+ /********************************************* IEEE 1588v2 (PTP) **************************/
+
+ mwtnTopologyApp.controller('mwtnTopologyIeee1588ViewController', ['$scope', '$q', '$timeout', '$state', '$window', '$mwtnTopology', function ($scope, $q, $timeout, $state, $window, $mwtnTopology) {
+ var vm = this;
+ vm.status = {
+ topologyIsOpen: false,
+ portsOpen: false,
+ ethConnectionsIsOpen: false
+ }
+
+ $scope.$watchCollection(function () { return [vm.status.topologyIsOpen, vm.status.portsOpen, vm.status.ethConnectionsIsOpen] }, function (newVal, oldVal) {
+ if (newVal[1] || newVal[2]) {
+ $timeout(function () {
+ $window.dispatchEvent(new Event("resize"));
+ });
+ }
+ });
+ }]);
+
+ mwtnTopologyApp.directive('mwtnTopologyIeee1588View', function () {
+ return {
+ restrict: 'E',
+ controller: 'mwtnTopologyIeee1588ViewController',
+ controllerAs: 'vm',
+ bindToController: true,
+ templateUrl: 'src/app/mwtnTopology/templates/ieee1588View.tpl.html',
+ scope: {
+
+ }
+ };
+ });
+
+ mwtnTopologyApp.factory("mwtnTopologyIeee1588PathData", function () {
+ var colors = {
+ root: '#f54',
+ port: '#377',
+ device: '#252',
+ site: '#525',
+ edge: '#49a',
+ white: '#eed',
+ grey: '#555',
+ selected: '#ff0'
+ };
+
+ var styles = [
+ {
+ selector: 'node',
+ css: {
+ 'content': 'data(label)',
+ 'text-valign': 'center',
+ 'text-halign': 'center',
+ 'background-color': '#aaaaaa',
+ 'border-color': '#000000',
+ 'border-width': '1px',
+ 'color': '#ffffff'
+ }
+ },
+ {
+ selector: '$node > node',
+ css: {
+ 'shape': 'roundrectangle',
+ 'padding-top': '10px',
+ 'padding-left': '10px',
+ 'padding-bottom': '10px',
+ 'padding-right': '10px',
+ 'text-valign': 'top',
+ 'text-halign': 'center',
+ 'background-color': '#eeeeee',
+ 'color': '#444444',
+ 'border-color': '#888888'
+ }
+ },
+ {
+ selector: 'node[type = "site"]',
+ css: {
+ 'shape': 'roundrectangle',
+ 'padding-top': '10px',
+ 'padding-left': '10px',
+ 'padding-bottom': '10px',
+ 'padding-right': '10px',
+ 'text-valign': 'center',
+ 'text-halign': 'center',
+ 'background-color': '#fefefe',
+ 'color': '#444444',
+ 'border-color': '#888888',
+ 'font-weight': 'bold'
+ }
+ },
+ {
+ selector: 'node[type = "ptp-clock"][active = "true"]',
+ css: {
+ 'background-color': '#316ac5',
+ 'background-opacity': '0.2',
+ 'border-color': '#316ac5',
+ 'border-opacity': '0.8',
+ 'border-width': '2px',
+ 'color': '#444444'
+ }
+ },
+ {
+ selector: 'node[type = "port"][active = "true"]',
+ css: {
+ 'background-opacity': '1.0',
+ }
+ },
+ {
+ selector: 'node[active = "false"]',
+ css: {
+ 'background-opacity': '0.3',
+ 'border-opacity': '0.5'
+ }
+ },
+ {
+ selector: 'node[path = "true"]',
+ css: {
+ 'background-color': '#ff00ff',
+ 'background-opacity': '0.9',
+ 'border-color': '#880088',
+ }
+ },
+ {
+ selector: '$node > node[path = "true"]',
+ css: {
+ 'background-color': '#ff00ff',
+ 'background-opacity': '0.3',
+ 'border-color': '#ff00ff',
+ 'border-opacity': '1.0',
+ 'border-width': '2px',
+ }
+ },
+ {
+ selector: 'edge',
+ css: {
+ 'content': 'data(id)',
+ 'target-arrow-shape': 'triangle',
+ 'line-color': '#666666',
+ 'color': '#444444'
+ }
+ },
+ {
+ selector: 'edge[active = "false"]',
+ css: {
+ 'line-color': '#cccccc',
+ 'text-opacity': '0.9'
+ }
+ },
+ {
+ selector: 'edge[path = "true"]',
+ css: {
+ 'line-color': '#ff00ff',
+ 'width': '5px'
+ }
+ },
+ {
+ selector: ':selected',
+ css: {
+ 'background-color': 'black',
+ 'line-color': 'black',
+ 'target-arrow-color': 'black',
+ 'source-arrow-color': 'black'
+ }
+ }];
+
+ var elements = {
+ nodes: [
+ { data: { id: 'north', label: 'north', type: 'site', latitude: 50.734916, longitude: 7.137636 } },
+ { data: { id: 'north-east', label: 'north-east', type: 'site', latitude: 50.733028, longitude: 7.151086 } },
+ { data: { id: 'north-west', label: 'north-west', type: 'site', latitude: 50.730230, longitude: 7.126017 } },
+ { data: { id: 'east', label: 'east', type: 'site', latitude: 50.725672, longitude: 7.158488 } },
+ { data: { id: 'west', label: 'west', type: 'site', latitude: 50.721914, longitude: 7.120521 } },
+ { data: { id: 'south-east', label: 'south-east', type: 'site', latitude: 50.717158, longitude: 7.155506 } },
+ { data: { id: 'south-west', label: 'south-west', type: 'site', latitude: 50.714359, longitude: 7.130437 } },
+ { data: { id: 'south', label: 'south', type: 'site', latitude: 50.712472, longitude: 7.143887 } },
+
+ {  data:  {  id:  'ADVA-A',  label :  'ADVA-A' ,  parent :  'west', type: 'ptp-clock', base64: 'AIDq//6MFzA=', hex: '0x47 0x4D 0x30 0x30 0x30 0x30 0x30 0x31', active: 'true' , latitude: 50.721914, longitude: 7.120521, parentDs: '', path: 'false' }   },
+ {  data:  {  id:  'ADVA-B',  label :  'ADVA-B' ,  parent :  'north-west', type: 'ptp-clock', base64: 'AIDq//6MGDA=', hex: '0x47 0x4D 0x30 0x30 0x30 0x30 0x30 0x32', active: 'true' , latitude: 50.730230, longitude: 7.126017, parentDs: '', path: 'false' }   },
+ {  data:  {  id:  'ADVA-Y',  label :  'ADVA-Y' ,  parent :  'north-east', type: 'ptp-clock', base64: 'AIDqb0hQAAE=', hex: '0x53 0x4C 0x41 0x56 0x45 0x30 0x30 0x31', active: 'true' , latitude: 50.733028, longitude: 7.151086, parentDs: 'NEC-Z#4', path: 'false' }   },
+ {  data:  {  id:  'ADVA-Z',  label :  'ADVA-Z' ,  parent :  'south', type: 'ptp-clock', base64: 'AIDqb0mAAAA=', hex: '0x53 0x4C 0x41 0x56 0x45 0x30 0x30 0x32', active: 'true' , latitude: 50.712472, longitude: 7.143887, parentDs: 'Huawei-Z#3', path: 'false' }   },
+ {  data:  {  id:  'Ericsson-A',  label :  'Ericsson-A' ,  parent :  'north-east', type: 'ptp-clock', base64: 'BE4G//4jsio=', hex: '0x42 0x43 0x30 0x30 0x30 0x30 0x30 0x31', active: 'true' , latitude: 50.733028, longitude: 7.151086, parentDs: 'NEC-Z#2', path: 'false' }   },
+ {  data:  {  id:  'Ericsson-Z',  label :  'Ericsson-Z' ,  parent :  'east', type: 'ptp-clock', base64: 'BE4G//4jtBA=', hex: '0x42 0x43 0x30 0x30 0x30 0x30 0x30 0x32', active: 'true' , latitude: 50.725672, longitude: 7.158488, parentDs: 'Ericsson-A#6', path: 'false' }   },
+ {  data:  {  id:  'Huawei-A',  label :  'Huawei-A' ,  parent :  'south-west', type: 'ptp-clock', base64: 'ACWeIQAJq6A=', hex: '0x42 0x43 0x30 0x30 0x30 0x30 0x30 0x33', active: 'true' , latitude: 50.714359, longitude: 7.130437, parentDs: 'Nokia-Z33', path: 'false' }   },
+ {  data:  {  id:  'Huawei-Z',  label :  'Huawei-Z' ,  parent :  'south', type: 'ptp-clock', base64: 'ACWeIQAJAm8=', hex: '0x42 0x43 0x30 0x30 0x30 0x30 0x30 0x34', active: 'true' , latitude: 50.712472, longitude: 7.143887, parentDs: 'Huawei-A#6', path: 'false' }   },
+ {  data:  {  id:  'NEC-A',  label :  'NEC-A' ,  parent :  'north', type: 'ptp-clock', base64: 'jN+d//5XDuA=', hex: '0x42 0x43 0x30 0x30 0x30 0x30 0x30 0x35', active: 'true' , latitude: 50.734916, longitude: 7.137636, parentDs: 'ZTE-Z#5', path: 'false' }   },
+ {  data:  {  id:  'NEC-Z',  label :  'NEC-Z' ,  parent :  'north-east', type: 'ptp-clock', base64: 'jN+d//5XDwA=', hex: '0x42 0x43 0x30 0x30 0x30 0x30 0x30 0x36', active: 'true' , latitude: 50.733028, longitude: 7.151086, parentDs: 'NEC-A#6', path: 'false' }   },
+ {  data:  {  id:  'Nokia-A',  label :  'Nokia-A' ,  parent :  'west', type: 'ptp-clock', base64: 'ACGu//4Crac=', hex: '0x42 0x43 0x30 0x30 0x30 0x30 0x30 0x37', active: 'true' , latitude: 50.721914, longitude: 7.120521, parentDs: 'ADVA-A#1', path: 'false' }   },
+ {  data:  {  id:  'Nokia-Z',  label :  'Nokia-Z' ,  parent :  'south-west', type: 'ptp-clock', base64: 'ACGu//4CrZY=', hex: '0x42 0x43 0x30 0x30 0x30 0x30 0x30 0x38', active: 'true' , latitude: 50.714359, longitude: 7.130437, parentDs: 'Nokia-A#6', path: 'false' }   },
+ {  data:  {  id:  'SIAE-A',  label :  'SIAE-A' ,  parent :  'south', type: 'ptp-clock', base64: 'ALCs//4R6K8=', hex: '0x42 0x43 0x30 0x30 0x30 0x30 0x30 0x39', active: 'true' , latitude: 50.712472, longitude: 7.143887, parentDs: 'Huawei-Z#5', path: 'false' }   },
+ {  data:  {  id:  'SIAE-Z',  label :  'SIAE-Z' ,  parent :  'south-east', type: 'ptp-clock', base64: 'ALCs//4RucQ=', hex: '0x42 0x43 0x30 0x30 0x30 0x30 0x31 0x30', active: 'true' , latitude: 50.717158, longitude: 7.155506, parentDs: 'SIAE-A#6', path: 'false' }   },
+ {  data:  {  id:  'ZTE-A',  label :  'ZTE-A' ,  parent :  'north-west', type: 'ptp-clock', base64: 'DBJi//7Zdpo=', hex: '0x42 0x43 0x30 0x30 0x30 0x30 0x31 0x31', active: 'true' , latitude: 50.730230, longitude: 7.126017, parentDs: 'ADVA-B#2', path: 'false' }   },
+ {  data:  {  id:  'ZTE-Z',  label :  'ZTE-Z' ,  parent :  'north', type: 'ptp-clock', base64: 'DBJi//7ZdqQ=', hex: '0x42 0x43 0x30 0x30 0x30 0x30 0x31 0x32', active: 'true' , latitude: 50.734916, longitude: 7.137636, parentDs: 'ZTE-A#6', path: 'false' }   },
+
+ { data: { id: 'ADVA-A#1', label: '#1', parent: 'ADVA-A', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.721914, longitude: 7.120521 }, position: { x: 124, y: 564 } },
+ { data: { id: 'ADVA-A#2', label: '#2', parent: 'ADVA-A', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.721914, longitude: 7.120521 }, position: { x: 124, y: 507 } },
+ { data: { id: 'ADVA-B#1', label: '#1', parent: 'ADVA-B', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.730230, longitude: 7.126017 }, position: { x: 380, y: 381 } },
+ { data: { id: 'ADVA-B#2', label: '#2', parent: 'ADVA-B', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.730230, longitude: 7.126017 }, position: { x: 380, y: 301 } },
+ { data: { id: 'ADVA-Y#1', label: '#1', parent: 'ADVA-Y', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1392, y: 229 } },
+ { data: { id: 'ADVA-Y#2', label: '#2', parent: 'ADVA-Y', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1449, y: 172 } },
+ { data: { id: 'ADVA-Z#1', label: '#1', parent: 'ADVA-Z', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.712472, longitude: 7.143887 }, position: { x: 1139, y: 1001 } },
+ { data: { id: 'ADVA-Z#2', label: '#2', parent: 'ADVA-Z', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.712472, longitude: 7.143887 }, position: { x: 1196, y: 944 } },
+ { data: { id: 'Ericsson-A#5', label: '#5', parent: 'Ericsson-A', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1605, y: 172 } },
+ { data: { id: 'Ericsson-A#6', label: '#6', parent: 'Ericsson-A', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1664, y: 226 } },
+ { data: { id: 'Ericsson-Z#6', label: '#6', parent: 'Ericsson-Z', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.725672, longitude: 7.158488 }, position: { x: 1765, y: 325 } },
+ { data: { id: 'Huawei-A#5', label: '#5', parent: 'Huawei-A', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.714359, longitude: 7.130437 }, position: { x: 605, y: 1028 } },
+ { data: { id: 'Huawei-A#6', label: '#6', parent: 'Huawei-A', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.714359, longitude: 7.130437 }, position: { x: 685, y: 1028 } },
+ { data: { id: 'Huawei-Z#3', label: '#3', parent: 'Huawei-Z', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.712472, longitude: 7.143887 }, position: { x: 1046, y: 1094 } },
+ { data: { id: 'Huawei-Z#4', label: '#4', parent: 'Huawei-Z', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.712472, longitude: 7.143887 }, position: { x: 989, y: 1094 } },
+ { data: { id: 'Huawei-Z#5', label: '#5', parent: 'Huawei-Z', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.712472, longitude: 7.143887 }, position: { x: 1058, y: 1123 } },
+ { data: { id: 'Huawei-Z#6', label: '#6', parent: 'Huawei-Z', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.712472, longitude: 7.143887 }, position: { x: 978, y: 1123 } },
+ { data: { id: 'NEC-A#3', label: '#3', parent: 'NEC-A', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.734916, longitude: 7.137636 }, position: { x: 955, y: -52 } },
+ { data: { id: 'NEC-A#6', label: '#6', parent: 'NEC-A', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.734916, longitude: 7.137636 }, position: { x: 1035, y: -52 } },
+ { data: { id: 'NEC-Z#2', label: '#2', parent: 'NEC-Z', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1449, y: -41 } },
+ { data: { id: 'NEC-Z#3', label: '#3', parent: 'NEC-Z', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1392, y: 16 } },
+ { data: { id: 'NEC-Z#4', label: '#4', parent: 'NEC-Z', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1449, y: 16 } },
+ { data: { id: 'NEC-Z#6', label: '#6', parent: 'NEC-Z', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.733028, longitude: 7.151086 }, position: { x: 1382, y: -23 } },
+ { data: { id: 'Nokia-A13', label: '13', parent: 'Nokia-A', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.721914, longitude: 7.120521 }, position: { x: 40, y: 801 } },
+ { data: { id: 'Nokia-A34', label: '34', parent: 'Nokia-A', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.721914, longitude: 7.120521 }, position: { x: 28, y: 772 } },
+ { data: { id: 'Nokia-A#6', label: '#6', parent: 'Nokia-A', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.721914, longitude: 7.120521 }, position: { x: 28, y: 829 } },
+ { data: { id: 'Nokia-Z33', label: '33', parent: 'Nokia-Z', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.714359, longitude: 7.130437 }, position: { x: 385, y: 1028 } },
+ { data: { id: 'Nokia-Z#6', label: '#6', parent: 'Nokia-Z', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.714359, longitude: 7.130437 }, position: { x: 305, y: 1028 } },
+ { data: { id: 'SIAE-A#5', label: '#5', parent: 'SIAE-A', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.712472, longitude: 7.143887 }, position: { x: 1278, y: 1123 } },
+ { data: { id: 'SIAE-A#6', label: '#6', parent: 'SIAE-A', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.712472, longitude: 7.143887 }, position: { x: 1346, y: 1094 } },
+ { data: { id: 'SIAE-Z#6', label: '#6', parent: 'SIAE-Z', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.717158, longitude: 7.155506 }, position: { x: 1588, y: 838 } },
+ { data: { id: 'ZTE-A#3', label: '#3', parent: 'ZTE-A', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.730230, longitude: 7.126017 }, position: { x: 380, y: 168 } },
+ { data: { id: 'ZTE-A#4', label: '#4', parent: 'ZTE-A', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.730230, longitude: 7.126017 }, position: { x: 345, y: 148 } },
+ { data: { id: 'ZTE-A#6', label: '#6', parent: 'ZTE-A', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.730230, longitude: 7.126017 }, position: { x: 408, y: 99 } },
+ { data: { id: 'ZTE-Z#5', label: '#5', parent: 'ZTE-Z', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.734916, longitude: 7.137636 }, position: { x: 747, y: -27 } },
+ { data: { id: 'ZTE-Z#6', label: '#6', parent: 'ZTE-Z', type: 'port', layer: 'PTP', active: 'true', path: 'false', latitude: 50.734916, longitude: 7.137636 }, position: { x: 667, y: -27 } },
+
+ ],
+ edges: [
+ { data: { id: 'ERI1', source: 'Ericsson-A#6', target: 'Ericsson-Z#6', label: 'ERI1', layer: 'PTP', active: 'true' } },
+ { data: { id: '71', source: 'Huawei-A#6', target: 'Huawei-Z#6', label: '71', layer: 'PTP', active: 'true' } },
+ { data: { id: '81', source: 'NEC-A#6', target: 'NEC-Z#6', label: '81', layer: 'PTP', active: 'true' } },
+ { data: { id: '91', source: 'Nokia-A#6', target: 'Nokia-Z#6', label: '91', layer: 'PTP', active: 'true' } },
+ { data: { id: '101', source: 'SIAE-A#6', target: 'SIAE-Z#6', label: '101', layer: 'PTP', active: 'true' } },
+ { data: { id: '111', source: 'ZTE-A#6', target: 'ZTE-Z#6', label: '111', layer: 'PTP', active: 'true' } },
+
+ { data: { id: 'ETY01', source: 'ADVA-A#1', target: 'Nokia-A34', label: 'ADVA-A#1-Nokia-A34', layer: 'PTP', active: 'true' } },
+ { data: { id: 'ETY02', source: 'ADVA-A#2', target: 'ZTE-A#4', label: 'ADVA-A#2-ZTE-A#4', layer: 'PTP', active: 'false' } },
+ { data: { id: 'ETY03', source: 'ADVA-B#1', target: 'Nokia-A13', label: 'ADVA-B#1-Nokia-A13', layer: 'PTP', active: 'true' } },
+ { data: { id: 'ETY04', source: 'ADVA-B#2', target: 'ZTE-A#3', label: 'ADVA-B#2-ZTE-A#3', layer: 'PTP', active: 'true' } },
+ { data: { id: 'ETY05', source: 'ADVA-Y#1', target: 'Huawei-Z#4', label: 'ADVA-Y#1-Huawei-Z#4', layer: 'PTP', active: 'true' } },
+ { data: { id: 'ETY06', source: 'ADVA-Y#2', target: 'NEC-Z#4', label: 'ADVA-Y#2-NEC-Z#4', layer: 'PTP', active: 'true' } },
+ { data: { id: 'ETY09', source: 'ADVA-Z#1', target: 'Huawei-Z#3', label: 'ADVA-Z#1-Huawei-Z#3', layer: 'PTP', active: 'true' } },
+ { data: { id: 'ETY10', source: 'ADVA-Z#2', target: 'NEC-Z#3', label: 'ADVA-Z#2-NEC-Z#3', layer: 'PTP', active: 'true' } },
+ { data: { id: 'ETY21', source: 'Ericsson-A#5', target: 'NEC-Z#2', label: 'Ericsson-A#5-NEC-Z#2', layer: 'PTP', active: 'true' } },
+ { data: { id: 'ETY24', source: 'Huawei-A#5', target: 'Nokia-Z33', label: 'Huawei-A#5-Nokia-Z33', layer: 'PTP', active: 'true' } },
+ { data: { id: 'ETY25', source: 'Huawei-Z#5', target: 'SIAE-A#5', label: 'Huawei-Z#5-SIAE-A#5', layer: 'PTP', active: 'true' } },
+ { data: { id: 'ETY26', source: 'NEC-A#3', target: 'ZTE-Z#5', label: 'NEC-A#3-ZTE-Z#5', layer: 'PTP', active: 'true' } },
+
+ ]
+ };
+
+ var events = eventsFabric();
+
+ var result = {
+ colors: colors,
+ elements: elements,
+ styles: styles,
+ events: events
+ };
+
+ var someMethodChangingTheElements = function () {
+ // @Martin: hier kannst Du die Elements ändern, anschließend mußt Du das Ereignis veröffentlichen
+ // das Ereigniss wird in der Directive aufgefangen und die Grig wird neu gezeichnet
+
+ // Hinweis: Die Reihenfolge muss so bleiben und du kannst NUR result.elements ändern.
+
+ events.publish("elementsChanged", {
+ elements: result.elements
+ });
+ };
+
+ return result;
+ });
+
+ mwtnTopologyApp.directive("mwtnTopologyIeee1588PathGraph", ["mwtnTopologyIeee1588PathData", "$mwtnPtp", function (mwtnTopologyIeee1588PathData, $mwtnPtp) {
+
+ return {
+ restrict: 'E',
+ replace: true,
+ template: '<div style="height: 750px; width: 100%;"></div>',
+ controller: function () {
+
+ },
+ scope: {
+
+ },
+ link: function (scope, element, attrs, ctrl) {
+
+ var cy = cytoscape({
+ container: element[0],
+
+ boxSelectionEnabled: false,
+ autounselectify: true,
+
+ style: mwtnTopologyIeee1588PathData.styles,
+ elements: mwtnTopologyIeee1588PathData.elements,
+ layout: {
+ name: 'preset',
+ padding: 5
+ }
+ });
+
+ // @ Martin: Hier wird das Ereignis aus dem Service aboniert.
+ // Es ist möglich mehrere Ereignisse zu definieren.
+ mwtnTopologyIeee1588PathData.events.subscribe("elementsChanged", function (data) {
+
+ // @Martin: cy aktualisiert sich mit Hilfe der Referenz auf die Elemente aus dem Service
+ cy.json({
+ elements: mwtnTopologyIeee1588PathData.elements // oder data.elements
+ });
+ });
+
+ // pathGraphData.events.subscribe("styleChanged", function () {
+ // cy.json({
+ // style: mwtnTopologyIeee1588PathData.styles
+ // });
+ // });
+
+ cy.viewport({
+ zoom: 0.5,
+ pan: { x: 150, y: 100 }
+ });
+
+ var clearPtpPath = function () {
+ var selector = "[path = 'true']";
+ cy.elements(selector).map(function (element) {
+ element.data('path', 'false');
+ });
+ };
+
+ var highlightPtpMaster = function (id) {
+ var selector = "[id = '" + id + "']";
+ cy.nodes(selector).map(function (node) {
+ if (node.data('parentDs') && node.data('parentDs') != '') {
+ // console.warn('parentDs', node.data('parentDs'));
+ var parentNode = node.data('parentDs').slice(0, -2);
+ if (parentNode !== id) {
+ highlightPtpMaster(parentNode);
+
+ // highlight edge
+ selector = "[id = '" + node.data('parentDs') + "']";
+ cy.nodes(selector).connectedEdges().map(function (edge) {
+ edge.data('path', 'true');
+ edge.connectedNodes().map(function (node) {
+ node.data('path', 'true');
+ });
+ });
+ }
+ }
+ });
+ };
+
+ var setAllDevicesInactive = function () {
+ cy.nodes().map(function (node) {
+ node.data('active', 'false');
+ });
+ };
+
+ var setPortAndEdgedActive = function () {
+ cy.edges().map(function (edge) {
+ var active = 'true';
+ edge.connectedNodes().map(function (port) {
+ // console.log(' node', JSON.stringify(edge.data()));
+ var parent = cy.getElementById(port.data('parent'));
+ if (parent.data('active') === 'false') {
+ port.data('active', 'false');
+ edge.data('active', 'false');
+ } else {
+ port.data('active', 'true');
+ }
+ });
+ });
+ };
+
+ var init = function () {
+ setAllDevicesInactive();
+ $mwtnPtp.getPtpClocks().then(function (clocks) {
+ // setDevicesActive(Object.keys(clocks));
+ var hex = true;
+ // update clock ids first
+ Object.keys(clocks).map(function (key) {
+ var clock = clocks[key];
+ var graphClock = cy.getElementById(key);
+ graphClock.data('active', 'true')
+ graphClock.data('base64', clock.getIdentity());
+ graphClock.data('hex', clock.getIdentity(hex));
+ });
+ // update rest
+ Object.keys(clocks).map(function (key) {
+ var clock = clocks[key];
+ var graphClock = cy.getElementById(key);
+ var graphParentDs = nodeId(clock.getParent().slice(0, -2));
+ graphClock.data('parentDs', graphParentDs + clock.getParent().slice(-2));
+ graphClock.data('grandMaster', nodeId(clock.getGrandMaster()));
+
+ // clock.getPtpPorts().map(function(port){
+ // // console.warn(port.getId(), port.getNumber(), port.getState(), port.isSlave(), port.isMaster(), port.getLogicalTerminationPointReference());
+ // var portKey = [key, port.getNumber() < 10 ? '#' : '', port.getNumber()].join('');
+ // var graphPort = cy.getElementById(portKey);
+ // // console.warn(JSON.stringify(graphPort.data()));
+ // if (graphPort === undefined) {
+ // console.error('PtpPort not found in graph:' , portKey);
+ // } else {
+ // console.info('PtpPort found in graph:' , portKey);
+ // }
+
+ // });
+ });
+ setPortAndEdgedActive();
+ }, function (error) {
+ setPortAndEdgedActive();
+ console.error(JSON.stringify(error));
+ });
+ };
+ // init();
+
+ var getNodeId = function (base64) {
+ if (base64 === undefined || base64 === '') return '';
+
+ var selector = "[type = 'ptp-clock']";
+ var result = cy.nodes(selector).filter(function (graphClock) {
+ // console.error(base64, graphClock.data('base64'), graphClock.data('base64') === base64);
+ return graphClock.data('base64') === base64;
+ });
+ if (result.length === 0) {
+ console.warn('Clock', base64, 'not found!');
+ return '';
+ } else {
+ return result[0].id();
+ }
+ };
+
+ var getParentClock = function (nodeId) {
+ $mwtnPtp.getParent(nodeId).then(function (parentPortIdentity) {
+ var parentNode = getNodeId(parentPortIdentity['clock-identity']);
+ console.log(JSON.stringify(parentPortIdentity), parentNode);
+ if (parentNode) {
+ getParentClock(parentNode);
+ }
+ });
+ };
+
+ cy.on('tap', function (event) {
+ console.log('tap');
+ clearPtpPath();
+ if (event.target !== cy) {
+ if (event.target.data('type') === 'ptp-clock') {
+ getParentClock(event.target.id());
+ // highlightPtpMaster(event.target.id());
+ } else if (event.target.data('type') === 'port') {
+ var parent = cy.getElementById(event.target.data('parent'));
+ // highlightPtpMaster(parent.id());
+ getParentClock(event.target.id());
+ }
+ } else {
+ // init();
+ }
+ });
+
+ }
+ }
+ }]);
+
+ mwtnTopologyApp.controller("mwtnTopologyClocksGridController", ['$scope', '$timeout', '$state', '$mwtnCommons', '$mwtnTopology', 'uiGridConstants', 'mwtnTopologyIeee1588PathData', function ($scope, $timeout, $state, $mwtnCommons, $mwtnTopology, uiGridConstants, mwtnTopologyIeee1588PathData) {
+ var vm = this;
+
+ // The page number to show in the grid.
+ var paginationPage = 1;
+ // The page size.
+ var paginationPageSize = 100;
+ // The grid column object with current sorting informations.
+ var sortColumn = null;
+ // The grid column object with current sorting informations.
+ var gridFilters = [];
+ // caches all sites at the current grid page
+
+ vm.gridOptions = Object.assign({}, $mwtnCommons.gridOptions, {
+ showGridFooter: false, // disable the grid footer because the pagination component sets its own.
+ paginationPageSizes: [10, 25, 50, 100],
+ paginationPageSize: paginationPageSize,
+ useExternalPagination: true,
+ useExternalFiltering: true,
+ useExternalSorting: true,
+ totalItems: 0,
+ columnDefs: [{
+ field: "id",
+ type: "string",
+ displayName: "Node id",
+ width: 120
+ },
+ {
+ field: "hex",
+ type: "string",
+ displayName: "Clock identity in hex",
+ width: 300
+ }, {
+ field: "base64",
+ type: "string",
+ displayName: "... in base64",
+ width: 150
+ },
+ {
+ field: "parentDs",
+ type: "string",
+ displayName: "parentDs",
+ width: 150
+ },
+ {
+ field: "grandMaster",
+ type: "string",
+ displayName: "grandmaster",
+ width: 300
+ },
+ {
+ field: "active",
+ type: "string",
+ displayName: "Active",
+ width: 80
+ }
+ ],
+ data: [],
+ onRegisterApi: function (gridApi) {
+ // http://ui-grid.info/docs/#/api/ui.grid.core.api:PublicApi
+ vm.gridApi = gridApi;
+
+ vm.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
+ // Save the current sort column for later use.
+ sortColumn = (!sortColumns || sortColumns.length === 0) ?
+ null :
+ sortColumns[0];
+ loadPage();
+ });
+
+ vm.gridApi.pagination.on.paginationChanged($scope, function (newPage, newPageSize) {
+ // Save the pagination informations for later use.
+ paginationPage = newPage;
+ paginationPageSize = newPageSize;
+ loadPage();
+ });
+
+ vm.gridApi.core.on.filterChanged($scope, function () {
+ // Save the all filters for later use.
+ var filters = [];
+ this.grid.columns.forEach(function (col, ind) {
+ if (col.filters[0] && col.filters[0].term) {
+ filters.push({
+ field: col.field,
+ term: col.filters[0].term
+ });
+ }
+ });
+ gridFilters = filters;
+ loadPage();
+ });
+
+ loadPage();
+ }
+ });
+
+ /**
+ * Calculates the page content of the grid and sets the values to the gridOprions.data object.
+ */
+ function loadPage() {
+
+ // extract all ports
+ var ports = mwtnTopologyIeee1588PathData.elements.nodes.filter(function (node, ind, arr) {
+ return node && node.data && node.data.type === 'ptp-clock';
+ }).reduce(function (acc, cur, ind, arr) {
+ if (cur.data) acc[cur.data.id] = cur.data;
+ return acc;
+ }, {});
+
+ // get all port ids
+ var portIds = Object.keys(ports);
+
+ // apply the grid filters
+ var tempData = portIds.filter(function (portId, ind, arr) {
+ var port = ports[portId];
+ return gridFilters.map(function (filter) {
+ switch (filter.field) {
+ case "active":
+ return port[filter.field].toString().contains(filter.term);
+ default:
+ return port[filter.field].contains(filter.term);
+ }
+ }).and(true);
+ }).map(function (portId) {
+ var port = ports[portId];
+ var orderBy;
+
+ if (!sortColumn || !sortColumn.sort.direction) {
+ return {
+ id: port.id
+ }
+ }
+
+ switch (sortColumn.field) {
+ case "active":
+ orderBy = port[sortColumn.field] ? 1 : 0;
+ break;
+ default:
+ orderBy = port[sortColumn.field];
+ break;
+ }
+
+ return {
+ id: port.id,
+ orderBy: orderBy
+ };
+ });
+
+ if (sortColumn && sortColumn.sort.direction) {
+ tempData.sort(function (left, right) {
+ if (left === right || left.orderBy === right.orderBy) {
+ return 0;
+ }
+ if (left.orderBy > right.orderBy) {
+ return (sortColumn.sort.direction === uiGridConstants.ASC) ? 1 : -1;
+ }
+ return (sortColumn.sort.direction === uiGridConstants.ASC) ? -1 : 1;
+ });
+ }
+
+ var maxPageNumber = Math.max(1, Math.ceil(tempData.length / paginationPageSize));
+ var currentPage = Math.min(maxPageNumber, paginationPage);
+ var orderedPortsAtCurrentPage = tempData.slice((currentPage - 1) * paginationPageSize, currentPage * paginationPageSize);
+
+ portsAtCurrentPageCache = {};
+ var orderedData = [];
+
+ orderedPortsAtCurrentPage.forEach(function (orderedPort) {
+ var port = ports[orderedPort.id]; // TODO [sko] varible port should be renamed to clock
+ portsAtCurrentPageCache[port.id] = port;
+ orderedData.push({
+ id: orderedPort.id,
+ layer: port.layer,
+ active: port.active,
+ hex: port.hex,
+ base64: port.base64,
+ parentDs: port.parentDs,
+ grandMaster: port.grandMaster
+ });
+ });
+
+ $timeout(function () {
+ vm.gridOptions.data = orderedData;
+ vm.gridOptions.totalItems = tempData.length;
+ });
+ }
+
+ // subscribe to the elementsChanged event to reload the grid page if the data in the service has chenged
+ mwtnTopologyIeee1588PathData.events.subscribe("elementsChanged", function (data) {
+ loadPage();
+ });
+
+ }]);
+
+ mwtnTopologyApp.directive("mwtnTopologyClocksGrid", [function () {
+ return {
+ restrict: 'E',
+ replace: false,
+ controller: 'mwtnTopologyClocksGridController',
+ controllerAs: 'vm',
+ scope: {
+
+ },
+ templateUrl: 'src/app/mwtnTopology/templates/clocksGrid.tpl.html'
+ };
+ }]);
+
+ mwtnTopologyApp.controller("mwtnTopologyPtpLinksGridController", ['$scope', '$timeout', '$state', '$mwtnCommons', '$mwtnTopology', 'uiGridConstants', 'mwtnTopologyIeee1588PathData', function ($scope, $timeout, $state, $mwtnCommons, $mwtnTopology, uiGridConstants, mwtnTopologyIeee1588PathData) {
+ var vm = this;
+
+ // The page number to show in the grid.
+ var paginationPage = 1;
+ // The page size.
+ var paginationPageSize = 100;
+ // The grid column object with current sorting informations.
+ var sortColumn = null;
+ // The grid column object with current sorting informations.
+ var gridFilters = [];
+ // caches all sites at the current grid page
+
+ vm.gridOptions = Object.assign({}, $mwtnCommons.gridOptions, {
+ showGridFooter: false, // disable the grid footer because the pagination component sets its own.
+ paginationPageSizes: [10, 25, 50, 100],
+ paginationPageSize: paginationPageSize,
+ useExternalPagination: true,
+ useExternalFiltering: true,
+ useExternalSorting: true,
+ totalItems: 0,
+ columnDefs: [{
+ field: "id",
+ type: "string",
+ displayName: "Id"
+ },
+ {
+ field: "source",
+ type: "string",
+ displayName: "PortA"
+ },
+ {
+ field: "target",
+ type: "string",
+ displayName: "PortZ"
+ },
+ {
+ field: "active",
+ type: "string",
+ displayName: "active"
+ }
+ ],
+ data: [],
+ onRegisterApi: function (gridApi) {
+ // http://ui-grid.info/docs/#/api/ui.grid.core.api:PublicApi
+ vm.gridApi = gridApi;
+
+ vm.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
+ // Save the current sort column for later use.
+ sortColumn = (!sortColumns || sortColumns.length === 0) ?
+ null :
+ sortColumns[0];
+ loadPage();
+ });
+
+ vm.gridApi.pagination.on.paginationChanged($scope, function (newPage, newPageSize) {
+ // Save the pagination informations for later use.
+ paginationPage = newPage;
+ paginationPageSize = newPageSize;
+ loadPage();
+ });
+
+ vm.gridApi.core.on.filterChanged($scope, function () {
+ // Save the all filters for later use.
+ var filters = [];
+ this.grid.columns.forEach(function (col, ind) {
+ if (col.filters[0] && col.filters[0].term) {
+ filters.push({
+ field: col.field,
+ term: col.filters[0].term
+ });
+ }
+ });
+ gridFilters = filters;
+ loadPage();
+ });
+
+ loadPage();
+ }
+ });
+
+ /**
+ * Calculates the page content of the grid and sets the values to the gridOprions.data object.
+ */
+ function loadPage() {
+
+ // extract all links
+ var links = mwtnTopologyIeee1588PathData.elements.edges.filter(function (node, ind, arr) {
+ return true; // node && node.data && node.data.type === 'port';
+ }).reduce(function (acc, cur, ind, arr) {
+ if (cur.data) {
+ acc[cur.data.id] = cur.data;
+ }
+ return acc;
+ }, {});
+
+ // get all link ids
+ var linkIds = Object.keys(links);
+
+ // apply the grid filters
+ var tempData = linkIds.filter(function (linkId, ind, arr) {
+ var link = links[linkId];
+ return gridFilters.map(function (filter) {
+ switch (filter.field) {
+ default:
+ return link[filter.field].contains(filter.term);
+ }
+ }).and(true);
+ }).map(function (linkId) {
+ var link = links[linkId];
+ var orderBy;
+
+ if (!sortColumn || !sortColumn.sort.direction) {
+ return {
+ id: link.id
+ }
+ }
+
+ switch (sortColumn.field) {
+ case "active":
+ orderBy = link[sortColumn.field] ? 1 : 0;
+ default:
+ orderBy = link[sortColumn.field];
+ break;
+ }
+
+ return {
+ id: link.id,
+ orderBy: orderBy
+ };
+ });
+
+ if (sortColumn && sortColumn.sort.direction) {
+ tempData.sort(function (left, right) {
+ if (left === right || left.orderBy === right.orderBy) {
+ return 0;
+ }
+ if (left.orderBy > right.orderBy) {
+ return (sortColumn.sort.direction === uiGridConstants.ASC) ? 1 : -1;
+ }
+ return (sortColumn.sort.direction === uiGridConstants.ASC) ? -1 : 1;
+ });
+ }
+
+ var maxPageNumber = Math.max(1, Math.ceil(tempData.length / paginationPageSize));
+ var currentPage = Math.min(maxPageNumber, paginationPage);
+ var orderedLinksAtCurrentPage = tempData.slice((currentPage - 1) * paginationPageSize, currentPage * paginationPageSize);
+
+ linksAtCurrentPageCache = {};
+ var orderedData = [];
+
+ orderedLinksAtCurrentPage.forEach(function (orderedLink) {
+ var link = links[orderedLink.id];
+ linksAtCurrentPageCache[link.id] = link;
+ orderedData.push({
+ id: orderedLink.id,
+ source: link.source,
+ target: link.target,
+ active: link.active
+ });
+ });
+
+ $timeout(function () {
+ vm.gridOptions.data = orderedData;
+ vm.gridOptions.totalItems = tempData.length;
+ });
+ }
+
+ // subscribe to the elementsChanged event to reload the grid page if the data in the service has chenged
+ var subscription = mwtnTopologyIeee1588PathData.events.subscribe("elementsChanged", function (data) {
+ loadPage();
+ // to unsubscribe call subscription.remove();
+ });
+
+ }]);
+
+ mwtnTopologyApp.directive("mwtnTopologyPtpLinksGrid", [function () {
+ return {
+ restrict: 'E',
+ replace: false,
+ controller: 'mwtnTopologyPtpLinksGridController',
+ controllerAs: 'vm',
+ scope: {
+
+ },
+ templateUrl: 'src/app/mwtnTopology/templates/ptpLinksGrid.tpl.html'
+ };
+ }]);
+
+ mwtnTopologyApp.filter('coordinateFilter', coordinateFilter);
+
+ function coordinateFilter($sce) {
+
+ return function (coordinate, conversion, type, places) {
+
+ // The filter will be running as we type values into the input boxes, which returns undefined
+ // and brings up an error in the console. Here wait until the coordinate is defined
+ if (coordinate != undefined) {
+
+ // Check for user input that is a positive or negative number with the option
+ // that it is a float. Match only the numbers and not the white space or other characters
+ var pattern = /[-+]?[0-9]*\.?[0-9]+/g
+ var match = String(coordinate).match(pattern);
+
+ if (conversion === "toDD" && match && coordinateIsValid(match, type)) {
+ // If the match array only has one item, the user has provided decimal degrees
+ // and we can just return what the user typed in
+ if (match.length === 1) {
+ return parseFloat(match);
+ }
+
+ // If the match array has a length of three then we know degrees, minutes, and seconds
+ // were provided so we can convert it to decimal degrees
+ if (match.length === 3) {
+ return toDecimalDegrees(match);
+ }
+ }
+
+ else if (conversion === 'toDMS' && match && coordinateIsValid(match, type)) {
+ // When converting from decimal degrees to degrees, minutes and seconds, if
+ // the match array has one item we know the user has input decimal degrees
+ // so we can convert it to degrees, minutes and seconds
+ if (match.length === 1) {
+ return toDegreesMinutesSeconds(match, type);
+ }
+
+ // To properly format the converted coordinates we will need to add in HTML entities
+ // which means we'll need to bind the returned string as HTML and thus we need
+ // to use $sce (Strict Contextual Escaping) to say that we trust what is being bound as HTML
+ if (match.length === 3) {
+ return $sce.trustAsHtml(match[0] + '&deg; ' + match[1] + '&prime; ' + match[2] + '&Prime; ');
+ }
+ }
+
+ // Output a notice that the coordinates are invalid if they are
+ else if (!coordinateIsValid(match, type)) {
+ return "Invalid Coordinate!";
+ }
+
+ function toDecimalDegrees(coord) {
+ // Setup for all parts of the DMS coordinate and the necessary math to convert
+ // from DMS to DD
+ var degrees = parseInt(coord[0]);
+ var minutes = parseInt(coord[1]) / 60;
+ var seconds = parseInt(coord[2]) / 3600;
+
+ // When the degrees value is negative, the math is a bit different
+ // than when the value is positive. This checks whether the value is below zero
+ // and does subtraction instead of addition if it is.
+ if (degrees < 0) {
+ var calculated = degrees - minutes - seconds;
+ return calculated.toFixed(places || 4);
+ }
+ else {
+ var calculated = degrees + minutes + seconds
+ return calculated.toFixed(places || 4);
+ }
+ }
+
+ // This function converts from DD to DMS. Math.abs is used a lot because
+ // for the minutes and seconds, negative values aren't valid
+ function toDegreesMinutesSeconds(decimal_degrees, type) {
+
+ var dd = decimal_degrees[0];
+ var direction = 'E';
+
+ if (type === 'lat') {
+ if (dd < 0) {
+ direction = 'S';
+ } else {
+ direction = 'N'
+ }
+ } else {
+ if (dd < 0) {
+ direction = 'W';
+ } else {
+ direction = 'E'
+ }
+ }
+
+ dd = Math.abs(dd);
+ var degrees = Math.floor(dd);
+ var frac = dd - degrees; // get fractional part
+ var min = Math.floor(frac * 60);
+ var sec = frac * 3600 - min * 60;
+
+ var formated = [degrees, '° ', ("0" + min).slice(-2), '\' ', ("0" + sec.toFixed(4)).slice(-7), '\" ', direction];
+ return formated.join('');
+
+ // var degrees = coordinate[0].split('.')[0];
+ // var minutes = Math.abs(Math.floor(60 * (Math.abs(coordinate[0]) - Math.abs(degrees))));
+ // var seconds = 3600 * (Math.abs(coordinate[0]) - Math.abs(degrees) - Math.abs(minutes) / 60).toFixed(2);
+
+ // return $sce.trustAsHtml(degrees + '° ' + minutes + '\' ' + seconds + '\" ');
+
+ }
+
+ // This function checks whether the coordinate value the user enters is valid or not.
+ // If the coordinate doesn't pass one of these rules, the function will return false
+ // which will then alert the user that the coordinate is invalid.
+ function coordinateIsValid(coordinate, type) {
+ if (coordinate) {
+
+ // The degree values of latitude coordinates have a range between -90 and 90
+ if (coordinate[0] && type === 'lat') {
+ if (!(-90 <= +(coordinate[0]) <= 90)) return false;
+ }
+ // The degree values longitude coordinates have a range between -180 and 180
+ else if (coordinate[0] && type === 'lon') {
+ if (!(-180 <= +(coordinate[0]) <= 180)) return false;
+ }
+ // Minutes and seconds can only be between 0 and 60
+ if (coordinate[1]) {
+ if (!(0 <= +(coordinate[1]) <= 60)) return false;
+ }
+ if (coordinate[2]) {
+ if (!(0 <= +(coordinate[2]) <= 60)) return false;
+ }
+ }
+
+ // If the coordinate made it through all the rules above, the function
+ // returns true because the coordinate is good
+ return true;
+ }
+ }
+ }
+ }
+}); \ No newline at end of file
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology.module.js b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology.module.js
new file mode 100755
index 00000000..db795d22
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology.module.js
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2016 highstreet technologies GmbH and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+define(['angularAMD',
+ 'app/routingConfig',
+ 'app/core/core.services',
+ 'common/config/env.module',
+ 'app/mwtnCommons/mwtnCommons.module'], function(ng) {
+
+ var mwtnTopologyApp = angular.module('app.mwtnTopology', ['app.core', 'ui.router.state', 'ui.grid', 'ui.grid.pagination', 'ui.grid.selection', 'ui.bootstrap', 'config', 'app.mwtnCommons']);
+
+ mwtnTopologyApp.config(function ($stateProvider, $compileProvider, $controllerProvider, $provide, NavHelperProvider, $translateProvider) {
+ // // AF/MF: Warum? (Remove as soon as possible)
+ // mwtnTopologyApp.register = {
+ // controller: $controllerProvider.register,
+ // directive: $compileProvider.directive,
+ // factory: $provide.factory,
+ // service: $provide.service
+ // };
+
+ NavHelperProvider.addControllerUrl('app/mwtnTopology/mwtnTopology.controller');
+ NavHelperProvider.addToMenu('mwtnTopology', {
+ "link": "#/pnfTopology/site",
+ "active": "main.mwtnTopology",
+ "title": "pnf Topology",
+ "icon": "fa fa-connectdevelop", // Add navigation icon css class here
+ "page": {
+ "title": "pnf Topology",
+ "description": "mwtnTopology"
+ }
+ });
+
+ var access = routingConfig.accessLevels;
+
+ $stateProvider.state('main.mwtnTopology', {
+ url: 'pnfTopology/:tab?&top&bottom&right&left&lat&lng&zoom&site&siteLink&sitePath',
+ reloadOnSearch: false,
+ access: access.admin,
+ params: {
+ internal: false
+ },
+ views: {
+ content: {
+ template: '<mwtn-topology-frame />'
+ }
+ }
+ });
+
+ });
+
+ return mwtnTopologyApp;
+}); \ No newline at end of file
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology.rest b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology.rest
new file mode 100755
index 00000000..323b13ad
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology.rest
@@ -0,0 +1,209 @@
+###
+# get outer bounding box for all sites.
+POST http://03-wtp-poc:9200/mwtn/site/_search HTTP/1.1
+content-type: application/json
+
+{
+ "aggregations": {
+ "top": {
+ "max": {
+ "field": "location.lat"
+ }
+ },
+ "right": {
+ "max": {
+ "field": "location.lon"
+ }
+ },
+ "bottom": {
+ "min": {
+ "field": "location.lat"
+ }
+ },
+ "left": {
+ "min": {
+ "field": "location.lon"
+ }
+ }
+ },
+ "size": 0
+}
+
+###
+# get sites in bounding box
+POST http://03-wtp-poc:9200/mwtn/site/_search HTTP/1.1
+content-type: application/json
+
+{
+ "query": {
+ "geo_bounding_box": {
+ "location": {
+ "top": -10.681254175812176,
+ "right": -48.3738302863769,
+ "bottom": -10.736012963621369,
+ "left": -48.433356043212834
+ }
+ }
+ },
+ "size": 100
+}
+
+###
+# get single site by id
+POST http://03-wtp-poc:9200/mwtn/site/_search HTTP/1.1
+content-type: application/json
+
+{
+ "query": {
+ "bool": {
+ "must": [
+ {
+ "term": {
+ "id": "vivsp"
+ }
+ }
+ ]
+ }
+ }
+}
+
+###
+# get sites with site links
+POST http://03-wtp-poc:9200/mwtn/site/_search HTTP/1.1
+content-type: application/json
+
+{
+ "query": {
+ "bool": {
+ "must": {
+ "exists": {
+ "field": "references.site-links"
+ }
+ }
+ }
+ },
+ "size": 100
+}
+
+###
+# get the first 3 site links
+POST http://03-wtp-poc:9200/mwtn/site-link/_search HTTP/1.1
+content-type: application/json
+
+{
+ "query": {
+ "match_all": {}
+ },
+ "size": 3
+}
+
+###
+# get site links where siteA or siteZ refers to a list ob site ids.
+POST http://03-wtp-poc:9200/mwtn/site-link/_search HTTP/1.1
+content-type: application/json
+
+{
+ "query": {
+ "bool": {
+ "filter": {
+ "term": {
+ "siteA": "atdrj"
+ }
+ }
+ }
+ }
+}
+
+###
+# get site links where siteLinkId is one of the ids in given List.
+POST http://03-wtp-poc:9200/mwtn/site-link/_search
+content-type: application/json
+
+{
+ "query": {
+ "bool": {
+ "should": [
+ {"term": {"id" : "aaapb:aacal"}},
+ {"term": {"id" : "site-6:site-7"}},
+ {"term": {"id" : "site-7:site-8"}}
+ ]
+ }
+ }
+}
+
+###
+# get the first 3 site links
+POST http://03-wtp-poc:9200/mwtn/site/_search HTTP/1.1
+content-type: application/json
+
+{
+ "query": {
+ "match_all": {}
+ },
+ "sort" : {
+ "name": {
+ "order": "asc"
+ }
+ },
+ "size": 30
+}
+
+###
+# get the first 3 site links
+POST http://03-wtp-poc:9200/mwtn/site/_search HTTP/1.1
+content-type: application/json
+
+{
+ "query": {
+ "match_all": {}
+ },
+ "sort" : {
+ "_script" : {
+ "script" : {
+ "lang": "expression",
+ "inline" : "doc['references'].value['site-links'].length"
+ } ,
+ "type" : "number",
+ "order" : "desc"
+ }
+ },
+ "size": 30
+}
+
+
+###
+# get the first 3 site links
+POST http://03-wtp-poc:9200/mwtn/site/_search HTTP/1.1
+content-type: application/json
+
+{
+ "from":0,
+ "size":10,
+ "sort": {
+ "id":{
+ "order":"asc"
+ }
+ },
+ "query":{
+ "regexp":{
+ "name":".*00.*"
+ }
+ }
+}
+
+
+###
+# get the first 3 site links
+GET http://03-wtp-poc:9200/mwtn/site/_search HTTP/1.1
+content-type: application/json
+
+{
+ "query": {
+ "function_score": {
+ "script_score": {
+ "script": "Math.pow(2, 2)",
+ "lang": "groovy"
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology.services.js b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology.services.js
new file mode 100755
index 00000000..195c08f0
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/mwtnTopology.services.js
@@ -0,0 +1,1020 @@
+/*
+ * Copyright (c) 2016 highstreet technologies GmbH and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+/** Type Definitions
+ * @typedef {{id: string, siteLink: string, radio: string, polarization: string }} AirInterfaceLink
+ * @typedef {{id: string, siteA: string, siteZ: string, siteNameA: string, siteNameZ: string, airInterfaceLinks: AirInterfaceLink[] }} DbLink
+ * @typedef {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: DbLink, _type: string}[], max_score: number, total: number}}, status: number}} DbLinkResult */
+
+define(['app/mwtnTopology/mwtnTopology.module'], function (mwtnTopologyApp) {
+// module.exports = function () {
+// const mwtnTopologyApp = require('app/mwtnTopology/mwtnTopology.module');
+// const mwtnTopologyCommons = require('app/mwtnCommons/mwtnCommons.service');
+
+ mwtnTopologyApp.factory('$mwtnTopology', function ($q, $mwtnCommons, $mwtnDatabase, $mwtnLog) {
+ var service = {};
+
+ // AF/MF: Obsolete - will removed soon. All data access function
+ service.getRequiredNetworkElements = $mwtnCommons.getRequiredNetworkElements;
+ service.gridOptions = $mwtnCommons.gridOptions;
+ service.highlightFilteredHeader = $mwtnCommons.highlightFilteredHeader;
+ service.getAllData = $mwtnDatabase.getAllData;
+
+
+ /**
+ * Since not all browsers implement this we have our own utility that will
+ * convert from degrees into radians
+ *
+ * @param deg - The degrees to be converted into radians
+ * @return radians
+ */
+ var _toRad = function (deg) {
+ return deg * Math.PI / 180;
+ };
+
+ /**
+ * Since not all browsers implement this we have our own utility that will
+ * convert from radians into degrees
+ *
+ * @param rad - The radians to be converted into degrees
+ * @return degrees
+ */
+ var _toDeg = function (rad) {
+ return rad * 180 / Math.PI;
+ };
+
+ // public functions
+ /**
+ * Calculate the bearing between two positions as a value from 0-360
+ *
+ * @param lat1 - The latitude of the first position
+ * @param lng1 - The longitude of the first position
+ * @param lat2 - The latitude of the second position
+ * @param lng2 - The longitude of the second position
+ *
+ * @return int - The bearing between 0 and 360
+ */
+ service.bearing = function (lat1, lng1, lat2, lng2) {
+ var dLon = (lng2 - lng1);
+ var y = Math.sin(dLon) * Math.cos(lat2);
+ var x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon);
+ var brng = _toDeg(Math.atan2(y, x));
+ return 360 - ((brng + 360) % 360);
+ },
+
+
+ /**
+ * Gets the geospatial distance between two points
+ * @param lat1 {number} The latitude of the first point.
+ * @param lon1 {number} The longitude of the first point.
+ * @param lat2 {number} The latitude of the second point.
+ * @param lon2 {number} The longitude of the second point.
+ * @returns {number} The distance between the two given points.
+ */
+ service.getDistance = function (lat1, lon1, lat2, lon2) {
+ var R = 6371; // km
+ var φ1 = _toRad(lat1);
+ var φ2 = _toRad(lat2);
+ var Δφ = _toRad(lat2 - lat1);
+ var Δλ = _toRad(lon2 - lon1);
+
+ var a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
+ Math.cos(φ1) * Math.cos(φ2) *
+ Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
+ var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+
+ return (R * c).toFixed(3);
+ };
+
+ /**
+ * Gets a promise which is resolved if the database has been calculated the bounds containing all sites.
+ * @returns {promise} The promise which is resolved if the database has completed its calculation.
+ */
+ service.getOuterBoundingRectangleForSites = function () {
+ var getOuterBoundingRectangleForSitesDefer = $q.defer();
+ var aggregation = {
+ "aggregations": {
+ "top": {
+ "max": {
+ "field": "location.lat"
+ }
+ },
+ "right": {
+ "max": {
+ "field": "location.lon"
+ }
+ },
+ "bottom": {
+ "min": {
+ "field": "location.lat"
+ }
+ },
+ "left": {
+ "min": {
+ "field": "location.lon"
+ }
+ }
+ },
+ "size": 0
+ };
+
+ $mwtnDatabase.getAggregatedData('mwtn', 'site', aggregation).then(function (result) {
+ getOuterBoundingRectangleForSitesDefer.resolve({
+ top: result.data.aggregations.top.value,
+ right: result.data.aggregations.right.value,
+ bottom: result.data.aggregations.bottom.value,
+ left: result.data.aggregations.left.value
+ });
+ }, function (error) {
+ getOuterBoundingRectangleForSitesDefer.reject(error);
+ });
+
+ return getOuterBoundingRectangleForSitesDefer.promise;
+ };
+
+ /**
+ * Gets a promise which resolved with an array of sites within the given bounding box.
+ * @param boundingBox {{top: number, right: number, bottom: number, left: number}} The bounding box to get all sites for.
+ * @param chunkSize {number} The maximum count of sites who should return.
+ * @param chunkSiteStartIndex {number} The index of the first site element to get.
+ */
+ service.getSitesInBoundingBox = function (boundingBox, chunkSize, chunkSiteStartIndex) {
+ var resultDefer = $q.defer();
+
+ var filter = {
+ "geo_bounding_box": {
+ "location": {
+ "top": boundingBox.top,
+ "right": boundingBox.right,
+ "bottom": boundingBox.bottom,
+ "left": boundingBox.left
+ }
+ }
+ };
+
+ $mwtnDatabase.getFilteredData("mwtn", "site", chunkSiteStartIndex, chunkSize, filter)
+ .then(processResult, resultDefer.reject);
+
+ return resultDefer.promise;
+
+ /**
+ * Callback for the database request.
+ * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, name: string, location: {lat: number, lon: number}, "amsl-ground": number, references: {"network-elements": string[], "site-links": string[]}}, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
+ */
+ function processResult(result) {
+ var hits = result && result.data && result.data.hits;
+ if (!hits) {
+ resultDefer.reject("Invalid result.");
+ return;
+ }
+
+ var total = hits.total;
+ var sites = [];
+
+ for (var hitIndex = 0; hitIndex < hits.hits.length; ++hitIndex) {
+ var site = hits.hits[hitIndex];
+ sites.push({
+ id: site._source.id,
+ name: site._source.name,
+ type: site._source.type,
+ location: {
+ lat: site._source.location.lat,
+ lng: site._source.location.lon
+ },
+ amslGround: site._source["amsl-ground"],
+ references: {
+ siteLinks: site._source.references["site-links"]
+ }
+ });
+ }
+
+ resultDefer.resolve({
+ chunkSize: chunkSize,
+ chunkSiteStartIndex: chunkSiteStartIndex,
+ total: total,
+ sites: sites
+ })
+ }
+ };
+
+ /**
+ * Gets a promise which is resolved with an array of sites filtered by given site ids.
+ * This function does not use chunks!
+ * @param siteIds {string[]} The ids of the sites to return.
+ */
+ service.getSitesByIds = function (siteIds) {
+ var resultDefer = $q.defer();
+
+ if (!siteIds || siteIds.length === 0) {
+ resultDefer.resolve([]);
+ return resultDefer.promise;
+ }
+
+ var query = {
+ bool: {
+ should: siteIds.map(function (siteId) {
+ return { term: { id: siteId } };
+ })
+ }
+ };
+
+ $mwtnDatabase.getFilteredData("mwtn", "site", 0, siteIds.length, query)
+ .then(processResult, resultDefer.reject);
+
+ return resultDefer.promise;
+
+ /**
+ * Callback for the database request.
+ * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, name: string, location: {lat: number, lon: number}, "amsl-ground": number, references: {"network-elements": string[], "site-links": string[]}}, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
+ */
+ function processResult(result) {
+ var hits = result && result.data && result.data.hits;
+ if (!hits) {
+ resultDefer.reject("Invalid result.");
+ return;
+ }
+
+ var total = hits.total;
+ var sites = [];
+
+ for (var hitIndex = 0; hitIndex < hits.hits.length; ++hitIndex) {
+ var site = hits.hits[hitIndex];
+ sites.push({
+ id: site._source.id,
+ name: site._source.name,
+ type: site._source.type,
+ location: {
+ lat: site._source.location.lat,
+ lng: site._source.location.lon
+ },
+ amslGround: site._source["amsl-ground"],
+ references: {
+ siteLinks: site._source.references["site-links"]
+ }
+ });
+ }
+
+ resultDefer.resolve({
+ total: total,
+ sites: sites
+ })
+ }
+ };
+
+ /**
+ * Gets a promise which is resolved with an array of sites using the given filter expression.
+ */
+ service.getSites = function (sortColumn, sortDirection, filters, chunkSize, chunkSiteStartIndex) {
+ var resultDefer = $q.defer();
+
+ // determine the sort parameter
+ var sort = null;
+ if (sortColumn != null && sortDirection != null) {
+ sort = {};
+ switch (sortColumn) {
+ case 'countNetworkElements':
+ case 'countLinks':
+ sort = null;
+ break;
+ case 'amslGround':
+ sort['amsl-ground'] = {
+ order: sortDirection === 'desc' ? 'desc' : 'asc'
+ }
+ break;
+ default:
+ sort[sortColumn] = {
+ order: sortDirection === 'desc' ? 'desc' : 'asc'
+ }
+ break;
+ }
+ }
+
+ // determine the query parameter
+ var query = {};
+ if (filters == null || filters.length == 0) {
+ query["match_all"] = {};
+ } else {
+ var regexp = {};
+ filters.forEach(function (filter) {
+ if (filter && filter.field) {
+ regexp[filter.field] = '.*'+ filter.term + '.*';
+ }
+ });
+ query["regexp"] = regexp;
+ }
+
+
+ if (sort) {
+ $mwtnDatabase.getFilteredSortedData("mwtn", "site", chunkSiteStartIndex, chunkSize, sort, query).then(processResult, resultDefer.reject);
+ } else {
+ $mwtnDatabase.getFilteredData("mwtn", "site", chunkSiteStartIndex, chunkSize, query).then(processResult, resultDefer.reject);
+ }
+
+ return resultDefer.promise;
+
+ /**
+ * Callback for the database request.
+ * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, name: string, location: {lat: number, lon: number}, "amsl-ground": number, references: {"network-elements": string[], "site-links": string[]}}, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
+ */
+ function processResult(result) {
+ var hits = result && result.data && result.data.hits;
+ if (!hits) {
+ resultDefer.reject("Invalid result.");
+ return;
+ }
+
+ var total = hits.total;
+ var sites = [];
+
+ for (var hitIndex = 0; hitIndex < hits.hits.length; ++hitIndex) {
+ var site = hits.hits[hitIndex];
+ sites.push({
+ id: site._source.id,
+ name: site._source.name,
+ type: site._source.type,
+ location: {
+ lat: site._source.location.lat,
+ lng: site._source.location.lon
+ },
+ amslGround: site._source["amsl-ground"],
+ references: {
+ siteLinks: site._source.references["site-links"]
+ }
+ });
+ }
+
+ resultDefer.resolve({
+ total: total,
+ sites: sites
+ })
+ }
+ };
+
+ /**
+ * Gets a promise which resolved with an array of site links referenced by given sites.
+ * @param sites {({id: string, name: string, location: {lat: number, lng: number}, amslGround: number, references: {siteLinks: string[]})[]}
+ * @param chunkSize {number} The maximum count of site links who should return.
+ * @param chunkSiteLinkStartIndex {number} The index of the first site link element to get.
+ */
+ service.getSiteLinksForSites = function (sites, chunkSize, chunkSiteLinkStartIndex) {
+ var resultDefer = $q.defer();
+
+ if (!sites || sites.length === 0) {
+ resultDefer.resolve([]);
+ return resultDefer.promise;
+ }
+
+ var siteLinkIds = Object.keys(sites.reduce(function (accumulator, currentSite) {
+ // Add all site link ids referenced by the current site to the accumulator object.
+ currentSite.references.siteLinks.forEach(function (siteLinkId) {
+ // The value "true"" isnt important, i only use the key (siteLinkId) later.
+ // But this way i dont have to check, if the key is already known.
+ accumulator[siteLinkId] = true;
+ });
+ return accumulator;
+ }, {})).map(function (siteLinkId) {
+ return { term: { id: siteLinkId } };
+ });
+
+ var query = {
+ bool: { should: siteLinkIds }
+ };
+
+ $mwtnDatabase.getFilteredData("mwtn", "site-link", chunkSiteLinkStartIndex, chunkSize, query).then(
+ /**
+ * Callback for the database request.
+ * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, siteA: string, siteZ: string, siteNameA: string, siteNameZ: string, airInterfaceLinks: {id: string, siteLink: string, radio: string, polarization: string }[] }, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
+ */
+ function (result) {
+ var hits = result && result.data && result.data.hits;
+ if (!hits) {
+ resultDefer.reject("Invalid result.");
+ return;
+ }
+
+ if (hits.total === 0) {
+ resultDefer.resolve([]);
+ return;
+ }
+
+ // get additional sites that wont be given in the sites array but are referenced by the site links.
+ // get all sites, referenced by the site links.
+ var allReferencedSiteIds = hits.hits.reduce(function (accumulator, currentSiteLink) {
+ accumulator[currentSiteLink._source.siteA] = true;
+ accumulator[currentSiteLink._source.siteZ] = true;
+ return accumulator;
+ }, {});
+ // remove all known sites
+ sites.forEach(function (site) {
+ if (allReferencedSiteIds.hasOwnProperty(site.id)) {
+ delete allReferencedSiteIds[site.id];
+ }
+ });
+
+ var additionalReferencedSiteIds = Object.keys(allReferencedSiteIds).map(function (referencedSiteId) {
+ return { term: { id: referencedSiteId } };
+ });
+
+ if (additionalReferencedSiteIds.length > 0) {
+ query = {
+ bool: { should: additionalReferencedSiteIds }
+ };
+
+ $mwtnDatabase.getFilteredData("mwtn", "site", 0, 400, query).then(
+ /**
+ * Callback for the database request.
+ * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, name: string, location: {lat: number, lon: number}, "amsl-ground": number, references: {"network-elements": string[], "site-links": string[]}}, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
+ */
+ function (result) {
+ var siteHits = result && result.data && result.data.hits;
+ if (!siteHits) {
+ resultDefer.reject("Invalid result.");
+ return;
+ }
+
+ var additionalSites = siteHits.hits.map(function (site) {
+ return {
+ id: site._source.id,
+ name: site._source.name,
+ type: site._source.type,
+ location: {
+ lat: site._source.location.lat,
+ lng: site._source.location.lon
+ },
+ amslGround: site._source["amsl-ground"],
+ type: site._source.type,
+ references: {
+ siteLinks: site._source.references["site-links"]
+ }
+ };
+ });
+
+ var siteLinks = hits.hits.map(function (siteLink) {
+ return {
+ id: siteLink._source.id,
+ siteA: sites.find(function (site) { return site.id === siteLink._source.siteA; }) || additionalSites.find(function (site) { return site.id === siteLink._source.siteA; }),
+ siteZ: sites.find(function (site) { return site.id === siteLink._source.siteZ; }) || additionalSites.find(function (site) { return site.id === siteLink._source.siteZ; }),
+ type: siteLink._source.type,
+ length: 5000 // AF/MF: the length will be served from the database in the next version.
+ };
+ });
+
+ resultDefer.resolve(siteLinks);
+ },
+ resultDefer.reject);
+
+ return;
+ }
+
+ var siteLinks = hits.hits.map(function (siteLink) {
+ return {
+ id: siteLink._source.id,
+ siteA: sites.find(function (site) { return site.id === siteLink._source.siteA; }),
+ siteZ: sites.find(function (site) { return site.id === siteLink._source.siteZ; }),
+ type: siteLink._source.type,
+ length: 5000 // AF/MF: the length will be served from the database in the next version.
+ };
+ });
+
+ resultDefer.resolve(siteLinks);
+ },
+ resultDefer.reject);
+
+ return resultDefer.promise;
+ };
+
+ /**
+ * Gets a promise which is resolved with an array of planned filtered by given network element ids.
+ * This function does not use chunks!
+ * @param neIds {string[]} The ids of the site links to return.
+ */
+ service.getPlannedNetworkElementsByIds = function (neIds) {
+ var resultDefer = $q.defer();
+
+ if (!neIds || neIds.length === 0) {
+ resultDefer.resolve([]);
+ return resultDefer.promise;
+ }
+
+ var query = {
+ bool: {
+ should: neIds.map(function (neId) {
+ return { term: { id: neId } };
+ })
+ }
+ };
+
+ $mwtnDatabase.getFilteredData("mwtn", "planned-network-elements", 0, neIds.length, query).then(
+ /**
+ * Callback for the database request.
+ * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, name: string, type: string}, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
+ */
+ function (result) {
+ var hits = result && result.data && result.data.hits;
+ if (!hits) {
+ resultDefer.reject("Invalid result.");
+ return;
+ }
+
+ if (hits.total === 0) {
+ resultDefer.resolve([]);
+ return;
+ }
+
+ var plannedNetworkElements = hits.hits.map(function (plannedNetworkElement) {
+ return {
+ id: plannedNetworkElement._source.id,
+ name: plannedNetworkElement._source.name,
+ type: plannedNetworkElement._source.radioType
+ };
+ });
+
+ resultDefer.resolve(plannedNetworkElements);
+ }, resultDefer.reject);
+
+ return resultDefer.promise;
+ };
+
+
+ /**
+ * Gets a promise which is resolved with an array of site links filtered by given site link ids.
+ * This function does not use chunks!
+ * @param siteLinkIds {string[]} The ids of the site links to return.
+ */
+ service.getSiteLinksByIds = function (siteLinkIds) {
+ var resultDefer = $q.defer();
+
+ if (!siteLinkIds || siteLinkIds.length === 0) {
+ resultDefer.resolve([]);
+ return resultDefer.promise;
+ }
+
+ var query = {
+ bool: {
+ should: siteLinkIds.map(function (siteLinkId) {
+ return { term: { id: siteLinkId } };
+ })
+ }
+ };
+
+ $mwtnDatabase.getFilteredData("mwtn", "site-link", 0, siteLinkIds.length, query).then(
+ /**
+ * Callback for the database request.
+ * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, siteA: string, siteZ: string}, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
+ */
+ function (result) {
+ var hits = result && result.data && result.data.hits;
+ if (!hits) {
+ resultDefer.reject("Invalid result.");
+ return;
+ }
+
+ if (hits.total === 0) {
+ resultDefer.resolve([]);
+ return;
+ }
+
+ var siteLinks = hits.hits.map(function (siteLink) {
+ return {
+ id: siteLink._source.id,
+ siteA: siteLink._source.siteA,
+ siteZ: siteLink._source.siteZ,
+ azimuthAz: siteLink._source.azimuthAZ,
+ azimuthZa: siteLink._source.azimuthZA,
+ length: siteLink._source.length,
+ type: siteLink._source.type
+ };
+ });
+
+ resultDefer.resolve(siteLinks);
+ }, resultDefer.reject);
+
+ return resultDefer.promise;
+ };
+
+ /**
+ * Gets a promise with all details for a given site by its id.
+ * @param siteId {string} The id of the site to request the details for.
+ */
+ service.getSiteDetailsBySiteId = function (siteId) {
+ var resultDefer = $q.defer();
+
+ var siteQuery = {
+ "bool": {
+ "must": [
+ {
+ "term": {
+ "id": siteId
+ }
+ }
+ ]
+ }
+ };
+
+ // get all site details
+ $mwtnDatabase.getFilteredData("mwtn", "site", 0, 1, siteQuery).then(
+ /**
+ * Callback for the database request.
+ * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, name: string, location: {lat: number, lon: number}, "amsl-ground": number, references: {"network-elements": string[], "site-links": string[]}}, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
+ */
+ function (result) {
+ if (result.data.hits.total != 1) {
+ // todo: handle this error
+ resultDefer.reject("Error loading details for " + siteId + ((result.data.hits.total) ? ' Too many recoeds found.' : ' No record found.'));
+ }
+ var site = result.data.hits.hits[0];
+ var siteDetails = {
+ id: site._source.id,
+ name: site._source.name,
+ type: site._source.type,
+ location: {
+ lat: site._source.location.lat,
+ lng: site._source.location.lon
+ },
+ amslGround: site._source["amsl-ground"],
+ references: {
+ siteLinks: site._source.references["site-links"],
+ networkElements: site._source.references["network-elements"]
+ }
+ };
+
+ service.getSiteLinksByIds(siteDetails.references.siteLinks).then(
+ /** Callback for the database request.
+ * @param result {{id: string, siteA: string, siteZ: string }[]}
+ */
+ function (result) {
+ siteDetails.siteLinks = result;
+ resultDefer.resolve(siteDetails);
+ });
+
+ service.getPlannedNetworkElementsByIds(siteDetails.references.networkElements).then(
+ /** Callback for the database request.
+ * @param result {{id: string, name: string, type: string }[]}
+ */
+ function (result) {
+ siteDetails.plannedNetworkElements = result;
+ resultDefer.resolve(siteDetails);
+ });
+ }
+ );
+ return resultDefer.promise;
+ };
+
+ /**
+ * Gets a promise with all details for a given link by its id.
+ * @param siteId {string} The id of the link to request the details for.
+ */
+ service.getLinkDetailsByLinkId = function (linkId) {
+ var resultDefer = $q.defer();
+
+ var linkQuery = {
+ "bool": {
+ "must": [
+ {
+ "term": {
+ "id": linkId
+ }
+ }
+ ]
+ }
+ };
+
+ // get all site details
+ $mwtnDatabase.getFilteredData("mwtn", "site-link", 0, 1, linkQuery).then(
+ /**
+ * Callback for the database request.
+ * @param result { DbLinkResult } The database result.
+ */
+ function (result) {
+ if (result.data.hits.total != 1) {
+ // todo: handle this error
+ resultDefer.reject("Error loading details for " + siteId + ((result.data.hits.total) ? ' Too many recoeds found.' : ' No record found.'));
+ }
+ var link = result.data.hits.hits[0];
+ var linkDetails = {
+ id: link._source.id,
+ siteA: link._source.siteA,
+ siteZ: link._source.siteZ,
+ siteNameA: link._source.siteNameA,
+ siteNameZ: link._source.siteNameZ,
+ length: link._source.length,
+ azimuthA: link._source.azimuthAZ,
+ azimuthB: link._source.azimuthZA,
+ airInterfaceLinks: link._source.airInterfaceLinks,
+ type: link._source.type,
+ airInterfaceLinks: link._source.airInterfaceLinks
+ };
+
+ service.getSitesByIds([link._source.siteA, link._source.siteZ]).then(
+ /** Callback for the database request.
+ * @param result {{ total: number, sites: { id: string, name: string } []}}
+ */
+ function (result) {
+ var siteA = result.sites.find(function (site) { return site.id == linkDetails.siteA });
+ var siteZ = result.sites.find(function (site) { return site.id == linkDetails.siteZ });
+ if (result.total != 2 || !siteA || !siteZ) {
+ resultDefer.reject("Could not load Sites for link "+linkDetails.id);
+ }
+ linkDetails.siteA = siteA;
+ linkDetails.siteZ = siteZ;
+
+ resultDefer.resolve(linkDetails);
+ })
+ }
+ );
+ return resultDefer.promise;
+ }
+
+ /**
+ * Gets a promise which is resolved with an array of links using the given filter expression.
+ */
+ service.getLinks = function (sortColumn, sortDirection, filters, chunkSize, chunkSiteStartIndex) {
+ var resultDefer = $q.defer();
+
+ // determine the sort parameter
+ var sort = null;
+ if (sortColumn != null && sortDirection != null) {
+ sort = {};
+ switch (sortColumn) {
+ case 'siteIdA':
+ sort['siteA'] = {
+ order: sortDirection === 'desc' ? 'desc' : 'asc'
+ }
+ break;
+ case 'siteIdZ':
+ sort['siteZ'] = {
+ order: sortDirection === 'desc' ? 'desc' : 'asc'
+ }
+ break;
+ default:
+ sort[sortColumn] = {
+ order: sortDirection === 'desc' ? 'desc' : 'asc'
+ }
+ break;
+ }
+ }
+
+ // determine the query parameter
+ var query = {};
+ if (filters == null || filters.length == 0) {
+ query["match_all"] = {};
+ } else {
+ var regexp = {};
+ filters.forEach(function (filter) {
+ if (filter && filter.field) {
+ switch (filter.field) {
+ case 'siteIdA':
+ regexp['siteIdA'] = '.*' + filter.term + '.*';
+ break;
+ case 'siteIdZ':
+ regexp['siteZ'] = '.*' + filter.term + '.*';
+ break;
+ default:
+ regexp[filter.field] = '.*' + filter.term + '.*';
+ break;
+ }
+ }
+ });
+ query["regexp"] = regexp;
+ }
+
+
+ if (sort) {
+ $mwtnDatabase.getFilteredSortedData("mwtn", "site-link", chunkSiteStartIndex, chunkSize, sort, query).then(processResult, resultDefer.reject);
+ } else {
+ $mwtnDatabase.getFilteredData("mwtn", "site-link", chunkSiteStartIndex, chunkSize, query).then(processResult, resultDefer.reject);
+ }
+
+ /**
+ * Callback for the database request.
+ * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, siteA: string, siteZ: string}, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
+ */
+ function processResult (result) {
+ var hits = result && result.data && result.data.hits;
+ if (!hits) {
+ resultDefer.reject("Invalid result.");
+ return;
+ }
+
+ var total = hits.total;
+ var links = [];
+
+ var siteLinks = hits.hits.map(function (siteLink) {
+ return {
+ id: siteLink._source.id,
+ siteA: siteLink._source.siteA,
+ siteZ: siteLink._source.siteZ,
+ type: siteLink._source.type,
+ };
+ });
+
+ resultDefer.resolve({
+ total: total,
+ links: siteLinks
+ });
+ }
+
+ return resultDefer.promise;
+
+
+ };
+
+ /**
+ * Determines if a coordinate is in a bounding box
+ * @param bounds {{ top: number, left: number, right: number, bottom: number}} The bounding box.
+ * @param coordinate {{ lat: number, lng: number }} The coordinate.
+ * @return if the bounding box contains the coordinate
+ */
+ service.isInBounds = function(bounds, coordinate) {
+ var isLongInRange = (bounds.right < bounds.left)
+ ? coordinate.lng >= bounds.left || coordinate.lng <= bounds.right
+ : coordinate.lng >= bounds.left && coordinate.lng <= bounds.right ;
+
+ return coordinate.lat >= bounds.bottom && coordinate.lat <= bounds.top && isLongInRange;
+ }
+
+ service.getAllEdges = function () {
+ var edges = [];
+ return getGenericChunk("edge", edges).then(function () {
+ return edges;
+ });
+ };
+
+ /**
+ * Retrieves all nodes.
+ * @return a Promis containing an array of nodes
+ */
+ service.getAllNodes = function () {
+ var resultDefer = $q.defer();
+ var nodes = [];
+ getGenericChunk("node", nodes).then(function () {
+
+ // recreate the tree structure from the flat list
+ var finalNodes = nodes.reduce(
+ /** @param acc { Node[] } */
+ function (acc, cur, ind, arr) {
+ // the site will be added with the first device, so it can not be missing after the first device is in
+ if (!acc.some(node => node.data.type == "site" && node.data.id == cur.data.grandparent)) {
+ acc.push({
+ data: {
+ type: "site",
+ id: cur.data.grandparent,
+ label: cur.data.grandparent,
+ active: cur.data.active
+ }
+ });
+ }
+ if (!acc.some(node => node.data.type == "device" && node.data.id == cur.data.parent)) {
+ acc.push({
+ data: {
+ _parent: cur.data.grandparent,
+ type: "device",
+ id: cur.data.parent,
+ get parent() { return cur.data.grandparent },
+ set parent(val) { debugger; },
+ label: cur.data.parent,
+ active: cur.data.active
+ }
+ });
+ }
+ acc.push({
+ data: Object.keys(cur.data).reduce(function (obj, key) {
+ if (key == 'grandparent') {
+ obj['type'] = 'port'
+ } else {
+ cur.data.hasOwnProperty(key) && (obj[key] = cur.data[key]);
+ }
+ return obj;
+ }, {}),
+ position: cur.position
+ });
+ return acc;
+ }, []);
+
+ resultDefer.resolve(finalNodes);
+ }, function (err) {
+ console.error(err);
+ resultDefer.reject(err);
+ });
+ return resultDefer.promise;
+ }
+
+ /** @param nodes {{id: string,position:{ x:number, y:number}}}[]} */
+ service.saveChangedNodes = function (nodes) {
+ return $mwtnDatabase.getBase('topology').then(function (base) {
+ var resultDefer = $q.when();
+
+ nodes.forEach(function (node) {
+ resultDefer = resultDefer.then(function () {
+ return $mwtnDatabase.genericRequest({
+ method: 'POST',
+ base: base.base,
+ index: base.index,
+ docType: 'node',
+ command: encodeURI(node.id) +'/_update',
+ data: {
+ "doc": {
+ "position": node.position
+ }
+ }
+ });
+ })
+ });
+ return resultDefer;
+ });
+ }
+
+ // Start to initialize google maps api and save the returned promise.
+ service.googleMapsApiPromise = initializeGoogleMapsApi();
+
+ return service;
+
+ // private helper functions of $mwtnTopology
+
+ function getGenericChunk(docType, target) {
+ var size = 30;
+ return $mwtnDatabase.getAllData('topology', docType, target.length || 0, size, undefined)
+ .then(function (result) {
+ if (result.status === 200 && result.data) {
+ var total = (result.data.hits && result.data.hits.total) || 0;
+ var hits = (total && result.data.hits.hits) || [];
+ hits.forEach(function (hit) {
+ target.push(hit._source || {});
+ });
+ if (total > target.length) {
+ return getGenericChunk(docType, target);
+ }
+ return $q.resolve(true);
+ } else {
+ return $q.reject("Could not load " + docType + ".");
+ }
+ }, function (err) {
+ resultDefer.reject(err);
+ });
+
+ return resultDefer.promise;
+ }
+
+
+ /**
+ * Gets a promise which is resolved if the google maps api initialization is completed.
+ * @returns {promise} The promise which is resolved if the initialization is completed.
+ */
+ function initializeGoogleMapsApi() {
+ var googleMapsApiDefer = $q.defer();
+ window.googleMapsApiLoadedEvent = new Event("googleMapsApiLoaded");
+
+ window.addEventListener("googleMapsApiLoaded", function (event) {
+
+ /**
+ * Calculates the bounds this map would display at a given zoom level.
+ *
+ * @member google.maps.Map
+ * @method boundsAt
+ * @param {Number} zoom Zoom level to use for calculation.
+ * @param {google.maps.LatLng} [center] May be set to specify a different center than the current map center.
+ * @param {google.maps.Projection} [projection] May be set to use a different projection than that returned by this.getProjection().
+ * @param {Element} [div] May be set to specify a different map viewport than this.getDiv() (only used to get dimensions).
+ * @return {google.maps.LatLngBounds} the calculated bounds.
+ *
+ * @example
+ * var bounds = map.boundsAt(5); // same as map.boundsAt(5, map.getCenter(), map.getProjection(), map.getDiv());
+ */
+ google.maps.Map.prototype.boundsAt = function (zoom, center, projection, div) {
+ var p = projection || this.getProjection();
+ if (!p) return undefined;
+ var d = $(div || this.getDiv());
+ var zf = Math.pow(2, zoom) * 2;
+ var dw = d.getStyle('width').toInt() / zf;
+ var dh = d.getStyle('height').toInt() / zf;
+ var cpx = p.fromLatLngToPoint(center || this.getCenter());
+ return new google.maps.LatLngBounds(
+ p.fromPointToLatLng(new google.maps.Point(cpx.x - dw, cpx.y + dh)),
+ p.fromPointToLatLng(new google.maps.Point(cpx.x + dw, cpx.y - dh)));
+ }
+
+ googleMapsApiDefer.resolve();
+ });
+
+ var head = document.getElementsByTagName('head')[0];
+
+ var callbackScript = document.createElement("script");
+ callbackScript.appendChild(document.createTextNode("function googleMapsApiLoadedCallback() { window.dispatchEvent(window.googleMapsApiLoadedEvent); };"));
+
+ var googleScript = document.createElement('script');
+ googleScript.src = "https://maps.googleapis.com/maps/api/js?key=AIzaSyBWyNNhRUhXxQpvR7i-Roh23PaWqi-kNdQ&callback=googleMapsApiLoadedCallback";
+
+ head.appendChild(callbackScript);
+ head.appendChild(googleScript);
+
+ return googleMapsApiDefer.promise;
+ }
+
+ });
+
+}); \ No newline at end of file
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/accordeonHeader.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/accordeonHeader.tpl.html
new file mode 100755
index 00000000..21b2a28c
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/accordeonHeader.tpl.html
@@ -0,0 +1,13 @@
+<div role="tab" id="{{::headingId}}" aria-selected="{{isOpen}}" class="panel-heading" ng-keypress="toggleOpen($event)">
+ <h4 class="panel-title">
+ <a role="button" data-toggle="collapse" href aria-expanded="{{isOpen}}" aria-controls="{{::panelId}}" tabindex="0" class="accordion-toggle"
+ ng-click="toggleOpen()" uib-accordion-transclude="heading" ng-disabled="isDisabled" uib-tabindex-toggle>
+ <i class="pull-left fa" ng-class="{'fa-chevron-down': isOpen, 'fa-chevron-right': !isOpen}"></i>
+ <span uib-accordion-header ng-class="{'text-muted': isDisabled}">{{heading}}</span>
+ </a>
+ </h4>
+</div>
+<div id="{{::panelId}}" aria-labelledby="{{::headingId}}" aria-hidden="{{!isOpen}}" role="tabpanel" class="panel-collapse collapse"
+ uib-collapse="!isOpen">
+ <div class="panel-body" ng-transclude></div>
+</div>
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/clocksGrid.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/clocksGrid.tpl.html
new file mode 100755
index 00000000..b5d3cbc7
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/clocksGrid.tpl.html
@@ -0,0 +1 @@
+<div class="mwtnTopologyGrid" ui-grid="vm.gridOptions" ui-grid-pagination ui-grid-pinning ui-grid-selection ui-grid-resize-columns ui-grid-move-columns></div> \ No newline at end of file
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/ethConnectionsGrid.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/ethConnectionsGrid.tpl.html
new file mode 100755
index 00000000..b5d3cbc7
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/ethConnectionsGrid.tpl.html
@@ -0,0 +1 @@
+<div class="mwtnTopologyGrid" ui-grid="vm.gridOptions" ui-grid-pagination ui-grid-pinning ui-grid-selection ui-grid-resize-columns ui-grid-move-columns></div> \ No newline at end of file
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/ethernetView.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/ethernetView.tpl.html
new file mode 100755
index 00000000..7f234477
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/ethernetView.tpl.html
@@ -0,0 +1,18 @@
+<uib-accordion close-others="false">
+
+ <div uib-accordion-group="" class="panel-primary" is-open="vm.status.topologyIsOpen" template-url="src/app/mwtnTopology/templates/accordeonHeader.tpl.html"
+ heading="{{'Topology' | translate }}">
+ <mwtn-topology-ethernet-path-graph ng-if="vm.status.topologyIsOpen" />
+ </div>
+
+ <div uib-accordion-group="" class="panel-primary" is-open="vm.status.portsOpen" template-url="src/app/mwtnTopology/templates/accordeonHeader.tpl.html"
+ heading="{{'Ports' | translate}}">
+ <mwtn-topology-ports-grid />
+ </div>
+
+ <div uib-accordion-group="" class="panel-primary" is-open="vm.status.ethConnectionsIsOpen" template-url="src/app/mwtnTopology/templates/accordeonHeader.tpl.html"
+ heading="{{'ETH connections' | translate}}">
+ <mwtn-topology-eth-connections-grid />
+ </div>
+
+</uib-accordion>
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/frame.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/frame.tpl.html
new file mode 100755
index 00000000..7e15cfb9
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/frame.tpl.html
@@ -0,0 +1,34 @@
+<ht-header help-link='sdnr/mwtnTopology/0.4.0/README.md'></ht-header>
+<div class="owl">
+ <!-- Display a loading indicator as long as google isn't initialized. -->
+ <div class="bbg" ng-if="!vm.googleIsReady">
+ <i class="fa fa-spinner fa-pulse" aria-hidden="true"></i>
+ <span>{{'MWTN_LOADING' | translate}}</span>
+ </div>
+
+ <uib-tabset id="mwtnTopology" active="vm.activeTab" ng-if="vm.googleIsReady">
+
+ <uib-tab index="0" heading="{{'MWTN_SITE' | translate}}">
+ <mwtn-topology-site-view />
+ </uib-tab>
+
+ <uib-tab index="1" heading="{{'MWTN_AIR_INTERFACE' | translate}}">
+ <mwtn-topology-physical-view />
+ </uib-tab>
+
+ <!-- [sko] no data for TST
+ <uib-tab index="2" heading="{{'MWTN_ETHERNET' | translate}}">
+ <mwtn-topology-ethernet-view />
+ </uib-tab>
+
+ <uib-tab index="3" heading="{{'MWTN_PTP' | translate}}">
+ <mwtn-topology-ieee1588-view />
+ </uib-tab> -->
+
+ </uib-tabset>
+
+ <hr />
+ <div class="owl">
+ <span>ONAP SDN-R | ONF Wireless for @distversion@ - Build: @buildtime@</span>
+ </div>
+</div>
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/ieee1588View.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/ieee1588View.tpl.html
new file mode 100755
index 00000000..4551aa6e
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/ieee1588View.tpl.html
@@ -0,0 +1,18 @@
+<uib-accordion close-others="false">
+
+ <div uib-accordion-group="" class="panel-primary" is-open="vm.status.topologyIsOpen" template-url="src/app/mwtnTopology/templates/accordeonHeader.tpl.html"
+ heading="{{'Graph' | translate }}">
+ <mwtn-topology-ieee1588-path-graph ng-if="vm.status.topologyIsOpen" />
+ </div>
+
+ <div uib-accordion-group="" class="panel-primary" is-open="vm.status.portsOpen" template-url="src/app/mwtnTopology/templates/accordeonHeader.tpl.html"
+ heading="{{'Clocks' | translate}}">
+ <mwtn-topology-clocks-grid />
+ </div>
+
+ <div uib-accordion-group="" class="panel-primary" is-open="vm.status.ethConnectionsIsOpen" template-url="src/app/mwtnTopology/templates/accordeonHeader.tpl.html"
+ heading="{{'PTP links' | translate}}">
+ <mwtn-topology-ptp-links-grid />
+ </div>
+
+</uib-accordion>
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/linkDetails.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/linkDetails.tpl.html
new file mode 100755
index 00000000..e26979a1
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/linkDetails.tpl.html
@@ -0,0 +1,110 @@
+<div>
+ <uib-alert type="{{status.type}}" ng-show="status.message"> <i ng-show="status.isWorking" class="fa fa-spinner fa-pulse"></i>
+ <b><span>{{status.message}}</span></b>
+ </uib-alert>
+ <div ng-show="!status.message">
+ <p>
+ <span>Sitelink name: </span>
+ <b>{{vm.link.name || vm.link.id }}</b>
+ <br/>
+ <span>Length: </span>
+ <b> {{vm.link.length || 0 | number:3 }} </b>
+ <span> km</span>
+ <br/>
+ <span>Type: </span>
+ <b> {{vm.link.type || 'traffic' }} </b>
+ <span> </span>
+ </p>
+ <table>
+ <caption class="text-left">
+ <b>{{'SITE' | translate}} A</b>
+ </caption>
+ <tbody>
+ <tr>
+ <td class="text-right" style="padding-right: 5px;">{{'MWTN_SITE_ID' | translate}}:</td>
+ <td class="text-left">
+ <b>{{vm.link.siteA.id}}</b>
+ </td>
+ </tr>
+ <tr>
+ <td class="text-right" style="padding-right: 5px;">{{'MWTN_NAME' | translate}}:</td>
+ <td class="text-left">
+ <b>{{vm.link.siteA.name}}</b>
+ </td>
+ </tr>
+ <tr>
+ <td class="text-right" style="padding-right: 5px;">Lat/Lon:</td>
+ <td class="text-left">
+ <b>{{vm.link.siteA.location.lat | coordinateFilter:'toDMS':'lat'}} | {{vm.link.siteA.location.lng | coordinateFilter:'toDMS':'lon'}}</b>
+ </td>
+ </tr>
+ <tr>
+ <td class="text-right" style="padding-right: 5px;">Azimuth:</td>
+ <td class="text-left">
+ <b> {{vm.link.azimuthA || 0 | number:1 }} </b>
+ <span> °</span>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <table>
+ <caption class="text-left">
+ <b>{{'MWTN_SITE' | translate}} Z</b>
+ </caption>
+ <tbody>
+ <tr>
+ <td class="text-right" style="padding-right: 5px;">{{'MWTN_SITE_ID' | translate}}:</td>
+ <td class="text-left">
+ <b>{{vm.link.siteZ.id}}</b>
+ </td>
+ </tr>
+ <tr>
+ <td class="text-right" style="padding-right: 5px;">Site-Name:</td>
+ <td class="text-left">
+ <b>{{vm.link.siteZ.name}}</b>
+ </td>
+ </tr>
+ <tr>
+ <td class="text-right" style="padding-right: 5px;">Lat/Lon:</td>
+ <td class="text-left">
+ <b>{{vm.link.siteZ.location.lat | coordinateFilter:'toDMS':'lat'}} | {{vm.link.siteZ.location.lng | coordinateFilter:'toDMS':'lon'}}</b>
+ </td>
+ </tr>
+ <tr>
+ <td class="text-right" style="padding-right: 5px;">Azimuth:</td>
+ <td class="text-left">
+ <b> {{vm.link.azimuthB || 0 | number:1 }} </b>
+ <span> °</span>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <br/>
+ <br/>
+ <table class='table table-striped' ng-show="vm.link.airInterfaceLinks && vm.link.airInterfaceLinks.length">
+ <caption class="text-left">
+ <b>{{'MWTN_AIR_INTERFACE_LINKS' | translate}}</b>
+ </caption>
+ <thead>
+ <tr>
+ <th>{{'MWTN_ID' | translate}}</th>
+ <th>{{'MWTN_RADIO' | translate}}</th>
+ <th>{{'MWTN_BAND' | translate}}</th>
+ <th>{{'MWTN_POLARIZATION' | translate}}</th>
+ <th>{{'MWTN_STATUS' | translate}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="airIf in vm.link.airInterfaceLinks">
+ <td> {{ airIf.id}} </td>
+ <td> {{ airIf.radio}} </td>
+ <td> {{ airIf.bandDesignator }} </td>
+ <td> {{ airIf.polarization }} </td>
+ <td> {{ airIf.mainStatus }} </td>
+
+
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div> \ No newline at end of file
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/links.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/links.tpl.html
new file mode 100755
index 00000000..8299a834
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/links.tpl.html
@@ -0,0 +1,2 @@
+<div id="mwtnNodesGrid" ui-grid="gridOptionsLinks" ui-grid-exporter ui-grid-selection ui-grid-pinning ui-grid-resize-columns ui-grid-move-columns
+ class="mwtnTopologyGrid"></div>
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/linksGrid.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/linksGrid.tpl.html
new file mode 100755
index 00000000..b5d3cbc7
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/linksGrid.tpl.html
@@ -0,0 +1 @@
+<div class="mwtnTopologyGrid" ui-grid="vm.gridOptions" ui-grid-pagination ui-grid-pinning ui-grid-selection ui-grid-resize-columns ui-grid-move-columns></div> \ No newline at end of file
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/maps.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/maps.tpl.html
new file mode 100755
index 00000000..71e6c0ee
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/maps.tpl.html
@@ -0,0 +1,59 @@
+<!--
+<ui-gmap-google-map bounds="mapObjects.map.bounds"
+ center="mapObjects.map.center"
+ control="mapObjects.map.control"
+ dragging="mapObjects.map.dragging"
+ events="mapObjects.map.events"
+ options="mapObjects.map.options"
+ zoom="mapObjects.map.zoom">
+
+ <ui-gmap-markers
+ models="mapObjects.sites.models"
+ idkey="marker.id"
+ fit="true"
+ coords="'self'"
+ options="'options'"
+ icon="mapObjects.sites.icon"
+ doCluster="doGoogleCluster"
+ events="mapObjects.sites.events"
+ >
+ </ui-gmap-markers>
+
+ <ui-gmap-window
+ coords="mapObjects.sites.tooltip"
+ options="mapObjects.sites.tooltipOptions"
+ show="mapObjects.sites.tooltip.show">
+ </ui-gmap-window>-->
+
+ <!-- siteLinks -->
+<!--
+ <ui-gmap-polylines
+ models="mapObjects.siteLinks.models"
+ events="mapObjects.siteLinks.events"
+ path="'path'"
+ stroke="'stroke'"
+ geodesic="'geodesic'"
+ editable="'editable'"
+ draggable="'draggable'"
+ static="true"
+ visible="mapObjects.siteLinks.visible"
+ zindex="'zIndex'"
+ >
+ </ui-gmap-polylines>
+
+</ui-gmap-google-map>-->
+
+<map center="{{mapObjects.map.center}}"
+ map-type-id=“MapTypeId.HYBRID”
+ street-view-control="false">
+
+ <marker data-ng-repeat="mapObjects.sites.models"
+ position="{{}}"
+ />
+</map>
+
+<!--
+ <sigma-topology drag-nodes="true" topology-data="topologyData" trigger-resize-sigma="triggerResizeSigma" panel="panel"
+ topology-custfunc="topologyCustfunc">
+ </sigma-toppology>
+ -->
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/networkElementsGrid.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/networkElementsGrid.tpl.html
new file mode 100755
index 00000000..b5d3cbc7
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/networkElementsGrid.tpl.html
@@ -0,0 +1 @@
+<div class="mwtnTopologyGrid" ui-grid="vm.gridOptions" ui-grid-pagination ui-grid-pinning ui-grid-selection ui-grid-resize-columns ui-grid-move-columns></div> \ No newline at end of file
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/nodes.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/nodes.tpl.html
new file mode 100755
index 00000000..826e7dd9
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/nodes.tpl.html
@@ -0,0 +1,2 @@
+<div id="mwtnNodesGrid" ui-grid="gridOptionsNodes" ui-grid-exporter ui-grid-selection ui-grid-pinning ui-grid-resize-columns ui-grid-move-columns
+ class="mwtnTopologyGrid"></div>
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/physicalView.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/physicalView.tpl.html
new file mode 100755
index 00000000..dfeabe4e
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/physicalView.tpl.html
@@ -0,0 +1,18 @@
+<uib-accordion close-others="false">
+
+ <div uib-accordion-group="" class="panel-primary" is-open="vm.status.graphIsOpen" template-url="src/app/mwtnTopology/templates/accordeonHeader.tpl.html"
+ heading="{{'MWTN_GRAPH' | translate }}">
+ <mwtn-topology-physical-path-graph ng-if="vm.status.graphIsOpen" />
+ </div>
+
+ <div uib-accordion-group="" class="panel-primary" is-open="vm.status.networkElementsIsOpen" template-url="src/app/mwtnTopology/templates/accordeonHeader.tpl.html"
+ heading="{{'MWTN_AIR_INTERFACES' | translate}}">
+ <mwtn-topology-network-elements-grid />
+ </div>
+
+ <div uib-accordion-group="" class="panel-primary" is-open="vm.status.LinksIsOpen" template-url="src/app/mwtnTopology/templates/accordeonHeader.tpl.html"
+ heading="{{'MWTN_LINKS' | translate}}">
+ <mwtn-topology-links-grid />
+ </div>
+
+</uib-accordion> \ No newline at end of file
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/portsGrid.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/portsGrid.tpl.html
new file mode 100755
index 00000000..b5d3cbc7
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/portsGrid.tpl.html
@@ -0,0 +1 @@
+<div class="mwtnTopologyGrid" ui-grid="vm.gridOptions" ui-grid-pagination ui-grid-pinning ui-grid-selection ui-grid-resize-columns ui-grid-move-columns></div> \ No newline at end of file
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/ptpLinksGrid.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/ptpLinksGrid.tpl.html
new file mode 100755
index 00000000..b5d3cbc7
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/ptpLinksGrid.tpl.html
@@ -0,0 +1 @@
+<div class="mwtnTopologyGrid" ui-grid="vm.gridOptions" ui-grid-pagination ui-grid-pinning ui-grid-selection ui-grid-resize-columns ui-grid-move-columns></div> \ No newline at end of file
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/siteDetails.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/siteDetails.tpl.html
new file mode 100755
index 00000000..3a06fa8d
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/siteDetails.tpl.html
@@ -0,0 +1,77 @@
+<div class="owl">
+ <uib-alert type="{{status.type}}" ng-show="status.message">
+ <i ng-show="status.isWorking" class="fa fa-spinner fa-pulse" aria-hidden="true"></i>
+ <b><span>{{status.message}}</span></b>
+ </uib-alert>
+ <div ng-show="!status.message">
+ <p>
+ <span>{{'MWTN_SITE_ID' | translate}}: </span>
+ <b>{{vm.site.id}}</b>
+ </p>
+ <hr />
+ <p>
+ <span>{{'MWTN_SITE_NAME' | translate}}: </span>
+ <b>{{vm.site.name}}</b>
+ <br />
+ <span>{{'MWTN_LAT_LON' | translate}}</span>
+ <b>{{ vm.site.location.lat | coordinateFilter:'toDMS':'lat'}} | {{ vm.site.location.lng | coordinateFilter:'toDMS':'lon'}}</b>
+ <br />
+ <span>{{'MWTN_AMSL_GRD' | translate}}: </span>
+ <b> {{vm.site.amslGround | number:2 }} </b>
+ <span> [m]</span>
+ <br />
+ <span>{{'MWTN_TYPE' | translate}}: </span>
+ <b> {{vm.site.type || 'pyhsical-network-function' }} </b>
+ <span> </span>
+ </p>
+ <p>
+ <b>Site-Links ({{vm.site.siteLinks.length}}): </b>
+ <span style="color: #337ab7;" class="pointer glyphicon glyphicon-plus-sign" aria-hidden="true" ng-class="{'selected-site' : createNewLinkMode}"
+ ng-show="showNewLinkButton" ng-click="toggleCreateNewLinkMode()"></span>
+ <p />
+ <table class='table table-striped' ng-show="vm.site.siteLinks.length">
+ <thead>
+ <tr>
+ <th class='text-left'>Site-Link-Name</th>
+ <th class='text-right'>Azimuth A [°]</th>
+ <th class='text-right'>Azimuth Z [°]</th>
+ <th class='text-right'>Length [km]</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat='siteLink in vm.site.siteLinks'>
+ <td class='text-left'>
+ <a href='/ux/#/siteLinks/{{siteIndex}}/{{siteLink._source.id.high}}/{{siteLink._source.id.low}}' target='htSolutions'>{{siteLink.id}}</a>
+ </td>
+ <td class='text-right'>{{ siteLink.azimuthAz | number : 1 }}</td>
+ <td class='text-right'>{{ siteLink.azimuthZa | number : 1 }}</td>
+ <td class='text-right'>{{ siteLink.length | number : 3 }}</td>
+ </tr>
+ </tbody>
+ </table>
+ <p>
+ <b>Network elements ({{vm.site.plannedNetworkElements.length}}): </b>
+ <!--
+ <span style="color: #337ab7;" class="pointer glyphicon glyphicon-plus-sign" aria-hidden="true" ng-class="{'selected-site' : createNewLinkMode}"
+ ng-show="showNewLinkButton" ng-click="toggleCreateNewLinkMode()"></span> -->
+ <p />
+ <table class='table table-striped' ng-show="vm.site.plannedNetworkElements.length">
+ <thead>
+ <tr>
+ <th class='text-left'>{{'MWTN_ID' | translate}}</th>
+ <th class='text-left'>{{'MWTN_NAME' | translate}}</th>
+ <th class='text-left'>{{'MWTN_TYPE' | translate}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat='ne in vm.site.plannedNetworkElements'>
+ <td class='text-left'>
+ <a href='#/pnfInventory/{{ne.id}}' target='htSolutions'>{{ne.id}}</a>
+ </td>
+ <td class='text-left'>{{ ne.name }}</td>
+ <td class='text-left'>{{ ne.type }}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div> \ No newline at end of file
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/siteGrid.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/siteGrid.tpl.html
new file mode 100755
index 00000000..612fb92e
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/siteGrid.tpl.html
@@ -0,0 +1,9 @@
+<!--<h1 style="color:#000">Here is the sites grid</h1>-->
+<div class="checkbox">
+ <label>
+ <input type="checkbox" ng-model="vm.showAllSites">
+ Show all sites
+ </label>
+</div>
+
+<div class="mwtnTopologyGrid" ui-grid="vm.gridOptions" ui-grid-pagination ui-grid-pinning ui-grid-selection ui-grid-resize-columns ui-grid-move-columns></div>
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/siteLinkGrid.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/siteLinkGrid.tpl.html
new file mode 100755
index 00000000..ba4f270c
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/siteLinkGrid.tpl.html
@@ -0,0 +1,8 @@
+<div class="checkbox">
+ <label>
+ <input type="checkbox" ng-model="vm.showAllLinks">
+ Show all links
+ </label>
+</div>
+
+<div class="mwtnTopologyGrid" ui-grid="vm.gridOptions" ui-grid-pagination ui-grid-pinning ui-grid-selection ui-grid-resize-columns ui-grid-move-columns></div> \ No newline at end of file
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/sitePathGrid.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/sitePathGrid.tpl.html
new file mode 100755
index 00000000..bc1372e6
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/sitePathGrid.tpl.html
@@ -0,0 +1 @@
+<h1 style="color:#000">Here is the site paths grid</h1>
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/siteView.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/siteView.tpl.html
new file mode 100755
index 00000000..e56a2273
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/siteView.tpl.html
@@ -0,0 +1,31 @@
+<uib-accordion close-others="false">
+
+ <div uib-accordion-group="" class="panel-primary" is-open="vm.status.siteMapIsOpen" template-url="src/app/mwtnTopology/templates/accordeonHeader.tpl.html"
+ heading="{{'Map (' + vm.status.loadedSitesInBoundingBox + ' of ' + vm.status.totalSitesInBoundingBox + ' sites loaded)'}}">
+
+ <mwtn-topology-map ng-if="vm.status.siteMapIsOpen" manual-map-bounds="vm.status.manualMapBounds" on-bounds-changed="vm.mapBoundsChanged(data, userOriginated)">
+ <mwtn-topology-map-sites sites="vm.visibleSites" selected-site="vm.status.site" api="vm.mapSitesComponentApi" />
+ <mwtn-topology-map-site-links site-links="vm.knownSiteLinks" selected-site-link="vm.status.siteLink"
+ api="vm.mapSiteLinksComponentApi" />
+ </mwtn-topology-map>
+ </div>
+
+ <div uib-accordion-group="" class="panel-primary" is-open="vm.status.siteGridIsOpen" template-url="src/app/mwtnTopology/templates/accordeonHeader.tpl.html"
+ heading="{{'Sites' | translate}}">
+
+ <mwtn-topology-site-grid visible-sites="vm.visibleSites" />
+ </div>
+
+ <div uib-accordion-group="" class="panel-primary" is-open="vm.status.siteLinkGridIsOpen" template-url="src/app/mwtnTopology/templates/accordeonHeader.tpl.html"
+ heading="{{'Site links' | translate}}">
+
+ <mwtn-topology-site-link-grid known-site-links="vm.knownSiteLinks" />
+ </div>
+
+ <!-- [sko, 2017-06-23] TODO deferred
+ <div uib-accordion-group="" class="panel-primary" is-open="vm.status.sitePathGridIsOpen" template-url="src/app/mwtnTopology/templates/accordeonHeader.tpl.html"
+ heading="{{'Site paths' | translate}}">
+
+ <mwtn-topology-site-path-grid />
+ </div> -->
+</uib-accordion>
diff --git a/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/topology.tpl.html b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/topology.tpl.html
new file mode 100755
index 00000000..6a71bbf8
--- /dev/null
+++ b/sdnr/wireless-transport/code-Carbon-SR1/ux/mwtnTopology/mwtnTopology-module/src/main/resources/mwtnTopology/templates/topology.tpl.html
@@ -0,0 +1,2 @@
+ <sigma-topology drag-nodes="true" topology-data="topologyData" trigger-resize-sigma="triggerResizeSigma" panel="panel" topology-custfunc="topologyCustfunc">
+ </sigma-toppology>