Merge branch 'release/1.0.0'
diff --git a/xos/core/xoslib/.eslintignore b/xos/core/xoslib/.eslintignore
index be5c014..f848d64 100644
--- a/xos/core/xoslib/.eslintignore
+++ b/xos/core/xoslib/.eslintignore
@@ -1,4 +1,6 @@
 node_modules/**/*.js
+xos-builder/node_modules/**/*.js
 static/js/xsh/**/*.js
 static/js/vendor/**/*.js
-spec/helpers/*.js
\ No newline at end of file
+spec/helpers/*.js
+coverage/**/*
\ No newline at end of file
diff --git a/xos/core/xoslib/.eslintrc b/xos/core/xoslib/.eslintrc
index 3615cae..5c8b35d 100644
--- a/xos/core/xoslib/.eslintrc
+++ b/xos/core/xoslib/.eslintrc
@@ -15,7 +15,8 @@
     }],
     "valid-jsdoc": 2,
     "max-len": [1, 120, 4, {"ignoreComments": true, "ignoreUrls": true}],
-    brace-style: [2, "stroustrup"]
+    brace-style: [2, "stroustrup"],
+    space-before-blocks: [1, 'never']
   },
   "globals": {
     "$": false,
diff --git a/xos/core/xoslib/karma.conf.js b/xos/core/xoslib/karma.conf.js
index 752b994..0d0b15b 100644
--- a/xos/core/xoslib/karma.conf.js
+++ b/xos/core/xoslib/karma.conf.js
@@ -53,7 +53,7 @@
     exclude: [
       // '**/xos-utils.test.js', //skip this test, useful in dev, comment before commit
       // '**/xos-backbone.test.js',
-      //'**/xoslib/**/*.js'
+      // '**/xoslib/**/*.js'
     ],
 
 
diff --git a/xos/core/xoslib/spec/views/contentprovider.test.js b/xos/core/xoslib/spec/views/contentprovider.test.js
index 64b676a..6d9a9c2 100644
--- a/xos/core/xoslib/spec/views/contentprovider.test.js
+++ b/xos/core/xoslib/spec/views/contentprovider.test.js
@@ -10,29 +10,31 @@
   // preload Html Templates with ng-html2js
   beforeEach(module('templates'));
 
-  beforeEach(function() {
-    module(function($provide) {
+  beforeEach(function(){
+    module(function($provide){
       // mocking routeParams to pass 1 as id
-      $provide.provider('$routeParams', function() {
-        this.$get = function() {
+      $provide.provider('$routeParams', function(){
+        /* eslint-disable no-invalid-this*/
+        this.$get = function(){
           return {id: 1};
         };
+        /* eslint-enable no-invalid-this*/
       });
     });
   });
 
-  beforeEach(inject(function(_$location_, $httpBackend) {
+  beforeEach(inject(function(_$location_, $httpBackend){
     spyOn(_$location_, 'url');
     mockLocation = _$location_;
     httpBackend = $httpBackend;
     // Setting up mock request
-    $httpBackend.whenGET('/hpcapi/contentproviders/').respond(CPmock.CPlist);
-    $httpBackend.whenGET('/hpcapi/serviceproviders/').respond(CPmock.SPlist);
-    $httpBackend.whenDELETE('/hpcapi/contentproviders/1/').respond();
+    $httpBackend.whenGET('/hpcapi/contentproviders/?no_hyperlinks=1').respond(CPmock.CPlist);
+    $httpBackend.whenGET('/hpcapi/serviceproviders/?no_hyperlinks=1').respond(CPmock.SPlist);
+    $httpBackend.whenDELETE('/hpcapi/contentproviders/1/?no_hyperlinks=1').respond();
   }));
 
   describe('the action directive', () => {
-    beforeEach(inject(function($compile, $rootScope) {
+    beforeEach(inject(function($compile, $rootScope){
       scope = $rootScope.$new();
 
       element = angular.element('<cp-actions id="\'1\'"></cp-actions>');
@@ -49,7 +51,7 @@
   });
 
   describe('the contentProvider list', () => {
-    beforeEach(inject(function($compile, $rootScope) {
+    beforeEach(inject(function($compile, $rootScope){
       scope = $rootScope.$new();
 
       element = angular.element('<content-provider-list></content-provider-list>');
@@ -73,26 +75,16 @@
 
   describe('the contentProviderDetail directive', () => {
 
-    beforeEach(inject(function($compile, $rootScope) {
+    beforeEach(inject(function($compile, $rootScope){
       scope = $rootScope.$new();
       element = angular.element('<content-provider-detail></content-provider-detail>');
       $compile(element)(scope);
-      httpBackend.expectGET('/hpcapi/contentproviders/1/').respond(CPmock.CPlist[0]);
+      httpBackend.expectGET('/hpcapi/contentproviders/1/?no_hyperlinks=1').respond(CPmock.CPlist[0]);
       scope.$digest();
       httpBackend.flush();
       isolatedScope = element.isolateScope().vm;
     }));
 
-    it('should select the active service provider', () => {
-      var res = isolatedScope.activeServiceProvide(1, 'http://0.0.0.0:9000/hpcapi/serviceproviders/1/');
-      expect(res).toBe(true);
-    });
-
-    it('should not select a non active service provider', () => {
-      var res = isolatedScope.activeServiceProvide(1, 'http://0.0.0.0:9000/hpcapi/serviceproviders/3/');
-      expect(res).toBe(false);
-    });
-
     describe('when an id is set in the route', () => {
 
       beforeEach(() => {
@@ -117,11 +109,11 @@
       scope = $rootScope.$new();
       element = angular.element('<content-provider-cdn></content-provider-cdn>');
       $compile(element)(scope);
-      httpBackend.expectGET('/hpcapi/contentproviders/1/').respond(CPmock.CPlist[0]);
-      httpBackend.expectGET('/hpcapi/cdnprefixs/?contentProvider=1').respond([CPmock.CDNlist[0]]);
-      httpBackend.expectGET('/hpcapi/cdnprefixs/').respond(CPmock.CDNlist);
-      httpBackend.whenPOST('/hpcapi/cdnprefixs/').respond(CPmock.CDNlist[0]);
-      httpBackend.whenDELETE('/hpcapi/cdnprefixs/5/').respond();
+      httpBackend.expectGET('/hpcapi/contentproviders/1/?no_hyperlinks=1').respond(CPmock.CPlist[0]);
+      // httpBackend.expectGET('/hpcapi/cdnprefixs/?no_hyperlinks=1&contentProvider=1').respond([CPmock.CDNlist[0]]);
+      httpBackend.expectGET('/hpcapi/cdnprefixs/?no_hyperlinks=1').respond(CPmock.CDNlist);
+      httpBackend.whenPOST('/hpcapi/cdnprefixs/?no_hyperlinks=1').respond(CPmock.CDNlist[0]);
+      httpBackend.whenDELETE('/hpcapi/cdnprefixs/5/?no_hyperlinks=1').respond();
       scope.$digest();
       httpBackend.flush();
       isolatedScope = element.isolateScope().vm;
@@ -150,10 +142,10 @@
       scope = $rootScope.$new();
       element = angular.element('<content-provider-server></content-provider-server>');
       $compile(element)(scope);
-      httpBackend.expectGET('/hpcapi/contentproviders/1/').respond(CPmock.CPlist[0]);
-      httpBackend.expectGET('/hpcapi/originservers/?contentProvider=1').respond(CPmock.OSlist);
-      httpBackend.whenPOST('/hpcapi/originservers/').respond(CPmock.OSlist[0]);
-      httpBackend.whenDELETE('/hpcapi/originservers/8/').respond();
+      httpBackend.expectGET('/hpcapi/contentproviders/1/?no_hyperlinks=1').respond(CPmock.CPlist[0]);
+      httpBackend.expectGET('/hpcapi/originservers/?no_hyperlinks=1&contentProvider=1').respond(CPmock.OSlist);
+      httpBackend.whenPOST('/hpcapi/originservers/?no_hyperlinks=1').respond(CPmock.OSlist[0]);
+      httpBackend.whenDELETE('/hpcapi/originservers/8/?no_hyperlinks=1').respond();
       scope.$digest();
       httpBackend.flush();
       isolatedScope = element.isolateScope().vm;
@@ -175,4 +167,49 @@
       expect(isolatedScope.cp_os.length).toBe(3);
     });
   });
+
+  describe('the contentProviderUsers directive', () => {
+    beforeEach(inject(($compile, $rootScope) => {
+      scope = $rootScope.$new();
+      element = angular.element('<content-provider-users></content-provider-users>');
+      $compile(element)(scope);
+      httpBackend.expectGET('/xos/users/?no_hyperlinks=1').respond(CPmock.UserList);
+      httpBackend.expectGET('/hpcapi/contentproviders/1/?no_hyperlinks=1').respond(CPmock.CPlist[0]);
+      httpBackend.whenPUT('/hpcapi/contentproviders/1/?no_hyperlinks=1').respond(CPmock.CPlist[0]);
+      scope.$digest();
+      httpBackend.flush();
+      isolatedScope = element.isolateScope().vm;
+    }));
+
+    it('should render one user', () => {
+      expect(isolatedScope.cp.users.length).toBe(1);
+      expect(typeof isolatedScope.cp.users[0]).toEqual('object');
+    });
+
+    it('should add a user', () => {
+      isolatedScope.addUserToCp({name: 'teo'});
+      expect(isolatedScope.cp.users.length).toBe(2);
+    });
+
+    it('should remove a user', () => {
+      isolatedScope.addUserToCp({name: 'teo'});
+      expect(isolatedScope.cp.users.length).toBe(2);
+      isolatedScope.removeUserFromCp({name: 'teo'});
+      expect(isolatedScope.cp.users.length).toBe(1);
+    });
+
+    it('should save and reformat users', () => {
+      // add a user
+      isolatedScope.cp.users.push(1);
+
+      //trigger save
+      isolatedScope.saveContentProvider(isolatedScope.cp);
+
+      httpBackend.flush();
+
+      // I'll get one as the BE is mocked, the important is to check the conversion
+      expect(isolatedScope.cp.users.length).toBe(1);
+      expect(typeof isolatedScope.cp.users[0]).toEqual('object');
+    });
+  });
 });
diff --git a/xos/core/xoslib/spec/views/mocks/contentProvider.mock.js b/xos/core/xoslib/spec/views/mocks/contentProvider.mock.js
index c0d1750..46a0b0e 100644
--- a/xos/core/xoslib/spec/views/mocks/contentProvider.mock.js
+++ b/xos/core/xoslib/spec/views/mocks/contentProvider.mock.js
@@ -1,4 +1,4 @@
-/* eslint-disable key-spacing */
+/* eslint-disable key-spacing, no-unused-vars */
 
 var CPmock = {
   CPlist: [
@@ -69,6 +69,7 @@
       'name':'on_lab_content',
       'enabled':true,
       'description':null,
+      users: [2],
       'serviceProvider':'http://0.0.0.0:9000/hpcapi/serviceproviders/1/'
     },
     {
@@ -281,7 +282,7 @@
       'no_sync':false,
       'cdn_prefix_id':null,
       'prefix':'onlab.vicci.org',
-      'contentProvider':'http://0.0.0.0:9000/hpcapi/contentproviders/1/',
+      'contentProvider':1,
       'description':null,
       'defaultOriginServer':'http://0.0.0.0:9000/hpcapi/originservers/2/',
       'enabled':true
@@ -354,7 +355,7 @@
       'no_sync':false,
       'cdn_prefix_id':null,
       'prefix':'downloads.onosproject.org',
-      'contentProvider':'http://0.0.0.0:9000/hpcapi/contentproviders/2/',
+      'contentProvider':2,
       'description':null,
       'defaultOriginServer':'http://0.0.0.0:9000/hpcapi/originservers/1/',
       'enabled':true
@@ -685,5 +686,225 @@
       'redirects':true,
       'description':null
     }
+  ],
+  UserList: [
+    {
+      'humanReadableName':'teo@onlab.us',
+      'validators':{
+        'policed':[
+          'notBlank'
+        ],
+        'site':[
+          'notBlank'
+        ],
+        'is_appuser':[
+
+        ],
+        'is_staff':[
+
+        ],
+        'timezone':[
+          'notBlank'
+        ],
+        'backend_status':[
+          'notBlank'
+        ],
+        'id':[
+
+        ],
+        'is_registering':[
+
+        ],
+        'last_login':[
+          'notBlank'
+        ],
+        'email':[
+          'notBlank'
+        ],
+        'username':[
+          'notBlank'
+        ],
+        'updated':[
+
+        ],
+        'login_page':[
+
+        ],
+        'firstname':[
+          'notBlank'
+        ],
+        'user_url':[
+          'url'
+        ],
+        'deleted':[
+
+        ],
+        'lastname':[
+          'notBlank'
+        ],
+        'is_active':[
+
+        ],
+        'phone':[
+
+        ],
+        'is_admin':[
+
+        ],
+        'password':[
+          'notBlank'
+        ],
+        'enacted':[
+          'notBlank'
+        ],
+        'public_key':[
+
+        ],
+        'is_readonly':[
+
+        ],
+        'created':[
+
+        ],
+        'write_protect':[
+
+        ]
+      },
+      'id':2,
+      'password':'pbkdf2_sha256$12000$2Uzp1YCyjEBO$uU2irK//ZpEZYOIgLzanuApFoPnwfG1jNol2jD273wQ=',
+      'last_login':'2015-10-26T14:11:27.625Z',
+      'email':'teo@onlab.us',
+      'username':'teo@onlab.us',
+      'firstname':'Matteo',
+      'lastname':'Scandolo',
+      'phone':'',
+      'user_url':null,
+      'site':1,
+      'public_key':'',
+      'is_active':true,
+      'is_admin':false,
+      'is_staff':true,
+      'is_readonly':false,
+      'is_registering':false,
+      'is_appuser':false,
+      'login_page':null,
+      'created':'2015-10-26T14:11:27.699Z',
+      'updated':'2015-10-26T14:11:27.699Z',
+      'enacted':null,
+      'policed':null,
+      'backend_status':'Provisioning in progress',
+      'deleted':false,
+      'write_protect':false,
+      'timezone':'America/New_York'
+    },
+    {
+      'humanReadableName':'padmin@vicci.org',
+      'validators':{
+        'policed':[
+          'notBlank'
+        ],
+        'site':[
+          'notBlank'
+        ],
+        'is_appuser':[
+
+        ],
+        'is_staff':[
+
+        ],
+        'timezone':[
+          'notBlank'
+        ],
+        'backend_status':[
+          'notBlank'
+        ],
+        'id':[
+
+        ],
+        'is_registering':[
+
+        ],
+        'last_login':[
+          'notBlank'
+        ],
+        'email':[
+          'notBlank'
+        ],
+        'username':[
+          'notBlank'
+        ],
+        'updated':[
+
+        ],
+        'login_page':[
+
+        ],
+        'firstname':[
+          'notBlank'
+        ],
+        'user_url':[
+          'url'
+        ],
+        'deleted':[
+
+        ],
+        'lastname':[
+          'notBlank'
+        ],
+        'is_active':[
+
+        ],
+        'phone':[
+
+        ],
+        'is_admin':[
+
+        ],
+        'password':[
+          'notBlank'
+        ],
+        'enacted':[
+          'notBlank'
+        ],
+        'public_key':[
+
+        ],
+        'is_readonly':[
+
+        ],
+        'created':[
+
+        ],
+        'write_protect':[
+
+        ]
+      },
+      'id':1,
+      'password':'pbkdf2_sha256$12000$Qufx9iqtaYma$xs0YurPOcj9qYQna/Qrb3K+im9Yr2XEVr0J4Kqek7AE=',
+      'last_login':'2015-10-27T10:07:09.065Z',
+      'email':'padmin@vicci.org',
+      'username':'padmin@vicci.org',
+      'firstname':'XOS',
+      'lastname':'admin',
+      'phone':null,
+      'user_url':null,
+      'site':1,
+      'public_key':null,
+      'is_active':true,
+      'is_admin':true,
+      'is_staff':true,
+      'is_readonly':false,
+      'is_registering':false,
+      'is_appuser':false,
+      'login_page':null,
+      'created':'2015-02-17T22:06:38.059Z',
+      'updated':'2015-10-27T09:00:44.672Z',
+      'enacted':null,
+      'policed':null,
+      'backend_status':'Provisioning in progress',
+      'deleted':false,
+      'write_protect':false,
+      'timezone':'America/New_York'
+    }
   ]
 };
\ No newline at end of file
diff --git a/xos/core/xoslib/spec/xoslib/xos-backbone.test.js b/xos/core/xoslib/spec/xoslib/xos-backbone.test.js
index c971af7..90d3c83 100644
--- a/xos/core/xoslib/spec/xoslib/xos-backbone.test.js
+++ b/xos/core/xoslib/spec/xoslib/xos-backbone.test.js
@@ -41,7 +41,7 @@
     var testLib;
 
     beforeEach(() => {
-      var TestLibDefinition = function() {
+      var TestLibDefinition = function(){
         /* eslint-disable no-invalid-this*/
         this.allCollectionNames = [];
         this.allCollections = [];
diff --git a/xos/core/xoslib/spec/xoslib/xos-helper.test.js b/xos/core/xoslib/spec/xoslib/xos-helper.test.js
index 9e3e33d..7c0a506 100644
--- a/xos/core/xoslib/spec/xoslib/xos-helper.test.js
+++ b/xos/core/xoslib/spec/xoslib/xos-helper.test.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-unused-vars*/
 'use strict';
 
 describe('The Xos Helper', () => {
@@ -16,7 +17,7 @@
       const err = {name: 'must start with mysite_'};
       var view;
       beforeEach(() => {
-        try {
+        try{
           f.set(`
             <script type="text/template" id="fake-template">
               <div>
@@ -39,9 +40,7 @@
 
         // view.onFormDataInvalid(err);
         // view().onFormDataInvalid(err)
-        
-        console.log(new view()); 
-        
+
         expect($('.alert').length).toBe(1);
       });
     });
diff --git a/xos/core/xoslib/spec/xoslib/xos-utils.test.js b/xos/core/xoslib/spec/xoslib/xos-utils.test.js
index fd0193e..52de8d8 100644
--- a/xos/core/xoslib/spec/xoslib/xos-utils.test.js
+++ b/xos/core/xoslib/spec/xoslib/xos-utils.test.js
@@ -1,6 +1,6 @@
 'use strict';
 
-describe('The XOS Lib Utilities', function() {
+describe('The XOS Lib Utilities', function(){
 
   var f;
 
@@ -9,7 +9,7 @@
     f.fixturesPath = 'base/spec/xoslib/fixtures/xos-utils';
   });
 
-  describe('The idInArray method', function() {
+  describe('The idInArray method', function(){
     it('should match a string ID', () => {
       let res = idInArray('1', [1, 2, 3]);
       expect(res).toBeTruthy();
@@ -169,7 +169,7 @@
 
     it('should set elements to same width', () => {
       make_same_width('.container', 'div');
-      $('.container div').each(function(index, item) {
+      $('.container div').each(function(index, item){
         expect($(item)).toHaveCss({width: '400px'});
       });
     });
diff --git a/xos/core/xoslib/spec/xoslib/xoslib.test.js b/xos/core/xoslib/spec/xoslib/xoslib.test.js
index 5227b80..97d85f9 100644
--- a/xos/core/xoslib/spec/xoslib/xoslib.test.js
+++ b/xos/core/xoslib/spec/xoslib/xoslib.test.js
@@ -1,3 +1,4 @@
+/* eslint-disable new-cap*/
 'use strict';
 
 describe('When XOS Lib is created', () => {
diff --git a/xos/core/xoslib/static/js/gentle.js b/xos/core/xoslib/static/js/gentle.js
index 008886a..a0d8dde 100644
--- a/xos/core/xoslib/static/js/gentle.js
+++ b/xos/core/xoslib/static/js/gentle.js
@@ -21,7 +21,7 @@
   childView: ContactManager.ContactItemView
 });
 
-ContactManager.on('start', function() {
+ContactManager.on('start', function(){
   var contacts = new ContactManager.ContactCollection([
     {
       firstName: 'Bob',
diff --git a/xos/core/xoslib/static/js/helloworld.js b/xos/core/xoslib/static/js/helloworld.js
index d34a968..166d183 100644
--- a/xos/core/xoslib/static/js/helloworld.js
+++ b/xos/core/xoslib/static/js/helloworld.js
@@ -1,4 +1,4 @@
-/* eslint-disable guard-for-in */
+/* eslint-disable guard-for-in, space-before-blocks */
 
 // helloworld.js
 function updateHelloWorldData() {
diff --git a/xos/core/xoslib/static/js/picker.js b/xos/core/xoslib/static/js/picker.js
index 35b6cd1..3303197 100644
--- a/xos/core/xoslib/static/js/picker.js
+++ b/xos/core/xoslib/static/js/picker.js
@@ -1,3 +1,5 @@
+/* eslint-disable space-before-blocks, no-unused-vars */

+

 function init_picker(selector, ordered) {

   //console.log("init_picker");

   //console.log($(selector));

@@ -31,7 +33,9 @@
       var newPos = to.find('option').index(this) - 1;

 

       if (newPos > -1) {

-        to.find('option').eq(newPos).before('<option value="' + $(this).val() + '" selected="selected">' + $(this).text() + '</option>');

+        to.find('option').eq(newPos).before(

+          '<option value="' + $(this).val() + '" selected="selected">' + $(this).text() + '</option>'

+        );

         $(this).remove();

       }

     });

@@ -43,7 +47,9 @@
       var newPos = to.find('option').index(this) + 1;

 

       if (newPos < countOptions) {

-        to.find('option').eq(newPos).after('<option value="' + $(this).val() + '" selected="selected">' + $(this).text() + '</option>');

+        to.find('option').eq(newPos).after(

+          '<option value="' + $(this).val() + '" selected="selected">' + $(this).text() + '</option>'

+        );

         $(this).remove();

       }

     });

diff --git a/xos/core/xoslib/static/js/xosContentProvider.js b/xos/core/xoslib/static/js/xosContentProvider.js
index 0c21fe3..98aaedd 100644
--- a/xos/core/xoslib/static/js/xosContentProvider.js
+++ b/xos/core/xoslib/static/js/xosContentProvider.js
@@ -14,7 +14,7 @@
   'ngCookies',
   'ngLodash'
 ])
-.config(function($interpolateProvider, $routeProvider, $resourceProvider) {
+.config(function($interpolateProvider, $routeProvider, $resourceProvider){
   $interpolateProvider.startSymbol('{$');
   $interpolateProvider.endSymbol('$}');
 
@@ -39,39 +39,45 @@
   })
   .otherwise('/');
 })
-.config(function($httpProvider) {
+.config(function($httpProvider){
 
   // add X-CSRFToken header for update, create, delete (!GET)
   $httpProvider.interceptors.push('SetCSRFToken');
 })
-.factory('SetCSRFToken', function($cookies) {
+.factory('SetCSRFToken', function($cookies){
   return {
-    request: function(request) {
-      if(request.method !== 'GET') {
+    request: function(request){
+
+      // if request is not HTML
+      if(request.url.indexOf('.html') === -1){
+        request.url += '?no_hyperlinks=1';
+      }
+
+      if(request.method !== 'GET'){
         request.headers['X-CSRFToken'] = $cookies.get('csrftoken');
       }
       return request;
     }
   };
 })
-.service('ContentProvider', function($resource, $q, User) {
+.service('ContentProvider', function($resource){
   return $resource('/hpcapi/contentproviders/:id/', {id: '@id'}, {
     'update': {method: 'PUT'}
   });
 })
-.service('ServiceProvider', function($resource) {
+.service('ServiceProvider', function($resource){
   return $resource('/hpcapi/serviceproviders/:id/', {id: '@id'});
 })
-.service('CdnPrefix', function($resource) {
+.service('CdnPrefix', function($resource){
   return $resource('/hpcapi/cdnprefixs/:id/', {id: '@id'});
 })
-.service('OriginServer', function($resource) {
+.service('OriginServer', function($resource){
   return $resource('/hpcapi/originservers/:id/', {id: '@id'});
 })
-.service('User', function($resource) {
+.service('User', function($resource){
   return $resource('/xos/users/:id/', {id: '@id'});
 })
-.directive('cpActions', function(ContentProvider, $location) {
+.directive('cpActions', function(ContentProvider, $location){
   return {
     restrict: 'E',
     scope: {
@@ -80,105 +86,94 @@
     bindToController: true,
     controllerAs: 'vm',
     templateUrl: '../../static/templates/contentProvider/cp_actions.html',
-    controller: function() {
-      this.deleteCp = function(id) {
+    controller: function(){
+      this.deleteCp = function(id){
         ContentProvider.delete({id: id}).$promise
-        .then(function() {
+        .then(function(){
           $location.url('/');
         });
       };
     }
   };
 })
-.directive('contentProviderList', function(ContentProvider, lodash) {
+.directive('contentProviderList', function(ContentProvider, lodash){
   return {
     restrict: 'E',
     controllerAs: 'vm',
     scope: {},
     templateUrl: '../../static/templates/contentProvider/cp_list.html',
-    controller: function() {
+    controller: function(){
       var _this = this;
 
       ContentProvider.query().$promise
-      .then(function(cp) {
+      .then(function(cp){
         _this.contentProviderList = cp;
       })
-      .catch(function(e) {
+      .catch(function(e){
         throw new Error(e);
       });
 
-      this.deleteCp = function(id) {
+      this.deleteCp = function(id){
         ContentProvider.delete({id: id}).$promise
-        .then(function() {
+        .then(function(){
           lodash.remove(_this.contentProviderList, {id: id});
         });
       };
     }
   };
 })
-.directive('contentProviderDetail', function(ContentProvider, ServiceProvider, $routeParams, $location) {
+.directive('contentProviderDetail', function(ContentProvider, ServiceProvider, $routeParams, $location){
   return {
     restrict: 'E',
     controllerAs: 'vm',
     scope: {},
     templateUrl: '../../static/templates/contentProvider/cp_detail.html',
-    controller: function() {
+    controller: function(){
       this.pageName = 'detail';
       var _this = this;
 
-      if($routeParams.id) {
+      if($routeParams.id){
         ContentProvider.get({id: $routeParams.id}).$promise
-        .then(function(cp) {
+        .then(function(cp){
           _this.cp = cp;
-        }).catch(function(e) {
+        }).catch(function(e){
           _this.result = {
             status: 0,
             msg: e.data.detail
           };
         });
       }
-      else {
-        console.log('new');
+      else{
         _this.cp = new ContentProvider();
       }
 
       ServiceProvider.query().$promise
-      .then(function(sp) {
+      .then(function(sp){
         _this.sp = sp;
       });
 
-      // check if the list id match with item url
-      this.activeServiceProvide = function(id, SPurl) {
-        if(SPurl && SPurl.length > 0) {
-          // take the last 2 char and remove trailing /
-          return parseInt(SPurl.substr(SPurl.length - 2).replace('/','')) === id;
-        }
-        return false;
-      };
-
-      this.saveContentProvider = function(cp) {
+      this.saveContentProvider = function(cp){
         var p, isNew = false;
 
-        if(cp.id) {
+        if(cp.id){
           p = cp.$update();
         }
-        else {
+        else{
           isNew = true;
           cp.name = cp.humanReadableName;
-          console.log('save');
           p = cp.$save();
         }
 
-        p.then(function(res) {
+        p.then(function(res){
           _this.result = {
             status: 1,
             msg: 'Content Provider Saved'
           };
-          if(isNew) {
+          if(isNew){
             $location.url('contentProvider/' + res.id + '/');
           }
         })
-        .catch(function(e) {
+        .catch(function(e){
           _this.result = {
             status: 0,
             msg: e.data.detail
@@ -188,22 +183,22 @@
     }
   };
 })
-.directive('contentProviderCdn', function($routeParams, CdnPrefix, ContentProvider, lodash) {
+.directive('contentProviderCdn', function($routeParams, CdnPrefix, ContentProvider, lodash){
   return{
     restrict: 'E',
     controllerAs: 'vm',
     scope: {},
     templateUrl: '../../static/templates/contentProvider/cp_cdn_prefix.html',
-    controller: function() {
+    controller: function(){
       var _this = this;
 
       this.pageName = 'cdn';
 
-      if($routeParams.id) {
+      if($routeParams.id){
         ContentProvider.get({id: $routeParams.id}).$promise
-        .then(function(cp) {
+        .then(function(cp){
           _this.cp = cp;
-        }).catch(function(e) {
+        }).catch(function(e){
           _this.result = {
             status: 0,
             msg: e.data.detail
@@ -211,37 +206,28 @@
         });
       }
 
-      // TODO filter on client
-      CdnPrefix.query({contentProvider: $routeParams.id}).$promise
-      .then(function(cp_prf) {
-        _this.cp_prf = cp_prf;
-      }).catch(function(e) {
-        _this.result = {
-          status: 0,
-          msg: e.data.detail
-        };
-      });
-
       CdnPrefix.query().$promise
-      .then(function(prf) {
+      .then(function(prf){
         _this.prf = prf;
-      }).catch(function(e) {
+        // set the active CdnPrefix for this contentProvider
+        _this.cp_prf = lodash.where(prf, {contentProvider: parseInt($routeParams.id)});
+      }).catch(function(e){
         _this.result = {
           status: 0,
           msg: e.data.detail
         };
       });
 
-      this.addPrefix = function(prf) {
-        prf.contentProvider = '/hpcapi/contentproviders/' + $routeParams.id + '/';
+      this.addPrefix = function(prf){
+        prf.contentProvider = $routeParams.id;
 
         var item = new CdnPrefix(prf);
 
         item.$save()
-        .then(function(res) {
+        .then(function(res){
           _this.cp_prf.push(res);
         })
-        .catch(function(e) {
+        .catch(function(e){
           _this.result = {
             status: 0,
             msg: e.data.detail
@@ -249,12 +235,12 @@
         });
       };
 
-      this.removePrefix = function(item) {
+      this.removePrefix = function(item){
         item.$delete()
-        .then(function() {
+        .then(function(){
           lodash.remove(_this.cp_prf, item);
         })
-        .catch(function(e) {
+        .catch(function(e){
           _this.result = {
             status: 0,
             msg: e.data.detail
@@ -264,23 +250,23 @@
     }
   };
 })
-.directive('contentProviderServer', function($routeParams, OriginServer, ContentProvider, lodash) {
+.directive('contentProviderServer', function($routeParams, OriginServer, ContentProvider, lodash){
   return{
     restrict: 'E',
     controllerAs: 'vm',
     scope: {},
     templateUrl: '../../static/templates/contentProvider/cp_origin_server.html',
-    controller: function() {
+    controller: function(){
       this.pageName = 'server';
       this.protocols = {'http': 'HTTP', 'rtmp': 'RTMP', 'rtp': 'RTP','shout': 'SHOUTcast'};
 
       var _this = this;
 
-      if($routeParams.id) {
+      if($routeParams.id){
         ContentProvider.get({id: $routeParams.id}).$promise
-        .then(function(cp) {
+        .then(function(cp){
           _this.cp = cp;
-        }).catch(function(e) {
+        }).catch(function(e){
           _this.result = {
             status: 0,
             msg: e.data.detail
@@ -288,28 +274,26 @@
         });
       }
 
-      // TODO filter on client
       OriginServer.query({contentProvider: $routeParams.id}).$promise
-      .then(function(cp_os) {
+      .then(function(cp_os){
         _this.cp_os = cp_os;
-      }).catch(function(e) {
+      }).catch(function(e){
         _this.result = {
           status: 0,
           msg: e.data.detail
         };
       });
 
-      // TODO everytime protocall error, ask Scott
-      this.addOrigin = function(os) {
-        os.contentProvider = '/hpcapi/contentproviders/' + $routeParams.id + '/';
+      this.addOrigin = function(os){
+        os.contentProvider = $routeParams.id;
 
         var item = new OriginServer(os);
 
         item.$save()
-        .then(function(res) {
+        .then(function(res){
           _this.cp_os.push(res);
         })
-        .catch(function(e) {
+        .catch(function(e){
           _this.result = {
             status: 0,
             msg: e.data.detail
@@ -317,12 +301,12 @@
         });
       };
 
-      this.removeOrigin = function(item) {
+      this.removeOrigin = function(item){
         item.$delete()
-        .then(function() {
+        .then(function(){
           lodash.remove(_this.cp_os, item);
         })
-        .catch(function(e) {
+        .catch(function(e){
           _this.result = {
             status: 0,
             msg: e.data.detail
@@ -332,36 +316,32 @@
     }
   };
 })
-.directive('contentProviderUsers', function($routeParams, ContentProvider, User, lodash, $q) {
+.directive('contentProviderUsers', function($routeParams, ContentProvider, User, lodash){
   return{
     restrict: 'E',
     controllerAs: 'vm',
+    scope: {},
     templateUrl: '../../static/templates/contentProvider/cp_user.html',
-    controller: function() {
+    controller: function(){
       var _this = this;
 
       this.pageName = 'user';
 
       this.cp_users = [];
 
-      if($routeParams.id) {
+      if($routeParams.id){
         User.query().$promise
-        .then(function(users) {
+        .then(function(users){
           _this.users = users;
           return ContentProvider.get({id: $routeParams.id}).$promise;
         })
-        .then(function(res) {
-          for(var i = 0; i < res.users.length; i++) {
-            var url = res.users[i];
-            var id = parseInt(url.substr(url.length - 2).replace('/',''));
-
-            res.users[i] = lodash.find(_this.users, {id: id});
-          }
+        .then(function(res){
+          res.users = _this.populateUser(res.users, _this.users);
           return res;
         })
-        .then(function(cp) {
+        .then(function(cp){
           _this.cp = cp;
-        }).catch(function(e) {
+        }).catch(function(e){
           _this.result = {
             status: 0,
             msg: e.data.detail
@@ -369,25 +349,38 @@
         });
       }
 
-      this.addUserToCp = function(user) {
+      this.populateUser = function(ids, list){
+        for(var i = 0; i < ids.length; i++){
+          ids[i] = lodash.find(list, {id: ids[i]});
+        }
+        return ids;
+      };
+
+      this.addUserToCp = function(user){
         _this.cp.users.push(user);
       };
 
-      this.removeUserFromCp = function(user) {
+      this.removeUserFromCp = function(user){
         lodash.remove(_this.cp.users, user);
       };
 
-      this.saveContentProvider = function(cp) {
+      this.saveContentProvider = function(cp){
+
+        // flatten the user to id of array
+        cp.users = lodash.pluck(cp.users, 'id');
 
         cp.$update()
-        .then(function() {
+        .then(function(res){
+
+          _this.cp.users = _this.populateUser(res.users, _this.users);
+
           _this.result = {
             status: 1,
             msg: 'Content Provider Saved'
           };
 
         })
-        .catch(function(e) {
+        .catch(function(e){
           _this.result = {
             status: 0,
             msg: e.data.detail
diff --git a/xos/core/xoslib/static/js/xosDeveloper.js b/xos/core/xoslib/static/js/xosDeveloper.js
index e4e5afb..c1af537 100644
--- a/xos/core/xoslib/static/js/xosDeveloper.js
+++ b/xos/core/xoslib/static/js/xosDeveloper.js
@@ -1,4 +1,4 @@
-/* eslint-disable guard-for-in, no-undef*/

+/* eslint-disable guard-for-in, no-undef, space-before-blocks*/

 /* This is an example that uses xoslib + marionette to display the developer

    view.

 

@@ -42,7 +42,7 @@
   attachHtml: function(compositeView, childView, index) {

     // The REST API will let admin users see everything. For the developer

     // view we still want to hide slices we are not members of.

-    if(childView.model.get('sliceInfo').roles.length == 0) {

+    if(childView.model.get('sliceInfo').roles.length === 0) {

       return;

     }

     DeveloperApp.SliceListView.__super__.attachHtml(compositeView, childView, index);

diff --git a/xos/core/xoslib/static/js/xosDeveloper_datatables.js b/xos/core/xoslib/static/js/xosDeveloper_datatables.js
index fc0153b..e3df4c3 100644
--- a/xos/core/xoslib/static/js/xosDeveloper_datatables.js
+++ b/xos/core/xoslib/static/js/xosDeveloper_datatables.js
@@ -1,4 +1,4 @@
-/* eslint-disable guard-for-in, no-undef, indent*/
+/* eslint-disable guard-for-in, no-undef, indent, space-before-blocks*/
 
 // TODO write test and then fix lint errors
 
@@ -9,7 +9,9 @@
 */
 
 function updateSliceTable(data) {
-    $('#developerView').html('<table cellpadding="0" cellspacing="0" border="0" class="display" id="dynamicusersliceinfo"></table>');
+    $('#developerView').html(
+        '<table cellpadding="0" cellspacing="0" border="0" class="display" id="dynamicusersliceinfo"></table>'
+    );
     var actualEntries = [];
 
     for (rowkey in data.models) {
diff --git a/xos/core/xoslib/static/js/xosTenant.js b/xos/core/xoslib/static/js/xosTenant.js
index f933c89..0bd3604 100644
--- a/xos/core/xoslib/static/js/xosTenant.js
+++ b/xos/core/xoslib/static/js/xosTenant.js
@@ -1,6 +1,6 @@
 

 /* globals XOSModel, XOSCollection */

-/* eslint-disable no-undef, guard-for-in, new-cap */

+/* eslint-disable no-undef, guard-for-in, new-cap, space-before-blocks, no-unused-vars, no-alert, eqeqeq */

 

 XOSTenantSite = XOSModel.extend({

   listFields: ['name', 'allocated'],

diff --git a/xos/core/xoslib/static/js/xoslib/xos-defaults.js b/xos/core/xoslib/static/js/xoslib/xos-defaults.js
index 06ee0d9..4ce5f6b 100644
--- a/xos/core/xoslib/static/js/xoslib/xos-defaults.js
+++ b/xos/core/xoslib/static/js/xoslib/xos-defaults.js
@@ -1,7 +1,7 @@
 /* eslint-disable quotes, no-undef, max-len, new-cap*/
 /* eslint indent: [2, 2]*/
 console.warn('**** XOS DEFAULT ****');
-function xos_get_defaults() {
+function xos_get_defaults(){
   this.account = {"updated": null, "policed": null, "created": null, "deleted": false, "site": null, "lazy_blocked": false, "backend_register": "{}", "write_protect": false, "backend_status": "0 - Provisioning in progress", "no_sync": false, "enacted": null};
   this.charge = {"updated": null, "slice": null, "date": null, "policed": null, "created": null, "deleted": false, "object": null, "account": null, "lazy_blocked": false, "backend_register": "{}", "write_protect": false, "amount": 0.0, "state": "pending", "invoice": null, "coreHours": 0.0, "backend_status": "0 - Provisioning in progress", "kind": "besteffort", "no_sync": false, "enacted": null};
   this.coarseTenant = {"subscriber_service": null, "connect_method": "na", "updated": null, "backend_status": "0 - Provisioning in progress", "policed": null, "created": null, "deleted": false, "service_specific_attribute": null, "kind": "coarse", "lazy_blocked": false, "backend_register": "{}", "write_protect": false, "enacted": null, "service_specific_id": null, "subscriber_tenant": null, "subscriber_root": null, "subscriber_user": null, "no_sync": false, "provider_service": null};
diff --git a/xos/core/xoslib/static/js/xoslib/xos-util.js b/xos/core/xoslib/static/js/xoslib/xos-util.js
index 66c01ce..2b574e7 100644
--- a/xos/core/xoslib/static/js/xoslib/xos-util.js
+++ b/xos/core/xoslib/static/js/xoslib/xos-util.js
@@ -1,4 +1,4 @@
-/* eslint-disable indent*/
+/* eslint-disable indent, space-before-blocks, no-unused-vars*/
 ////////////////////////////
 // misc utility functions //
 ////////////////////////////
@@ -79,7 +79,9 @@
     /* eslint-disable default-case */
     switch (validatorName) {
     case 'url':
+        /* eslint-disable max-len*/
         if (! /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value)) {
+        /* eslint-enable max-len*/
             return 'must be a valid url';
         }
         break;
diff --git a/xos/core/xoslib/static/js/xoslib/xos-validators.js b/xos/core/xoslib/static/js/xoslib/xos-validators.js
index f762ef2..aa8e544 100644
--- a/xos/core/xoslib/static/js/xoslib/xos-validators.js
+++ b/xos/core/xoslib/static/js/xoslib/xos-validators.js
@@ -6,7 +6,7 @@
 
 /* eslint-disable quotes, no-undef, max-len, new-cap*/
 /* eslint indent: [2, 2]*/
-function xos_get_validators() {
+function xos_get_validators(){
   this.account = {"updated": [], "policed": [], "created": [], "deleted": [], "site": ["notBlank"], "lazy_blocked": [], "backend_register": ["notBlank"], "write_protect": [], "backend_status": ["notBlank"], "id": [], "no_sync": [], "enacted": []};
   this.charge = {"updated": [], "slice": [], "policed": [], "created": [], "deleted": [], "amount": ["notBlank"], "object": ["notBlank"], "account": ["notBlank"], "kind": ["notBlank"], "lazy_blocked": [], "backend_register": ["notBlank"], "write_protect": [], "state": ["notBlank"], "coreHours": ["notBlank"], "invoice": [], "date": ["notBlank"], "backend_status": ["notBlank"], "id": [], "no_sync": [], "enacted": []};
   this.coarseTenant = {"subscriber_service": [], "connect_method": ["notBlank"], "updated": [], "policed": [], "created": [], "deleted": [], "service_specific_attribute": [], "kind": ["notBlank"], "lazy_blocked": [], "backend_register": ["notBlank"], "write_protect": [], "subscriber_user": [], "provider_service": ["notBlank"], "service_specific_id": [], "subscriber_tenant": [], "subscriber_root": [], "backend_status": ["notBlank"], "id": [], "no_sync": [], "enacted": []};
diff --git a/xos/core/xoslib/static/templates/contentProvider/cp_cdn_prefix.html b/xos/core/xoslib/static/templates/contentProvider/cp_cdn_prefix.html
index 7367f30..cc39aab 100644
--- a/xos/core/xoslib/static/templates/contentProvider/cp_cdn_prefix.html
+++ b/xos/core/xoslib/static/templates/contentProvider/cp_cdn_prefix.html
@@ -18,7 +18,7 @@
           {{item.humanReadableName}}
         </div>
         <div class="span6">
-          <!-- TODO shoe the name instead that url -->
+          <!-- TODO show the name instead that id -->
           {{item.defaultOriginServer}}
         </div>
         <div class="span2">
@@ -38,7 +38,7 @@
         <div class="span6">
           <label>Default Origin Server</label>
           <select ng-model="vm.new_prf.defaultOriginServer" style="max-width: 100%">
-            <option ng-repeat="prf in vm.prf" ng-value="'/hpcapi/originservers/' + prf.id + '/'">{$ prf.humanReadableName $}</option>
+            <option ng-repeat="prf in vm.prf" ng-value="prf.id">{$ prf.humanReadableName $}</option>
           </select>
         </div>
         <div class="span2 text-right">
diff --git a/xos/core/xoslib/static/templates/contentProvider/cp_detail.html b/xos/core/xoslib/static/templates/contentProvider/cp_detail.html
index 7fab7f2..0c11329 100644
--- a/xos/core/xoslib/static/templates/contentProvider/cp_detail.html
+++ b/xos/core/xoslib/static/templates/contentProvider/cp_detail.html
@@ -35,9 +35,7 @@
         <div class="row-fluid">
           <div class="span12">
             <label>Service provider</label>
-            <select ng-model="vm.cp.serviceProvider" required>
-              <option ng-repeat="sp in vm.sp" ng-value="'/hpcapi/serviceproviders/' + sp.id + '/'" ng-selected="vm.activeServiceProvide(sp.id, vm.cp.serviceProvider)">{$ sp.humanReadableName $}</option>
-            </select>
+            <select required ng-model="vm.cp.serviceProvider" ng-options="sp.id as sp.humanReadableName for sp in vm.sp"></select>
           </div>
         </div>
         <div class="row-fluid">
diff --git a/xos/core/xoslib/static/templates/contentProvider/cp_user.html b/xos/core/xoslib/static/templates/contentProvider/cp_user.html
index a8d12f6..b35c2e9 100644
--- a/xos/core/xoslib/static/templates/contentProvider/cp_user.html
+++ b/xos/core/xoslib/static/templates/contentProvider/cp_user.html
@@ -38,11 +38,14 @@
           <select ng-model="vm.user" ng-options="u as u.username for u in vm.users" ng-change="vm.addUserToCp(vm.user)"></select>
         </div>  
         <div class="span4 text-right">
-          <button class="btn btn-success margin-wells" disabled="disabled">
+          <button class="btn btn-success margin-wells">
             Save
           </button>
         </div>
       </div>
     </form>
+    <div class="alert" ng-show="vm.result" ng-class="{'alert-success': vm.result.status === 1,'alert-error': vm.result.status === 0}">
+      {$ vm.result.msg $}
+    </div>
   </div>
 </div>
\ No newline at end of file