You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@trafficcontrol.apache.org by GitBox <gi...@apache.org> on 2018/06/18 15:13:05 UTC

[GitHub] mitchell852 closed pull request #2401: Implement Origin UI in Traffic Portal

mitchell852 closed pull request #2401: Implement Origin UI in Traffic Portal
URL: https://github.com/apache/trafficcontrol/pull/2401
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/traffic_portal/app/src/app.js b/traffic_portal/app/src/app.js
index d41d49747..4df60e0d6 100644
--- a/traffic_portal/app/src/app.js
+++ b/traffic_portal/app/src/app.js
@@ -104,6 +104,7 @@ var trafficPortal = angular.module('trafficPortal', [
         require('./modules/private/deliveryServices/jobs').name,
         require('./modules/private/deliveryServices/jobs/list').name,
         require('./modules/private/deliveryServices/jobs/new').name,
+        require('./modules/private/deliveryServices/origins').name,
         require('./modules/private/deliveryServices/regexes').name,
         require('./modules/private/deliveryServices/regexes/edit').name,
         require('./modules/private/deliveryServices/regexes/list').name,
@@ -129,6 +130,10 @@ var trafficPortal = angular.module('trafficPortal', [
         require('./modules/private/jobs').name,
         require('./modules/private/jobs/list').name,
         require('./modules/private/jobs/new').name,
+        require('./modules/private/origins').name,
+        require('./modules/private/origins/edit').name,
+        require('./modules/private/origins/list').name,
+        require('./modules/private/origins/new').name,
         require('./modules/private/physLocations').name,
         require('./modules/private/physLocations/edit').name,
         require('./modules/private/physLocations/list').name,
@@ -259,6 +264,9 @@ var trafficPortal = angular.module('trafficPortal', [
         require('./common/modules/form/iso').name,
         require('./common/modules/form/job').name,
         require('./common/modules/form/job/new').name,
+        require('./common/modules/form/origin').name,
+        require('./common/modules/form/origin/edit').name,
+        require('./common/modules/form/origin/new').name,
         require('./common/modules/form/physLocation').name,
         require('./common/modules/form/physLocation/edit').name,
         require('./common/modules/form/physLocation/new').name,
@@ -312,6 +320,7 @@ var trafficPortal = angular.module('trafficPortal', [
         require('./common/modules/table/coordinates').name,
         require('./common/modules/table/deliveryServices').name,
         require('./common/modules/table/deliveryServiceJobs').name,
+        require('./common/modules/table/deliveryServiceOrigins').name,
         require('./common/modules/table/deliveryServiceRegexes').name,
         require('./common/modules/table/deliveryServiceRequests').name,
         require('./common/modules/table/deliveryServiceRequestComments').name,
@@ -322,6 +331,7 @@ var trafficPortal = angular.module('trafficPortal', [
         require('./common/modules/table/divisions').name,
         require('./common/modules/table/divisionRegions').name,
         require('./common/modules/table/jobs').name,
+        require('./common/modules/table/origins').name,
         require('./common/modules/table/physLocations').name,
         require('./common/modules/table/physLocationServers').name,
         require('./common/modules/table/parameters').name,
diff --git a/traffic_portal/app/src/common/api/CoordinateService.js b/traffic_portal/app/src/common/api/CoordinateService.js
index 5aa714691..7d5882c87 100644
--- a/traffic_portal/app/src/common/api/CoordinateService.js
+++ b/traffic_portal/app/src/common/api/CoordinateService.js
@@ -23,24 +23,24 @@ var CoordinateService = function($http, $q, Restangular, locationUtils, messageM
         return Restangular.all('coordinates').getList(queryParams);
     };
 
-	this.createCoordinate = function(coordinate) {
-		var request = $q.defer();
+    this.createCoordinate = function(coordinate) {
+        var request = $q.defer();
 
-		$http.post(ENV.api['root'] + "coordinates", coordinate)
-			.then(
-				function(response) {
+        $http.post(ENV.api['root'] + "coordinates", coordinate)
+            .then(
+                function(response) {
                     messageModel.setMessages(response.data.alerts, true);
                     locationUtils.navigateToPath('/coordinates');
-					request.resolve(result);
-				},
-				function(fault) {
+                    request.resolve(response);
+                },
+                function(fault) {
                     messageModel.setMessages(fault.data.alerts, false)
-					request.reject(fault);
-				}
-			);
+                    request.reject(fault);
+                }
+            );
 
-		return request.promise;
-	};
+        return request.promise;
+    };
 
     this.updateCoordinate = function(id, coordinate) {
         var request = $q.defer();
diff --git a/traffic_portal/app/src/common/api/OriginService.js b/traffic_portal/app/src/common/api/OriginService.js
new file mode 100644
index 000000000..2c2a66726
--- /dev/null
+++ b/traffic_portal/app/src/common/api/OriginService.js
@@ -0,0 +1,82 @@
+/*
+ * 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 OriginService = function($http, $q, Restangular, locationUtils, messageModel, ENV) {
+
+    this.getOrigins = function(queryParams) {
+        return Restangular.all('origins').getList(queryParams);
+    };
+
+    this.createOrigin = function(origin) {
+        var request = $q.defer();
+
+        $http.post(ENV.api['root'] + "origins", origin)
+            .then(
+                function(response) {
+                    messageModel.setMessages(response.data.alerts, true);
+                    locationUtils.navigateToPath('/origins');
+                    request.resolve(response);
+                },
+                function(fault) {
+                    messageModel.setMessages(fault.data.alerts, false)
+                    request.reject(fault);
+                }
+            );
+
+        return request.promise;
+    };
+
+    this.updateOrigin = function(id, origin) {
+        var request = $q.defer();
+
+        $http.put(ENV.api['root'] + "origins?id=" + id, origin)
+            .then(
+                function(response) {
+                    messageModel.setMessages(response.data.alerts, false);
+                    request.resolve();
+                },
+                function(fault) {
+                    messageModel.setMessages(fault.data.alerts, false);
+                    request.reject();
+                }
+            );
+        return request.promise;
+    };
+
+    this.deleteOrigin = function(id) {
+        var deferred = $q.defer();
+
+        $http.delete(ENV.api['root'] + "origins?id=" + id)
+            .then(
+                function(response) {
+                    messageModel.setMessages(response.data.alerts, true);
+                    deferred.resolve(response);
+                },
+                function(fault) {
+                    messageModel.setMessages(fault.data.alerts, false);
+                    deferred.reject(fault);
+                }
+            );
+        return deferred.promise;
+    };
+
+};
+
+OriginService.$inject = ['$http', '$q', 'Restangular', 'locationUtils', 'messageModel', 'ENV'];
+module.exports = OriginService;
diff --git a/traffic_portal/app/src/common/api/index.js b/traffic_portal/app/src/common/api/index.js
index 771faa59a..12964aa6f 100644
--- a/traffic_portal/app/src/common/api/index.js
+++ b/traffic_portal/app/src/common/api/index.js
@@ -22,23 +22,24 @@ module.exports = angular.module('trafficPortal.api', [])
     .service('asnService', require('./ASNService'))
     .service('cacheGroupService', require('./CacheGroupService'))
     .service('cacheGroupParameterService', require('./CacheGroupParameterService'))
-	.service('cacheStatsService', require('./CacheStatsService'))
-	.service('capabilityService', require('./CapabilityService'))
-	.service('cdnService', require('./CDNService'))
+    .service('cacheStatsService', require('./CacheStatsService'))
+    .service('capabilityService', require('./CapabilityService'))
+    .service('cdnService', require('./CDNService'))
     .service('changeLogService', require('./ChangeLogService'))
     .service('coordinateService', require('./CoordinateService'))
     .service('deliveryServiceService', require('./DeliveryServiceService'))
-	.service('deliveryServiceRegexService', require('./DeliveryServiceRegexService'))
-	.service('deliveryServiceRequestService', require('./DeliveryServiceRequestService'))
+    .service('deliveryServiceRegexService', require('./DeliveryServiceRegexService'))
+    .service('deliveryServiceRequestService', require('./DeliveryServiceRequestService'))
     .service('deliveryServiceUrlSigKeysService', require('./DeliveryServiceUrlSigKeysService'))
     .service('deliveryServiceUriSigningKeysService', require('./DeliveryServiceUriSigningKeysService'))
     .service('deliveryServiceSslKeysService', require('./DeliveryServiceSslKeysService'))
-	.service('divisionService', require('./DivisionService'))
-	.service('federationService', require('./FederationService'))
-	.service('endpointService', require('./EndpointService'))
-	.service('federationResolverService', require('./FederationResolverService'))
+    .service('divisionService', require('./DivisionService'))
+    .service('federationService', require('./FederationService'))
+    .service('endpointService', require('./EndpointService'))
+    .service('federationResolverService', require('./FederationResolverService'))
     .service('httpService', require('./HttpService'))
     .service('jobService', require('./JobService'))
+    .service('originService', require('./OriginService'))
     .service('physLocationService', require('./PhysLocationService'))
     .service('parameterService', require('./ParameterService'))
     .service('profileService', require('./ProfileService'))
diff --git a/traffic_portal/app/src/common/modules/form/deliveryService/FormDeliveryServiceController.js b/traffic_portal/app/src/common/modules/form/deliveryService/FormDeliveryServiceController.js
index e6065bd79..eae7ac81a 100644
--- a/traffic_portal/app/src/common/modules/form/deliveryService/FormDeliveryServiceController.js
+++ b/traffic_portal/app/src/common/modules/form/deliveryService/FormDeliveryServiceController.js
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-var FormDeliveryServiceController = function(deliveryService, dsCurrent, type, types, $scope, $location, $uibModal, $window, formUtils, locationUtils, tenantUtils, deliveryServiceUtils, cdnService, profileService, tenantService, propertiesModel) {
+var FormDeliveryServiceController = function(deliveryService, dsCurrent, origin, type, types, $scope, $location, $uibModal, $window, formUtils, locationUtils, tenantUtils, deliveryServiceUtils, cdnService, profileService, tenantService, propertiesModel) {
 
     var getCDNs = function() {
         cdnService.getCDNs()
@@ -47,13 +47,15 @@ var FormDeliveryServiceController = function(deliveryService, dsCurrent, type, t
 
     $scope.dsCurrent = dsCurrent; // this ds is used primarily for showing the diff between a ds request and the current DS
 
+    $scope.origin = origin[0];
+
     $scope.showChartsButton = propertiesModel.properties.deliveryServices.charts.show;
 
     $scope.openCharts = deliveryServiceUtils.openCharts;
 
     $scope.dsRequestsEnabled = propertiesModel.properties.dsRequests.enabled;
 
-	$scope.edgeFQDNs = function(ds) {
+    $scope.edgeFQDNs = function(ds) {
         var urlString = '';
         if (_.isArray(ds.exampleURLs) && ds.exampleURLs.length > 0) {
             for (var i = 0; i < ds.exampleURLs.length; i++) {
@@ -238,6 +240,10 @@ var FormDeliveryServiceController = function(deliveryService, dsCurrent, type, t
         $location.path($location.path() + '/targets');
     };
 
+    $scope.viewOrigins = function() {
+        $location.path($location.path() + '/origins');
+    };
+
     $scope.viewServers = function() {
         $location.path($location.path() + '/servers');
     };
@@ -281,5 +287,5 @@ var FormDeliveryServiceController = function(deliveryService, dsCurrent, type, t
 
 };
 
-FormDeliveryServiceController.$inject = ['deliveryService', 'dsCurrent', 'type', 'types', '$scope', '$location', '$uibModal', '$window', 'formUtils', 'locationUtils', 'tenantUtils', 'deliveryServiceUtils', 'cdnService', 'profileService', 'tenantService', 'propertiesModel'];
+FormDeliveryServiceController.$inject = ['deliveryService', 'dsCurrent', 'origin', 'type', 'types', '$scope', '$location', '$uibModal', '$window', 'formUtils', 'locationUtils', 'tenantUtils', 'deliveryServiceUtils', 'cdnService', 'profileService', 'tenantService', 'propertiesModel'];
 module.exports = FormDeliveryServiceController;
diff --git a/traffic_portal/app/src/common/modules/form/deliveryService/edit/FormEditDeliveryServiceController.js b/traffic_portal/app/src/common/modules/form/deliveryService/edit/FormEditDeliveryServiceController.js
index 7576c8b74..9f6f3407c 100644
--- a/traffic_portal/app/src/common/modules/form/deliveryService/edit/FormEditDeliveryServiceController.js
+++ b/traffic_portal/app/src/common/modules/form/deliveryService/edit/FormEditDeliveryServiceController.js
@@ -17,10 +17,10 @@
  * under the License.
  */
 
-var FormEditDeliveryServiceController = function(deliveryService, type, types, $scope, $state, $controller, $uibModal, $anchorScroll, locationUtils, deliveryServiceService, deliveryServiceRequestService, messageModel, propertiesModel, userModel) {
+var FormEditDeliveryServiceController = function(deliveryService, origin, type, types, $scope, $state, $controller, $uibModal, $anchorScroll, locationUtils, deliveryServiceService, deliveryServiceRequestService, messageModel, propertiesModel, userModel) {
 
 	// extends the FormDeliveryServiceController to inherit common methods
-	angular.extend(this, $controller('FormDeliveryServiceController', { deliveryService: deliveryService, dsCurrent: deliveryService, type: type, types: types, $scope: $scope }));
+	angular.extend(this, $controller('FormDeliveryServiceController', { deliveryService: deliveryService, dsCurrent: deliveryService, origin: origin, type: type, types: types, $scope: $scope }));
 
 	var createDeliveryServiceDeleteRequest = function(deliveryService) {
 		var params = {
@@ -273,5 +273,5 @@ var FormEditDeliveryServiceController = function(deliveryService, type, types, $
 
 };
 
-FormEditDeliveryServiceController.$inject = ['deliveryService', 'type', 'types', '$scope', '$state', '$controller', '$uibModal', '$anchorScroll', 'locationUtils', 'deliveryServiceService', 'deliveryServiceRequestService', 'messageModel', 'propertiesModel', 'userModel'];
+FormEditDeliveryServiceController.$inject = ['deliveryService', 'origin', 'type', 'types', '$scope', '$state', '$controller', '$uibModal', '$anchorScroll', 'locationUtils', 'deliveryServiceService', 'deliveryServiceRequestService', 'messageModel', 'propertiesModel', 'userModel'];
 module.exports = FormEditDeliveryServiceController;
diff --git a/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.DNS.tpl.html b/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.DNS.tpl.html
index 5725970e6..3c5352e8d 100644
--- a/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.DNS.tpl.html
+++ b/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.DNS.tpl.html
@@ -52,6 +52,7 @@
                     <li role="menuitem"><a ng-click="manageUrlSigKeys()">Manage URL Sig Keys</a></li>
                     <li role="menuitem"><a ng-click="manageUriSigningKeys()">Manage URI Signing Keys</a></li>
                     <li class="divider"></li>
+                    <li role="menuitem"><a ng-click="viewOrigins()">View Origins</a></li>
                     <li role="menuitem"><a ng-click="viewServers()">View Servers</a></li>
                     <li role="menuitem"><a ng-click="viewRegexes()">View Regexes</a></li>
                     <li role="menuitem"><a ng-click="viewJobs()">View Invalidation Requests</a></li>
@@ -155,6 +156,7 @@
                     <small class="input-error" ng-show="hasPropertyError(deliveryServiceForm.orgServerFqdn, 'required')">Required</small>
                     <small class="input-error" ng-show="hasPropertyError(deliveryServiceForm.orgServerFqdn, 'pattern')">Must start with http:// or https:// and be followed by a valid hostname with an optional port (no trailing slash)</small>
                     <small class="input-diff" ng-show="settings.isRequest && open() && deliveryService.orgServerFqdn != dsCurrent.orgServerFqdn">Current Value: [ {{dsCurrent.orgServerFqdn}} ]</small>
+                    <small ng-show="!settings.isNew && deliveryService.orgServerFqdn"><a href="/#!/origins/{{origin.id}}" target="_blank">View Details&nbsp;&nbsp;<i class="fa fs-xs fa-external-link"></i></a></small>
                     <span ng-show="hasError(deliveryServiceForm.orgServerFqdn)" class="form-control-feedback"><i class="fa fa-times"></i></span>
                 </div>
             </div>
@@ -492,7 +494,7 @@
                         <small class="input-diff" ng-show="settings.isRequest && open() && deliveryService.fqPacingRate != dsCurrent.fqPacingRate">Current Value: [ {{dsCurrent.fqPacingRate}} ]</small>
                         <span ng-show="hasError(deliveryServiceForm.fqPacingRate)" class="form-control-feedback"><i class="fa fa-times"></i></span>
                     </div>
-                </div>		
+                </div>
 
                 <div class="form-group" ng-class="{'has-error': hasError(deliveryServiceForm.edgeHeaderRewrite), 'has-feedback': hasError(deliveryServiceForm.edgeHeaderRewrite)}">
                     <label class="control-label col-md-2 col-sm-2 col-xs-12">
diff --git a/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.HTTP.tpl.html b/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.HTTP.tpl.html
index 24db8a4c6..090829574 100644
--- a/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.HTTP.tpl.html
+++ b/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.HTTP.tpl.html
@@ -52,6 +52,7 @@
                     <li role="menuitem"><a ng-click="manageUrlSigKeys()">Manage URL Sig Keys</a></li>
                     <li role="menuitem"><a ng-click="manageUriSigningKeys()">Manage URI Signing Keys</a></li>
                     <li class="divider"></li>
+                    <li role="menuitem"><a ng-click="viewOrigins()">View Origins</a></li>
                     <li role="menuitem"><a ng-click="viewServers()">View Servers</a></li>
                     <li role="menuitem"><a ng-click="viewRegexes()">View Regexes</a></li>
                     <li role="menuitem"><a ng-click="viewJobs()">View Invalidation Requests</a></li>
@@ -155,6 +156,7 @@
                     <small class="input-error" ng-show="hasPropertyError(deliveryServiceForm.orgServerFqdn, 'required')">Required</small>
                     <small class="input-error" ng-show="hasPropertyError(deliveryServiceForm.orgServerFqdn, 'pattern')">Must start with http:// or https:// and be followed by a valid hostname with an optional port (no trailing slash)</small>
                     <small class="input-diff" ng-show="settings.isRequest && open() && deliveryService.orgServerFqdn != dsCurrent.orgServerFqdn">Current Value: [ {{dsCurrent.orgServerFqdn}} ]</small>
+                    <small ng-show="!settings.isNew && deliveryService.orgServerFqdn"><a href="/#!/origins/{{origin.id}}" target="_blank">View Details&nbsp;&nbsp;<i class="fa fs-xs fa-external-link"></i></a></small>
                     <span ng-show="hasError(deliveryServiceForm.orgServerFqdn)" class="form-control-feedback"><i class="fa fa-times"></i></span>
                 </div>
             </div>
@@ -498,7 +500,7 @@
                     </div>
                 </div>
 
-		<div class="form-group" ng-class="{'has-error': hasError(deliveryServiceForm.fqPacingRate), 'has-feedback': hasError(deliveryServiceForm.fqPacingRate)}">
+        <div class="form-group" ng-class="{'has-error': hasError(deliveryServiceForm.fqPacingRate), 'has-feedback': hasError(deliveryServiceForm.fqPacingRate)}">
                     <label class="control-label col-md-2 col-sm-2 col-xs-12">
                         <span uib-popover-html="label('fqPacingRate', 'desc')" popover-trigger="click" popover-placement="top" popover-append-to-body="true" popover-class="popover-class">{{label('fqPacingRate', 'title')}}</span>
                     </label>
@@ -508,7 +510,7 @@
                         <small class="input-diff" ng-show="settings.isRequest && open() && deliveryService.fqPacingRate != dsCurrent.fqPacingRate">Current Value: [ {{dsCurrent.fqPacingRate}} ]</small>
                         <span ng-show="hasError(deliveryServiceForm.fqPacingRate)" class="form-control-feedback"><i class="fa fa-times"></i></span>
                     </div>
-                </div>				
+                </div>
 
                 <div class="form-group" ng-class="{'has-error': hasError(deliveryServiceForm.edgeHeaderRewrite), 'has-feedback': hasError(deliveryServiceForm.edgeHeaderRewrite)}">
                     <label class="control-label col-md-2 col-sm-2 col-xs-12">
diff --git a/traffic_portal/app/src/common/modules/form/deliveryService/new/FormNewDeliveryServiceController.js b/traffic_portal/app/src/common/modules/form/deliveryService/new/FormNewDeliveryServiceController.js
index acd913316..bcf16b6d0 100644
--- a/traffic_portal/app/src/common/modules/form/deliveryService/new/FormNewDeliveryServiceController.js
+++ b/traffic_portal/app/src/common/modules/form/deliveryService/new/FormNewDeliveryServiceController.js
@@ -17,10 +17,10 @@
  * under the License.
  */
 
-var FormNewDeliveryServiceController = function(deliveryService, type, types, $scope, $controller, $uibModal, $anchorScroll, locationUtils, deliveryServiceService, deliveryServiceRequestService, messageModel, propertiesModel, userModel) {
+var FormNewDeliveryServiceController = function(deliveryService, origin, type, types, $scope, $controller, $uibModal, $anchorScroll, locationUtils, deliveryServiceService, deliveryServiceRequestService, messageModel, propertiesModel, userModel) {
 
 	// extends the FormDeliveryServiceController to inherit common methods
-	angular.extend(this, $controller('FormDeliveryServiceController', { deliveryService: deliveryService, dsCurrent: deliveryService, type: type, types: types, $scope: $scope }));
+	angular.extend(this, $controller('FormDeliveryServiceController', { deliveryService: deliveryService, dsCurrent: deliveryService, origin: origin, type: type, types: types, $scope: $scope }));
 
 	$scope.deliveryServiceName = 'New';
 
@@ -138,5 +138,5 @@ var FormNewDeliveryServiceController = function(deliveryService, type, types, $s
 
 };
 
-FormNewDeliveryServiceController.$inject = ['deliveryService', 'type', 'types', '$scope', '$controller', '$uibModal', '$anchorScroll', 'locationUtils', 'deliveryServiceService', 'deliveryServiceRequestService', 'messageModel', 'propertiesModel', 'userModel'];
+FormNewDeliveryServiceController.$inject = ['deliveryService', 'origin', 'type', 'types', '$scope', '$controller', '$uibModal', '$anchorScroll', 'locationUtils', 'deliveryServiceService', 'deliveryServiceRequestService', 'messageModel', 'propertiesModel', 'userModel'];
 module.exports = FormNewDeliveryServiceController;
diff --git a/traffic_portal/app/src/common/modules/form/origin/FormOriginController.js b/traffic_portal/app/src/common/modules/form/origin/FormOriginController.js
new file mode 100644
index 000000000..2231a1fc6
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/form/origin/FormOriginController.js
@@ -0,0 +1,101 @@
+/*
+ * 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 FormOriginController = function(origin, $scope, $window, $location, formUtils, locationUtils, tenantUtils, deliveryServiceService, profileService, tenantService, coordinateService, cacheGroupService, originService) {
+
+    var getProfiles = function() {
+        profileService.getProfiles({ orderby: 'name' })
+            .then(function(result) {
+                $scope.profiles = _.filter(result, function(profile) {
+                    return profile.type == 'ORG_PROFILE';
+                });
+            });
+    };
+
+    var getTenants = function() {
+        tenantService.getTenants()
+            .then(function(result) {
+                $scope.tenants = result;
+                tenantUtils.addLevels($scope.tenants);
+            });
+    };
+
+    var getCacheGroups = function() {
+        cacheGroupService.getCacheGroups({ orderby: 'name' })
+            .then(function(result) {
+                $scope.cacheGroups = _.filter(result, function(cachegroup) {
+                    return cachegroup.typeName == 'ORG_LOC';
+                });
+            });
+    };
+
+    var getCoordinates = function() {
+        coordinateService.getCoordinates({ orderby: 'name' })
+            .then(function(result) {
+                $scope.coordinates = result;
+            });
+    };
+
+    var getDeliveryServices = function() {
+        deliveryServiceService.getDeliveryServices()
+            .then(function(result) {
+                $scope.deliveryServices =  _.sortBy(result, 'xmlId');
+            });
+    };
+
+    $scope.origin = origin;
+
+    $scope.protocols = [
+        { value: 'http', label: 'http' },
+        { value: 'https', label: 'https' }
+    ];
+
+    $scope.tenantLabel = function(tenant) {
+        return '-'.repeat(tenant.level) + ' ' + tenant.name;
+    };
+
+    $scope.nullifyIfEmptyIP = function(origin) {
+        origin.ipAddress = origin.ipAddress == '' ? null : origin.ipAddress;
+        origin.ip6Address = origin.ip6Address == '' ? null : origin.ip6Address;
+    }
+
+    $scope.navigateToPath = locationUtils.navigateToPath;
+
+    $scope.editDeliveryService = function(deliveryServiceId) {
+        ds = _.findWhere($scope.deliveryServices, { id: deliveryServiceId });
+        $window.open('/#!/delivery-services/' + ds.id + '?type=' + ds.type, '_blank');
+    };
+
+    $scope.hasError = formUtils.hasError;
+
+    $scope.hasPropertyError = formUtils.hasPropertyError;
+
+    var init = function () {
+        getProfiles();
+        getTenants();
+        getCacheGroups();
+        getCoordinates();
+        getDeliveryServices();
+    };
+    init();
+
+};
+
+FormOriginController.$inject = ['origin', '$scope', '$window', '$location', 'formUtils', 'locationUtils', 'tenantUtils', 'deliveryServiceService', 'profileService', 'tenantService', 'coordinateService', 'cacheGroupService', 'originService'];
+module.exports = FormOriginController;
diff --git a/traffic_portal/app/src/common/modules/form/origin/edit/FormEditOriginController.js b/traffic_portal/app/src/common/modules/form/origin/edit/FormEditOriginController.js
new file mode 100644
index 000000000..398d50b14
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/form/origin/edit/FormEditOriginController.js
@@ -0,0 +1,80 @@
+/*
+ * 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 FormEditOriginController = function(origin, $scope, $state, $controller, $uibModal, $anchorScroll, locationUtils, originService) {
+
+    $scope.origin = origin[0]
+
+    // extends the FormOriginController to inherit common methods
+    angular.extend(this, $controller('FormOriginController', { origin: $scope.origin, $scope: $scope }));
+
+    $scope.originName = angular.copy($scope.origin.name);
+
+    $scope.settings = {
+        isNew: false,
+        saveLabel: 'Update',
+        deleteLabel: 'Delete'
+    };
+
+    $scope.save = function(origin) {
+        originService.updateOrigin(origin.id, origin).
+            then(
+                function(result) {
+                    $state.reload(); // reloads all the resolves for the view
+                },
+                function(fault) {
+                    $anchorScroll(); // scrolls window to top
+                }
+            );
+    };
+
+    $scope.confirmDelete = function(origin) {
+        var params = {
+            title: 'Delete Origin: ' + origin.name,
+            key: origin.name
+        };
+        var modalInstance = $uibModal.open({
+            templateUrl: 'common/modules/dialog/delete/dialog.delete.tpl.html',
+            controller: 'DialogDeleteController',
+            size: 'md',
+            resolve: {
+                params: function () {
+                    return params;
+                }
+            }
+        });
+        modalInstance.result.then(function() {
+            originService.deleteOrigin(origin.id)
+                .then(
+                    function(result) {
+                        locationUtils.navigateToPath('/origins');
+                    },
+                    function(fault) {
+                        $anchorScroll(); // scrolls window to top
+                    }
+                );
+        }, function () {
+            // do nothing
+        });
+    };
+
+};
+
+FormEditOriginController.$inject = ['origin', '$scope', '$state', '$controller', '$uibModal', '$anchorScroll', 'locationUtils', 'originService'];
+module.exports = FormEditOriginController;
diff --git a/traffic_portal/app/src/common/modules/form/origin/edit/index.js b/traffic_portal/app/src/common/modules/form/origin/edit/index.js
new file mode 100644
index 000000000..ad0402c24
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/form/origin/edit/index.js
@@ -0,0 +1,21 @@
+/*
+ * 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('trafficPortal.form.origin.edit', [])
+    .controller('FormEditOriginController', require('./FormEditOriginController'));
diff --git a/traffic_portal/app/src/common/modules/form/origin/form.origin.tpl.html b/traffic_portal/app/src/common/modules/form/origin/form.origin.tpl.html
new file mode 100644
index 000000000..0cedc54b8
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/form/origin/form.origin.tpl.html
@@ -0,0 +1,147 @@
+<!--
+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('/origins')">Origins</a></li>
+            <li class="active">{{originName}}</li>
+        </ol>
+        <div class="clearfix"></div>
+    </div>
+    <div class="x_content">
+        <br>
+        <form name="originForm" class="form-horizontal form-label-left" novalidate>
+            <div ng-show='!settings.isNew' class="form-group">
+                <label class="control-label col-md-2 col-sm-2 col-xs-12">Primary:</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <input name="isPrimary" type="text" class="form-control" ng-value="origin.isPrimary" readonly autofocus>
+                </div>
+            </div>
+            <div class="form-group" ng-class="{'has-error': hasError(originForm.name), 'has-feedback': hasError(originForm.name)}">
+                <label class="control-label col-md-2 col-sm-2 col-xs-12">Name *</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <input name="name" type="text" class="form-control" ng-model="origin.name" ng-maxlength="100" ng-pattern="/^\S*$/" required autofocus>
+                    <small class="input-error" ng-show="hasPropertyError(originForm.hostName, 'required')">Required</small>
+                    <small class="input-error" ng-show="hasPropertyError(originForm.hostName, 'maxlength')">Too Long</small>
+                    <small class="input-error" ng-show="hasPropertyError(originForm.hostName, 'pattern')">No spaces</small>
+                    <span ng-show="hasError(originForm.name)" class="form-control-feedback"><i class="fa fa-times"></i></span>
+                </div>
+            </div>
+            <div class="form-group" ng-class="{'has-error': hasError(originForm.tenantId), 'has-feedback': hasError(originForm.tenantId)}">
+                <label class="control-label col-md-2 col-sm-2 col-xs-12">Tenant</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <select name="tenantId" class="form-control" ng-model="origin.tenantId" ng-options="tenant.id as tenantLabel(tenant) for tenant in tenants" required>
+                        <option value="">Select...</option>
+                    </select>
+                    <small class="input-error" ng-show="hasPropertyError(originForm.tenantId, 'required')">Required</small>
+                    <small ng-show="origin.tenantId"><a href="/#!/tenants/{{origin.tenantId}}" target="_blank">View Details&nbsp;&nbsp;<i class="fa fs-xs fa-external-link"></i></a></small>
+                </div>
+            </div>
+            <div class="form-group" ng-class="{'has-error': hasError(originForm.fqdn), 'has-feedback': hasError(originForm.fqdn)}">
+                <label class="control-label col-md-2 col-sm-2 col-xs-12">FQDN *</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <input name="fqdn" type="text" class="form-control" ng-model="origin.fqdn" ng-maxlength="100" ng-pattern="/^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*$/" required autofocus>
+                    <small class="input-error" ng-show="hasPropertyError(originForm.fqdn, 'required')">Required</small>
+                    <small class="input-error" ng-show="hasPropertyError(originForm.fqdn, 'maxlength')">Too Long</small>
+                    <small class="input-error" ng-show="hasPropertyError(originForm.fqdn, 'pattern')">Invalid</small>
+                    <span ng-show="hasError(originForm.fqdn)" class="form-control-feedback"><i class="fa fa-times"></i></span>
+                </div>
+            </div>
+            <div class="form-group" ng-class="{'has-error': hasError(originForm.ipAddress), 'has-feedback': hasError(originForm.ipAddress)}">
+                <label class="control-label col-md-2 col-sm-2 col-xs-12">IPv4 Address</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <input name="ipAddress" type="text" class="form-control" ng-model="origin.ipAddress" ng-change="nullifyIfEmptyIP(origin)" ng-maxlength="15" ng-pattern="/^\d{1,3}(\.\d{1,3}){3}$/" autofocus>
+                    <small class="input-error" ng-show="hasPropertyError(originForm.ipAddress, 'maxlength')">Too Long</small>
+                    <small class="input-error" ng-show="hasPropertyError(originForm.ipAddress, 'pattern')">Invalid</small>
+                    <span ng-show="hasError(originForm.ipAddress)" class="form-control-feedback"><i class="fa fa-times"></i></span>
+                </div>
+            </div>
+            <div class="form-group" ng-class="{'has-error': hasError(originForm.ip6Address), 'has-feedback': hasError(originForm.ip6Address)}">
+                <label class="control-label col-md-2 col-sm-2 col-xs-12">IPv6 Address</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <input name="ip6Address" type="text" class="form-control" ng-model="origin.ip6Address" ng-change="nullifyIfEmptyIP(origin)" ng-maxlength="39" ng-pattern="/^[0-9a-fA-F:]+$/" autofocus>
+                    <small class="input-error" ng-show="hasPropertyError(originForm.ip6Address, 'maxlength')">Too Long</small>
+                    <small class="input-error" ng-show="hasPropertyError(originForm.ip6Address, 'pattern')">Invalid</small>
+                    <span ng-show="hasError(originForm.ip6Address)" class="form-control-feedback"><i class="fa fa-times"></i></span>
+                </div>
+            </div>
+            <div class="form-group" ng-class="{'has-error': hasError(originForm.protocol), 'has-feedback': hasError(originForm.protocol)}">
+                <label class="control-label col-md-2 col-sm-2 col-xs-12">Protocol *</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <select name="protocol" class="form-control" ng-model="origin.protocol" ng-options="protocol.value as protocol.label for protocol in protocols" required>
+                        <option value="">Select...</option>
+                    </select>
+                    <small class="input-error" ng-show="hasPropertyError(originForm.protocol, 'required')">Required</small>
+                </div>
+            </div>
+            <div class="form-group" ng-class="{'has-error': hasError(originForm.port), 'has-feedback': hasError(originForm.port)}">
+                <label class="control-label col-md-2 col-sm-2 col-xs-12">TCP Port</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <input name="port" type="number" class="form-control" ng-model="origin.port" ng-min="1" ng-max="65535" ng-pattern="/^\d+$/" autofocus>
+                    <small class="input-error" ng-show="hasPropertyError(originForm.port, 'pattern')">Whole Number</small>
+                    <small class="input-error" ng-show="hasPropertyError(originForm.port, 'min')">Invalid port. Must be between 1 and 65535</small>
+                    <small class="input-error" ng-show="hasPropertyError(originForm.port, 'max')">Invalid port. Must be between 1 and 65535</small>
+                    <span ng-show="hasError(originForm.port)" class="form-control-feedback"><i class="fa fa-times"></i></span>
+                </div>
+            </div>
+            <div class="form-group" ng-class="{'has-error': hasError(originForm.deliveryServiceId), 'has-feedback': hasError(originForm.deliveryServiceId)}">
+                <label class="control-label col-md-2 col-sm-2 col-xs-12">Delivery Service *</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <select name="deliveryServiceId" class="form-control" ng-model="origin.deliveryServiceId" ng-options="deliveryService.id as deliveryService.xmlId for deliveryService in deliveryServices" ng-disabled="origin.isPrimary" required>
+                        <option value="">Select...</option>
+                    </select>
+                    <small class="input-error" ng-show="hasPropertyError(originForm.deliveryServiceId, 'required')">Required</small>
+                    <small ng-show="origin.deliveryServiceId"><a ng-click="editDeliveryService(origin.deliveryServiceId)" target="_blank">View Details&nbsp;&nbsp;<i class="fa fs-xs fa-external-link"></i></a></small>
+                </div>
+            </div>
+            <div class="form-group" ng-class="{'has-error': hasError(originForm.coordinateId), 'has-feedback': hasError(originForm.coordinateId)}">
+                <label class="control-label col-md-2 col-sm-2 col-xs-12">Coordinate</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <select name="coordinateId" class="form-control" ng-model="origin.coordinateId" ng-options="coordinate.id as coordinate.name for coordinate in coordinates">
+                        <option value="">Select...</option>
+                    </select>
+                    <small ng-show="origin.coordinateId"><a href="/#!/coordinates/{{origin.coordinateId}}" target="_blank">View Details&nbsp;&nbsp;<i class="fa fs-xs fa-external-link"></i></a></small>
+                </div>
+            </div>
+            <div class="form-group" ng-class="{'has-error': hasError(originForm.cachegroup), 'has-feedback': hasError(originForm.cachegroup)}">
+                <label class="control-label col-md-2 col-sm-2 col-xs-12">Cache Group</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <select name="cachegroup" class="form-control" ng-model="origin.cachegroupId" ng-options="cachegroup.id as cachegroup.name for cachegroup in cacheGroups">
+                        <option value="">Select...</option>
+                    </select>
+                    <small ng-show="origin.cachegroupId"><a href="/#!/cache-groups/{{origin.cachegroupId}}" target="_blank">View Details&nbsp;&nbsp;<i class="fa fs-xs fa-external-link"></i></a></small>
+                </div>
+            </div>
+            <div class="form-group" ng-class="{'has-error': hasError(originForm.profile), 'has-feedback': hasError(originForm.profile)}">
+                <label class="control-label col-md-2 col-sm-2 col-xs-12">Profile</label>
+                <div class="col-md-10 col-sm-10 col-xs-12">
+                    <select name="profile" class="form-control" ng-model="origin.profileId" ng-options="profile.id as profile.name for profile in profiles">
+                        <option value="">Select...</option>
+                    </select>
+                    <small ng-show="origin.profileId"><a href="/#!/profiles/{{origin.profileId}}" target="_blank">View Details&nbsp;&nbsp;<i class="fa fs-xs fa-external-link"></i></a></small>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-danger" ng-show="!settings.isNew" ng-disabled="origin.isPrimary" ng-click="confirmDelete(origin)">Delete</button>
+                <button type="button" class="btn btn-success" ng-disabled="originForm.$pristine || originForm.$invalid" ng-click="save(origin)">{{settings.saveLabel}}</button>
+            </div>
+        </form>
+    </div>
+</div>
diff --git a/traffic_portal/app/src/common/modules/form/origin/index.js b/traffic_portal/app/src/common/modules/form/origin/index.js
new file mode 100644
index 000000000..322a981de
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/form/origin/index.js
@@ -0,0 +1,21 @@
+/*
+ * 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('trafficPortal.form.origin', [])
+    .controller('FormOriginController', require('./FormOriginController'));
diff --git a/traffic_portal/app/src/common/modules/form/origin/new/FormNewOriginController.js b/traffic_portal/app/src/common/modules/form/origin/new/FormNewOriginController.js
new file mode 100644
index 000000000..b7c098067
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/form/origin/new/FormNewOriginController.js
@@ -0,0 +1,39 @@
+/*
+ * 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 FormNewOriginController = function(origin, $scope, $controller, $anchorScroll, originService) {
+
+    // extends the FormOriginController to inherit common methods
+    angular.extend(this, $controller('FormOriginController', { origin: origin, $scope: $scope }));
+
+    $scope.originName = 'New';
+
+    $scope.settings = {
+        isNew: true,
+        saveLabel: 'Create'
+    };
+
+    $scope.save = function(origin) {
+        originService.createOrigin(origin);
+    };
+
+};
+
+FormNewOriginController.$inject = ['origin', '$scope', '$controller', '$anchorScroll', 'originService'];
+module.exports = FormNewOriginController;
diff --git a/traffic_portal/app/src/common/modules/form/origin/new/index.js b/traffic_portal/app/src/common/modules/form/origin/new/index.js
new file mode 100644
index 000000000..884648ca0
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/form/origin/new/index.js
@@ -0,0 +1,21 @@
+/*
+ * 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('trafficPortal.form.origin.new', [])
+    .controller('FormNewOriginController', require('./FormNewOriginController'));
diff --git a/traffic_portal/app/src/common/modules/navigation/navigation.tpl.html b/traffic_portal/app/src/common/modules/navigation/navigation.tpl.html
index bcd7bc5c0..d2c4ce85c 100644
--- a/traffic_portal/app/src/common/modules/navigation/navigation.tpl.html
+++ b/traffic_portal/app/src/common/modules/navigation/navigation.tpl.html
@@ -42,6 +42,7 @@
                 <li class="side-menu-category"><a href="javascript:void(0);"><i class="fa fa-sm fa-chevron-right"></i> Configure</span></a>
                     <ul class="nav child_menu" style="display: none">
                         <li class="side-menu-category-item" ng-class="{'current-page': isState('trafficPortal.private.servers')}"><a href="/#!/servers">Servers</a></li>
+                        <li class="side-menu-category-item" ng-class="{'current-page': isState('trafficPortal.private.origins')}"><a href="/#!/origins">Origins</a></li>
                         <li class="side-menu-category-item" ng-class="{'current-page': isState('trafficPortal.private.profiles')}"><a href="/#!/profiles">Profiles</a></li>
                         <li class="side-menu-category-item" ng-class="{'current-page': isState('trafficPortal.private.parameters')}"><a href="/#!/parameters">Parameters</a></li>
                         <li class="side-menu-category-item" ng-class="{'current-page': isState('trafficPortal.private.types')}"><a href="/#!/types">Types</a></li>
diff --git a/traffic_portal/app/src/common/modules/table/deliveryServiceOrigins/TableDeliveryServiceOriginsController.js b/traffic_portal/app/src/common/modules/table/deliveryServiceOrigins/TableDeliveryServiceOriginsController.js
new file mode 100644
index 000000000..eee379ab3
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/table/deliveryServiceOrigins/TableDeliveryServiceOriginsController.js
@@ -0,0 +1,53 @@
+/*
+ * 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 TableDeliveryServiceOriginsController = function(deliveryService, origins, $scope, $state, $location, locationUtils) {
+
+    $scope.createOrigin = function() {
+        var path = '/origins/new';
+        locationUtils.navigateToPath(path);
+    };
+
+    $scope.deliveryService = deliveryService;
+
+    $scope.origins = origins;
+
+    $scope.editOrigin = function(id) {
+        var path = '/origins/' + id;
+        locationUtils.navigateToPath(path);
+    };
+
+    $scope.refresh = function() {
+        $state.reload(); // reloads all the resolves for the view
+    };
+
+    $scope.navigateToPath = locationUtils.navigateToPath;
+
+    angular.element(document).ready(function () {
+        $('#originsTable').dataTable({
+            "aLengthMenu": [[25, 50, 100, -1], [25, 50, 100, "All"]],
+            "iDisplayLength": 25,
+            "aaSorting": []
+        });
+    });
+
+};
+
+TableDeliveryServiceOriginsController.$inject = ['deliveryService', 'origins', '$scope', '$state', '$location', 'locationUtils'];
+module.exports = TableDeliveryServiceOriginsController;
diff --git a/traffic_portal/app/src/common/modules/table/deliveryServiceOrigins/index.js b/traffic_portal/app/src/common/modules/table/deliveryServiceOrigins/index.js
new file mode 100644
index 000000000..0b427d0fc
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/table/deliveryServiceOrigins/index.js
@@ -0,0 +1,21 @@
+/*
+ * 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('trafficPortal.table.deliveryServiceOrigins', [])
+    .controller('TableDeliveryServiceOriginsController', require('./TableDeliveryServiceOriginsController'));
diff --git a/traffic_portal/app/src/common/modules/table/deliveryServiceOrigins/table.deliveryServiceOrigins.tpl.html b/traffic_portal/app/src/common/modules/table/deliveryServiceOrigins/table.deliveryServiceOrigins.tpl.html
new file mode 100644
index 000000000..fa33ce581
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/table/deliveryServiceOrigins/table.deliveryServiceOrigins.tpl.html
@@ -0,0 +1,71 @@
+<!--
+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('/delivery-services')">Delivery Services</a></li>
+            <li><a ng-click="navigateToPath('/delivery-services/' + deliveryService.id + '?type=' + deliveryService.type)">{{::deliveryService.xmlId}}</a></li>
+            <li class="active">Origins</li>
+        </ol>
+        <div class="pull-right" role="group">
+            <button class="btn btn-primary" title="Create Origin" ng-click="createOrigin()"><i class="fa fa-plus"></i></button>
+            <button class="btn btn-default" title="Refresh" ng-click="refresh()"><i class="fa fa-refresh"></i></button>
+        </div>
+        <div class="clearfix"></div>
+    </div>
+    <div class="x_content">
+        <br>
+        <table id="originsTable" class="table responsive-utilities jambo_table">
+            <thead>
+                <tr class="headings">
+                    <th>Name</th>
+                    <th>Tenant</th>
+                    <th>Primary</th>
+                    <th>Delivery Service</th>
+                    <th>FQDN</th>
+                    <th>IPv4 Address</th>
+                    <th>IPv6 Address</th>
+                    <th>Protocol</th>
+                    <th>Port</th>
+                    <th>Coordinate</th>
+                    <th>Cachegroup</th>
+                    <th>Profile</th>
+                </tr>
+            </thead>
+            <tbody>
+                <tr ng-click="editOrigin(o.id)" ng-repeat="o in ::origins">
+                    <td data-search="^{{::o.name}}$">{{::o.name}}</td>
+                    <td data-search="^{{::o.tenant}}$">{{::o.tenant}}</td>
+                    <td data-search="^{{::o.isPrimary}}$">{{::o.isPrimary}}</td>
+                    <td data-search="^{{::o.deliveryService}}$">{{::o.deliveryService}}</td>
+                    <td data-search="^{{::o.fqdn}}$">{{::o.fqdn}}</td>
+                    <td data-search="^{{::o.ipAddress}}$">{{::o.ipAddress}}</td>
+                    <td data-search="^{{::o.ip6Address}}$">{{::o.ip6Address}}</td>
+                    <td data-search="^{{::o.protocol}}$">{{::o.protocol}}</td>
+                    <td data-search="^{{::o.port}}$">{{::o.port}}</td>
+                    <td data-search="^{{::o.coordinate}}$">{{::o.coordinate}}</td>
+                    <td data-search="^{{::o.cachegroup}}$">{{::o.cachegroup}}</td>
+                    <td data-search="^{{::o.profile}}$">{{::o.profile}}</td>
+                </tr>
+            </tbody>
+        </table>
+    </div>
+</div>
+
diff --git a/traffic_portal/app/src/common/modules/table/origins/TableOriginsController.js b/traffic_portal/app/src/common/modules/table/origins/TableOriginsController.js
new file mode 100644
index 000000000..fb1ec46be
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/table/origins/TableOriginsController.js
@@ -0,0 +1,49 @@
+/*
+ * 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 TableOriginsController = function(origins, $scope, $state, $location, locationUtils) {
+
+    $scope.createOrigin = function() {
+        var path = '/origins/new';
+        locationUtils.navigateToPath(path);
+    };
+
+    $scope.origins = origins;
+
+    $scope.editOrigin = function(id) {
+        var path = '/origins/' + id;
+        locationUtils.navigateToPath(path);
+    };
+
+    $scope.refresh = function() {
+        $state.reload(); // reloads all the resolves for the view
+    };
+
+    angular.element(document).ready(function () {
+        $('#originsTable').dataTable({
+            "aLengthMenu": [[25, 50, 100, -1], [25, 50, 100, "All"]],
+            "iDisplayLength": 25,
+            "aaSorting": []
+        });
+    });
+
+};
+
+TableOriginsController.$inject = ['origins', '$scope', '$state', '$location', 'locationUtils'];
+module.exports = TableOriginsController;
diff --git a/traffic_portal/app/src/common/modules/table/origins/index.js b/traffic_portal/app/src/common/modules/table/origins/index.js
new file mode 100644
index 000000000..15093fb6f
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/table/origins/index.js
@@ -0,0 +1,21 @@
+/*
+ * 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('trafficPortal.table.origins', [])
+    .controller('TableOriginsController', require('./TableOriginsController'));
diff --git a/traffic_portal/app/src/common/modules/table/origins/table.origins.tpl.html b/traffic_portal/app/src/common/modules/table/origins/table.origins.tpl.html
new file mode 100644
index 000000000..d6fd01f3f
--- /dev/null
+++ b/traffic_portal/app/src/common/modules/table/origins/table.origins.tpl.html
@@ -0,0 +1,69 @@
+<!--
+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 class="active">Origins</li>
+        </ol>
+        <div class="pull-right" role="group">
+            <button class="btn btn-primary" title="Create Origin" ng-click="createOrigin()"><i class="fa fa-plus"></i></button>
+            <button class="btn btn-default" title="Refresh" ng-click="refresh()"><i class="fa fa-refresh"></i></button>
+        </div>
+        <div class="clearfix"></div>
+    </div>
+    <div class="x_content">
+        <br>
+        <table id="originsTable" class="table responsive-utilities jambo_table">
+            <thead>
+                <tr class="headings">
+                    <th>Name</th>
+                    <th>Tenant</th>
+                    <th>Primary</th>
+                    <th>Delivery Service</th>
+                    <th>FQDN</th>
+                    <th>IPv4 Address</th>
+                    <th>IPv6 Address</th>
+                    <th>Protocol</th>
+                    <th>Port</th>
+                    <th>Coordinate</th>
+                    <th>Cachegroup</th>
+                    <th>Profile</th>
+                </tr>
+            </thead>
+            <tbody>
+                <tr ng-click="editOrigin(o.id)" ng-repeat="o in ::origins">
+                    <td data-search="^{{::o.name}}$">{{::o.name}}</td>
+                    <td data-search="^{{::o.tenant}}$">{{::o.tenant}}</td>
+                    <td data-search="^{{::o.isPrimary}}$">{{::o.isPrimary}}</td>
+                    <td data-search="^{{::o.deliveryService}}$">{{::o.deliveryService}}</td>
+                    <td data-search="^{{::o.fqdn}}$">{{::o.fqdn}}</td>
+                    <td data-search="^{{::o.ipAddress}}$">{{::o.ipAddress}}</td>
+                    <td data-search="^{{::o.ip6Address}}$">{{::o.ip6Address}}</td>
+                    <td data-search="^{{::o.protocol}}$">{{::o.protocol}}</td>
+                    <td data-search="^{{::o.port}}$">{{::o.port}}</td>
+                    <td data-search="^{{::o.coordinate}}$">{{::o.coordinate}}</td>
+                    <td data-search="^{{::o.cachegroup}}$">{{::o.cachegroup}}</td>
+                    <td data-search="^{{::o.profile}}$">{{::o.profile}}</td>
+                </tr>
+            </tbody>
+        </table>
+    </div>
+</div>
+
diff --git a/traffic_portal/app/src/modules/private/deliveryServices/edit/index.js b/traffic_portal/app/src/modules/private/deliveryServices/edit/index.js
index 4e52dbe00..8d4cb88ca 100644
--- a/traffic_portal/app/src/modules/private/deliveryServices/edit/index.js
+++ b/traffic_portal/app/src/modules/private/deliveryServices/edit/index.js
@@ -47,6 +47,9 @@ module.exports = angular.module('trafficPortal.private.deliveryServices.edit', [
                             deliveryService: function($stateParams, deliveryServiceService) {
                                 return deliveryServiceService.getDeliveryService($stateParams.deliveryServiceId);
                             },
+                            origin: function($stateParams, originService) {
+                                return originService.getOrigins({ deliveryservice: $stateParams.deliveryServiceId, primary: true })
+                            },
                             type: function($stateParams) {
                                 return $stateParams.type;
                             },
diff --git a/traffic_portal/app/src/modules/private/deliveryServices/new/index.js b/traffic_portal/app/src/modules/private/deliveryServices/new/index.js
index 7637c2a88..3edb26f68 100644
--- a/traffic_portal/app/src/modules/private/deliveryServices/new/index.js
+++ b/traffic_portal/app/src/modules/private/deliveryServices/new/index.js
@@ -61,6 +61,9 @@ module.exports = angular.module('trafficPortal.private.deliveryServices.new', []
                                     return {};
                                 }
                             },
+                            origin: function() {
+                                return [{}];
+                            },
                             type: function($stateParams) {
                                 return $stateParams.type;
                             },
diff --git a/traffic_portal/app/src/modules/private/deliveryServices/origins/index.js b/traffic_portal/app/src/modules/private/deliveryServices/origins/index.js
new file mode 100644
index 000000000..037350c4d
--- /dev/null
+++ b/traffic_portal/app/src/modules/private/deliveryServices/origins/index.js
@@ -0,0 +1,42 @@
+/*
+ * 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('trafficPortal.private.deliveryServices.origins', [])
+    .config(function($stateProvider, $urlRouterProvider) {
+        $stateProvider
+            .state('trafficPortal.private.deliveryServices.origins', {
+                url: '/{deliveryServiceId}/origins',
+                views: {
+                    deliveryServicesContent: {
+                        templateUrl: 'common/modules/table/deliveryServiceOrigins/table.deliveryServiceOrigins.tpl.html',
+                        controller: 'TableDeliveryServiceOriginsController',
+                        resolve: {
+                            deliveryService: function($stateParams, deliveryServiceService) {
+                                return deliveryServiceService.getDeliveryService($stateParams.deliveryServiceId);
+                            },
+                            origins: function($stateParams, originService) {
+                                return originService.getOrigins({ deliveryservice: $stateParams.deliveryServiceId });
+                            }
+                        }
+                    }
+                }
+            })
+        ;
+        $urlRouterProvider.otherwise('/');
+    });
diff --git a/traffic_portal/app/src/modules/private/origins/edit/index.js b/traffic_portal/app/src/modules/private/origins/edit/index.js
new file mode 100644
index 000000000..96be57627
--- /dev/null
+++ b/traffic_portal/app/src/modules/private/origins/edit/index.js
@@ -0,0 +1,39 @@
+/*
+ * 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('trafficPortal.private.origins.edit', [])
+    .config(function($stateProvider, $urlRouterProvider) {
+        $stateProvider
+            .state('trafficPortal.private.origins.edit', {
+                url: '/{originId:[0-9]{1,8}}',
+                views: {
+                    originsContent: {
+                        templateUrl: 'common/modules/form/origin/form.origin.tpl.html',
+                        controller: 'FormEditOriginController',
+                        resolve: {
+                            origin: function($stateParams, originService) {
+                                return originService.getOrigins({ id: $stateParams.originId });
+                            }
+                        }
+                    }
+                }
+            })
+        ;
+        $urlRouterProvider.otherwise('/');
+    });
diff --git a/traffic_portal/app/src/modules/private/origins/index.js b/traffic_portal/app/src/modules/private/origins/index.js
new file mode 100644
index 000000000..5a3d135ba
--- /dev/null
+++ b/traffic_portal/app/src/modules/private/origins/index.js
@@ -0,0 +1,34 @@
+/*
+ * 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('trafficPortal.private.origins', [])
+    .config(function($stateProvider, $urlRouterProvider) {
+        $stateProvider
+            .state('trafficPortal.private.origins', {
+                url: 'origins',
+                abstract: true,
+                views: {
+                    privateContent: {
+                        templateUrl: 'modules/private/origins/origins.tpl.html'
+                    }
+                }
+            })
+        ;
+        $urlRouterProvider.otherwise('/');
+    });
diff --git a/traffic_portal/app/src/modules/private/origins/list/index.js b/traffic_portal/app/src/modules/private/origins/list/index.js
new file mode 100644
index 000000000..8c8ab2d7d
--- /dev/null
+++ b/traffic_portal/app/src/modules/private/origins/list/index.js
@@ -0,0 +1,39 @@
+/*
+ * 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('trafficPortal.private.origins.list', [])
+    .config(function($stateProvider, $urlRouterProvider) {
+        $stateProvider
+            .state('trafficPortal.private.origins.list', {
+                url: '',
+                views: {
+                    originsContent: {
+                        templateUrl: 'common/modules/table/origins/table.origins.tpl.html',
+                        controller: 'TableOriginsController',
+                        resolve: {
+                            origins: function(originService) {
+                                return originService.getOrigins();
+                            }
+                        }
+                    }
+                }
+            })
+        ;
+        $urlRouterProvider.otherwise('/');
+    });
diff --git a/traffic_portal/app/src/modules/private/origins/new/index.js b/traffic_portal/app/src/modules/private/origins/new/index.js
new file mode 100644
index 000000000..c280f6434
--- /dev/null
+++ b/traffic_portal/app/src/modules/private/origins/new/index.js
@@ -0,0 +1,39 @@
+/*
+ * 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('trafficPortal.private.origins.new', [])
+    .config(function($stateProvider, $urlRouterProvider) {
+        $stateProvider
+            .state('trafficPortal.private.origins.new', {
+                url: '/new',
+                views: {
+                    originsContent: {
+                        templateUrl: 'common/modules/form/origin/form.origin.tpl.html',
+                        controller: 'FormNewOriginController',
+                        resolve: {
+                            origin: function($stateParams, propertiesModel) {
+                                return {};
+                            }
+                        }
+                    }
+                }
+            })
+        ;
+        $urlRouterProvider.otherwise('/');
+    });
diff --git a/traffic_portal/app/src/modules/private/origins/origins.tpl.html b/traffic_portal/app/src/modules/private/origins/origins.tpl.html
new file mode 100644
index 000000000..d5d9f6340
--- /dev/null
+++ b/traffic_portal/app/src/modules/private/origins/origins.tpl.html
@@ -0,0 +1,22 @@
+<!--
+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 id="originsContainer">
+    <div ui-view="originsContent"></div>
+</div>


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services