2 * Copyright (c) 2016 highstreet technologies GmbH and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 if (typeof (Number.prototype.toRad) === "undefined") {
9 Number.prototype.toRadians = function () {
10 return this * Math.PI / 180;
14 if (Number.prototype.toDegrees === undefined) {
15 Number.prototype.toDegrees = function () {
16 return this * 180 / Math.PI;
20 // If array is empty, undefined is returned. If not empty, the first element
21 // that evaluates to false is returned. If no elements evaluate to false, the
22 // last element in the array is returned.
23 Array.prototype.and = function (defaultValue) {
24 for (var i = 0, len = this.length - 1; i < len && this[i]; i++);
25 return this.length ? this[i] : defaultValue;
28 // If array is empty, undefined is returned. If not empty, the first element
29 // that evaluates to true is returned. If no elements evaluate to true, the
30 // last element in the array is returned.
31 Array.prototype.or = function () {
32 for (var i = 0, len = this.length - 1; i < len && !this[i]; i++);
36 var eventsFabric = function () {
38 var hOP = topics.hasOwnProperty;
41 subscribe: function (topic, listener) {
42 // Create the topic's object if not yet created
43 if (!hOP.call(topics, topic)) topics[topic] = [];
45 // Add the listener to queue
46 var index = topics[topic].push(listener) - 1;
48 // Provide handle back for removal of topic
51 delete topics[topic][index];
55 publish: function (topic, info) {
56 // If the topic doesn't exist, or there's no listeners in queue, just leave
57 if (!hOP.call(topics, topic)) return;
59 // Cycle through topics queue, fire!
60 topics[topic].forEach(function (item) {
61 item(info != undefined ? info : {});
67 var lastOpenedInfoWindow = null;
69 /*********************************************************************************************/
71 /** @typedef {{ id: string, siteA: string, siteZ: string, siteNameA: string, siteNameZ: string, airInterfaceLinks: {id: string, siteLink: string, radio: string, polarization: string }[]}} SiteLinkDetails */
73 define(['app/mwtnCommons/bower_components/lodash/dist/lodash',
74 'app/mwtnCommons/bower_components/cytoscape/dist/cytoscape',
75 'app/mwtnTopology/mwtnTopology.module',
76 'app/mwtnTopology/mwtnTopology.services',
77 'app/mwtnCommons/mwtnCommons.services'
78 ], function (_, cytoscape, mwtnTopologyApp) {
80 // module.exports = function () {
81 // const mwtnTopologyApp = require('app/mwtnTopology/mwtnTopology.module');
82 // const mwtnTopologyService = require('app/mwtnTopology/mwtnTopology.services');
83 // const cytoscape = require('cytoscape');
85 // remove '_' from global scope but use it here as '_'.
88 /********************************************* Sites *************************/
90 mwtnTopologyApp.directive('mwtnTopologyMap', ["$timeout", "$q", "$mwtnTopology", function ($timeout, $q, $mwtnTopology) {
94 template: '<div style="position:realtive;width:100%;height:700px" ng-transclude=""></div>',
95 controller: function () {
100 manualMapBounds: "=?",
101 onBoundsChanged: "&?"
103 link: function (scope, element, attrs, ctrl) {
105 var mapApiReadyDefer = $q.defer();
106 var disbaleNotifications = true;
107 //var includeCenter = false;
109 var initialMapOptions = {
110 center: new google.maps.LatLng(0, 0),
111 mapTypeId: google.maps.MapTypeId.HYBRID,
113 rotateControl: false,
114 streetViewControl: false,
120 // Wait at least one digest cycle before init the google map.
121 $timeout(function () {
122 ctrl.map = new google.maps.Map(element[0], initialMapOptions);
124 // Wait until google maps is fully intialized to attach nessessary event handler.
125 waitUntilGoogleMapsIsReady();
128 element.on("$destroy", function () {
129 if (scope.waitUntilGoogleMapsIsReadyTimeoutId) {
130 $timeout.cancel(scope.waitUntilGoogleMapsIsReadyTimeoutId);
132 if (scope.onDragEndListener) {
133 google.maps.event.removeListener(scope.onDragEndListener);
134 delete (scope.onDragEndListener);
136 if (scope.onZoomChangedListener) {
137 google.maps.event.removeListener(scope.onZoomChangedListener);
138 delete (scope.onZoomChangedListener);
141 // window.removeEventListener('resize', onBoundsChanged);
145 scope.$watch("manualMapBounds", function (newBounds, oldBounds) {
146 mapApiReadyDefer.promise.then(function () {
148 // if this was an uri change from an not user originated action just return;
149 if (newBounds.internal) return;
151 // values must exist and at least one must be different or initial load (new === old)
152 if (newBounds.bottom != null && newBounds.left != null && newBounds.top != null && newBounds.right != null) {
153 fitBounds(ctrl.map, new google.maps.LatLngBounds(
154 new google.maps.LatLng(newBounds.bottom, newBounds.left),
155 new google.maps.LatLng(newBounds.top, newBounds.right)));
156 } 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)) {
157 disbaleNotifications = true;
158 google.maps.event.addListenerOnce(ctrl.map, 'bounds_changed', function (event) {
159 disbaleNotifications = false;
160 onBoundsChanged(false);
163 ctrl.map.setCenter({ lat: newBounds.lat, lng: newBounds.lng });
164 ctrl.map.setZoom(newBounds.zoom);
167 console.log("manualMapBounds", newBounds);
172 * An asynchronous function that is called continuously every 100 milliseconds until the google maps control has finished its first complete draw.
173 * Then it sets up all event handlers and reports its bounds.
175 function waitUntilGoogleMapsIsReady() {
176 var getBoundsApi = ctrl.map.getBounds();
178 delete (scope.waitUntilGoogleMapsIsReadyTimeoutId);
179 // Change to dragend https://developers.google.com/maps/documentation/javascript/events?hl=de
180 scope.onDragEndListener = ctrl.map.addListener('dragend', function () {
181 onBoundsChanged(true);
183 scope.onZoomChangedListener = ctrl.map.addListener('zoom_changed', function () {
184 onBoundsChanged(true);
186 // window.addEventListener('resize', function () {
187 // onBoundsChanged(false);
190 disbaleNotifications = false;
191 mapApiReadyDefer.resolve();
193 scope.waitUntilGoogleMapsIsReadyTimeoutId = $timeout(waitUntilGoogleMapsIsReady, 100);
198 * Gets the current bound and zoom level of the map control and reports it to its parent component.
199 * Hint: do not call directly use fitBounds helper function since there is a google bug in the fit bounds function.
201 function onBoundsChanged(userOriginated) {
202 if (!disbaleNotifications && scope.onBoundsChanged && angular.isFunction(scope.onBoundsChanged)) {
204 var getBoundsApi = ctrl.map.getBounds();
205 var northEastApi = getBoundsApi.getNorthEast();
206 var southWestApi = getBoundsApi.getSouthWest();
207 var center = ctrl.map.getCenter();
210 top: northEastApi.lat(),
211 right: northEastApi.lng(),
212 bottom: southWestApi.lat(),
213 left: southWestApi.lng(),
216 zoom: ctrl.map.getZoom()
219 console.log("onBoundsChanged", data);
221 scope.onBoundsChanged({
223 userOriginated: userOriginated === true
228 // bug: the original google code will fit bounds will add some extra space, this function will fix it
229 function fitBounds(map, bounds) {
230 var oldBounds = map.getBounds();
232 // ensure there is any change
233 if (oldBounds.toUrlValue() == bounds.toUrlValue()) {
237 disbaleNotifications = true;
238 google.maps.event.addListenerOnce(map, 'bounds_changed', function (event) {
239 // bugfix: this is a fix for the https://issuetracker.google.com/issues/35820423
240 // the map does sometime zoom out if it gets its one bounds
242 // var newSpan = map.getBounds().toSpan(); // the span of the map set by Google fitBounds (always larger by what we ask)
243 // var askedSpan = bounds.toSpan(); // the span of what we asked for
244 // var latRatio = (newSpan.lat() / askedSpan.lat()) - 1; // the % of increase on the latitude
245 // var lngRatio = (newSpan.lng() / askedSpan.lng()) - 1; // the % of increase on the longitude
246 // // if the % of increase is too big (> to a threshold) we zoom in
247 // if (Math.min(latRatio, lngRatio) > 0.46) {
248 // // 0.46 is the threshold value for zoming in. It has been established empirically by trying different values.
249 // this.setZoom(this.getZoom() + 1);
251 disbaleNotifications = false;
252 onBoundsChanged(false);
255 map.fitBounds(bounds); // does the job asynchronously
261 mwtnTopologyApp.directive('mwtnTopologyMapSites', ['$compile', '$rootScope', function ($compile, $rootScope) {
265 require: '^mwtnTopologyMap',
271 link: function (scope, element, attrs, mwtnTopologyMapController) {
272 // todo: shouild come from a service
274 path: google.maps.SymbolPath.CIRCLE,
276 strokeColor: '#00ccff',
279 fillColor: '#00ccff',
284 var highlightIcon = {
285 path: google.maps.SymbolPath.CIRCLE,
287 strokeColor: '#eaae3c',
290 fillColor: '#eaae3c',
296 path: google.maps.SymbolPath.CIRCLE,
298 strokeColor: '#00FD30',
301 fillColor: '#00FD30',
306 scope.displayedSites = {};
307 scope.knownSites = {};
309 scope.$watch("selectedSite", function (newSiteId, oldSiteId) {
311 if (oldSiteId && scope.displayedSites[oldSiteId]) {
312 scope.displayedSites[oldSiteId].setOptions({
317 if (newSiteId && scope.displayedSites[newSiteId]) {
318 scope.displayedSites[newSiteId].setOptions({
325 if (!scope.api) return;
328 * Updates the google map markers
329 * @param addedSiteIds {string[]} The ids of the sites which are added to the scope.displayedSites dictionary.
330 * @param removedFromVisibleIds {string[]} The ids of sites which are removed from the visible bounding rectangle.
331 * @param movedFromVisibleToKnownIds {string[]} The ids of sites which are moved from the visible bounding rectangle to the extended bounding rectangle.
332 * @param removedFromKnownIds {string[]} The ids of sites which are removed from the extended bounding rectangle.
333 * @param movedFromKnownToVisibleIds {string[]} The ids of sites which are moved from the extended bounding rectangle to the visible bounding rectangle.
335 scope.api.updateSites = function (addedSiteIds, removedFromVisibleIds, movedFromVisibleToKnownIds, removedFromKnownIds, movedFromKnownToVisibleIds) {
336 if (!addedSiteIds) addedSiteIds = [];
337 if (!removedFromVisibleIds) removedFromVisibleIds = [];
338 if (!movedFromVisibleToKnownIds) movedFromVisibleToKnownIds = [];
339 if (!removedFromKnownIds) removedFromKnownIds = [];
340 if (!movedFromKnownToVisibleIds) movedFromKnownToVisibleIds = [];
342 removedFromVisibleIds.forEach(function (siteId) {
343 var marker = scope.displayedSites[siteId];
347 delete scope.displayedSites[siteId];
351 removedFromKnownIds.forEach(function (siteId) {
352 var marker = scope.knownSites[siteId];
356 delete scope.knownSites[siteId];
360 movedFromVisibleToKnownIds.forEach(function (siteId) {
361 var marker = scope.displayedSites[siteId];
363 marker.setVisible(false);
364 scope.knownSites[siteId] = marker;
365 delete scope.displayedSites[siteId];
369 movedFromKnownToVisibleIds.forEach(function (siteId) {
370 var marker = scope.knownSites[siteId];
372 marker.setVisible(true);
373 scope.displayedSites[siteId] = marker;
374 delete scope.knownSites[siteId];
378 addedSiteIds.forEach(function (newSiteId) {
379 var newSite = scope.sites[newSiteId];
380 var newSiteNormalIcon = Object.assign({}, normalIcon, {
381 fillColor: (newSite.type === 'data-center') ? "#ffff00": "#00ccff",
382 strokeColor: (newSite.type === 'data-center') ? "#ffff00" : "#00ccff"
384 var newSiteHighlightIcon = Object.assign({}, highlightIcon, {
385 fillColor: (newSite.type === 'data-center') ? "#ffff00": "#00ccff",
386 strokeColor: (newSite.type === 'data-center') ? "#ffff00" : "#00ccff"
388 if (newSite && !scope.displayedSites[newSiteId]) {
391 var marker = new google.maps.Marker({
392 map: mwtnTopologyMapController.map,
393 position: newSite.location,
395 icon: newSiteNormalIcon
398 // add event listeners to the marker
399 marker.addListener('click', function () {
400 // cloase all already opened windows
401 if (lastOpenedInfoWindow) {
402 lastOpenedInfoWindow.close();
403 lastOpenedInfoWindow = null;
406 // compile the content
407 var infoWindowTemplate = '<mwtn-topology-site-details site-id="siteId" />';
408 var infoWindowScope = $rootScope.$new();
409 infoWindowScope['siteId'] = newSiteId;
410 var infoWindowContent = $compile(infoWindowTemplate)(infoWindowScope)[0];
412 // create the info window
413 var infowindow = new google.maps.InfoWindow({
414 content: infoWindowContent,
418 // open the window and keek a refenrece to close it if new window opens
419 infowindow.open(mwtnTopologyMapController.map, marker);
420 lastOpenedInfoWindow = infowindow;
422 // remove the reference if the windows is closed
423 infowindow.addListener('closeclick', function () {
424 lastOpenedInfoWindow = null;
428 marker.addListener('mouseover', function () {
429 marker.setOptions({ icon: newSiteHighlightIcon });
432 marker.addListener('mouseout', function () {
433 marker.setOptions({ icon: newSiteNormalIcon });
437 scope.displayedSites[newSiteId] = marker;
446 mwtnTopologyApp.directive('mwtnTopologyMapSiteLinks', ['$compile', '$rootScope', function ($compile, $rootScope) {
449 require: '^mwtnTopologyMap',
451 selectedSiteLink: "=",
455 link: function (scope, element, attrs, mwtnTopologyMapController) {
457 var normalColor = "#FF0000";
458 var selectedColor = "#00FD30";
460 scope.$watch("selectedSiteLink", function (newSiteLinkId, oldSiteLinkId) {
462 if (oldSiteLinkId && scope.displayedSiteLinks[oldSiteLinkId]) {
463 scope.displayedSiteLinks[oldSiteLinkId].setOptions({
464 strokeColor: normalColor,
468 if (newSiteLinkId && scope.displayedSiteLinks[newSiteLinkId]) {
469 scope.displayedSiteLinks[newSiteLinkId].setOptions({
470 strokeColor: selectedColor,
476 scope.displayedSiteLinks = {};
479 * Updates the google map Polylines
480 * @param addedSiteLinkIds {string[]} The ids of sites links which are added to the scope.displayedSiteLinks dictionary and the google map.
481 * @param removedSiteLinkIds {string[]} The ids of sites links which are removed from the visible bounding rectangle within the google map.
483 scope.api.updateSiteLinks = function (addedSiteLinkIds, removedSiteLinkIds) {
484 if (!addedSiteLinkIds) addedSiteLinkIds = [];
485 if (!removedSiteLinkIds) removedSiteLinkIds = [];
487 removedSiteLinkIds.forEach(function (siteLinkId) {
488 var polyline = scope.displayedSiteLinks[siteLinkId];
490 polyline.setMap(null);
492 delete scope.displayedSiteLinks[siteLinkId];
496 addedSiteLinkIds.forEach(function (siteLinkId) {
497 var siteLink = scope.siteLinks[siteLinkId];
498 console.log(siteLink);
500 if (siteLink && !scope.displayedSiteLinks[siteLinkId]) {
502 // create the poly line
503 var polyline = new google.maps.Polyline({
504 map: mwtnTopologyMapController.map,
505 path: [siteLink.siteA.location, siteLink.siteZ.location],
506 strokeColor: siteLink.type === 'traffic' ? '#00ccff': '#ffff00',
508 strokeWeight: siteLink.type === 'traffic' ? 4 : 2,
512 console.log(siteLink.type, polyline.strokeColor);
514 // add event listeners to the polyline
515 polyline.addListener('click', function (event) {
516 // cloase all already opened windows
517 if (lastOpenedInfoWindow) {
518 lastOpenedInfoWindow.close();
519 lastOpenedInfoWindow = null;
522 // compile the content
523 var infoWindowTemplate = '<mwtn-topology-link-details link-id="linkId"></mwtn-topology-link-details>';
524 var infoWindowScope = $rootScope.$new();
525 infoWindowScope['linkId'] = siteLinkId;
526 var infoWindowContent = $compile(infoWindowTemplate)(infoWindowScope)[0];
528 // create the info window
529 var infowindow = new google.maps.InfoWindow({
530 content: infoWindowContent,
534 // open the window and keek a refenrece to close it if new window opens
535 // adjust and show the info window
536 infowindow.setPosition(event.latLng);
537 infowindow.open(mwtnTopologyMapController.map);
538 lastOpenedInfoWindow = infowindow;
540 // remove the reference if the windows is closed
541 infowindow.addListener('closeclick', function () {
542 lastOpenedInfoWindow = null;
547 polyline.addListener('mouseover', function () {
548 polyline.setOptions({ strokeWeight: 6 });
551 polyline.addListener('mouseout', function () {
552 polyline.setOptions({ strokeWeight: 3 });
555 // store the ployline
556 scope.displayedSiteLinks[siteLinkId] = polyline;
565 mwtnTopologyApp.controller('mwtnTopologySiteDetailsController', ['$scope', '$timeout', '$mwtnTopology', function ($scope, $timeout, $mwtnTopology) {
569 $scope.$watch(function () { return vm.siteId }, function (newSiteId, oldSiteId) {
570 if (!newSiteId) return;
573 message: 'Searching...',
578 $mwtnTopology.getSiteDetailsBySiteId(newSiteId).then(function (siteDetails) {
579 vm.site = siteDetails;
597 mwtnTopologyApp.directive('mwtnTopologySiteDetails', function () {
599 scope: { siteId: "=" },
601 controller: 'mwtnTopologySiteDetailsController',
603 bindToController: true,
604 templateUrl: 'src/app/mwtnTopology/templates/siteDetails.tpl.html'
608 mwtnTopologyApp.controller('mwtnTopologyLinkDetailsController', ['$scope', '$timeout', '$mwtnTopology', function ($scope, $timeout, $mwtnTopology) {
609 /** @type {{ link: SiteLinkDetails } & {[key: string]: any}} */
614 $scope.$watch(function () { return vm.linkId }, function (newLinkId, oldLinkId) {
615 if (!newLinkId) return;
618 message: 'Searching...',
623 // getLinkDetailsByLinkId
624 $mwtnTopology.getLinkDetailsByLinkId(newLinkId).then(
625 /** @param {SiteLinkDetails} linkDetails*/
626 function (linkDetails) {
627 vm.link = linkDetails;
645 mwtnTopologyApp.directive('mwtnTopologyLinkDetails', function () {
647 scope: { linkId: "=" },
649 controller: 'mwtnTopologyLinkDetailsController',
651 bindToController: true,
652 templateUrl: 'src/app/mwtnTopology/templates/linkDetails.tpl.html'
656 mwtnTopologyApp.controller('mwtnTopologyFrameController', ['$scope', '$rootScope', '$timeout', '$state', '$window', '$mwtnTopology', function ($scope, $rootScope, $timeout, $state, $window, $mwtnTopology) {
659 $rootScope.section_logo = 'src/app/mwtnTopology/images/mwtnTopology.png'; // Add your topbar logo location here such as 'assets/images/logo_topology.gif'
661 /** @type {{ [tabName: string] : { [parameterName : string] : any } }} */
662 var tabParameters = {};
666 tabs[tabs["site"] = 0] = "site";
667 tabs[tabs["physical"] = 1] = "physical";
668 tabs[tabs["ethernet"] = 2] = "ethernet";
669 tabs[tabs["ieee1588"] = 3] = "ieee1588";
670 })(tabs || (tabs = {}));
672 $scope.$watch(function () { return vm.activeTab; }, function (newVal, oldVal, scope) {
673 if (newVal == null) return;
675 var newName = tabs[newVal];
677 if (oldVal != null && tabs[oldVal]) {
678 var oldName = tabs[oldVal];
679 tabParameters[oldName] = $state.params;
682 var newParameters = Object.keys($state.params).reduce(function (acc, cur, ind, arr) { acc[cur] = null; return acc; }, {});
683 Object.assign(newParameters, tabParameters[newName] || {}, { tab: newName, internal: false });
685 $state.go('main.mwtnTopology', newParameters, { notify: false });
686 console.log("activeTab: ", newName);
689 $scope.$watch(function () { return $state.params.tab; }, function (newVal, oldVal, scope) {
690 if (newVal == oldVal) return;
691 if (newVal && !$state.params.internal) {
692 vm.activeTab = tabs[newVal || "site"];
693 console.log("navigationTab: ", vm.activeTab);
697 // hide all tabs until the google api is fully loaded.
698 // initialize with true is the promise is resolved === 1
699 vm.googleIsReady = $mwtnTopology.googleMapsApiPromise.$$state.status === 1;
701 if (!vm.googleIsReady) $mwtnTopology.googleMapsApiPromise.then(function () {
702 $timeout(function () {
703 vm.googleIsReady = true;
709 mwtnTopologyApp.directive('mwtnTopologyFrame', function () {
712 controller: 'mwtnTopologyFrameController',
714 templateUrl: 'src/app/mwtnTopology/templates/frame.tpl.html'
718 mwtnTopologyApp.controller('mwtnTopologySiteViewController', ['$scope', '$q', '$timeout', '$state', '$window', '$mwtnTopology', function ($scope, $q, $timeout, $state, $window, $mwtnTopology) {
720 // Determines if a database request loop should be canceld.
721 var cancelRequested = false;
722 // Represents a defer which is resolved, if a cancel was requested and fulfilled.
723 var cancelDefer = $q.defer();
724 // Represents the new bounding rectangle who was set with the last user action to change the map bounds.
725 // The value is saved until the cancel operation is fulfilled.
726 /** @type {{top: number, right: number, bottom: number, left: number, zoom: number}} */
727 var waitForCancelMapBoundAndZoom;
728 // While getting all sites collect all site link ids which are available through visible and known sites.
729 // This dictionary will be cleared each time a new map bound is changed and a new database request loop is started.
730 var collectedSiteLinkIds = {};
733 // Determines the bounds of the map which is manually set by the code. These are the initial values.
735 top: !Number.isNaN(+$state.params.top) ? +$state.params.top : undefined,
736 bottom: !Number.isNaN(+$state.params.bottom) ? +$state.params.bottom : undefined,
737 left: !Number.isNaN(+$state.params.left) ? +$state.params.left : undefined,
738 right: !Number.isNaN(+$state.params.right) ? +$state.params.right : undefined,
739 zoom: !Number.isNaN(+$state.params.zoom) ? +$state.params.zoom : undefined,
740 lat: !Number.isNaN(+$state.params.lat) ? +$state.params.lat : undefined,
741 lng: !Number.isNaN(+$state.params.lng) ? +$state.params.lng : undefined
744 // Determines if the accordeon group element for SiteMap, SiteGrid or SiteLinkGrid is open.
746 siteGridIsOpen: true,
747 siteLinkGridIsOpen: true,
748 sitePathGridIsOpen: false,
750 // Determines if a processing is running.
752 totalSitesInBoundingBox: 0,
753 loadedSitesInBoundingBox: 0,
754 maximumCountOfVisibleSites: 300,
762 // Contains the known and visible sites as well as known site links as dictionaries.
764 vm.visibleSites = {};
765 vm.knownSiteLinks = {};
767 // Api-Object that will be filled with update methods from the mwtnTopologyMapSites Component.
768 vm.mapSitesComponentApi = {};
769 // Api-Object that will be filled with update methods from the mwtnTopologyMapSiteLinks Component.
770 vm.mapSiteLinksComponentApi = {};
772 //addedSiteLinkIds, removedSiteLinkIds
774 initializeMapBounds();
776 function initializeMapBounds() {
778 vm.status.manualMapBounds = {
779 top: !Number.isNaN(+$state.params.top) ? +$state.params.top : undefined,
780 bottom: !Number.isNaN(+$state.params.bottom) ? +$state.params.bottom : undefined,
781 left: !Number.isNaN(+$state.params.left) ? +$state.params.left : undefined,
782 right: !Number.isNaN(+$state.params.right) ? +$state.params.right : undefined,
783 zoom: !Number.isNaN(+$state.params.zoom) ? +$state.params.zoom : undefined,
784 lat: !Number.isNaN(+$state.params.lat) ? +$state.params.lat : undefined,
785 lng: !Number.isNaN(+$state.params.lng) ? +$state.params.lng : undefined
788 // Calculate the initial map control bound, if we have no initial bounds
789 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)) {
790 $mwtnTopology.getOuterBoundingRectangleForSites().then(function (bounds) {
791 // Ensures that the new value is set within a digest cycle.
792 vm.status.manualMapBounds = bounds;
797 $scope.$watch(function () { return $state.params.site; }, function (newVal, oldVal, scope) {
799 $mwtnTopology.getSiteDetailsBySiteId(newVal).then(function (siteDetails) {
801 // just update the location of the browser if the location has changed
802 $state.go('main.mwtnTopology', {
804 lat: siteDetails.location.lat,
805 lng: siteDetails.location.lng,
811 site: $state.params.site,
825 $scope.$watch(function () { return $state.params.siteLink; }, function (newVal, oldVal, scope) {
827 $mwtnTopology.getLinkDetailsByLinkId(newVal).then(function (link) {
830 var top = link.siteA.location.lat >= link.siteZ.location.lat ? link.siteA.location.lat : link.siteZ.location.lat;
831 var bottom = link.siteA.location.lat >= link.siteZ.location.lat ? link.siteZ.location.lat : link.siteA.location.lat;
833 // todo: this code is not able to handle links which overlap the -100|180 ° borderline
834 var left = link.siteA.location.lng <= link.siteZ.location.lng ? link.siteA.location.lng : link.siteZ.location.lng;
835 var right = link.siteA.location.lng <= link.siteZ.location.lng ? link.siteZ.location.lng : link.siteA.location.lng;
836 // just update the location of the browser if the location has changed
837 $state.go('main.mwtnTopology', {
841 zoom: $state.params.zoom,
847 siteLink: $state.params.siteLink,
860 $scope.$watchCollection(function () { return [vm.status.siteMapIsOpen, vm.status.siteGridIsOpen, vm.status.siteLinkGridIsOpen, vm.status.sitePathGridIsOpen] }, function (newVal, oldVal) {
861 if (newVal[1] || newVal[2] || newVal[3]) {
862 $timeout(function () {
863 $window.dispatchEvent(new Event("resize"));
868 $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) {
870 if (oldVal == null || oldVal === newVal) return; // ignore initial value
872 if (!vm.status.siteMapIsOpen) {
873 console.log("open Site Map")
874 vm.status.siteMapIsOpen = true;
878 vm.status.manualMapBounds = {
879 top: !Number.isNaN(newVal[0]) ? newVal[0] : undefined,
880 bottom: !Number.isNaN(newVal[1]) ? newVal[1] : undefined,
881 left: !Number.isNaN(newVal[2]) ? newVal[2] : undefined,
882 right: !Number.isNaN(newVal[3]) ? newVal[3] : undefined,
883 zoom: !Number.isNaN(newVal[6]) ? newVal[6] : undefined,
884 internal: !!$state.params.internal
887 if (!Number.isNaN(newVal[4]) && !Number.isNaN(newVal[5])) {
888 vm.status.manualMapBounds.lat = newVal[4];
889 vm.status.manualMapBounds.lng = newVal[5];
891 vm.status.manualMapBounds.lat = undefined;
892 vm.status.manualMapBounds.lng = undefined;
895 if (!!$state.params.internal) $state.params.internal = false;
899 $scope.$watchCollection(function () { return vm.status.siteMapIsOpen }, function (newVal, oldVal, scope) {
900 if (oldVal == newVal) return; // ignore initial value
903 initializeMapBounds();
906 vm.status.loadedSitesInBoundingBox = 0;
907 vm.status.totalSitesInBoundingBox = 0;
908 vm.status.manualMapBounds = {};
910 vm.visibleSites = {};
911 vm.knownSiteLinks = {};
913 // tell all sub components the visuability of the map view
914 $scope.$broadcast('mapViewVisuability', newVal);
917 /* callback and helper methods */
919 * A callback function which is called by the map control of the user changed the bounds of the map.
920 * @param mapBoundsAndZoom {{top: number, right: number, bottom: number, left: number, zoom: number}} The new bounds and the new zoom level of the map control.
922 vm.mapBoundsChanged = function (mapBoundsAndZoom, userOriginated) {
924 // just update the location of the browser if the location has changed
925 $state.go('main.mwtnTopology', {
927 lat: mapBoundsAndZoom.lat,
928 lng: mapBoundsAndZoom.lng,
929 zoom: mapBoundsAndZoom.zoom,
934 site: !userOriginated ? $state.params.site : null,
935 siteLink: !userOriginated ? $state.params.siteLink : null,
936 sitePath: !userOriginated ? $state.params.sitePath : null,
941 console.log("handleBoundsChanged", mapBoundsAndZoom);
943 if (vm.status.siteMapIsOpen) refreshAllSites(mapBoundsAndZoom);
947 * Refreshes all visible elements in the given view port
948 * @param mapBoundsAndZoom {{top: number, right: number, bottom: number, left: number, zoom: number}} The new bounds and the new zoom level of the map control.
950 function refreshAllSites(mapBoundsAndZoom) {
952 // Datenbankabfragen werden werden in kleine Chunks zerlegt.
953 // Jeder Chunk enthält maximal ${chunkSize} (100) Sites und die Anzahl aller Sites für die spezifische Abfrage in der Antwort.
954 // Sind noch nicht alle Chunks von der Datenbank angefordert worden, wird zuerst der nächste Chunk angefordert.
955 // Der aktuell zurückgegebene Chunk wird aber erst verarbeitet, bevor in einer weiteren Callback-Situation der nächste Datenbank-Chunk abgeholt wird.
956 // Dadurch werden kontinuierlich alle Sites auch auf der Map ergänzt, sobald sie verarbeitet wurden und es kommt zu keiner Verzögerung im Benutzerinterface.
957 // Im Maximum werden aber ${vm.status.maximumCountOfVisibleSites} (2500) Sites abgerufen, um die Google Map nicht zu überlasten.
959 // Fallunterscheidung: gibt es bereits eine aktive Datenbankabfrage zu den Sites?
960 // => Nein: Starte eine neue Datenbankabfrage
961 // => Ja: Breche die aktuelle Verarbeitung ab, aktualisiere den Status und starte dann eine neue Datenbankabfrage.
962 if (vm.status.processing) {
963 waitForCancelMapBoundAndZoom = mapBoundsAndZoom;
965 if (cancelRequested) {
966 console.log("Cancel is already requested.");
970 // Request a database loop cancel.
971 cancelRequested = true;
972 console.log("New Cancel requested.")
974 // Wait until the current database loos was canceled.
975 cancelDefer.promise.then(function () {
976 // The database loop is canceled. Create a new defer for the next possible cancel request.
977 console.log("Cancel fulfilled.");
978 cancelDefer = $q.defer();
979 // Restart the database loop with the new bounding rectangle.
980 beginProcessing(waitForCancelMapBoundAndZoom);
983 // Do not start a database loop here - so leave the method.
987 // Start a new database loop here.
988 beginProcessing(mapBoundsAndZoom);
991 * The private function which begins a new database processing loop.
992 * @param mapBoundsAndZoom {{top: number, right: number, bottom: number, left: number, zoom: number}} The new bounds and the new zoom level of the map control.
994 function beginProcessing(mapBoundsAndZoom) {
995 // Set the processing flag to true.
996 vm.status.processing = true;
999 var chunkSiteStartIndex = 0;
1001 // If a site link cannot convert its siteA or siteZ (id) Parameter to a real site object these ids will collected.
1002 // After a subsequent database call to get these sites the queued site links will be converted.
1003 var additionalSiteIds = [];
1004 var queuedSiteLinksToConvert = [];
1006 // Entferne Sites, welche aufgrund des neuen Bounds leicht außer Sicht sind von den sichtbaren Sites.
1007 // Hinzufügen von Sites aus den leicht außer Sicht Bereich zum sichtbaren Bereich.
1008 var movedOrRemovedSites = refreshOutOfSightSites(mapBoundsAndZoom);
1009 var removedFromVisibleIds = movedOrRemovedSites.removedFromVisibleIds;
1010 var movedFromVisibleToKnownIds = movedOrRemovedSites.movedFromVisibleToKnownIds;
1011 var removedFromKnownIds = movedOrRemovedSites.removedFromKnownIds;
1012 var movedFromKnownToVisibleIds = movedOrRemovedSites.movedFromKnownToVisibleIds;
1013 var removedSiteLinkIds = movedOrRemovedSites.removedSiteLinkIds;
1015 // While getting all sites collect all site link ids which are available through visible and known sites.
1016 collectedSiteLinkIds = {};
1018 doSiteRequestLoop();
1020 /* internal function within beginProcessing */
1022 * Starts a new database request loop to get all sites.
1024 function doSiteRequestLoop() {
1025 requestNextSiteChunk(mapBoundsAndZoom, chunkSize, chunkSiteStartIndex).then(
1027 * @param result {{addedSiteIds: string[], total: number}} An array with the ids of the sites which are added to the vm.visibleSites dictionary.
1030 // update the frontend before continue with the next database request.
1031 vm.mapSitesComponentApi.updateSites && vm.mapSitesComponentApi.updateSites(result.addedSiteIds, removedFromVisibleIds, movedFromVisibleToKnownIds, removedFromKnownIds, movedFromKnownToVisibleIds);
1032 removedSiteLinkIds && removedSiteLinkIds.length && vm.mapSiteLinksComponentApi.updateSiteLinks && vm.mapSiteLinksComponentApi.updateSiteLinks([], removedSiteLinkIds);
1033 // to not re-remove and re-move already removed and moved sites clear the arrays for any subsequent database requests.
1034 removedFromVisibleIds = [];
1035 movedFromVisibleToKnownIds = [];
1036 removedFromKnownIds = [];
1037 movedFromKnownToVisibleIds = [];
1038 removedSiteLinkIds = [];
1040 if (cancelRequested) {
1041 // reset the cancelRequested flag and resolve the cancel request.
1042 cancelRequested = false;
1043 cancelDefer.resolve();
1047 // Enforce angular to process a new digest cycle.
1048 $timeout(function () {
1049 vm.status.totalSitesInBoundingBox = result.total;
1050 vm.status.loadedSitesInBoundingBox = Math.min(chunkSiteStartIndex + chunkSize, result.total);
1051 if (chunkSiteStartIndex + chunkSize >= result.total || chunkSiteStartIndex + chunkSize >= vm.status.maximumCountOfVisibleSites) {
1052 // This was the last database request to get sites.
1053 // Remove all known site link ids from the collectedSiteLinkIds dictionary.
1054 var knownSiteLinkIds = Object.keys(vm.knownSiteLinks);
1055 knownSiteLinkIds.forEach(function (knownSiteLinkId) {
1056 if (collectedSiteLinkIds[knownSiteLinkId]) {
1057 delete collectedSiteLinkIds[knownSiteLinkId];
1061 // Start the request loop for site links now.
1062 doSiteLinkRequestLoop(Object.keys(collectedSiteLinkIds));
1064 // There are more data, request the database again.
1065 chunkSiteStartIndex += chunkSize;
1067 doSiteRequestLoop();
1074 function endProcessing() {
1075 vm.status.processing = false;
1077 $timeout(function () {
1078 vm.status.site = $state.params.site;
1079 vm.status.siteLink = $state.params.siteLink;
1084 * Starts a new database request loop to get all site links.
1086 function doSiteLinkRequestLoop(siteLinkIds) {
1087 var requestedSiteLinkIds = siteLinkIds.splice(0, chunkSize);
1089 $mwtnTopology.getSiteLinksByIds(requestedSiteLinkIds).then(
1091 * Processes the database result.
1092 * The returned site links doesn't contain site objects but site ids.
1093 * In a subsequent step these ids will be converted into real site objects.
1094 * @param result {{id: string, siteA: string, siteZ: string}[]} The database result.
1097 // Subsequent convert.
1098 // All site links which siteA and siteZ site ids are known could be added to vm.knownSiteLinks.
1099 // The rest has to saved until all requests are finished.
1100 // Another database call will get the additional sites to convert these site links.
1101 var addedSiteLinkIds = result.reduce(function (accumulator, currentSiteLink) {
1102 var siteA = vm.visibleSites[currentSiteLink.siteA] || vm.knownSites[currentSiteLink.siteA];
1103 var siteZ = vm.visibleSites[currentSiteLink.siteZ] || vm.knownSites[currentSiteLink.siteZ];
1104 if (siteA && siteZ) {
1105 vm.knownSiteLinks[currentSiteLink.id] = {
1106 id: currentSiteLink.id,
1109 type: currentSiteLink.type
1111 accumulator.push(currentSiteLink.id);
1113 // Theoretically, only siteA or siteZ can be unknown. I still check both separately.
1114 var isPushed = false;
1116 additionalSiteIds.push(currentSiteLink.siteA);
1117 queuedSiteLinksToConvert.push(currentSiteLink);
1121 additionalSiteIds.push(currentSiteLink.siteZ);
1122 isPushed || queuedSiteLinksToConvert.push(currentSiteLink);
1128 // update the frontend before continue with the next database request.
1129 vm.mapSiteLinksComponentApi.updateSiteLinks && vm.mapSiteLinksComponentApi.updateSiteLinks(addedSiteLinkIds);
1131 if (cancelRequested) {
1132 // reset the cancelRequested flag and resolve the cancel request.
1133 cancelRequested = false;
1134 cancelDefer.resolve();
1138 // Enforce angular to process a new digest cycle. (And to let angular time to redraw the map.)
1139 $timeout(function () {
1140 if (siteLinkIds.length === 0) {
1141 if (additionalSiteIds.length === 0) {
1146 // Only get a maximum of chunkSize additional sites.
1147 // Site links more than that will not be drawed in the map due to performance reasons.
1148 // If the user zooms in the additional site links will be requested again.
1149 $mwtnTopology.getSitesByIds(additionalSiteIds.splice(0, chunkSize)).then(
1151 * Processes the database result to gain new sites within the bounding box of the database request.
1152 * @param result {{total: number, sites: {id: string, name: string, location: {lat: number, lng: number}, amslGround: number, references: {siteLinks: string[]}}[]}} The database result.
1155 var addedSiteLinkIds = queuedSiteLinksToConvert.reduce(function (accumulator, currentSiteLink) {
1156 var siteA = vm.visibleSites[currentSiteLink.siteA] || vm.knownSites[currentSiteLink.siteA] || result.sites.find(
1158 return site.id === currentSiteLink.siteA
1160 var siteZ = vm.visibleSites[currentSiteLink.siteZ] || vm.knownSites[currentSiteLink.siteZ] || result.sites.find(
1162 return site.id === currentSiteLink.siteZ
1164 if (siteA && siteZ) {
1165 vm.knownSiteLinks[currentSiteLink.id] = {
1166 id: currentSiteLink.id,
1169 type: currentSiteLink.type
1171 accumulator.push(currentSiteLink.id);
1175 // update the frontend the last time until the user changed the map position.
1176 vm.mapSiteLinksComponentApi.updateSiteLinks && vm.mapSiteLinksComponentApi.updateSiteLinks(addedSiteLinkIds);
1183 // There are more data, request the database again.
1184 doSiteLinkRequestLoop(siteLinkIds);
1195 * Recalculates the targets site container for Sites within knownSites and visibleSites.
1196 * @param mapBoundsAndZoom {{top: number, right: number, bottom: number, left: number, zoom: number}} The new bounds and the new zoom level of the map control.
1198 function refreshOutOfSightSites(mapBoundsAndZoom) {
1199 var additionalWidth = (mapBoundsAndZoom.right - mapBoundsAndZoom.left) / 4;
1200 var additionalHeight = (mapBoundsAndZoom.top - mapBoundsAndZoom.bottom) / 4;
1201 var slightlyTop = mapBoundsAndZoom.top + additionalHeight;
1202 var slightlyRight = mapBoundsAndZoom.right + additionalWidth;
1203 var slightlyBottom = mapBoundsAndZoom.bottom - additionalHeight;
1204 var slightlyLeft = mapBoundsAndZoom.left - additionalWidth;
1206 var removedFromVisibleIds = [];
1207 var movedFromVisibleToKnownIds = [];
1208 var removedFromKnownIds = [];
1209 var movedFromKnownToVisibleIds = [];
1210 var removedSiteLinkIds = [];
1212 var visibleSiteKeys = Object.keys(vm.visibleSites);
1213 var knownSiteKeys = Object.keys(vm.knownSites);
1214 var knownSiteLinkKeys = Object.keys(vm.knownSiteLinks);
1216 visibleSiteKeys.forEach(function (siteId) {
1217 /** @var {{id: string, name: string, location: {lat: number, lng: number}, amslGround: number, references: {siteLinks: string[]}} site */
1218 var site = vm.visibleSites[siteId];
1220 if (site.location.lat > slightlyTop || site.location.lng > slightlyRight || site.location.lat < slightlyBottom || site.location.lng < slightlyLeft) {
1221 // This site is completly out of sight - so remove it and add the id to the removedFromVisibleIds list.
1222 delete vm.visibleSites[siteId];
1223 removedFromVisibleIds.push(siteId);
1227 if (site.location.lat > mapBoundsAndZoom.top || site.location.lng > mapBoundsAndZoom.right || site.location.lat < mapBoundsAndZoom.bottom || site.location.lng < mapBoundsAndZoom.left) {
1228 // This site is moved from the visible map bounds to the extended map bounds and will only hide but stay in the google object.
1229 vm.knownSites[siteId] = site;
1230 delete vm.visibleSites[siteId];
1231 movedFromVisibleToKnownIds.push(siteId);
1236 knownSiteKeys.forEach(function (siteId) {
1237 /** @type {{id: string, name: string, location: {lat: number, lng: number}, amslGround: number, references: {siteLinks: string[]}} site */
1238 var site = vm.knownSites[siteId];
1240 if (site.location.lat > slightlyTop || site.location.lng > slightlyRight || site.location.lat < slightlyBottom || site.location.lng < slightlyLeft) {
1241 // This site is completly out of sight - so remove it and add the id to the removedFromKnownIds list.
1242 delete vm.knownSites[siteId];
1243 removedFromKnownIds.push(siteId);
1247 if (site.location.lat <= mapBoundsAndZoom.top && site.location.lng <= mapBoundsAndZoom.right && site.location.lat >= mapBoundsAndZoom.bottom && site.location.lng >= mapBoundsAndZoom.left) {
1248 // This site is moved from the extended map bounds to the visible map bounds. Therefore the hidden google maps object should set visible again.
1249 vm.visibleSites[siteId] = site;
1250 delete vm.knownSites[siteId];
1251 movedFromKnownToVisibleIds.push(siteId);
1256 knownSiteLinkKeys.forEach(function (siteLinkId) {
1257 /** @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 */
1258 var siteLink = vm.knownSiteLinks[siteLinkId];
1259 var siteA = siteLink.siteA;
1260 var siteZ = siteLink.siteZ;
1262 if ((siteA.location.lat > slightlyTop || siteA.location.lng > slightlyRight || siteA.location.lat < slightlyBottom || siteA.location.lng < slightlyLeft) &&
1263 (siteZ.location.lat > slightlyTop || siteZ.location.lng > slightlyRight || siteZ.location.lat < slightlyBottom || siteZ.location.lng < slightlyLeft)) {
1264 // This site link is completly out of sight - so remove it and add the id to the removedSiteLinkIds list.
1265 delete vm.knownSiteLinks[siteLinkId];
1266 removedSiteLinkIds.push(siteLinkId);
1271 removedFromVisibleIds: removedFromVisibleIds,
1272 movedFromVisibleToKnownIds: movedFromVisibleToKnownIds,
1273 removedFromKnownIds: removedFromKnownIds,
1274 movedFromKnownToVisibleIds: movedFromKnownToVisibleIds,
1275 removedSiteLinkIds: removedSiteLinkIds
1280 * Requests a chunk of sites from the database.
1281 * @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.
1282 * @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.
1283 * @param chunkSiteStartIndex {number} The index of the first site returned by the database.
1285 function requestNextSiteChunk(mapBoundsAndZoom, chunkSize, chunkSiteStartIndex) {
1286 var requestNextSiteChunkDefer = $q.defer();
1287 var addedSiteIds = [];
1289 // Request the next chunk.
1290 $mwtnTopology.getSitesInBoundingBox(mapBoundsAndZoom, chunkSize, chunkSiteStartIndex).then(
1292 * Processes the database result to gain new sites within the bounding box of the database request.
1293 * @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.
1296 result.sites.forEach(function (site) {
1297 // The map bounds may have changed since the start of the database request.
1298 // Therefore check if the site is within the bounding rectangle.
1300 if (!$mwtnTopology.isInBounds(mapBoundsAndZoom, site.location)) {
1304 // add all site link ids to the selectedSiteLinkIds dictionary.
1305 site.references.siteLinks.forEach(function (siteLinkId) {
1306 collectedSiteLinkIds[siteLinkId] = true;
1309 // check, if the site is within the knownSites dictionary.
1310 if (vm.knownSites[site.id]) {
1311 delete vm.knownSites[site.id];
1314 // check if the site is within the visibleSites dictionary.
1315 if (vm.visibleSites[site.id]) {
1316 // Override the site (refresh)
1317 vm.visibleSites[site.id] = site;
1319 // Add the site to the dictionary and remember the siteId.
1320 vm.visibleSites[site.id] = site;
1321 addedSiteIds.push(site.id);
1325 requestNextSiteChunkDefer.resolve({
1326 addedSiteIds: addedSiteIds,
1329 }, requestNextSiteChunkDefer.reject);
1331 return requestNextSiteChunkDefer.promise;
1335 * Handles error messages by writing the information to the javascript console.
1336 * Resets the processing flag.
1337 * @param error Information about the error.
1339 function processError(error) {
1340 // Reset the processing flag.
1341 vm.status.processing = false;
1342 // Write the error information to the console.
1343 console.error(error);
1348 mwtnTopologyApp.directive('mwtnTopologySiteView', function () {
1351 controller: 'mwtnTopologySiteViewController',
1353 bindToController: true,
1354 templateUrl: 'src/app/mwtnTopology/templates/siteView.tpl.html',
1356 initialMapBounds: "="
1361 mwtnTopologyApp.controller('mwtnTopologySiteGridController', ['$scope', '$timeout', '$state', '$mwtnCommons', '$mwtnTopology', 'uiGridConstants', function ($scope, $timeout, $state, $mwtnCommons, $mwtnTopology, uiGridConstants) {
1364 // The page number to show in the grid.
1365 var paginationPage = 1;
1367 var paginationPageSize = 100;
1368 // The grid column object with current sorting informations.
1369 var sortColumn = null;
1370 // The grid column object with current sorting informations.
1371 var gridFilters = [];
1372 // caches all sites at the current grid page
1373 var sitesAtCurrentPageCache = {};
1375 vm.showAllSites = false;
1377 vm.onNavigateToSite = function (row) {
1378 var site = sitesAtCurrentPageCache[row.entity.id];
1379 $state.go("main.mwtnTopology", {
1381 lat: site.location.lat,
1382 lng: site.location.lng,
1386 }, { notify: false });
1389 // see http://ui-grid.info/docs/#/tutorial/317_custom_templates
1390 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>';
1392 vm.gridOptions = Object.assign({}, $mwtnCommons.gridOptions, {
1393 showGridFooter: false, // disable the grid footer because the pagination component sets its own.
1394 paginationPageSizes: [10, 25, 50, 100],
1395 paginationPageSize: paginationPageSize,
1396 useExternalPagination: true,
1397 useExternalFiltering: true,
1398 useExternalSorting: true,
1413 displayName: "Location"
1416 field: "amslGround",
1418 displayName: "AmslGround"
1421 field: "countLinks",
1423 displayName: "Count (Links)"
1426 field: "countNetworkElements",
1428 displayName: "Count (Network elements)"
1435 enableFiltering: false,
1436 enableSorting: false,
1437 cellTemplate: buttonCellTemplate,
1442 onRegisterApi: function (gridApi) {
1443 // http://ui-grid.info/docs/#/api/ui.grid.core.api:PublicApi
1444 vm.gridApi = gridApi;
1446 vm.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
1447 // Save the current sort column for later use.
1448 sortColumn = (!sortColumns || sortColumns.length === 0)
1454 vm.gridApi.pagination.on.paginationChanged($scope, function (newPage, newPageSize) {
1455 // Save the pagination informations for later use.
1456 paginationPage = newPage;
1457 paginationPageSize = newPageSize;
1461 vm.gridApi.core.on.filterChanged($scope, function () {
1462 // Save the all filters for later use.
1464 this.grid.columns.forEach(function (col, ind) {
1465 if (col.filters[0] && col.filters[0].term) {
1466 filters.push({ field: col.field, term: col.filters[0].term });
1469 gridFilters = filters;
1477 $scope.$on("mapViewVisuability", function (event, data) {
1478 vm.showAllSites = !data;
1481 $scope.$watch("visibleSites", function (newVisibleSites, oldVisibleSites) {
1482 console.log("watch: visibleSites");
1483 if (!vm.showAllSites) loadPage();
1484 }, true); // deep watch, maybe find a better solution; e.g. with an api object like the site in the map.
1486 $scope.$watch(function () { return vm.showAllSites; }, loadPage);
1488 function loadPage() {
1489 if (vm.showAllSites) {
1497 * Calculates the page content of the grid and sets the values to the gridOprions.data object.
1499 function loadLocalPage() {
1500 var siteIds = Object.keys($scope.visibleSites);
1502 var tempData = siteIds.filter(function (siteId, ind, arr) {
1503 var site = $scope.visibleSites[siteId];
1504 return gridFilters.map(function (filter) {
1505 switch (filter.field) {
1507 return (site.references.siteLinks ? site.references.siteLinks.length : 0) == +filter.term;
1509 return site[filter.field].contains(filter.term);
1512 }).map(function (siteId) {
1513 var site = $scope.visibleSites[siteId];
1516 if (!sortColumn || !sortColumn.sort.direction) {
1522 switch (sortColumn.field) {
1524 orderBy = site.references.siteLinks ? site.references.siteLinks.length : 0;
1526 case "countNetworkElements":
1527 orderBy = site.references.networkElements ? site.references.networkElements.length : 0;
1533 orderBy = site[sortColumn.field];
1543 if (sortColumn && sortColumn.sort.direction) {
1544 tempData.sort(function (left, right) {
1545 if (left === right || left.orderBy === right.orderBy) {
1548 if (left.orderBy > right.orderBy) {
1549 return (sortColumn.sort.direction === uiGridConstants.ASC) ? 1 : -1;
1551 return (sortColumn.sort.direction === uiGridConstants.ASC) ? -1 : 1;
1555 var maxPageNumber = Math.max(1, Math.ceil(tempData.length / paginationPageSize));
1556 var currentPage = Math.min(maxPageNumber, paginationPage);
1557 var orderedSitesAtCurrentPage = tempData.slice((currentPage - 1) * paginationPageSize, currentPage * paginationPageSize);
1559 sitesAtCurrentPageCache = {};
1560 var orderedData = [];
1562 orderedSitesAtCurrentPage.forEach(function (orderedSite) {
1563 var site = $scope.visibleSites[orderedSite.id];
1564 sitesAtCurrentPageCache[site.id] = site;
1568 location: site.location.lat.toLocaleString("en-US", {
1569 minimumFractionDigits: 4
1570 }) + ", " + site.location.lng.toLocaleString("en-US", {
1571 minimumFractionDigits: 4
1573 amslGround: site.amslGround,
1574 countLinks: site.references.siteLinks ? site.references.siteLinks.length : 0,
1575 countNetworkElements: site.references.networkElements ? site.references.networkElements.length : 0
1579 $timeout(function () {
1580 vm.gridOptions.data = orderedData;
1581 vm.gridOptions.totalItems = tempData.length;
1586 * Loads the page content for the grid and sets the values to the gridOprions.data object.
1588 function loadRemotePage() {
1590 $mwtnTopology.getSites((sortColumn && sortColumn.field), sortColumn && ((sortColumn.sort.direction && sortColumn.sort.direction === uiGridConstants.ASC) ? 'asc' : 'desc'), gridFilters, paginationPageSize, (paginationPage - 1) * paginationPageSize).then(function (result) {
1591 sitesAtCurrentPageCache = result.sites.reduce(function (acc, cur, ind, arr) {
1595 vm.gridOptions.data = result.sites.map(function (site) {
1599 location: site.location.lat.toLocaleString("en-US", {
1600 minimumFractionDigits: 4
1601 }) + ", " + site.location.lng.toLocaleString("en-US", {
1602 minimumFractionDigits: 4
1604 amslGround: site.amslGround,
1605 countLinks: site.references.siteLinks ? site.references.siteLinks.length : 0,
1606 countNetworkElements: site.references.networkElements ? site.references.networkElements.length : 0
1609 vm.gridOptions.totalItems = result.total;
1614 mwtnTopologyApp.directive('mwtnTopologySiteGrid', function () {
1618 controller: 'mwtnTopologySiteGridController',
1623 templateUrl: 'src/app/mwtnTopology/templates/siteGrid.tpl.html'
1627 mwtnTopologyApp.controller('mwtnTopologySiteLinkGridController', ['$scope', '$timeout', '$state', '$mwtnCommons', '$mwtnTopology', 'uiGridConstants', function ($scope, $timeout, $state, $mwtnCommons, $mwtnTopology, uiGridConstants) {
1630 // The page number to show in the grid.
1631 var paginationPage = 1;
1633 var paginationPageSize = 100;
1634 // The grid column object with current sorting informations.
1635 var sortColumn = null;
1636 // The grid column object with current sorting informations.
1637 var gridFilters = [];
1639 var linksAtCurrentPageCache = {};
1641 vm.showAllLinks = false;
1643 vm.onNavigateToLink = function (row) {
1644 var link = linksAtCurrentPageCache[row.entity.id];
1646 var top = link.siteA.location.lat >= link.siteZ.location.lat ? link.siteA.location.lat : link.siteZ.location.lat;
1647 var bottom = link.siteA.location.lat >= link.siteZ.location.lat ? link.siteZ.location.lat : link.siteA.location.lat;
1649 // todo: this code is not able to handle links which overlap the -100|180 ° borderline
1650 var left = link.siteA.location.lng <= link.siteZ.location.lng ? link.siteA.location.lng : link.siteZ.location.lng;
1651 var right = link.siteA.location.lng <= link.siteZ.location.lng ? link.siteZ.location.lng : link.siteA.location.lng;
1653 $state.go("main.mwtnTopology", {
1661 }, { notify: false });
1665 // see http://ui-grid.info/docs/#/tutorial/317_custom_templates
1666 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>';
1668 vm.gridOptions = Object.assign({}, $mwtnCommons.gridOptions, {
1669 // enableFiltering: false,
1670 showGridFooter: false, // disable the grid footer because the pagination component sets its own.
1671 paginationPageSizes: [10, 25, 50, 100],
1672 paginationPageSize: paginationPageSize,
1673 useExternalPagination: true,
1674 useExternalFiltering: true,
1675 useExternalSorting: true,
1690 displayName: "SiteA"
1695 displayName: "SiteZ"
1702 enableFiltering: false,
1703 enableSorting: false,
1704 cellTemplate: buttonCellTemplate,
1709 onRegisterApi: function (gridApi) {
1710 // http://ui-grid.info/docs/#/api/ui.grid.core.api:PublicApi
1711 vm.gridApi = gridApi;
1713 vm.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
1714 // save the current sort column for later use.
1715 sortColumn = (!sortColumns || sortColumns.length === 0) ?
1722 vm.gridApi.pagination.on.paginationChanged($scope, function (newPage, newPageSize) {
1723 paginationPage = newPage;
1724 paginationPageSize = newPageSize;
1728 vm.gridApi.core.on.filterChanged($scope, function () {
1729 // Save the all filters for later use.
1731 this.grid.columns.forEach(function (col, ind) {
1732 if (col.filters[0] && col.filters[0].term) {
1733 filters.push({ field: col.field, term: col.filters[0].term });
1736 gridFilters = filters;
1744 $scope.$on("mapViewVisuability", function (event, data) {
1745 vm.showAllLinks = !data;
1748 $scope.$watch("knownSiteLinks", function (newKnownSiteLinks, oldKnownSiteLinks) {
1749 console.log("watch: knownSiteLinks");
1751 }, true); // deep watch, maybe find a better solution; e.g. with an api object like the site in the map.
1753 $scope.$watch(function () { return vm.showAllLinks; }, loadPage);
1755 function loadPage() {
1756 if (vm.showAllLinks) {
1764 * Calculates the page content of the grid and sets the values to the gridOprions.data object.
1766 function loadLocalPage() {
1767 var linkIds = Object.keys($scope.knownSiteLinks);
1769 var tempData = linkIds.filter(function (linkId, ind, arr) {
1770 var link = $scope.knownSiteLinks[linkId];
1771 return gridFilters.map(function (filter) {
1772 switch (filter.field) {
1774 return link.siteA.id.contains(filter.term);
1776 return link.siteZ.id.contains(filter.term);
1778 return link[filter.field].contains(filter.term);
1781 }).map(function (linkId) {
1782 var link = $scope.knownSiteLinks[linkId];
1785 if (!sortColumn || !sortColumn.sort.direction) {
1791 switch (sortColumn.field) {
1793 orderBy = link.siteA.id;
1796 orderBy = link.siteZ.id;
1799 orderBy = link[sortColumn.field];
1809 if (sortColumn && sortColumn.sort.direction) {
1810 tempData.sort(function (left, right) {
1811 if (left === right || left.orderBy === right.orderBy) {
1814 if (left.orderBy > right.orderBy) {
1815 return (sortColumn.sort.direction === uiGridConstants.ASC) ? 1 : -1;
1817 return (sortColumn.sort.direction === uiGridConstants.ASC) ? -1 : 1;
1821 var maxPageNumber = Math.max(1, Math.ceil(tempData.length / paginationPageSize));
1822 var currentPage = Math.min(maxPageNumber, paginationPage);
1823 var orderedSitesAtCurrentPage = tempData.slice((currentPage - 1) * paginationPageSize, currentPage * paginationPageSize);
1825 linksAtCurrentPageCache = {};
1826 var orderedData = [];
1828 orderedSitesAtCurrentPage.forEach(function (orderdLink) {
1829 var link = $scope.knownSiteLinks[orderdLink.id];
1830 linksAtCurrentPageCache[link.id] = link;
1834 siteIdA: link.siteA.id,
1835 siteIdZ: link.siteZ.id,
1840 $timeout(function () {
1841 vm.gridOptions.data = orderedData;
1842 vm.gridOptions.totalItems = tempData.length;
1847 * Loads the page content for the grid and sets the values to the gridOprions.data object.
1849 function loadRemotePage() {
1850 linksAtCurrentPageCache = {};
1852 // the links for one page
1853 $mwtnTopology.getLinks((sortColumn && sortColumn.field), sortColumn && ((sortColumn.sort.direction && sortColumn.sort.direction === uiGridConstants.ASC) ? 'asc' : 'desc'), gridFilters, paginationPageSize, (paginationPage - 1) * paginationPageSize).then(function (result) {
1854 // get all site id s
1856 result.links.forEach(function (link) {
1857 siteIds[link.siteA] = link.siteA;
1858 siteIds[link.siteZ] = link.siteZ;
1862 $mwtnTopology.getSitesByIds(Object.keys(siteIds)).then(function (sitesResult) {
1863 var sites = sitesResult.sites.reduce(function (acc, cur, ind, arr) {
1867 result.links.forEach(function (link) {
1868 linksAtCurrentPageCache[link.id] = {
1871 siteA: sites[link.siteA],
1872 siteZ: sites[link.siteZ],
1876 vm.gridOptions.data = result.links.map(function (link) {
1880 siteIdA: link.siteA,
1881 siteIdZ: link.siteZ,
1885 vm.gridOptions.totalItems = result.total;
1894 mwtnTopologyApp.directive('mwtnTopologySiteLinkGrid', function () {
1898 controller: 'mwtnTopologySiteLinkGridController',
1900 templateUrl: 'src/app/mwtnTopology/templates/siteLinkGrid.tpl.html',
1907 mwtnTopologyApp.controller('mwtnTopologySitePathGridController', ['$scope', function ($scope) {
1910 vm.showAllPaths = false;
1912 $scope.$on("mapViewVisuability", function (event, data) {
1913 vm.showAllPaths = !data;
1918 mwtnTopologyApp.directive('mwtnTopologySitePathGrid', function () {
1922 controller: 'mwtnTopologySitePathGridController',
1924 templateUrl: 'src/app/mwtnTopology/templates/sitePathGrid.tpl.html'
1928 /********************************************* Physical ***********************************/
1930 mwtnTopologyApp.controller('mwtnTopologyPhysicalViewController', ['$scope', '$q', '$timeout', '$state', '$window', '$mwtnTopology', function ($scope, $q, $timeout, $state, $window, $mwtnTopology) {
1934 networkElementsIsOpen: false,
1938 $scope.$watchCollection(function () { return [vm.status.graphIsOpen, vm.status.networkElementsIsOpen, vm.status.LinksIsOpen] }, function (newVal, oldVal) {
1939 if (newVal[1] || newVal[2]) {
1940 $timeout(function () {
1941 $window.dispatchEvent(new Event("resize"));
1948 mwtnTopologyApp.directive('mwtnTopologyPhysicalView', function () {
1951 controller: 'mwtnTopologyPhysicalViewController',
1953 bindToController: true,
1954 templateUrl: 'src/app/mwtnTopology/templates/physicalView.tpl.html',
1962 * Typedefinitions for the mwtnTopologyPhysicalPathData service.
1963 * @typedef { { 'site' | 'device' | 'port' } } NodeLayerType
1964 * @typedef { { x: number, y: number } } PositionType
1965 * @typedef { { id: string, label: string, parent: string, grentparent: string, active: string, latitude: number, longitude: number } } NodeDataVo
1966 * @typedef { { data: NodeDataVo, position: PositionType } } NodeVo
1967 * @typedef { { id: string, label: string, parent: string, type: string, layer: NodeLayerType, active: boolean, latitude?: number, longitude?: number } } NodeData
1968 * @typedef { { data: NodeData, position: PositionType } } Node
1969 * @typedef { { id: string, source: string, target: string, label: string , lentgh: string, azimuthAZ: string , azimuthZA: string , layer: string , active: string } } EdgeData
1970 * @typedef { { data: EdgeData } Edge
1972 mwtnTopologyApp.factory("mwtnTopologyPhysicalPathData", ['$q','$mwtnTopology',
1973 /** @param $q { ng.IQService } */
1974 function ($q, $mwtnTopology) {
1990 'content': 'data(label)',
1991 'text-valign': 'center',
1992 'text-halign': 'center',
1993 'background-color': '#666666',
1994 'border-color': '#000000',
1995 'border-width': '1px',
2000 selector: 'node[layer = "MWPS"]',
2002 'content': 'data(label)',
2003 'text-valign': 'center',
2004 'text-halign': 'center',
2005 'background-color': '#316ac5',
2006 'border-color': '#000000',
2007 'border-width': '1px',
2012 selector: '$node > node',
2014 'shape': 'roundrectangle',
2015 'padding-top': '10px',
2016 'padding-left': '10px',
2017 'padding-bottom': '10px',
2018 'padding-right': '10px',
2019 'text-valign': 'top',
2020 'text-halign': 'center',
2021 'background-color': '#eeeeee',
2023 'border-color': '#888888'
2027 selector: 'node[type = "site"]',
2029 'shape': 'roundrectangle',
2030 'padding-top': '10px',
2031 'padding-left': '10px',
2032 'padding-bottom': '10px',
2033 'padding-right': '10px',
2034 'text-valign': 'center',
2035 'text-halign': 'center',
2036 'background-color': '#fefefe',
2038 'border-color': '#888888',
2039 'font-weight': 'bold'
2043 selector: 'node[type = "device"][active = "true"]',
2045 'background-color': '#316ac5',
2046 'background-opacity': '0.3',
2047 'border-color': '#316ac5',
2048 'border-width': '2px',
2053 selector: 'node[type = "port"][active = "true"]',
2055 'background-opacity': '1.0',
2059 selector: 'node[active = "false"]',
2061 'background-opacity': '0.3',
2062 'border-opacity': '0.5'
2069 'content': 'data(id)',
2070 'target-arrow-shape': 'triangle',
2071 'line-color': '#666666',
2076 selector: 'edge[active = "false"]',
2078 'line-color': '#cccccc',
2079 'text-opacity': '0.9'
2083 selector: 'edge[layer = "MWPS"]',
2085 'content': 'data(id)',
2086 'target-arrow-shape': 'triangle',
2088 'line-color': '#316ac5',
2093 selector: 'edge[layer = "MWPS"][active = "false"]',
2095 'line-color': '#C0D1EC',
2096 'text-opacity': '0.9'
2100 selector: ':selected',
2102 'background-color': 'black',
2103 'line-color': 'black',
2104 'target-arrow-color': 'black',
2105 'source-arrow-color': 'black'
2110 var events = eventsFabric();
2112 /** Heplerfunction to retrive all elements from the database and convert to the structure needed */
2113 function getElements() {
2114 var res = $q.defer();
2117 $mwtnTopology.getAllNodes(),
2118 $mwtnTopology.getAllEdges()
2119 ]).then(function (results) {
2120 res.resolve({ nodes: results[0], edges: results[1] });
2128 getElements: getElements,
2133 var someMethodChangingTheElements = function () {
2134 // @Martin: hier kannst Du die Elements ändern, anschließend mußt Du das Ereignis veröffentlichen
2135 // das Ereigniss wird in der Directive aufgefangen und die Grig wird neu gezeichnet
2137 // Hinweis: Die Reihenfolge muss so bleiben und du kannst NUR result.elements ändern.
2139 events.publish("elementsChanged", {
2140 elements: result.elements
2147 mwtnTopologyApp.directive("mwtnTopologyPhysicalPathGraph", ["mwtnTopologyPhysicalPathData", "$mwtnTopology", "$mwtnCommons", function (pathGraphData, $mwtnTopology, $mwtnCommons) {
2152 template: '<div style="height:750px; width: 100%;"></div>',
2153 controller: function () {
2159 link: function (scope, element, attrs, ctrl) {
2161 var cy = cytoscape({
2162 container: element[0],
2164 boxSelectionEnabled: false,
2165 autounselectify: true,
2167 style: pathGraphData.styles,
2177 pan: { x: 100, y: 50 }
2180 pathGraphData.getElements().then(function (elements) {
2185 // disable drag & drop
2186 cy.nodes().ungrabify();
2189 var filterActiveMountPoints = function (mountpoints) {
2190 return mountpoints.filter(function (mountpoint) {
2191 if (!mountpoint) return false;
2192 // console.warn(mountpoint['node-id'], mountpoint['netconf-node-topology:connection-status']);
2193 return mountpoint['netconf-node-topology:connection-status'] === 'connected';
2194 }).map(function (mountpoint) {
2195 return mountpoint['node-id'];
2199 var setDevicesActive = function (nodeIds) {
2200 // console.warn(nodeIds);
2201 cy.nodes().filter(function (node) {
2202 node.data('active', 'false');
2203 return node.data('type') === 'device' && nodeIds.contains(node.data('id'));
2204 }).map(function (node) {
2205 node.data('active', 'true');
2209 var setAllDevicesInactive = function () {
2210 cy.nodes().map(function (node) {
2211 node.data('active', 'false');
2215 var setPortAndEdgedActive = function () {
2216 cy.edges().map(function (edge) {
2217 var active = 'true';
2218 edge.connectedNodes().map(function (port) {
2219 // console.log(' node', JSON.stringify(edge.data()));
2220 var parent = cy.getElementById(port.data('parent'));
2221 if (parent.data('active') === 'false') {
2222 port.data('active', 'false');
2223 edge.data('active', 'false');
2225 port.data('active', 'true');
2231 var setCss = function () {
2232 var width = 5 / cy.zoom();
2233 var stroke = 1 / cy.zoom();
2234 cy.edges().css('width', width);
2235 cy.nodes().css('border-width', stroke);
2238 var init = function () {
2239 $mwtnCommons.getMountPoints().then(function (mountpoints) {
2240 var filtered = filterActiveMountPoints(mountpoints);
2241 setDevicesActive(filtered);
2242 setPortAndEdgedActive();
2243 }, function (error) {
2244 setAllDevicesInactive();
2245 setPortAndEdgedActive();
2252 var orderLtps = function () {
2258 var selector = "[type = 'device']";
2259 var devices = cy.nodes(selector).sort(function (a, b) {
2260 if (a.children().length > b.children().length) return -1;
2261 if (a.children().length < b.children().length) return 1;
2263 }).map(function (device) {
2264 device.children().filter(function (ltp) {
2265 return done.indexOf(ltp.id()) === -1;
2266 }).map(function (ltp) {
2267 ltp.position({ x: x, y: y });
2270 ltp.connectedEdges().map(function (edge) {
2271 edge.connectedNodes().sort(function (a, b) {
2272 if (a.data("parent") === device.id()) return -1;
2274 }).map(function (node) {
2275 // x = remeberX + 0 * offset;
2276 node.position({ x: x, y: y });
2279 done.push(node.id());
2285 done.push(ltp.id());
2287 y = row * 6 * offset;
2293 y = row * 6 * offset;
2299 var dragedNodes = [];
2300 // add an event handler for 'tabdrag' for all ports
2301 cy.on('drag', 'node[type = "port"]', function (event) {
2302 var id = event.target.data().id;
2303 dragedNodes.indexOf(id) <= -1 && dragedNodes.push(id);
2306 cy.on('tap', function (event) {
2307 if (event.target !== cy) {
2308 console.info('click', JSON.stringify(event.target.data()));
2317 cy.on('zoom', function (event) {
2321 // global keyboard event handler
2322 function handleKey(e) {
2323 if (!e.ctrlKey && !e.commandKey) return;
2327 cy.nodes().grabify();
2332 cy.nodes().ungrabify();
2334 var modifiedNodes = dragedNodes.map(id => ({ id: id, position: cy.nodes().getElementById(id).position() }));
2335 $mwtnTopology.saveChangedNodes(modifiedNodes);
2336 console.log("dragedNodes", modifiedNodes);
2340 // console.log(e.which);
2341 // e.preventDefault();
2347 // register global keyboard event handler
2348 window.addEventListener('keydown', handleKey, false);
2350 scope.$on('$destroy', function () {
2351 // un-register global keyboard event handler
2352 window.removeEventListener('keydown', handleKey, false);
2359 mwtnTopologyApp.controller("mwtnTopologyNetworkElementsGridController", ['$scope', '$timeout', '$state', '$mwtnCommons', '$mwtnTopology', 'uiGridConstants', 'mwtnTopologyPhysicalPathData', function ($scope, $timeout, $state, $mwtnCommons, $mwtnTopology, uiGridConstants, mwtnTopologyPhysicalPathData) {
2362 // The page number to show in the grid.
2363 var paginationPage = 1;
2365 var paginationPageSize = 100;
2366 // The grid column object with current sorting informations.
2367 var sortColumn = null;
2368 // The grid column object with current sorting informations.
2369 var gridFilters = [];
2370 // caches all sites at the current grid page
2372 vm.gridOptions = Object.assign({}, $mwtnCommons.gridOptions, {
2373 showGridFooter: false, // disable the grid footer because the pagination component sets its own.
2374 paginationPageSizes: [10, 25, 50, 100],
2375 paginationPageSize: paginationPageSize,
2376 useExternalPagination: true,
2377 useExternalFiltering: true,
2378 useExternalSorting: true,
2388 displayName: "Layer",
2394 displayName: "Active",
2400 displayName: "Latitude",
2406 displayName: "Longitude",
2410 field: "installed", // capacity in Mbit/s
2412 displayName: "Installed [Mbit/s]",
2413 className: "number",
2417 field: "configured", // capacity in Mbit/s
2419 displayName: "Configured [Mbit/s]",
2420 className: "number",
2424 field: "effective", // capacity in Mbit/s
2426 displayName: "Effective [Mbit/s]",
2427 className: "number",
2432 onRegisterApi: function (gridApi) {
2433 // http://ui-grid.info/docs/#/api/ui.grid.core.api:PublicApi
2434 vm.gridApi = gridApi;
2436 vm.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
2437 // Save the current sort column for later use.
2438 sortColumn = (!sortColumns || sortColumns.length === 0) ?
2444 vm.gridApi.pagination.on.paginationChanged($scope, function (newPage, newPageSize) {
2445 // Save the pagination informations for later use.
2446 paginationPage = newPage;
2447 paginationPageSize = newPageSize;
2451 vm.gridApi.core.on.filterChanged($scope, function () {
2452 // Save the all filters for later use.
2454 this.grid.columns.forEach(function (col, ind) {
2455 if (col.filters[0] && col.filters[0].term) {
2458 term: col.filters[0].term
2462 gridFilters = filters;
2471 * Calculates the page content of the grid and sets the values to the gridOprions.data object.
2473 function loadPage() {
2475 // extract all ports
2476 var ports = mwtnTopologyPhysicalPathData.elements.nodes.filter(function (node, ind, arr) {
2477 return node && node.data && node.data.type === 'port';
2478 }).reduce(function (acc, cur, ind, arr) {
2479 if (cur.data) acc[cur.data.id] = cur.data;
2484 var portIds = Object.keys(ports);
2486 // apply the grid filters
2487 var tempData = portIds.filter(function (portId, ind, arr) {
2488 var port = ports[portId];
2489 return gridFilters.map(function (filter) {
2490 switch (filter.field) {
2492 return port[filter.field].toString().contains(filter.term);
2494 return port[filter.field].contains(filter.term);
2497 }).map(function (portId) {
2498 var port = ports[portId];
2501 if (!sortColumn || !sortColumn.sort.direction) {
2507 switch (sortColumn.field) {
2509 orderBy = port[sortColumn.field] ? 1 : 0;
2512 orderBy = port[sortColumn.field];
2522 if (sortColumn && sortColumn.sort.direction) {
2523 tempData.sort(function (left, right) {
2524 if (left === right || left.orderBy === right.orderBy) {
2527 if (left.orderBy > right.orderBy) {
2528 return (sortColumn.sort.direction === uiGridConstants.ASC) ? 1 : -1;
2530 return (sortColumn.sort.direction === uiGridConstants.ASC) ? -1 : 1;
2534 var maxPageNumber = Math.max(1, Math.ceil(tempData.length / paginationPageSize));
2535 var currentPage = Math.min(maxPageNumber, paginationPage);
2536 var orderedPortsAtCurrentPage = tempData.slice((currentPage - 1) * paginationPageSize, currentPage * paginationPageSize);
2538 portsAtCurrentPageCache = {};
2539 var orderedData = [];
2541 orderedPortsAtCurrentPage.forEach(function (orderedPort) {
2542 var port = ports[orderedPort.id];
2543 portsAtCurrentPageCache[port.id] = port;
2547 active: port.active,
2548 latitude: port.latitude.toLocaleString("en-US", {
2549 minimumFractionDigits: 4
2551 longitude: port.longitude.toLocaleString("en-US", {
2552 minimumFractionDigits: 4
2560 $timeout(function () {
2561 vm.gridOptions.data = orderedData;
2562 vm.gridOptions.totalItems = tempData.length;
2566 // subscribe to the elementsChanged event to reload the grid page if the data in the service has chenged
2567 mwtnTopologyPhysicalPathData.events.subscribe("elementsChanged", function (data) {
2573 mwtnTopologyApp.directive("mwtnTopologyNetworkElementsGrid", [function () {
2577 controller: 'mwtnTopologyNetworkElementsGridController',
2582 templateUrl: 'src/app/mwtnTopology/templates/networkElementsGrid.tpl.html'
2586 mwtnTopologyApp.controller("mwtnTopologyLinksGridController", ['$scope', '$timeout', '$state', '$mwtnCommons', '$mwtnTopology', 'uiGridConstants', 'mwtnTopologyPhysicalPathData', function ($scope, $timeout, $state, $mwtnCommons, $mwtnTopology, uiGridConstants, mwtnTopologyPhysicalPathData) {
2589 // The page number to show in the grid.
2590 var paginationPage = 1;
2592 var paginationPageSize = 100;
2593 // The grid column object with current sorting informations.
2594 var sortColumn = null;
2595 // The grid column object with current sorting informations.
2596 var gridFilters = [];
2597 // caches all sites at the current grid page
2599 vm.gridOptions = Object.assign({}, $mwtnCommons.gridOptions, {
2600 showGridFooter: false, // disable the grid footer because the pagination component sets its own.
2601 paginationPageSizes: [10, 25, 50, 100],
2602 paginationPageSize: paginationPageSize,
2603 useExternalPagination: true,
2604 useExternalFiltering: true,
2605 useExternalSorting: true,
2615 displayName: "PortA"
2620 displayName: "PortZ"
2625 displayName: "Layer"
2630 displayName: "Length"
2635 displayName: "azimuthAZ"
2640 displayName: "azimuthZA"
2644 onRegisterApi: function (gridApi) {
2645 // http://ui-grid.info/docs/#/api/ui.grid.core.api:PublicApi
2646 vm.gridApi = gridApi;
2648 vm.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
2649 // Save the current sort column for later use.
2650 sortColumn = (!sortColumns || sortColumns.length === 0) ?
2656 vm.gridApi.pagination.on.paginationChanged($scope, function (newPage, newPageSize) {
2657 // Save the pagination informations for later use.
2658 paginationPage = newPage;
2659 paginationPageSize = newPageSize;
2663 vm.gridApi.core.on.filterChanged($scope, function () {
2664 // Save the all filters for later use.
2666 this.grid.columns.forEach(function (col, ind) {
2667 if (col.filters[0] && col.filters[0].term) {
2670 term: col.filters[0].term
2674 gridFilters = filters;
2683 * Calculates the page content of the grid and sets the values to the gridOprions.data object.
2685 function loadPage() {
2686 // extract all ports
2687 var ports = mwtnTopologyPhysicalPathData.elements.nodes.filter(function (node, ind, arr) {
2688 return node && node.data && node.data.type === 'port';
2689 }).reduce(function (acc, cur, ind, arr) {
2690 if (cur.data) acc[cur.data.id] = cur.data;
2694 // extract all links
2695 var links = mwtnTopologyPhysicalPathData.elements.edges.filter(function (node, ind, arr) {
2696 return true; // node && node.data && node.data.type === 'port';
2697 }).reduce(function (acc, cur, ind, arr) {
2700 if (cur.data.layer === 'MWPS') {
2701 var portA = ports[cur.data.source];
2702 var portZ = ports[cur.data.target];
2705 cur.data.length = (portA && portZ) ? $mwtnTopology.getDistance(portA.latitude, portA.longitude, portZ.latitude, portZ.longitude) : 0;
2706 cur.data.azimuthAZ = (portA && portZ) ? $mwtnTopology.bearing(portA.latitude, portA.longitude, portZ.latitude, portZ.longitude) : 0;
2707 cur.data.azimuthZA = (portA && portZ) ? $mwtnTopology.bearing(portZ.latitude, portZ.longitude, portA.latitude, portA.longitude) : 0;
2710 cur.data.length = 0;
2711 cur.data.azimuthAZ = 0;
2712 cur.data.azimuthZA = 0;
2714 acc[cur.data.id] = cur.data;
2720 var linkIds = Object.keys(links);
2722 // apply the grid filters
2723 var tempData = linkIds.filter(function (linkId, ind, arr) {
2724 var link = links[linkId];
2725 return gridFilters.map(function (filter) {
2726 switch (filter.field) {
2732 return link[filter.field].contains(filter.term);
2735 }).map(function (linkId) {
2736 var link = links[linkId];
2739 if (!sortColumn || !sortColumn.sort.direction) {
2745 switch (sortColumn.field) {
2747 orderBy = link[sortColumn.field];
2757 if (sortColumn && sortColumn.sort.direction) {
2758 tempData.sort(function (left, right) {
2759 if (left === right || left.orderBy === right.orderBy) {
2762 if (left.orderBy > right.orderBy) {
2763 return (sortColumn.sort.direction === uiGridConstants.ASC) ? 1 : -1;
2765 return (sortColumn.sort.direction === uiGridConstants.ASC) ? -1 : 1;
2769 var maxPageNumber = Math.max(1, Math.ceil(tempData.length / paginationPageSize));
2770 var currentPage = Math.min(maxPageNumber, paginationPage);
2771 var orderedLinksAtCurrentPage = tempData.slice((currentPage - 1) * paginationPageSize, currentPage * paginationPageSize);
2773 linksAtCurrentPageCache = {};
2774 var orderedData = [];
2776 orderedLinksAtCurrentPage.forEach(function (orderedLink) {
2777 var link = links[orderedLink.id];
2778 linksAtCurrentPageCache[link.id] = link;
2781 source: link.source,
2782 target: link.target,
2784 length: link.length,
2785 azimuthAZ: link.azimuthAZ.toLocaleString("en-US", {
2786 minimumFractionDigits: 4
2788 azimuthZA: link.azimuthZA.toLocaleString("en-US", {
2789 minimumFractionDigits: 4
2794 $timeout(function () {
2795 vm.gridOptions.data = orderedData;
2796 vm.gridOptions.totalItems = tempData.length;
2800 // subscribe to the elementsChanged event to reload the grid page if the data in the service has chenged
2801 var subscription = mwtnTopologyPhysicalPathData.events.subscribe("elementsChanged", function (data) {
2803 // to unsubscribe call subscription.remove();
2808 mwtnTopologyApp.directive("mwtnTopologyLinksGrid", [function () {
2812 controller: 'mwtnTopologyLinksGridController',
2817 templateUrl: 'src/app/mwtnTopology/templates/linksGrid.tpl.html'
2821 /********************************************* Ethernet ***********************************/
2823 mwtnTopologyApp.controller('mwtnTopologyEthernetViewController', ['$scope', '$q', '$timeout', '$state', '$window', '$mwtnTopology', function ($scope, $q, $timeout, $state, $window, $mwtnTopology) {
2826 topologyIsOpen: false,
2828 ethConnectionsIsOpen: false
2831 $scope.$watchCollection(function () { return [vm.status.topologyIsOpen, vm.status.portsOpen, vm.status.ethConnectionsIsOpen] }, function (newVal, oldVal) {
2832 if (newVal[1] || newVal[2]) {
2833 $timeout(function () {
2834 $window.dispatchEvent(new Event("resize"));
2841 mwtnTopologyApp.directive('mwtnTopologyEthernetView', function () {
2844 controller: 'mwtnTopologyEthernetViewController',
2846 bindToController: true,
2847 templateUrl: 'src/app/mwtnTopology/templates/ethernetView.tpl.html',
2854 mwtnTopologyApp.factory("mwtnTopologyEthernetPathData", function () {
2870 'content': 'data(label)',
2871 'text-valign': 'center',
2872 'text-halign': 'center',
2873 'background-color': '#eeeeee',
2874 'border-color': '#000000',
2875 'border-width': '1px',
2880 selector: 'node[type = "label"]',
2882 'content': 'data(label)',
2883 'border-width': '0px',
2884 'background-color': '#ffffff',
2885 'font-size': '50px',
2886 'text-valign': 'bottom',
2887 'text-halign': 'right',
2893 selector: 'node[layer = "MWPS"]',
2895 'content': 'data(label)',
2896 'text-valign': 'center',
2897 'text-halign': 'center',
2898 'background-color': '#316ac5',
2899 'border-color': '#000000',
2900 'border-width': '1px',
2905 selector: 'node[layer = "ETH-TTP"]',
2907 'content': 'data(label)',
2908 'text-valign': 'center',
2909 'text-halign': 'center',
2910 'background-color': '#008800',
2911 'border-color': '#004400',
2912 'border-width': '1px',
2917 selector: '$node > node',
2919 'shape': 'roundrectangle',
2920 'padding-top': '10px',
2921 'padding-left': '10px',
2922 'padding-bottom': '10px',
2923 'padding-right': '10px',
2924 'text-valign': 'top',
2925 'text-halign': 'center',
2926 'background-color': '#eeeeee',
2928 'border-color': '#888888'
2932 selector: 'node[type = "site"]',
2934 'shape': 'roundrectangle',
2935 'padding-top': '10px',
2936 'padding-left': '10px',
2937 'padding-bottom': '10px',
2938 'padding-right': '10px',
2939 'text-valign': 'center',
2940 'text-halign': 'center',
2941 'background-color': '#fefefe',
2943 'border-color': '#888888',
2944 'font-weight': 'bold'
2948 selector: 'node[type = "device"]',
2950 'background-color': '#eeeeee',
2951 'background-opacity': '0.1',
2952 'border-color': '#888888',
2953 'border-width': '2px',
2958 selector: 'node[type = "device"][active = "true"]',
2960 'background-color': '#316ac5',
2961 'background-opacity': '0.1',
2962 'border-color': '#316ac5',
2963 'border-width': '2px',
2968 selector: 'node[type = "port"][active = "true"]',
2970 'background-opacity': '1.0',
2974 selector: 'node[path = "working"]',
2976 'background-color': '#FFA500',
2977 'border-color': '#FFA500',
2978 'border-width': '2px',
2983 selector: 'node[path = "protection"]',
2985 'background-color': '#ffffff',
2986 'border-color': '#FFA500',
2987 'border-width': '2px',
2992 selector: '$node > node[path = "working"]',
2994 'background-color': '#FFA500',
2995 'border-color': '#FFA500',
2996 'border-width': '2px',
3001 selector: '$node > node[path = "protection"]',
3003 'border-color': '#FFA500',
3004 'border-width': '2px',
3009 selector: 'node[active = "false"]',
3011 'background-opacity': '0.3',
3012 'border-opacity': '0.5'
3018 'content': 'data(id)',
3019 'target-arrow-shape': 'triangle',
3020 'line-color': '#666666',
3025 selector: 'edge[active = "false"]',
3027 'line-color': '#cccccc',
3028 'text-opacity': '0.9'
3032 selector: 'edge[layer = "ETC"]',
3034 'content': 'data(id)',
3035 'target-arrow-shape': 'triangle',
3037 'line-color': '#316ac5',
3042 selector: 'edge[layer = "ETH"]',
3045 'target-arrow-shape': 'triangle',
3047 'line-color': '#FFA500',
3051 selector: 'edge[layer = "ETC"][active = "false"]',
3053 'line-color': '#C0D1EC',
3054 'text-opacity': '0.9'
3059 selector: 'edge[layer = "ETH"][path = "false"]',
3065 selector: 'edge[path = "working"]',
3067 'line-color': '#FFA500',
3073 selector: 'edge[path = "protection"]',
3075 'line-color': '#FFA500',
3076 'line-style': 'dashed',
3082 selector: ':selected',
3084 'background-color': 'black',
3085 'line-color': 'black',
3086 'target-arrow-color': 'black',
3087 'source-arrow-color': 'black'
3094 { data: { id: 'label', label : '' , type: 'label' }, position: { x: -259, y: -167 } },
3096 { data: { id: 'north', label : 'north' , type: 'site', latitude: 50.734916, longitude: 7.137636 } },
3097 { data: { id: 'north-east', label : 'north-east' , type: 'site', latitude: 50.733028, longitude: 7.151086 } },
3098 { data: { id: 'north-west', label : 'north-west' , type: 'site', latitude: 50.730230, longitude: 7.126017 } },
3099 { data: { id: 'east', label : 'east' , type: 'site', latitude: 50.725672, longitude: 7.158488 } },
3100 { data: { id: 'west', label : 'west' , type: 'site', latitude: 50.721914, longitude: 7.120521 } },
3101 { data: { id: 'south-east', label : 'south-east' , type: 'site', latitude: 50.717158, longitude: 7.155506 } },
3102 { data: { id: 'south-west', label : 'south-west' , type: 'site', latitude: 50.714359, longitude: 7.130437 } },
3103 { data: { id: 'south', label : 'south' , type: 'site', latitude: 50.712472, longitude: 7.143887 } },
3105 { data: { id: 'ADVA-Y', label : 'ADVA-Y' , parent : 'north-east', type: 'device', active: 'true' , latitude: 50.733028, longitude: 7.151086 } },
3106 { data: { id: 'ADVA-Z', label : 'ADVA-Z' , parent : 'south', type: 'device', active: 'true' , latitude: 50.712472, longitude: 7.143887 } },
3107 { data: { id: 'Aviat-A', label : 'Aviat-A' , parent : 'north-east', type: 'device', active: 'true' , latitude: 50.733028, longitude: 7.151086 } },
3108 { data: { id: 'Aviat-Z', label : 'Aviat-Z' , parent : 'east', type: 'device', active: 'true' , latitude: 50.725672, longitude: 7.158488 } },
3109 { data: { id: 'Ceragon-A', label : 'Ceragon-A' , parent : 'north-west', type: 'device', active: 'true' , latitude: 50.730230, longitude: 7.126017 } },
3110 { data: { id: 'Ceragon-Z', label : 'Ceragon-Z' , parent : 'west', type: 'device', active: 'true' , latitude: 50.721914, longitude: 7.120521 } },
3111 { data: { id: 'DragonWave-A', label : 'DragonWave-A' , parent : 'south-west', type: 'device', active: 'true' , latitude: 50.714359, longitude: 7.130437 } },
3112 { data: { id: 'DragonWave-Z', label : 'DragonWave-Z' , parent : 'south', type: 'device', active: 'true' , latitude: 50.712472, longitude: 7.143887 } },
3113 { data: { id: 'ELVA-1-A', label : 'ELVA-1-A' , parent : 'north', type: 'device', active: 'true' , latitude: 50.734916, longitude: 7.137636 } },
3114 { data: { id: 'ELVA-1-Z', label : 'ELVA-1-Z' , parent : 'south-west', type: 'device', active: 'true' , latitude: 50.714359, longitude: 7.130437 } },
3115 { data: { id: 'Ericsson-A', label : 'Ericsson-A' , parent : 'north-east', type: 'device', active: 'true' , latitude: 50.733028, longitude: 7.151086 } },
3116 { data: { id: 'Ericsson-Z', label : 'Ericsson-Z' , parent : 'east', type: 'device', active: 'true' , latitude: 50.725672, longitude: 7.158488 } },
3117 { data: { id: 'Fujitsu-A', label : 'Fujitsu-A' , parent : 'east', type: 'device', active: 'true' , latitude: 50.725672, longitude: 7.158488 } },
3118 { data: { id: 'Fujitsu-Z', label : 'Fujitsu-Z' , parent : 'south-east', type: 'device', active: 'true' , latitude: 50.717158, longitude: 7.155506 } },
3119 { data: { id: 'Huawei-A', label : 'Huawei-A' , parent : 'south-west', type: 'device', active: 'true' , latitude: 50.714359, longitude: 7.130437 } },
3120 { data: { id: 'Huawei-Z', label : 'Huawei-Z' , parent : 'south', type: 'device', active: 'true' , latitude: 50.712472, longitude: 7.143887 } },
3121 { data: { id: 'Intracom-A', label : 'Intracom-A' , parent : 'south', type: 'device', active: 'true' , latitude: 50.712472, longitude: 7.143887 } },
3122 { data: { id: 'Intracom-Z', label : 'Intracom-Z' , parent : 'south-east', type: 'device', active: 'true' , latitude: 50.717158, longitude: 7.155506 } },
3123 { data: { id: 'NEC-A', label : 'NEC-A' , parent : 'north', type: 'device', active: 'true' , latitude: 50.734916, longitude: 7.137636 } },
3124 { data: { id: 'NEC-Z', label : 'NEC-Z' , parent : 'north-east', type: 'device', active: 'true' , latitude: 50.733028, longitude: 7.151086 } },
3125 { data: { id: 'Nokia-A', label : 'Nokia-A' , parent : 'west', type: 'device', active: 'fasel' , latitude: 50.721914, longitude: 7.120521 } },
3126 { data: { id: 'Nokia-Z', label : 'Nokia-Z' , parent : 'south-west', type: 'device', active: 'true' , latitude: 50.714359, longitude: 7.130437 } },
3127 { data: { id: 'SIAE-A', label : 'SIAE-A' , parent : 'south', type: 'device', active: 'true' , latitude: 50.712472, longitude: 7.143887 } },
3128 { data: { id: 'SIAE-Z', label : 'SIAE-Z' , parent : 'south-east', type: 'device', active: 'true' , latitude: 50.717158, longitude: 7.155506 } },
3129 { data: { id: 'ZTE-A', label : 'ZTE-A' , parent : 'north-west', type: 'device', active: 'true' , latitude: 50.730230, longitude: 7.126017 } },
3130 { data: { id: 'ZTE-Z', label : 'ZTE-Z' , parent : 'north', type: 'device', active: 'true' , latitude: 50.734916, longitude: 7.137636 } },
3132 { 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 } },
3133 { 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 } },
3134 { 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 } },
3135 { 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 } },
3136 { 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 } },
3137 { 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 } },
3138 { 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 } },
3139 { 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 } },
3140 { 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 } },
3141 { 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 } },
3142 { 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 } },
3143 { 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 } },
3144 { 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 } },
3145 // { 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 } },
3146 { 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 } },
3147 { 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 } },
3148 { 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 } },
3149 { 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 } },
3150 { 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 } },
3151 { 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 } },
3152 { 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 } },
3153 { 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 } },
3154 // { 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 } },
3155 { 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 } },
3156 { 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 } },
3157 { 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 } },
3158 { 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 } },
3159 { 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 } },
3160 { 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 } },
3161 { 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 } },
3162 { 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 } },
3163 // { 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 } },
3164 // { 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 } },
3165 { 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 } },
3166 { 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 } },
3167 { 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 } },
3168 { 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 } },
3169 // { 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 } },
3170 { 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 } },
3171 { 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 } },
3172 { 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 } },
3173 { 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 } },
3174 { 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 } },
3175 { 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 } },
3176 { 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 } },
3177 { 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 } },
3178 { 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 } },
3179 { 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 } },
3180 { 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 } },
3181 { 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 } },
3182 { 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 } },
3183 { 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 } },
3184 { 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 } },
3185 { 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 } },
3186 { 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 } },
3187 { 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 } },
3188 { 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 } },
3189 { 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 } },
3190 { 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 } },
3191 { 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 } },
3192 { 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 } },
3193 { 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 } },
3194 { 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 } },
3195 // { 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 } },
3196 // { 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 } },
3197 { 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 } },
3198 { 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 } },
3200 { data: { id: 'Spirent#1', label : '#1' , parent : 'Spirent' , type: 'host', layer: 'ETH-TTP', active: 'true', service: 'service13' }, position: { x: 707, y: -167 } },
3201 { data: { id: 'Spirent#2', label : '#2' , parent : 'Spirent' , type: 'host', layer: 'ETH-TTP', active: 'true', service: 'service24' }, position: { x: -259, y: 547 } },
3202 { data: { id: 'Spirent#3', label : '#3' , parent : 'Spirent' , type: 'host', layer: 'ETH-TTP', active: 'true', service: 'service13' }, position: { x: 1292, y: 172 } },
3203 { data: { id: 'Spirent#4', label : '#4' , parent : 'Spirent' , type: 'host', layer: 'ETH-TTP', active: 'true', service: 'service24' }, position: { x: 1490, y: 784 } },
3204 { data: { id: 'Spirent#5', label : '#5' , parent : 'Spirent' , type: 'host', layer: 'ETH-TTP', active: 'true', service: 'service56' }, position: { x: 1754, y: -47 } },
3205 { data: { id: 'Spirent#6', label : '#6' , parent : 'Spirent' , type: 'host', layer: 'ETH-TTP', active: 'true', service: 'service56' }, position: { x: 455, y: 1278 } },
3206 { data: { id: 'Spirent#7', label : '#7' , parent : 'Spirent' , type: 'host', layer: 'ETH-TTP', active: 'true', service: 'service78' }, position: { x: 895, y: 250 } },
3207 { data: { id: 'Spirent#8', label : '#8' , parent : 'Spirent' , type: 'host', layer: 'ETH-TTP', active: 'true', service: 'service78' }, position: { x: 455, y: 778 } },
3212 { data: { id: '21', source: 'Aviat-A#6', target: 'Aviat-Z#6', label: '21', layer: 'ETC' , active: 'true' } },
3213 { data: { id: '31', source: 'Ceragon-A#6', target: 'Ceragon-Z#6', label: '31', layer: 'ETC' , active: 'true' } },
3214 { data: { id: '41', source: 'DragonWave-A#6', target: 'DragonWave-Z#6', label: '41', layer: 'ETC' , active: 'true' } },
3215 { data: { id: '121', source: 'ELVA-1-A#6', target: 'ELVA-1-Z#6', label: '121' , layer: 'ETC' , active: 'true' } },
3216 { data: { id: 'ERI1', source: 'Ericsson-A#6', target: 'Ericsson-Z#6', label: 'ERI1', layer: 'ETC' , active: 'true' } },
3217 { data: { id: '61', source: 'Fujitsu-A#6', target: 'Fujitsu-Z#6', label: '61', layer: 'ETC' , active: 'true' } },
3218 { data: { id: '71', source: 'Huawei-A#6', target: 'Huawei-Z#6', label: '71', layer: 'ETC' , active: 'true' } },
3219 { data: { id: '131', source: 'Intracom-A#6', target: 'Intracom-Z#6', label: '131', layer: 'ETC' , active: 'true' } },
3220 { data: { id: '81', source: 'NEC-A#6', target: 'NEC-Z#6', label: '81', layer: 'ETC' , active: 'true' } },
3221 { data: { id: '91', source: 'Nokia-A#6', target: 'Nokia-Z#6', label: '91', layer: 'ETC' , active: 'true' } },
3222 { data: { id: '101', source: 'SIAE-A#6', target: 'SIAE-Z#6', label: '101', layer: 'ETC' , active: 'true' } },
3223 { data: { id: '111', source: 'ZTE-A#6', target: 'ZTE-Z#6', label: '111', layer: 'ETC' , active: 'true' } },
3225 // { data: { id: 'ETY01', source: 'ADVA-A#1', target: 'Nokia-A34', label: 'ADVA-A#1-Nokia-A34' , layer: 'ETY' , active: 'true' } },
3226 // { data: { id: 'ETY02', source: 'ADVA-A#2', target: 'ZTE-A#4', label: 'ADVA-A#2-ZTE-A#4' , layer: 'ETY' , active: 'false' } },
3227 // { data: { id: 'ETY03', source: 'ADVA-B#1', target: 'Nokia-A13', label: 'ADVA-B#1-Nokia-A13' , layer: 'ETY' , active: 'true' } },
3228 // { data: { id: 'ETY04', source: 'ADVA-B#2', target: 'ZTE-A#3', label: 'ADVA-B#2-ZTE-A#3' , layer: 'ETY' , active: 'true' } },
3229 // { data: { id: 'ETY05', source: 'ADVA-Y#1', target: 'Huawei-Z#4', label: 'ADVA-Y#1-Huawei-Z#4' , layer: 'ETY' , active: 'true' } },
3230 { data: { id: 'ETY06', source: 'ADVA-Y#2', target: 'NEC-Z#4', label: 'ADVA-Y#2-NEC-Z#4' , layer: 'ETY' , active: 'true' } },
3231 { data: { id: 'ETY07', source: 'ADVA-Y#3', target: 'Spirent#3', label: 'ADVA-Y#3-Spirent#3' , layer: 'ETY' , active: 'true' } },
3232 { data: { id: 'ETY08', source: 'ADVA-Y#4', target: 'Ericsson-A#4', label: 'ADVA-Y#4-Ericsson-A#4' , layer: 'ETY' , active: 'true' } },
3233 { data: { id: 'ETY09', source: 'ADVA-Z#1', target: 'Huawei-Z#3', label: 'ADVA-Z#1-Huawei-Z#3' , layer: 'ETY' , active: 'true' } },
3234 // { data: { id: 'ETY10', source: 'ADVA-Z#2', target: 'NEC-Z#3', label: 'ADVA-Z#2-NEC-Z#3' , layer: 'ETY' , active: 'true' } },
3235 { data: { id: 'ETY11', source: 'ADVA-Z#4', target: 'SIAE-A#4', label: 'ADVA-Z#4-SIAE-A#4' , layer: 'ETY' , active: 'true' } },
3236 { data: { id: 'ETY12', source: 'Aviat-A#5', target: 'Spirent#5', label: 'Aviat-A#5-Spirent#5' , layer: 'ETY' , active: 'true' } },
3237 { data: { id: 'ETY13', source: 'Aviat-Z#5', target: 'Fujitsu-A#5', label: 'Aviat-Z#5-Fujitsu-A#5' , layer: 'ETY' , active: 'true' } },
3238 { data: { id: 'ETY14', source: 'Ceragon-A#5', target: 'ZTE-A#5', label: 'Ceragon-A#5-ZTE-A#5' , layer: 'ETY' , active: 'true' } },
3239 { data: { id: 'ETY15', source: 'Ceragon-Z#4', target: 'Spirent#2', label: 'Ceragon-Z#4-Spirent#2' , layer: 'ETY' , active: 'true' } },
3240 { data: { id: 'ETY16', source: 'Ceragon-Z#5', target: 'Nokia-A11', label: 'Ceragon-Z#5-Nokia-A11' , layer: 'ETY' , active: 'true' } },
3241 { data: { id: 'ETY17', source: 'DragonWave-A#2', target: 'Spirent#6', label: 'DragonWave-A#2-Spirent#6' , layer: 'ETY' , active: 'true' } },
3242 { data: { id: 'ETY18', source: 'DragonWave-Z#2', target: 'Intracom-A#2', label: 'DragonWave-Z#2-Intracom-A#2' , layer: 'ETY' , active: 'true' } },
3243 { data: { id: 'ETY19', source: 'ELVA-1-A#2', target: 'Spirent#7', label: 'ELVA-1-A#2-Spirent#7' , layer: 'ETY' , active: 'true' } },
3244 { data: { id: 'ETY20', source: 'ELVA-1-Z#2', target: 'Spirent#8', label: 'ELVA-1-Z#2-Spirent#8' , layer: 'ETY' , active: 'true' } },
3245 { data: { id: 'ETY21', source: 'Ericsson-A#5', target: 'NEC-Z#2', label: 'Ericsson-A#5-NEC-Z#2' , layer: 'ETY' , active: 'true' } },
3246 { data: { id: 'ETY22', source: 'Ericsson-Z#5', target: 'SIAE-Z#5', label: 'Ericsson-Z#5-SIAE-Z#5' , layer: 'ETY' , active: 'true' } },
3247 { data: { id: 'ETY23', source: 'Fujitsu-Z#5', target: 'Intracom-Z#5', label: 'Fujitsu-Z#5-Intracom-Z#5' , layer: 'ETY' , active: 'true' } },
3248 { data: { id: 'ETY24', source: 'Huawei-A#5', target: 'Nokia-Z33', label: 'Huawei-A#5-Nokia-Z33' , layer: 'ETY' , active: 'true' } },
3249 { data: { id: 'ETY25', source: 'Huawei-Z#5', target: 'SIAE-A#5', label: 'Huawei-Z#5-SIAE-A#5' , layer: 'ETY' , active: 'true' } },
3250 { data: { id: 'ETY26', source: 'NEC-A#3', target: 'ZTE-Z#5', label: 'NEC-A#3-ZTE-Z#5' , layer: 'ETY' , active: 'true' } },
3251 { data: { id: 'ETY27', source: 'SIAE-Z#4', target: 'Spirent#4', label: 'SIAE-Z#4-Spirent#4' , layer: 'ETY' , active: 'true' } },
3252 { data: { id: 'ETY28', source: 'ZTE-Z#4', target: 'Spirent#1', label: 'Spirent#1-ZTE-Z#4' , layer: 'ETY' , active: 'true' } },
3254 { 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' } },
3255 { 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' } },
3256 { 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' } },
3257 { 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' } },
3258 { 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' } },
3259 { 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' } },
3260 { 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' } },
3261 { 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' } },
3262 { 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' } },
3263 { 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' } },
3264 { 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' } },
3265 { 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' } },
3266 { 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' } },
3267 { 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' } },
3268 { 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' } },
3269 { 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' } },
3270 { 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' } },
3271 { 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' } },
3272 { 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' } },
3273 { 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' } },
3274 { 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' } },
3275 { 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' } },
3276 { 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' } },
3277 { 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' } },
3278 { 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' } },
3279 { 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' } },
3280 { 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' } },
3281 { 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' } },
3282 { 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' } },
3283 { 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' } },
3284 { 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' } },
3285 { 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' } },
3286 { 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' } },
3287 { 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' } },
3288 { 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' } },
3289 { 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' } },
3290 { 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' } },
3291 { 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' } },
3292 { 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' } },
3293 { 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' } },
3294 { 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' } },
3295 { 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' } },
3296 { 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' } },
3297 { 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' } },
3298 { 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' } },
3302 var events = eventsFabric();
3311 var someMethodChangingTheElements = function () {
3312 // @Martin: hier kannst Du die Elements ändern, anschließend mußt Du das Ereignis veröffentlichen
3313 // das Ereigniss wird in der Directive aufgefangen und die Grig wird neu gezeichnet
3315 // Hinweis: Die Reihenfolge muss so bleiben und du kannst NUR result.elements ändern.
3317 events.publish("elementsChanged", {
3318 elements: result.elements
3325 mwtnTopologyApp.directive("mwtnTopologyEthernetPathGraph", ["mwtnTopologyEthernetPathData", '$mwtnCommons', function (mwtnTopologyEthernetPathData, $mwtnCommons) {
3330 template: '<div id="cy" style="height: 750px; width: 100%;"></div>',
3331 controller: function () {
3337 link: function (scope, element, attrs, ctrl) {
3339 var cy = cytoscape({
3340 container: element[0],
3342 boxSelectionEnabled: false,
3343 autounselectify: true,
3345 style: mwtnTopologyEthernetPathData.styles,
3346 elements: mwtnTopologyEthernetPathData.elements,
3353 // @ Martin: Hier wird das Ereignis aus dem Service aboniert.
3354 // Es ist möglich mehrere Ereignisse zu definieren.
3355 mwtnTopologyEthernetPathData.events.subscribe("elementsChanged", function (data) {
3357 // @Martin: cy aktualisiert sich mit Hilfe der Referenz auf die Elemente aus dem Service
3359 elements: mwtnTopologyEthernetPathData.elements // oder data.elements
3364 // pathGraphData.events.subscribe("styleChanged", function () {
3366 // style: mwtnTopologyEthernetPathData.styles
3372 pan: { x: 150, y: 100 }
3375 var clearService = function () {
3376 var lable = cy.getElementById('label');
3377 lable.data('label', '');
3379 var pathState = ['working', 'protection', 'hidden'];
3380 pathState.map(function (state) {
3381 var selector = "[path = '" + state + "']";
3382 cy.elements(selector).map(function (element) {
3383 element.data('path', 'false');
3388 var highlightService = function (service) {
3389 var lable = cy.getElementById('label');
3390 lable.data('label', service);
3392 var selector = "[service = '" + service + "']";
3393 // start and end service node (host, traffic analyser)
3394 cy.nodes(selector).map(function (node) {
3395 // console.log(node.id());
3396 node.data('path', 'working');
3399 ['protection', 'working'].map(function (state) {
3400 selector = "[service = '" + service + "'][rule = '" + state + "']";
3401 cy.edges(selector).map(function (edge) {
3402 edge.connectedNodes().map(function (node) {
3403 node.data('path', edge.data('rule'));
3407 }).reverse().map(function (state) {
3408 selector = "[path = '" + state + "']";
3409 cy.nodes(selector).connectedEdges().filter(function (edge) {
3410 return edge.data('service') === service || edge.data('layer') !== "ETH";
3411 }).map(function (edge) {
3412 edge.data('path', state);
3416 var filterActiveMountPoints = function (mountpoints) {
3417 return mountpoints.filter(function (mountpoint) {
3418 if (!mountpoint) return false;
3419 // console.warn(mountpoint['node-id'], mountpoint['netconf-node-topology:connection-status']);
3420 return mountpoint['netconf-node-topology:connection-status'] === 'connected';
3421 }).map(function (mountpoint) {
3422 return mountpoint['node-id'];
3426 var setDevicesActive = function (nodeIds) {
3427 // console.warn(nodeIds);
3428 cy.nodes().filter(function (node) {
3429 node.data('active', 'false');
3430 return node.data('type') === 'device' && nodeIds.contains(node.data('id'));
3431 }).map(function (node) {
3432 node.data('active', 'true');
3436 var setAllDevicesInactive = function () {
3437 cy.nodes().map(function (node) {
3438 node.data('active', 'false');
3442 var setPortAndEdgedActive = function () {
3443 cy.edges().map(function (edge) {
3444 var active = 'true';
3445 edge.connectedNodes().map(function (port) {
3446 // console.log(' node', JSON.stringify(edge.data()));
3447 var parent = cy.getElementById(port.data('parent'));
3448 if (parent.data('active') === 'false') {
3449 port.data('active', 'false');
3450 edge.data('active', 'false');
3452 port.data('active', 'true');
3458 var init = function () {
3459 var timerName = 'init ethernet';
3460 console.time(timerName);
3461 $mwtnCommons.getMountPoints().then(function (mountpoints) {
3462 var filtered = filterActiveMountPoints(mountpoints);
3463 setDevicesActive(filtered);
3464 setPortAndEdgedActive();
3465 console.timeEnd(timerName);
3466 }, function (error) {
3467 setAllDevicesInactive();
3468 setPortAndEdgedActive();
3469 console.timeEnd(timerName);
3475 cy.on('tap', function (event) {
3477 if (event.target !== cy) {
3478 if (event.target.data('service')) {
3479 highlightService(event.target.data('service'));
3489 mwtnTopologyApp.controller("mwtnTopologyPortsGridController", ['$scope', '$timeout', '$state', '$mwtnCommons', '$mwtnTopology', 'uiGridConstants', 'mwtnTopologyEthernetPathData', function ($scope, $timeout, $state, $mwtnCommons, $mwtnTopology, uiGridConstants, mwtnTopologyEthernetPathData) {
3492 // The page number to show in the grid.
3493 var paginationPage = 1;
3495 var paginationPageSize = 100;
3496 // The grid column object with current sorting informations.
3497 var sortColumn = null;
3498 // The grid column object with current sorting informations.
3499 var gridFilters = [];
3500 // caches all sites at the current grid page
3502 vm.gridOptions = Object.assign({}, $mwtnCommons.gridOptions, {
3503 showGridFooter: false, // disable the grid footer because the pagination component sets its own.
3504 paginationPageSizes: [10, 25, 50, 100],
3505 paginationPageSize: paginationPageSize,
3506 useExternalPagination: true,
3507 useExternalFiltering: true,
3508 useExternalSorting: true,
3518 displayName: "Layer"
3523 displayName: "Active"
3527 onRegisterApi: function (gridApi) {
3528 // http://ui-grid.info/docs/#/api/ui.grid.core.api:PublicApi
3529 vm.gridApi = gridApi;
3531 vm.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
3532 // Save the current sort column for later use.
3533 sortColumn = (!sortColumns || sortColumns.length === 0) ?
3539 vm.gridApi.pagination.on.paginationChanged($scope, function (newPage, newPageSize) {
3540 // Save the pagination informations for later use.
3541 paginationPage = newPage;
3542 paginationPageSize = newPageSize;
3546 vm.gridApi.core.on.filterChanged($scope, function () {
3547 // Save the all filters for later use.
3549 this.grid.columns.forEach(function (col, ind) {
3550 if (col.filters[0] && col.filters[0].term) {
3553 term: col.filters[0].term
3557 gridFilters = filters;
3566 * Calculates the page content of the grid and sets the values to the gridOprions.data object.
3568 function loadPage() {
3570 // extract all ports
3571 var ports = mwtnTopologyEthernetPathData.elements.nodes.filter(function (node, ind, arr) {
3572 return node && node.data && node.data.type === 'port';
3573 }).reduce(function (acc, cur, ind, arr) {
3574 if (cur.data) acc[cur.data.id] = cur.data;
3579 var portIds = Object.keys(ports);
3581 // apply the grid filters
3582 var tempData = portIds.filter(function (portId, ind, arr) {
3583 var port = ports[portId];
3584 return gridFilters.map(function (filter) {
3585 switch (filter.field) {
3587 return port[filter.field].toString().contains(filter.term);
3589 return port[filter.field].contains(filter.term);
3592 }).map(function (portId) {
3593 var port = ports[portId];
3596 if (!sortColumn || !sortColumn.sort.direction) {
3602 switch (sortColumn.field) {
3604 orderBy = port[sortColumn.field] ? 1 : 0;
3607 orderBy = port[sortColumn.field];
3617 if (sortColumn && sortColumn.sort.direction) {
3618 tempData.sort(function (left, right) {
3619 if (left === right || left.orderBy === right.orderBy) {
3622 if (left.orderBy > right.orderBy) {
3623 return (sortColumn.sort.direction === uiGridConstants.ASC) ? 1 : -1;
3625 return (sortColumn.sort.direction === uiGridConstants.ASC) ? -1 : 1;
3629 var maxPageNumber = Math.max(1, Math.ceil(tempData.length / paginationPageSize));
3630 var currentPage = Math.min(maxPageNumber, paginationPage);
3631 var orderedPortsAtCurrentPage = tempData.slice((currentPage - 1) * paginationPageSize, currentPage * paginationPageSize);
3633 portsAtCurrentPageCache = {};
3634 var orderedData = [];
3636 orderedPortsAtCurrentPage.forEach(function (orderedPort) {
3637 var port = ports[orderedPort.id];
3638 portsAtCurrentPageCache[port.id] = port;
3646 $timeout(function () {
3647 vm.gridOptions.data = orderedData;
3648 vm.gridOptions.totalItems = tempData.length;
3652 // subscribe to the elementsChanged event to reload the grid page if the data in the service has chenged
3653 mwtnTopologyEthernetPathData.events.subscribe("elementsChanged", function (data) {
3659 mwtnTopologyApp.directive("mwtnTopologyPortsGrid", [function () {
3663 controller: 'mwtnTopologyPortsGridController',
3668 templateUrl: 'src/app/mwtnTopology/templates/portsGrid.tpl.html'
3672 mwtnTopologyApp.controller("mwtnTopologyEthConnectionsGridController", ['$scope', '$timeout', '$state', '$mwtnCommons', '$mwtnTopology', 'uiGridConstants', 'mwtnTopologyEthernetPathData', function ($scope, $timeout, $state, $mwtnCommons, $mwtnTopology, uiGridConstants, mwtnTopologyEthernetPathData) {
3675 // The page number to show in the grid.
3676 var paginationPage = 1;
3678 var paginationPageSize = 100;
3679 // The grid column object with current sorting informations.
3680 var sortColumn = null;
3681 // The grid column object with current sorting informations.
3682 var gridFilters = [];
3683 // caches all sites at the current grid page
3685 vm.gridOptions = Object.assign({}, $mwtnCommons.gridOptions, {
3686 showGridFooter: false, // disable the grid footer because the pagination component sets its own.
3687 paginationPageSizes: [10, 25, 50, 100],
3688 paginationPageSize: paginationPageSize,
3689 useExternalPagination: true,
3690 useExternalFiltering: true,
3691 useExternalSorting: true,
3702 displayName: "PortA",
3708 displayName: "PortZ",
3714 displayName: "Layer",
3720 displayName: "Service",
3726 displayName: "Rule",
3731 onRegisterApi: function (gridApi) {
3732 // http://ui-grid.info/docs/#/api/ui.grid.core.api:PublicApi
3733 vm.gridApi = gridApi;
3735 vm.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
3736 // Save the current sort column for later use.
3737 sortColumn = (!sortColumns || sortColumns.length === 0) ?
3743 vm.gridApi.pagination.on.paginationChanged($scope, function (newPage, newPageSize) {
3744 // Save the pagination informations for later use.
3745 paginationPage = newPage;
3746 paginationPageSize = newPageSize;
3750 vm.gridApi.core.on.filterChanged($scope, function () {
3751 // Save the all filters for later use.
3753 this.grid.columns.forEach(function (col, ind) {
3754 if (col.filters[0] && col.filters[0].term) {
3757 term: col.filters[0].term
3761 gridFilters = filters;
3770 * Calculates the page content of the grid and sets the values to the gridOprions.data object.
3772 function loadPage() {
3774 // extract all links
3775 var links = mwtnTopologyEthernetPathData.elements.edges.filter(function (link, ind, arr) {
3776 // return true; // node && node.data && node.data.type === 'port';
3777 return link.data.layer === 'ETH';
3778 }).reduce(function (acc, cur, ind, arr) {
3780 acc[cur.data.id] = cur.data;
3785 // get all relevant link ids
3786 var linkIds = Object.keys(links);
3788 // apply the grid filters
3789 var tempData = linkIds.filter(function (linkId, ind, arr) {
3790 var link = links[linkId];
3791 return gridFilters.map(function (filter) {
3792 switch (filter.field) {
3794 return link[filter.field].contains(filter.term);
3797 }).map(function (linkId) {
3798 var link = links[linkId];
3801 if (!sortColumn || !sortColumn.sort.direction) {
3807 switch (sortColumn.field) {
3809 orderBy = link[sortColumn.field];
3819 if (sortColumn && sortColumn.sort.direction) {
3820 tempData.sort(function (left, right) {
3821 if (left === right || left.orderBy === right.orderBy) {
3824 if (left.orderBy > right.orderBy) {
3825 return (sortColumn.sort.direction === uiGridConstants.ASC) ? 1 : -1;
3827 return (sortColumn.sort.direction === uiGridConstants.ASC) ? -1 : 1;
3831 var maxPageNumber = Math.max(1, Math.ceil(tempData.length / paginationPageSize));
3832 var currentPage = Math.min(maxPageNumber, paginationPage);
3833 var orderedLinksAtCurrentPage = tempData.slice((currentPage - 1) * paginationPageSize, currentPage * paginationPageSize);
3835 linksAtCurrentPageCache = {};
3836 var orderedData = [];
3838 orderedLinksAtCurrentPage.forEach(function (orderedLink) {
3839 var link = links[orderedLink.id];
3840 linksAtCurrentPageCache[link.id] = link;
3843 source: link.source,
3844 target: link.target,
3846 service: link.service,
3851 $timeout(function () {
3852 vm.gridOptions.data = orderedData;
3853 vm.gridOptions.totalItems = tempData.length;
3857 // subscribe to the elementsChanged event to reload the grid page if the data in the service has chenged
3858 var subscription = mwtnTopologyEthernetPathData.events.subscribe("elementsChanged", function (data) {
3860 // to unsubscribe call subscription.remove();
3865 mwtnTopologyApp.directive("mwtnTopologyEthConnectionsGrid", [function () {
3869 controller: 'mwtnTopologyEthConnectionsGridController',
3874 templateUrl: 'src/app/mwtnTopology/templates/ethConnectionsGrid.tpl.html'
3878 /********************************************* IEEE 1588v2 (PTP) **************************/
3880 mwtnTopologyApp.controller('mwtnTopologyIeee1588ViewController', ['$scope', '$q', '$timeout', '$state', '$window', '$mwtnTopology', function ($scope, $q, $timeout, $state, $window, $mwtnTopology) {
3883 topologyIsOpen: false,
3885 ethConnectionsIsOpen: false
3888 $scope.$watchCollection(function () { return [vm.status.topologyIsOpen, vm.status.portsOpen, vm.status.ethConnectionsIsOpen] }, function (newVal, oldVal) {
3889 if (newVal[1] || newVal[2]) {
3890 $timeout(function () {
3891 $window.dispatchEvent(new Event("resize"));
3897 mwtnTopologyApp.directive('mwtnTopologyIeee1588View', function () {
3900 controller: 'mwtnTopologyIeee1588ViewController',
3902 bindToController: true,
3903 templateUrl: 'src/app/mwtnTopology/templates/ieee1588View.tpl.html',
3910 mwtnTopologyApp.factory("mwtnTopologyIeee1588PathData", function () {
3926 'content': 'data(label)',
3927 'text-valign': 'center',
3928 'text-halign': 'center',
3929 'background-color': '#aaaaaa',
3930 'border-color': '#000000',
3931 'border-width': '1px',
3936 selector: '$node > node',
3938 'shape': 'roundrectangle',
3939 'padding-top': '10px',
3940 'padding-left': '10px',
3941 'padding-bottom': '10px',
3942 'padding-right': '10px',
3943 'text-valign': 'top',
3944 'text-halign': 'center',
3945 'background-color': '#eeeeee',
3947 'border-color': '#888888'
3951 selector: 'node[type = "site"]',
3953 'shape': 'roundrectangle',
3954 'padding-top': '10px',
3955 'padding-left': '10px',
3956 'padding-bottom': '10px',
3957 'padding-right': '10px',
3958 'text-valign': 'center',
3959 'text-halign': 'center',
3960 'background-color': '#fefefe',
3962 'border-color': '#888888',
3963 'font-weight': 'bold'
3967 selector: 'node[type = "ptp-clock"][active = "true"]',
3969 'background-color': '#316ac5',
3970 'background-opacity': '0.2',
3971 'border-color': '#316ac5',
3972 'border-opacity': '0.8',
3973 'border-width': '2px',
3978 selector: 'node[type = "port"][active = "true"]',
3980 'background-opacity': '1.0',
3984 selector: 'node[active = "false"]',
3986 'background-opacity': '0.3',
3987 'border-opacity': '0.5'
3991 selector: 'node[path = "true"]',
3993 'background-color': '#ff00ff',
3994 'background-opacity': '0.9',
3995 'border-color': '#880088',
3999 selector: '$node > node[path = "true"]',
4001 'background-color': '#ff00ff',
4002 'background-opacity': '0.3',
4003 'border-color': '#ff00ff',
4004 'border-opacity': '1.0',
4005 'border-width': '2px',
4011 'content': 'data(id)',
4012 'target-arrow-shape': 'triangle',
4013 'line-color': '#666666',
4018 selector: 'edge[active = "false"]',
4020 'line-color': '#cccccc',
4021 'text-opacity': '0.9'
4025 selector: 'edge[path = "true"]',
4027 'line-color': '#ff00ff',
4032 selector: ':selected',
4034 'background-color': 'black',
4035 'line-color': 'black',
4036 'target-arrow-color': 'black',
4037 'source-arrow-color': 'black'
4043 { data: { id: 'north', label: 'north', type: 'site', latitude: 50.734916, longitude: 7.137636 } },
4044 { data: { id: 'north-east', label: 'north-east', type: 'site', latitude: 50.733028, longitude: 7.151086 } },
4045 { data: { id: 'north-west', label: 'north-west', type: 'site', latitude: 50.730230, longitude: 7.126017 } },
4046 { data: { id: 'east', label: 'east', type: 'site', latitude: 50.725672, longitude: 7.158488 } },
4047 { data: { id: 'west', label: 'west', type: 'site', latitude: 50.721914, longitude: 7.120521 } },
4048 { data: { id: 'south-east', label: 'south-east', type: 'site', latitude: 50.717158, longitude: 7.155506 } },
4049 { data: { id: 'south-west', label: 'south-west', type: 'site', latitude: 50.714359, longitude: 7.130437 } },
4050 { data: { id: 'south', label: 'south', type: 'site', latitude: 50.712472, longitude: 7.143887 } },
4052 { 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' } },
4053 { 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' } },
4054 { 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' } },
4055 { 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' } },
4056 { 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' } },
4057 { 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' } },
4058 { 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' } },
4059 { 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' } },
4060 { 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' } },
4061 { 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' } },
4062 { 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' } },
4063 { 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' } },
4064 { 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' } },
4065 { 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' } },
4066 { 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' } },
4067 { 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' } },
4069 { 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 } },
4070 { 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 } },
4071 { 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 } },
4072 { 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 } },
4073 { 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 } },
4074 { 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 } },
4075 { 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 } },
4076 { 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 } },
4077 { 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 } },
4078 { 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 } },
4079 { 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 } },
4080 { 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 } },
4081 { 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 } },
4082 { 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 } },
4083 { 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 } },
4084 { 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 } },
4085 { 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 } },
4086 { 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 } },
4087 { 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 } },
4088 { 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 } },
4089 { 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 } },
4090 { 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 } },
4091 { 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 } },
4092 { 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 } },
4093 { 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 } },
4094 { 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 } },
4095 { 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 } },
4096 { 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 } },
4097 { 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 } },
4098 { 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 } },
4099 { 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 } },
4100 { 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 } },
4101 { 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 } },
4102 { 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 } },
4103 { 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 } },
4104 { 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 } },
4108 { data: { id: 'ERI1', source: 'Ericsson-A#6', target: 'Ericsson-Z#6', label: 'ERI1', layer: 'PTP', active: 'true' } },
4109 { data: { id: '71', source: 'Huawei-A#6', target: 'Huawei-Z#6', label: '71', layer: 'PTP', active: 'true' } },
4110 { data: { id: '81', source: 'NEC-A#6', target: 'NEC-Z#6', label: '81', layer: 'PTP', active: 'true' } },
4111 { data: { id: '91', source: 'Nokia-A#6', target: 'Nokia-Z#6', label: '91', layer: 'PTP', active: 'true' } },
4112 { data: { id: '101', source: 'SIAE-A#6', target: 'SIAE-Z#6', label: '101', layer: 'PTP', active: 'true' } },
4113 { data: { id: '111', source: 'ZTE-A#6', target: 'ZTE-Z#6', label: '111', layer: 'PTP', active: 'true' } },
4115 { data: { id: 'ETY01', source: 'ADVA-A#1', target: 'Nokia-A34', label: 'ADVA-A#1-Nokia-A34', layer: 'PTP', active: 'true' } },
4116 { data: { id: 'ETY02', source: 'ADVA-A#2', target: 'ZTE-A#4', label: 'ADVA-A#2-ZTE-A#4', layer: 'PTP', active: 'false' } },
4117 { data: { id: 'ETY03', source: 'ADVA-B#1', target: 'Nokia-A13', label: 'ADVA-B#1-Nokia-A13', layer: 'PTP', active: 'true' } },
4118 { data: { id: 'ETY04', source: 'ADVA-B#2', target: 'ZTE-A#3', label: 'ADVA-B#2-ZTE-A#3', layer: 'PTP', active: 'true' } },
4119 { data: { id: 'ETY05', source: 'ADVA-Y#1', target: 'Huawei-Z#4', label: 'ADVA-Y#1-Huawei-Z#4', layer: 'PTP', active: 'true' } },
4120 { data: { id: 'ETY06', source: 'ADVA-Y#2', target: 'NEC-Z#4', label: 'ADVA-Y#2-NEC-Z#4', layer: 'PTP', active: 'true' } },
4121 { data: { id: 'ETY09', source: 'ADVA-Z#1', target: 'Huawei-Z#3', label: 'ADVA-Z#1-Huawei-Z#3', layer: 'PTP', active: 'true' } },
4122 { data: { id: 'ETY10', source: 'ADVA-Z#2', target: 'NEC-Z#3', label: 'ADVA-Z#2-NEC-Z#3', layer: 'PTP', active: 'true' } },
4123 { data: { id: 'ETY21', source: 'Ericsson-A#5', target: 'NEC-Z#2', label: 'Ericsson-A#5-NEC-Z#2', layer: 'PTP', active: 'true' } },
4124 { data: { id: 'ETY24', source: 'Huawei-A#5', target: 'Nokia-Z33', label: 'Huawei-A#5-Nokia-Z33', layer: 'PTP', active: 'true' } },
4125 { data: { id: 'ETY25', source: 'Huawei-Z#5', target: 'SIAE-A#5', label: 'Huawei-Z#5-SIAE-A#5', layer: 'PTP', active: 'true' } },
4126 { data: { id: 'ETY26', source: 'NEC-A#3', target: 'ZTE-Z#5', label: 'NEC-A#3-ZTE-Z#5', layer: 'PTP', active: 'true' } },
4131 var events = eventsFabric();
4140 var someMethodChangingTheElements = function () {
4141 // @Martin: hier kannst Du die Elements ändern, anschließend mußt Du das Ereignis veröffentlichen
4142 // das Ereigniss wird in der Directive aufgefangen und die Grig wird neu gezeichnet
4144 // Hinweis: Die Reihenfolge muss so bleiben und du kannst NUR result.elements ändern.
4146 events.publish("elementsChanged", {
4147 elements: result.elements
4154 mwtnTopologyApp.directive("mwtnTopologyIeee1588PathGraph", ["mwtnTopologyIeee1588PathData", "$mwtnPtp", function (mwtnTopologyIeee1588PathData, $mwtnPtp) {
4159 template: '<div style="height: 750px; width: 100%;"></div>',
4160 controller: function () {
4166 link: function (scope, element, attrs, ctrl) {
4168 var cy = cytoscape({
4169 container: element[0],
4171 boxSelectionEnabled: false,
4172 autounselectify: true,
4174 style: mwtnTopologyIeee1588PathData.styles,
4175 elements: mwtnTopologyIeee1588PathData.elements,
4182 // @ Martin: Hier wird das Ereignis aus dem Service aboniert.
4183 // Es ist möglich mehrere Ereignisse zu definieren.
4184 mwtnTopologyIeee1588PathData.events.subscribe("elementsChanged", function (data) {
4186 // @Martin: cy aktualisiert sich mit Hilfe der Referenz auf die Elemente aus dem Service
4188 elements: mwtnTopologyIeee1588PathData.elements // oder data.elements
4192 // pathGraphData.events.subscribe("styleChanged", function () {
4194 // style: mwtnTopologyIeee1588PathData.styles
4200 pan: { x: 150, y: 100 }
4203 var clearPtpPath = function () {
4204 var selector = "[path = 'true']";
4205 cy.elements(selector).map(function (element) {
4206 element.data('path', 'false');
4210 var highlightPtpMaster = function (id) {
4211 var selector = "[id = '" + id + "']";
4212 cy.nodes(selector).map(function (node) {
4213 if (node.data('parentDs') && node.data('parentDs') != '') {
4214 // console.warn('parentDs', node.data('parentDs'));
4215 var parentNode = node.data('parentDs').slice(0, -2);
4216 if (parentNode !== id) {
4217 highlightPtpMaster(parentNode);
4220 selector = "[id = '" + node.data('parentDs') + "']";
4221 cy.nodes(selector).connectedEdges().map(function (edge) {
4222 edge.data('path', 'true');
4223 edge.connectedNodes().map(function (node) {
4224 node.data('path', 'true');
4232 var setAllDevicesInactive = function () {
4233 cy.nodes().map(function (node) {
4234 node.data('active', 'false');
4238 var setPortAndEdgedActive = function () {
4239 cy.edges().map(function (edge) {
4240 var active = 'true';
4241 edge.connectedNodes().map(function (port) {
4242 // console.log(' node', JSON.stringify(edge.data()));
4243 var parent = cy.getElementById(port.data('parent'));
4244 if (parent.data('active') === 'false') {
4245 port.data('active', 'false');
4246 edge.data('active', 'false');
4248 port.data('active', 'true');
4254 var init = function () {
4255 setAllDevicesInactive();
4256 $mwtnPtp.getPtpClocks().then(function (clocks) {
4257 // setDevicesActive(Object.keys(clocks));
4259 // update clock ids first
4260 Object.keys(clocks).map(function (key) {
4261 var clock = clocks[key];
4262 var graphClock = cy.getElementById(key);
4263 graphClock.data('active', 'true')
4264 graphClock.data('base64', clock.getIdentity());
4265 graphClock.data('hex', clock.getIdentity(hex));
4268 Object.keys(clocks).map(function (key) {
4269 var clock = clocks[key];
4270 var graphClock = cy.getElementById(key);
4271 var graphParentDs = nodeId(clock.getParent().slice(0, -2));
4272 graphClock.data('parentDs', graphParentDs + clock.getParent().slice(-2));
4273 graphClock.data('grandMaster', nodeId(clock.getGrandMaster()));
4275 // clock.getPtpPorts().map(function(port){
4276 // // console.warn(port.getId(), port.getNumber(), port.getState(), port.isSlave(), port.isMaster(), port.getLogicalTerminationPointReference());
4277 // var portKey = [key, port.getNumber() < 10 ? '#' : '', port.getNumber()].join('');
4278 // var graphPort = cy.getElementById(portKey);
4279 // // console.warn(JSON.stringify(graphPort.data()));
4280 // if (graphPort === undefined) {
4281 // console.error('PtpPort not found in graph:' , portKey);
4283 // console.info('PtpPort found in graph:' , portKey);
4288 setPortAndEdgedActive();
4289 }, function (error) {
4290 setPortAndEdgedActive();
4291 console.error(JSON.stringify(error));
4296 var getNodeId = function (base64) {
4297 if (base64 === undefined || base64 === '') return '';
4299 var selector = "[type = 'ptp-clock']";
4300 var result = cy.nodes(selector).filter(function (graphClock) {
4301 // console.error(base64, graphClock.data('base64'), graphClock.data('base64') === base64);
4302 return graphClock.data('base64') === base64;
4304 if (result.length === 0) {
4305 console.warn('Clock', base64, 'not found!');
4308 return result[0].id();
4312 var getParentClock = function (nodeId) {
4313 $mwtnPtp.getParent(nodeId).then(function (parentPortIdentity) {
4314 var parentNode = getNodeId(parentPortIdentity['clock-identity']);
4315 console.log(JSON.stringify(parentPortIdentity), parentNode);
4317 getParentClock(parentNode);
4322 cy.on('tap', function (event) {
4325 if (event.target !== cy) {
4326 if (event.target.data('type') === 'ptp-clock') {
4327 getParentClock(event.target.id());
4328 // highlightPtpMaster(event.target.id());
4329 } else if (event.target.data('type') === 'port') {
4330 var parent = cy.getElementById(event.target.data('parent'));
4331 // highlightPtpMaster(parent.id());
4332 getParentClock(event.target.id());
4343 mwtnTopologyApp.controller("mwtnTopologyClocksGridController", ['$scope', '$timeout', '$state', '$mwtnCommons', '$mwtnTopology', 'uiGridConstants', 'mwtnTopologyIeee1588PathData', function ($scope, $timeout, $state, $mwtnCommons, $mwtnTopology, uiGridConstants, mwtnTopologyIeee1588PathData) {
4346 // The page number to show in the grid.
4347 var paginationPage = 1;
4349 var paginationPageSize = 100;
4350 // The grid column object with current sorting informations.
4351 var sortColumn = null;
4352 // The grid column object with current sorting informations.
4353 var gridFilters = [];
4354 // caches all sites at the current grid page
4356 vm.gridOptions = Object.assign({}, $mwtnCommons.gridOptions, {
4357 showGridFooter: false, // disable the grid footer because the pagination component sets its own.
4358 paginationPageSizes: [10, 25, 50, 100],
4359 paginationPageSize: paginationPageSize,
4360 useExternalPagination: true,
4361 useExternalFiltering: true,
4362 useExternalSorting: true,
4367 displayName: "Node id",
4373 displayName: "Clock identity in hex",
4378 displayName: "... in base64",
4384 displayName: "parentDs",
4388 field: "grandMaster",
4390 displayName: "grandmaster",
4396 displayName: "Active",
4401 onRegisterApi: function (gridApi) {
4402 // http://ui-grid.info/docs/#/api/ui.grid.core.api:PublicApi
4403 vm.gridApi = gridApi;
4405 vm.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
4406 // Save the current sort column for later use.
4407 sortColumn = (!sortColumns || sortColumns.length === 0) ?
4413 vm.gridApi.pagination.on.paginationChanged($scope, function (newPage, newPageSize) {
4414 // Save the pagination informations for later use.
4415 paginationPage = newPage;
4416 paginationPageSize = newPageSize;
4420 vm.gridApi.core.on.filterChanged($scope, function () {
4421 // Save the all filters for later use.
4423 this.grid.columns.forEach(function (col, ind) {
4424 if (col.filters[0] && col.filters[0].term) {
4427 term: col.filters[0].term
4431 gridFilters = filters;
4440 * Calculates the page content of the grid and sets the values to the gridOprions.data object.
4442 function loadPage() {
4444 // extract all ports
4445 var ports = mwtnTopologyIeee1588PathData.elements.nodes.filter(function (node, ind, arr) {
4446 return node && node.data && node.data.type === 'ptp-clock';
4447 }).reduce(function (acc, cur, ind, arr) {
4448 if (cur.data) acc[cur.data.id] = cur.data;
4453 var portIds = Object.keys(ports);
4455 // apply the grid filters
4456 var tempData = portIds.filter(function (portId, ind, arr) {
4457 var port = ports[portId];
4458 return gridFilters.map(function (filter) {
4459 switch (filter.field) {
4461 return port[filter.field].toString().contains(filter.term);
4463 return port[filter.field].contains(filter.term);
4466 }).map(function (portId) {
4467 var port = ports[portId];
4470 if (!sortColumn || !sortColumn.sort.direction) {
4476 switch (sortColumn.field) {
4478 orderBy = port[sortColumn.field] ? 1 : 0;
4481 orderBy = port[sortColumn.field];
4491 if (sortColumn && sortColumn.sort.direction) {
4492 tempData.sort(function (left, right) {
4493 if (left === right || left.orderBy === right.orderBy) {
4496 if (left.orderBy > right.orderBy) {
4497 return (sortColumn.sort.direction === uiGridConstants.ASC) ? 1 : -1;
4499 return (sortColumn.sort.direction === uiGridConstants.ASC) ? -1 : 1;
4503 var maxPageNumber = Math.max(1, Math.ceil(tempData.length / paginationPageSize));
4504 var currentPage = Math.min(maxPageNumber, paginationPage);
4505 var orderedPortsAtCurrentPage = tempData.slice((currentPage - 1) * paginationPageSize, currentPage * paginationPageSize);
4507 portsAtCurrentPageCache = {};
4508 var orderedData = [];
4510 orderedPortsAtCurrentPage.forEach(function (orderedPort) {
4511 var port = ports[orderedPort.id]; // TODO [sko] varible port should be renamed to clock
4512 portsAtCurrentPageCache[port.id] = port;
4516 active: port.active,
4518 base64: port.base64,
4519 parentDs: port.parentDs,
4520 grandMaster: port.grandMaster
4524 $timeout(function () {
4525 vm.gridOptions.data = orderedData;
4526 vm.gridOptions.totalItems = tempData.length;
4530 // subscribe to the elementsChanged event to reload the grid page if the data in the service has chenged
4531 mwtnTopologyIeee1588PathData.events.subscribe("elementsChanged", function (data) {
4537 mwtnTopologyApp.directive("mwtnTopologyClocksGrid", [function () {
4541 controller: 'mwtnTopologyClocksGridController',
4546 templateUrl: 'src/app/mwtnTopology/templates/clocksGrid.tpl.html'
4550 mwtnTopologyApp.controller("mwtnTopologyPtpLinksGridController", ['$scope', '$timeout', '$state', '$mwtnCommons', '$mwtnTopology', 'uiGridConstants', 'mwtnTopologyIeee1588PathData', function ($scope, $timeout, $state, $mwtnCommons, $mwtnTopology, uiGridConstants, mwtnTopologyIeee1588PathData) {
4553 // The page number to show in the grid.
4554 var paginationPage = 1;
4556 var paginationPageSize = 100;
4557 // The grid column object with current sorting informations.
4558 var sortColumn = null;
4559 // The grid column object with current sorting informations.
4560 var gridFilters = [];
4561 // caches all sites at the current grid page
4563 vm.gridOptions = Object.assign({}, $mwtnCommons.gridOptions, {
4564 showGridFooter: false, // disable the grid footer because the pagination component sets its own.
4565 paginationPageSizes: [10, 25, 50, 100],
4566 paginationPageSize: paginationPageSize,
4567 useExternalPagination: true,
4568 useExternalFiltering: true,
4569 useExternalSorting: true,
4579 displayName: "PortA"
4584 displayName: "PortZ"
4589 displayName: "active"
4593 onRegisterApi: function (gridApi) {
4594 // http://ui-grid.info/docs/#/api/ui.grid.core.api:PublicApi
4595 vm.gridApi = gridApi;
4597 vm.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
4598 // Save the current sort column for later use.
4599 sortColumn = (!sortColumns || sortColumns.length === 0) ?
4605 vm.gridApi.pagination.on.paginationChanged($scope, function (newPage, newPageSize) {
4606 // Save the pagination informations for later use.
4607 paginationPage = newPage;
4608 paginationPageSize = newPageSize;
4612 vm.gridApi.core.on.filterChanged($scope, function () {
4613 // Save the all filters for later use.
4615 this.grid.columns.forEach(function (col, ind) {
4616 if (col.filters[0] && col.filters[0].term) {
4619 term: col.filters[0].term
4623 gridFilters = filters;
4632 * Calculates the page content of the grid and sets the values to the gridOprions.data object.
4634 function loadPage() {
4636 // extract all links
4637 var links = mwtnTopologyIeee1588PathData.elements.edges.filter(function (node, ind, arr) {
4638 return true; // node && node.data && node.data.type === 'port';
4639 }).reduce(function (acc, cur, ind, arr) {
4641 acc[cur.data.id] = cur.data;
4647 var linkIds = Object.keys(links);
4649 // apply the grid filters
4650 var tempData = linkIds.filter(function (linkId, ind, arr) {
4651 var link = links[linkId];
4652 return gridFilters.map(function (filter) {
4653 switch (filter.field) {
4655 return link[filter.field].contains(filter.term);
4658 }).map(function (linkId) {
4659 var link = links[linkId];
4662 if (!sortColumn || !sortColumn.sort.direction) {
4668 switch (sortColumn.field) {
4670 orderBy = link[sortColumn.field] ? 1 : 0;
4672 orderBy = link[sortColumn.field];
4682 if (sortColumn && sortColumn.sort.direction) {
4683 tempData.sort(function (left, right) {
4684 if (left === right || left.orderBy === right.orderBy) {
4687 if (left.orderBy > right.orderBy) {
4688 return (sortColumn.sort.direction === uiGridConstants.ASC) ? 1 : -1;
4690 return (sortColumn.sort.direction === uiGridConstants.ASC) ? -1 : 1;
4694 var maxPageNumber = Math.max(1, Math.ceil(tempData.length / paginationPageSize));
4695 var currentPage = Math.min(maxPageNumber, paginationPage);
4696 var orderedLinksAtCurrentPage = tempData.slice((currentPage - 1) * paginationPageSize, currentPage * paginationPageSize);
4698 linksAtCurrentPageCache = {};
4699 var orderedData = [];
4701 orderedLinksAtCurrentPage.forEach(function (orderedLink) {
4702 var link = links[orderedLink.id];
4703 linksAtCurrentPageCache[link.id] = link;
4706 source: link.source,
4707 target: link.target,
4712 $timeout(function () {
4713 vm.gridOptions.data = orderedData;
4714 vm.gridOptions.totalItems = tempData.length;
4718 // subscribe to the elementsChanged event to reload the grid page if the data in the service has chenged
4719 var subscription = mwtnTopologyIeee1588PathData.events.subscribe("elementsChanged", function (data) {
4721 // to unsubscribe call subscription.remove();
4726 mwtnTopologyApp.directive("mwtnTopologyPtpLinksGrid", [function () {
4730 controller: 'mwtnTopologyPtpLinksGridController',
4735 templateUrl: 'src/app/mwtnTopology/templates/ptpLinksGrid.tpl.html'
4739 mwtnTopologyApp.filter('coordinateFilter', coordinateFilter);
4741 function coordinateFilter($sce) {
4743 return function (coordinate, conversion, type, places) {
4745 // The filter will be running as we type values into the input boxes, which returns undefined
4746 // and brings up an error in the console. Here wait until the coordinate is defined
4747 if (coordinate != undefined) {
4749 // Check for user input that is a positive or negative number with the option
4750 // that it is a float. Match only the numbers and not the white space or other characters
4751 var pattern = /[-+]?[0-9]*\.?[0-9]+/g
4752 var match = String(coordinate).match(pattern);
4754 if (conversion === "toDD" && match && coordinateIsValid(match, type)) {
4755 // If the match array only has one item, the user has provided decimal degrees
4756 // and we can just return what the user typed in
4757 if (match.length === 1) {
4758 return parseFloat(match);
4761 // If the match array has a length of three then we know degrees, minutes, and seconds
4762 // were provided so we can convert it to decimal degrees
4763 if (match.length === 3) {
4764 return toDecimalDegrees(match);
4768 else if (conversion === 'toDMS' && match && coordinateIsValid(match, type)) {
4769 // When converting from decimal degrees to degrees, minutes and seconds, if
4770 // the match array has one item we know the user has input decimal degrees
4771 // so we can convert it to degrees, minutes and seconds
4772 if (match.length === 1) {
4773 return toDegreesMinutesSeconds(match, type);
4776 // To properly format the converted coordinates we will need to add in HTML entities
4777 // which means we'll need to bind the returned string as HTML and thus we need
4778 // to use $sce (Strict Contextual Escaping) to say that we trust what is being bound as HTML
4779 if (match.length === 3) {
4780 return $sce.trustAsHtml(match[0] + '° ' + match[1] + '′ ' + match[2] + '″ ');
4784 // Output a notice that the coordinates are invalid if they are
4785 else if (!coordinateIsValid(match, type)) {
4786 return "Invalid Coordinate!";
4789 function toDecimalDegrees(coord) {
4790 // Setup for all parts of the DMS coordinate and the necessary math to convert
4792 var degrees = parseInt(coord[0]);
4793 var minutes = parseInt(coord[1]) / 60;
4794 var seconds = parseInt(coord[2]) / 3600;
4796 // When the degrees value is negative, the math is a bit different
4797 // than when the value is positive. This checks whether the value is below zero
4798 // and does subtraction instead of addition if it is.
4800 var calculated = degrees - minutes - seconds;
4801 return calculated.toFixed(places || 4);
4804 var calculated = degrees + minutes + seconds
4805 return calculated.toFixed(places || 4);
4809 // This function converts from DD to DMS. Math.abs is used a lot because
4810 // for the minutes and seconds, negative values aren't valid
4811 function toDegreesMinutesSeconds(decimal_degrees, type) {
4813 var dd = decimal_degrees[0];
4814 var direction = 'E';
4816 if (type === 'lat') {
4831 var degrees = Math.floor(dd);
4832 var frac = dd - degrees; // get fractional part
4833 var min = Math.floor(frac * 60);
4834 var sec = frac * 3600 - min * 60;
4836 var formated = [degrees, '° ', ("0" + min).slice(-2), '\' ', ("0" + sec.toFixed(4)).slice(-7), '\" ', direction];
4837 return formated.join('');
4839 // var degrees = coordinate[0].split('.')[0];
4840 // var minutes = Math.abs(Math.floor(60 * (Math.abs(coordinate[0]) - Math.abs(degrees))));
4841 // var seconds = 3600 * (Math.abs(coordinate[0]) - Math.abs(degrees) - Math.abs(minutes) / 60).toFixed(2);
4843 // return $sce.trustAsHtml(degrees + '° ' + minutes + '\' ' + seconds + '\" ');
4847 // This function checks whether the coordinate value the user enters is valid or not.
4848 // If the coordinate doesn't pass one of these rules, the function will return false
4849 // which will then alert the user that the coordinate is invalid.
4850 function coordinateIsValid(coordinate, type) {
4853 // The degree values of latitude coordinates have a range between -90 and 90
4854 if (coordinate[0] && type === 'lat') {
4855 if (!(-90 <= +(coordinate[0]) <= 90)) return false;
4857 // The degree values longitude coordinates have a range between -180 and 180
4858 else if (coordinate[0] && type === 'lon') {
4859 if (!(-180 <= +(coordinate[0]) <= 180)) return false;
4861 // Minutes and seconds can only be between 0 and 60
4862 if (coordinate[1]) {
4863 if (!(0 <= +(coordinate[1]) <= 60)) return false;
4865 if (coordinate[2]) {
4866 if (!(0 <= +(coordinate[2]) <= 60)) return false;
4870 // If the coordinate made it through all the rules above, the function
4871 // returns true because the coordinate is good