You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by de...@apache.org on 2017/05/30 16:03:56 UTC

[2/2] incubator-trafficcontrol git commit: hooks up cdn config diff / snapshot

hooks up cdn config diff / snapshot


Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/c72b5027
Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/c72b5027
Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/c72b5027

Branch: refs/heads/master
Commit: c72b50271a2a5b1034a97929e160bbedaf9f08a3
Parents: a68cf70
Author: Jeremy Mitchell <mi...@gmail.com>
Authored: Thu May 25 11:43:25 2017 -0600
Committer: Dewayne Richardson <de...@apache.org>
Committed: Tue May 30 10:03:54 2017 -0600

----------------------------------------------------------------------
 traffic_ops/experimental/ui/app/src/app.js      |   1 +
 .../ui/app/src/common/api/CDNService.js         |  53 +++++
 .../modules/form/cdn/FormCDNController.js       |   4 +-
 .../common/modules/form/cdn/form.cdn.tpl.html   |   4 +-
 traffic_ops/experimental/ui/app/src/index.html  |   1 +
 .../admin/cdns/config/ConfigController.js       | 195 +++++++++++++++++++
 .../private/admin/cdns/config/_config.scss      |  50 +++++
 .../private/admin/cdns/config/config.tpl.html   | 113 +++++++++++
 .../modules/private/admin/cdns/config/index.js  |  46 +++++
 .../admin/physLocations/_physLocations.scss     |  16 --
 .../experimental/ui/app/src/styles/main.scss    |   3 +-
 11 files changed, 464 insertions(+), 22 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c72b5027/traffic_ops/experimental/ui/app/src/app.js
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/ui/app/src/app.js b/traffic_ops/experimental/ui/app/src/app.js
index 082872c..b02fd36 100644
--- a/traffic_ops/experimental/ui/app/src/app.js
+++ b/traffic_ops/experimental/ui/app/src/app.js
@@ -58,6 +58,7 @@ var trafficOps = angular.module('trafficOps', [
         require('./modules/private/admin/asns/list').name,
         require('./modules/private/admin/asns/new').name,
         require('./modules/private/admin/cdns').name,
+        require('./modules/private/admin/cdns/config').name,
         require('./modules/private/admin/cdns/deliveryServices').name,
         require('./modules/private/admin/cdns/edit').name,
         require('./modules/private/admin/cdns/list').name,

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c72b5027/traffic_ops/experimental/ui/app/src/common/api/CDNService.js
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/ui/app/src/common/api/CDNService.js b/traffic_ops/experimental/ui/app/src/common/api/CDNService.js
index 881c0f1..54e56d2 100644
--- a/traffic_ops/experimental/ui/app/src/common/api/CDNService.js
+++ b/traffic_ops/experimental/ui/app/src/common/api/CDNService.js
@@ -136,6 +136,59 @@ var CDNService = function($http, $q, Restangular, locationUtils, messageModel, E
         return request.promise;
     };
 
+    this.getCurrentSnapshot = function(cdnName) {
+        var request = $q.defer();
+
+        $http.get(ENV.api['root'] + "cdns/" + cdnName + "/snapshot")
+            .then(
+                function(result) {
+                    request.resolve(result.data.response);
+                },
+                function(fault) {
+                    request.reject();
+                }
+            );
+
+        return request.promise;
+    };
+
+    this.getNewSnapshot = function(cdnName) {
+        var request = $q.defer();
+
+        $http.get(ENV.api['root'] + "cdns/" + cdnName + "/snapshot/new")
+            .then(
+                function(result) {
+                    request.resolve(result.data.response);
+                },
+                function(fault) {
+                    request.reject();
+                }
+            );
+
+        return request.promise;
+    };
+
+    this.snapshot = function(cdn) {
+        var request = $q.defer();
+
+        $http.put(ENV.api['root'] + "cdns/" + cdn.id + "/snapshot")
+            .then(
+                function() {
+                    messageModel.setMessages([ { level: 'success', text: 'Snapshot performed' } ], true);
+                    locationUtils.navigateToPath('/admin/cdns/' + cdn.id);
+
+                },
+                function(fault) {
+                    messageModel.setMessages(fault.data.alerts, false);
+                }
+            );
+
+        return request.promise;
+    };
+
+
+
+
 };
 
 CDNService.$inject = ['$http', '$q', 'Restangular', 'locationUtils', 'messageModel', 'ENV'];

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c72b5027/traffic_ops/experimental/ui/app/src/common/modules/form/cdn/FormCDNController.js
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/ui/app/src/common/modules/form/cdn/FormCDNController.js b/traffic_ops/experimental/ui/app/src/common/modules/form/cdn/FormCDNController.js
index 41f8147..c628ff7 100644
--- a/traffic_ops/experimental/ui/app/src/common/modules/form/cdn/FormCDNController.js
+++ b/traffic_ops/experimental/ui/app/src/common/modules/form/cdn/FormCDNController.js
@@ -42,8 +42,8 @@ var FormCDNController = function(cdn, $scope, $location, formUtils, stringUtils,
         cdnService.clearServerUpdates(cdn.id);
     };
 
-    $scope.manageSnapshots = function() {
-        alert('not hooked up yet: manageSnapshots for CDN');
+    $scope.viewConfig = function() {
+        $location.path($location.path() + '/config/changes');
     };
 
     $scope.viewProfiles = function() {

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c72b5027/traffic_ops/experimental/ui/app/src/common/modules/form/cdn/form.cdn.tpl.html
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/ui/app/src/common/modules/form/cdn/form.cdn.tpl.html b/traffic_ops/experimental/ui/app/src/common/modules/form/cdn/form.cdn.tpl.html
index 8528350..ef0f752 100644
--- a/traffic_ops/experimental/ui/app/src/common/modules/form/cdn/form.cdn.tpl.html
+++ b/traffic_ops/experimental/ui/app/src/common/modules/form/cdn/form.cdn.tpl.html
@@ -24,7 +24,7 @@ under the License.
             <li class="active">{{cdnName}}</li>
         </ol>
         <div class="pull-right" ng-show="!settings.isNew">
-            <button class="btn btn-primary" title="Snapshot {{cdn.name}} Config" ng-click="manageSnapshots()"><i class="fa fa-camera"></i></button>
+            <button class="btn btn-primary" title="View / Snapshot {{cdn.name}} Config Changes" ng-click="viewConfig()"><i class="fa fa-camera"></i></button>
             <button class="btn btn-primary" title="Queue {{cdn.name}} Server Updates" ng-click="queueServerUpdates(cdn)"><i class="fa fa-flag"></i></button>
             <button class="btn btn-primary" title="Clear {{cdn.name}} Server Updates" ng-click="clearServerUpdates(cdn)"><i class="fa fa-ban"></i></button>
             <div class="btn-group" role="group" uib-dropdown is-open="more.isopen">
@@ -33,7 +33,7 @@ under the License.
                     <span class="caret"></span>
                 </button>
                 <ul class="dropdown-menu-right dropdown-menu" uib-dropdown-menu>
-                    <li role="menuitem"><a ng-click="manageSnapshots()">Snapshot {{cdn.name}} Config</a></li>
+                    <li role="menuitem"><a ng-click="viewConfig()">View / Snapshot {{cdn.name}} Config Changes</a></li>
                     <li class="divider"></li>
                     <li role="menuitem"><a ng-click="queueServerUpdates(cdn)">Queue {{cdn.name}} Server Updates</a></li>
                     <li role="menuitem"><a ng-click="clearServerUpdates(cdn)">Clear {{cdn.name}} Server Updates</a></li>

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c72b5027/traffic_ops/experimental/ui/app/src/index.html
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/ui/app/src/index.html b/traffic_ops/experimental/ui/app/src/index.html
index 7c5b598..d579753 100644
--- a/traffic_ops/experimental/ui/app/src/index.html
+++ b/traffic_ops/experimental/ui/app/src/index.html
@@ -44,6 +44,7 @@ under the License.
         <div class="app container body" ui-view></div>
 
         <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.12.0/moment.js"></script>
+        <script src="https://cdnjs.cloudflare.com/ajax/libs/jsdiff/3.2.0/diff.js"></script>
 
         <script src="resources/assets/js/shared-libs.js"></script>
         <script src="resources/assets/js/app.js"></script>

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c72b5027/traffic_ops/experimental/ui/app/src/modules/private/admin/cdns/config/ConfigController.js
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/ui/app/src/modules/private/admin/cdns/config/ConfigController.js b/traffic_ops/experimental/ui/app/src/modules/private/admin/cdns/config/ConfigController.js
new file mode 100644
index 0000000..deeac15
--- /dev/null
+++ b/traffic_ops/experimental/ui/app/src/modules/private/admin/cdns/config/ConfigController.js
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var ConfigController = function(cdn, currentSnapshot, newSnapshot, $scope, $state, $timeout, $uibModal, locationUtils, cdnService) {
+
+	$scope.cdn = cdn;
+
+	var oldConfig = currentSnapshot.config || null,
+		newConfig = newSnapshot.config;
+
+	var oldContentRouters = currentSnapshot.contentRouters || null,
+		newContentRouters = newSnapshot.contentRouters;
+
+	var oldContentServers = currentSnapshot.contentServers || null,
+		newContentServers = newSnapshot.contentServers;
+
+	var oldDeliveryServices = currentSnapshot.deliveryServices || null,
+		newDeliveryServices = newSnapshot.deliveryServices;
+
+	var oldEdgeLocations = currentSnapshot.edgeLocations || null,
+		newEdgeLocations = newSnapshot.edgeLocations;
+
+	var oldStats = currentSnapshot.stats || null,
+		newStats = newSnapshot.stats;
+
+	var performDiff = function(oldJSON, newJSON, destination) {
+		var div = null,
+			prepend = '',
+			added = 0,
+			removed = 0;
+
+		var display = document.getElementById(destination),
+			fragment = document.createDocumentFragment();
+
+		if (oldJSON) {
+			var diff = JsDiff.diffJson(oldJSON, newJSON);
+			diff.forEach(function(part){
+				if (part.added) {
+					added++;
+				} else if (part.removed) {
+					removed++;
+				}
+				prepend = part.added ? '++' : part.removed ? '--' : '';
+				div = document.createElement('div');
+				div.className = part.added ? 'added' : part.removed ? 'removed' : 'no-change';
+
+				div.appendChild(document.createTextNode(prepend + part.value));
+				fragment.appendChild(div);
+			});
+
+			$scope[destination + "Count"].added = added;
+			$scope[destination + "Count"].removed = removed;
+			display.innerHTML = '';
+			display.appendChild(fragment);
+		} else {
+			display.innerHTML = 'Existing snapshot cannot be found. Please perform snapshot.';
+		}
+
+	};
+
+	var snapshot = function() {
+		cdnService.snapshot(cdn);
+	};
+
+	$scope.configCount = {
+		added: 0,
+		removed: 0,
+		templateUrl: 'configPopoverTemplate.html'
+	};
+
+	$scope.contentRoutersCount = {
+		added: 0,
+		removed: 0,
+		templateUrl: 'crPopoverTemplate.html'
+	};
+
+	$scope.contentServersCount = {
+		added: 0,
+		removed: 0,
+		templateUrl: 'csPopoverTemplate.html'
+	};
+
+	$scope.deliveryServicesCount = {
+		added: 0,
+		removed: 0,
+		templateUrl: 'dsPopoverTemplate.html'
+	};
+
+	$scope.edgeLocationsCount = {
+		added: 0,
+		removed: 0,
+		templateUrl: 'elPopoverTemplate.html'
+	};
+
+	$scope.statsCount = {
+		added: 0,
+		removed: 0,
+		templateUrl: 'statsPopoverTemplate.html'
+	};
+
+	$scope.diffConfig = function(timeout) {
+		$('#config').html('<i class="fa fa-refresh fa-spin fa-1x fa-fw"></i> Generating diff...');
+		$timeout(function() {
+			performDiff(oldConfig, newConfig, 'config');
+		}, timeout);
+	};
+
+	$scope.diffContentRouters = function(timeout) {
+		$('#contentRouters').html('<i class="fa fa-refresh fa-spin fa-1x fa-fw"></i> Generating diff...');
+		$timeout(function() {
+			performDiff(oldContentRouters, newContentRouters, 'contentRouters');
+		}, timeout);
+	};
+
+	$scope.diffContentServers = function(timeout) {
+		$('#contentServers').html('<i class="fa fa-refresh fa-spin fa-1x fa-fw"></i> Generating diff...');
+		$timeout(function() {
+			performDiff(oldContentServers, newContentServers, 'contentServers');
+		}, timeout);
+	};
+
+	$scope.diffDeliveryServices = function(timeout) {
+		$('#deliveryServices').html('<i class="fa fa-refresh fa-spin fa-1x fa-fw"></i> Generating diff...');
+		$timeout(function() {
+			performDiff(oldDeliveryServices, newDeliveryServices, 'deliveryServices');
+		}, timeout);
+	};
+
+	$scope.diffEdgeLocations = function(timeout) {
+		$('#edgeLocations').html('<i class="fa fa-refresh fa-spin fa-1x fa-fw"></i> Generating diff...');
+		$timeout(function() {
+			performDiff(oldEdgeLocations, newEdgeLocations, 'edgeLocations');
+		}, timeout);
+	};
+
+	$scope.diffStats = function(timeout) {
+		$('#stats').html('<i class="fa fa-refresh fa-spin fa-1x fa-fw"></i> Generating diff...');
+		$timeout(function() {
+			performDiff(oldStats, newStats, 'stats');
+		}, timeout);
+	};
+
+	$scope.confirmSnapshot = function(cdn) {
+		var params = {
+			title: 'Perform Snapshot',
+			message: 'Are you sure you want to snapshot the ' + cdn.name + ' config?'
+		};
+		var modalInstance = $uibModal.open({
+			templateUrl: 'common/modules/dialog/confirm/dialog.confirm.tpl.html',
+			controller: 'DialogConfirmController',
+			size: 'sm',
+			resolve: {
+				params: function () {
+					return params;
+				}
+			}
+		});
+		modalInstance.result.then(function() {
+			snapshot();
+		}, function () {
+			// do nothing
+		});
+	};
+
+	$scope.navigateToPath = locationUtils.navigateToPath;
+
+	angular.element(document).ready(function () {
+		$scope.diffConfig(0);
+		$scope.diffContentRouters(0);
+		$scope.diffContentServers(0);
+		$scope.diffDeliveryServices(0);
+		$scope.diffEdgeLocations(0);
+		$scope.diffStats(0);
+	});
+
+};
+
+ConfigController.$inject = ['cdn', 'currentSnapshot', 'newSnapshot', '$scope', '$state', '$timeout', '$uibModal', 'locationUtils', 'cdnService'];
+module.exports = ConfigController;

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c72b5027/traffic_ops/experimental/ui/app/src/modules/private/admin/cdns/config/_config.scss
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/ui/app/src/modules/private/admin/cdns/config/_config.scss b/traffic_ops/experimental/ui/app/src/modules/private/admin/cdns/config/_config.scss
new file mode 100644
index 0000000..e75548e
--- /dev/null
+++ b/traffic_ops/experimental/ui/app/src/modules/private/admin/cdns/config/_config.scss
@@ -0,0 +1,50 @@
+/*
+
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+#snapshotContainer {
+
+  pre {
+    margin: 20px 0 20px 0;
+  }
+
+  .nav-tabs {
+    border-bottom: 1px solid #ddd;
+  }
+
+  .tab {
+    padding: 20px 20px 0 20px;
+  }
+
+  .tab-heading {
+    font-size: 17px;
+  }
+
+  .added {
+    background-color: #dff0d8;
+    color: #3c763d;
+  }
+
+  .removed {
+    background-color: #f2dede;
+    color: #a94442;
+  }
+
+  .no-change {
+    color: grey;
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c72b5027/traffic_ops/experimental/ui/app/src/modules/private/admin/cdns/config/config.tpl.html
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/ui/app/src/modules/private/admin/cdns/config/config.tpl.html b/traffic_ops/experimental/ui/app/src/modules/private/admin/cdns/config/config.tpl.html
new file mode 100644
index 0000000..ca87d9f
--- /dev/null
+++ b/traffic_ops/experimental/ui/app/src/modules/private/admin/cdns/config/config.tpl.html
@@ -0,0 +1,113 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+<div class="x_panel">
+    <div class="x_title">
+        <ol class="breadcrumb pull-left">
+            <li><a ng-click="navigateToPath('/admin/cdns')">CDNs</a></li>
+            <li><a ng-click="navigateToPath('/admin/cdns/' + cdn.id)">{{::cdn.name}}</a></li>
+            <li class="active">Config Changes</li>
+        </ol>
+        <div class="pull-right" ng-show="!settings.isNew">
+            <button type="button" class="btn btn-default" ng-click="navigateToPath('/admin/cdns/' + cdn.id)">Cancel</button>
+            <button class="btn btn-primary" title="Snapshot {{cdn.name}} Config" ng-click="confirmSnapshot(cdn)"><i class="fa fa-camera"></i>&nbsp;&nbsp;Perform Snapshot</button>
+        </div>
+
+        <div class="clearfix"></div>
+    </div>
+    <div id="snapshotContainer" class="x_content">
+        <uib-tabset active="active" justified="true">
+            <uib-tab index="0" class="tab" ng-click="diffConfig(500)">
+                <uib-tab-heading>
+                    General [ <span uib-popover-template="configCount.templateUrl" popover-title="{{configCount.added + configCount.removed}} Total Changes" popover-trigger="mouseenter" popover-placement="bottom" popover-append-to-body="true">{{configCount.added}} | {{configCount.removed}}</span> ]
+                </uib-tab-heading>
+                <pre id="config"></pre>
+            </uib-tab>
+            <uib-tab index="1" class="tab" ng-click="diffContentRouters(500)">
+                <uib-tab-heading>
+                    Traffic Routers [ <span uib-popover-template="contentRoutersCount.templateUrl" popover-title="{{contentRoutersCount.added + contentRoutersCount.removed}} Total Changes" popover-trigger="mouseenter" popover-placement="bottom" popover-append-to-body="true">{{contentRoutersCount.added}} | {{contentRoutersCount.removed}}</span> ]
+                </uib-tab-heading>
+                <pre id="contentRouters"></pre>
+            </uib-tab>
+            <uib-tab index="2" class="tab" ng-click="diffContentServers(500)">
+                <uib-tab-heading>
+                    Traffic Servers [ <span uib-popover-template="contentServersCount.templateUrl" popover-title="{{contentServersCount.added + contentServersCount.removed}} Total Changes" popover-trigger="mouseenter" popover-placement="bottom" popover-append-to-body="true">{{contentServersCount.added}} | {{contentServersCount.removed}}</span> ]
+                </uib-tab-heading>
+                <pre id="contentServers"></pre>
+            </uib-tab>
+            <uib-tab index="3" class="tab" ng-click="diffDeliveryServices(500)">
+                <uib-tab-heading>
+                    Delivery Services [ <span uib-popover-template="deliveryServicesCount.templateUrl" popover-title="{{deliveryServicesCount.added + deliveryServicesCount.removed}} Total Changes" popover-trigger="mouseenter" popover-placement="bottom" popover-append-to-body="true">{{deliveryServicesCount.added}} | {{deliveryServicesCount.removed}}</span> ]
+                </uib-tab-heading>
+                <pre id="deliveryServices"></pre>
+            </uib-tab>
+            <uib-tab index="4" class="tab" ng-click="diffEdgeLocations(500)">
+                <uib-tab-heading>
+                    Edge Cache Groups [ <span uib-popover-template="edgeLocationsCount.templateUrl" popover-title="{{edgeLocationsCount.added + edgeLocationsCount.removed}} Total Changes" popover-trigger="mouseenter" popover-placement="bottom" popover-append-to-body="true">{{edgeLocationsCount.added}} | {{edgeLocationsCount.removed}}</span> ]
+                </uib-tab-heading>
+                <pre id="edgeLocations"></pre>
+            </uib-tab>
+            <uib-tab index="5" class="tab" ng-click="diffStats(500)">
+                <uib-tab-heading>
+                    Stats [ <span uib-popover-template="statsCount.templateUrl" popover-title="{{statsCount.added + statsCount.removed}} Total Changes" popover-trigger="mouseenter" popover-placement="bottom" popover-append-to-body="true">{{statsCount.added}} | {{statsCount.removed}}</span> ]
+                </uib-tab-heading>
+                <pre id="stats"></pre>
+            </uib-tab>
+        </uib-tabset>
+        <div class="modal-footer">
+            <button type="button" class="btn btn-default" ng-click="navigateToPath('/admin/cdns/' + cdn.id)">Cancel</button>
+            <button class="btn btn-primary" title="Snapshot {{cdn.name}} Config" ng-click="confirmSnapshot(cdn)"><i class="fa fa-camera"></i>&nbsp;&nbsp;Perform Snapshot</button>
+        </div>
+    </div>
+</div>
+
+<!--- start: templates for popovers --->
+
+<script type="text/ng-template" id="configPopoverTemplate.html">
+    <div>{{configCount.added}} additions (++)</div>
+    <div>{{configCount.removed}} removals (--)</div>
+</script>
+
+<script type="text/ng-template" id="crPopoverTemplate.html">
+    <div>{{contentRoutersCount.added}} additions (++)</div>
+    <div>{{contentRoutersCount.removed}} removals (--)</div>
+</script>
+
+<script type="text/ng-template" id="csPopoverTemplate.html">
+    <div>{{contentServersCount.added}} additions (++)</div>
+    <div>{{contentServersCount.removed}} removals (--)</div>
+</script>
+
+<script type="text/ng-template" id="dsPopoverTemplate.html">
+    <div>{{deliveryServicesCount.added}} additions (++)</div>
+    <div>{{deliveryServicesCount.removed}} removals (--)</div>
+</script>
+
+<script type="text/ng-template" id="elPopoverTemplate.html">
+    <div>{{edgeLocationsCount.added}} additions (++)</div>
+    <div>{{edgeLocationsCount.removed}} removals (--)</div>
+</script>
+
+<script type="text/ng-template" id="statsPopoverTemplate.html">
+    <div>{{statsCount.added}} additions (++)</div>
+    <div>{{statsCount.removed}} removals (--)</div>
+</script>
+
+<!--- end: templates for popovers --->
+

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c72b5027/traffic_ops/experimental/ui/app/src/modules/private/admin/cdns/config/index.js
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/ui/app/src/modules/private/admin/cdns/config/index.js b/traffic_ops/experimental/ui/app/src/modules/private/admin/cdns/config/index.js
new file mode 100644
index 0000000..4bdf0ac
--- /dev/null
+++ b/traffic_ops/experimental/ui/app/src/modules/private/admin/cdns/config/index.js
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+module.exports = angular.module('trafficOps.private.admin.cdns.config', [])
+	.controller('ConfigController', require('./ConfigController'))
+	.config(function($stateProvider, $urlRouterProvider) {
+		$stateProvider
+			.state('trafficOps.private.admin.cdns.config', {
+				url: '/{cdnId}/config/changes',
+				views: {
+					cdnsContent: {
+						templateUrl: 'modules/private/admin/cdns/config/config.tpl.html',
+						controller: 'ConfigController',
+						resolve: {
+							cdn: function($stateParams, cdnService) {
+								return cdnService.getCDN($stateParams.cdnId);
+							},
+							currentSnapshot: function(cdn, cdnService) {
+								return cdnService.getCurrentSnapshot(cdn.name);
+							},
+							newSnapshot: function(cdn, cdnService) {
+								return cdnService.getNewSnapshot(cdn.name);
+							}
+						}
+					}
+				}
+			})
+		;
+		$urlRouterProvider.otherwise('/');
+	});

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c72b5027/traffic_ops/experimental/ui/app/src/modules/private/admin/physLocations/_physLocations.scss
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/ui/app/src/modules/private/admin/physLocations/_physLocations.scss b/traffic_ops/experimental/ui/app/src/modules/private/admin/physLocations/_physLocations.scss
deleted file mode 100644
index d57b9c6..0000000
--- a/traffic_ops/experimental/ui/app/src/modules/private/admin/physLocations/_physLocations.scss
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
-
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
-*/

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/c72b5027/traffic_ops/experimental/ui/app/src/styles/main.scss
----------------------------------------------------------------------
diff --git a/traffic_ops/experimental/ui/app/src/styles/main.scss b/traffic_ops/experimental/ui/app/src/styles/main.scss
index c172877..8cc2d8b 100755
--- a/traffic_ops/experimental/ui/app/src/styles/main.scss
+++ b/traffic_ops/experimental/ui/app/src/styles/main.scss
@@ -48,8 +48,7 @@ $fa-font-path: "../assets/fonts";
 
 // admin
 @import "../modules/private/admin/admin";
-@import "../modules/private/admin/divisions/divisions";
-@import "../modules/private/admin/physLocations/physLocations";
+@import "../modules/private/admin/cdns/config/config";
 @import "../modules/private/admin/regions/regions";
 @import "../modules/private/admin/tenants/tenants";
 @import "../modules/private/admin/users/users";