Merge branch 'develop'
diff --git a/xos/core/xoslib/dashboards/contentProvider.html b/xos/core/xoslib/dashboards/contentProvider.html
index 7a3eb3c..24cd7f1 100644
--- a/xos/core/xoslib/dashboards/contentProvider.html
+++ b/xos/core/xoslib/dashboards/contentProvider.html
@@ -1,8 +1,18 @@
+<!--
+To setup hpc:
+- cd /opt/xos/tosca
+- python ./run.py padmin@vicci.org samples/cdn.yaml
+
+To generate hpcapi:
+- cd /opt/xos
+- python apigen/modelgen apigen/hpc-api.template.py -n User -n Service -a hpc > xos/hpcapi.py
+ -->
+
<div ng-app="xos.contentProviderApp">
<ng-view></ng-view>
</div>
-
+<link rel="stylesheet" href="{{ STATIC_URL }}/css/xosLib.css">
<script src="{{ STATIC_URL }}/js/vendor/angular/angular.min.js"></script>
<script src="{{ STATIC_URL }}/js/vendor/angular-resource/angular-resource.min.js"></script>
<script src="{{ STATIC_URL }}/js/vendor/angular-route/angular-route.min.js"></script>
diff --git a/xos/core/xoslib/karma.conf.js b/xos/core/xoslib/karma.conf.js
index 8fc9d11..752b994 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 ca9b727..64b676a 100644
--- a/xos/core/xoslib/spec/views/contentprovider.test.js
+++ b/xos/core/xoslib/spec/views/contentprovider.test.js
@@ -12,6 +12,7 @@
beforeEach(function() {
module(function($provide) {
+ // mocking routeParams to pass 1 as id
$provide.provider('$routeParams', function() {
this.$get = function() {
return {id: 1};
@@ -71,22 +72,33 @@
});
describe('the contentProviderDetail directive', () => {
+
+ 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]);
+ 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(inject(function($compile, $rootScope, ContentProvider) {
- scope = $rootScope.$new();
-
- httpBackend.expectGET('/hpcapi/contentproviders/1/').respond(CPmock.CPlist[0]);
- httpBackend.whenPUT('/hpcapi/contentproviders/1/').respond({name: 'done'});
-
- spyOn(ContentProvider, 'save').and.callThrough();
-
- element = angular.element('<content-provider-detail></content-provider-detail>');
- $compile(element)(scope);
- scope.$digest();
- httpBackend.flush();
- isolatedScope = element.isolateScope().vm;
- }));
+ beforeEach(() => {
+ // spy the instance update method
+ spyOn(isolatedScope.cp, '$update').and.callThrough();
+ });
it('should request the correct contentProvider', () => {
expect(isolatedScope.cp.name).toEqual(CPmock.CPlist[0].name);
@@ -95,9 +107,72 @@
it('should update a contentProvider', () => {
isolatedScope.cp.name = 'new name';
isolatedScope.saveContentProvider(isolatedScope.cp);
- httpBackend.flush();
- expect(isolatedScope.cp.name).toEqual('done');
+ expect(isolatedScope.cp.$update).toHaveBeenCalled();
});
});
});
+
+ describe('the contentProviderCdn directive', () => {
+ beforeEach(inject(($compile, $rootScope) => {
+ 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();
+ scope.$digest();
+ httpBackend.flush();
+ isolatedScope = element.isolateScope().vm;
+ }));
+
+ it('should load associated CDN prefix', () => {
+ expect(isolatedScope.cp_prf.length).toBe(1);
+ expect(isolatedScope.prf.length).toBe(2);
+ });
+
+ it('should add a CDN Prefix', () => {
+ isolatedScope.addPrefix({prefix: 'test.io', defaultOriginServer: '/hpcapi/originservers/2/'});
+ httpBackend.flush();
+ expect(isolatedScope.cp_prf.length).toBe(2);
+ });
+
+ it('should remove a CDN Prefix', () => {
+ isolatedScope.removePrefix(isolatedScope.cp_prf[0]);
+ httpBackend.flush();
+ expect(isolatedScope.cp_prf.length).toBe(0);
+ });
+ });
+
+ describe('the contentProviderServer directive', () => {
+ beforeEach(inject(($compile, $rootScope) => {
+ 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();
+ scope.$digest();
+ httpBackend.flush();
+ isolatedScope = element.isolateScope().vm;
+ }));
+
+ it('should load associated OriginServer', () => {
+ expect(isolatedScope.cp_os.length).toBe(4);
+ });
+
+ it('should add a OriginServer', () => {
+ isolatedScope.addOrigin({protocol: 'http', url: 'test.io'});
+ httpBackend.flush();
+ expect(isolatedScope.cp_os.length).toBe(5);
+ });
+
+ it('should remove a OriginServer', () => {
+ isolatedScope.removeOrigin(isolatedScope.cp_os[0]);
+ httpBackend.flush();
+ expect(isolatedScope.cp_os.length).toBe(3);
+ });
+ });
});
diff --git a/xos/core/xoslib/spec/views/mocks/contentProvider.mock.js b/xos/core/xoslib/spec/views/mocks/contentProvider.mock.js
index 97ee31b..c0d1750 100644
--- a/xos/core/xoslib/spec/views/mocks/contentProvider.mock.js
+++ b/xos/core/xoslib/spec/views/mocks/contentProvider.mock.js
@@ -211,5 +211,479 @@
'description':null,
'enabled':true
}
+ ],
+ CDNlist: [
+ {
+ 'humanReadableName':'onlab.vicci.org',
+ 'validators':{
+ 'updated':[
+
+ ],
+ 'contentProvider':[
+ 'notBlank'
+ ],
+ 'policed':[
+
+ ],
+ 'created':[
+
+ ],
+ 'deleted':[
+
+ ],
+ 'description':[
+
+ ],
+ 'enabled':[
+
+ ],
+ 'cdn_prefix_id':[
+
+ ],
+ 'lazy_blocked':[
+
+ ],
+ 'backend_register':[
+ 'notBlank'
+ ],
+ 'write_protect':[
+
+ ],
+ 'prefix':[
+ 'notBlank'
+ ],
+ 'defaultOriginServer':[
+
+ ],
+ 'backend_status':[
+ 'notBlank'
+ ],
+ 'id':[
+
+ ],
+ 'no_sync':[
+
+ ],
+ 'enacted':[
+
+ ]
+ },
+ 'id':5,
+ 'created':'2015-10-26T13:09:44.343Z',
+ 'updated':'2015-10-26T13:09:44.343Z',
+ 'enacted':null,
+ 'policed':null,
+ 'backend_register':'{}',
+ 'backend_status':'0 - Provisioning in progress',
+ 'deleted':false,
+ 'write_protect':false,
+ 'lazy_blocked':false,
+ 'no_sync':false,
+ 'cdn_prefix_id':null,
+ 'prefix':'onlab.vicci.org',
+ 'contentProvider':'http://0.0.0.0:9000/hpcapi/contentproviders/1/',
+ 'description':null,
+ 'defaultOriginServer':'http://0.0.0.0:9000/hpcapi/originservers/2/',
+ 'enabled':true
+ },
+ {
+ 'humanReadableName':'downloads.onosproject.org',
+ 'validators':{
+ 'updated':[
+
+ ],
+ 'contentProvider':[
+ 'notBlank'
+ ],
+ 'policed':[
+
+ ],
+ 'created':[
+
+ ],
+ 'deleted':[
+
+ ],
+ 'description':[
+
+ ],
+ 'enabled':[
+
+ ],
+ 'cdn_prefix_id':[
+
+ ],
+ 'lazy_blocked':[
+
+ ],
+ 'backend_register':[
+ 'notBlank'
+ ],
+ 'write_protect':[
+
+ ],
+ 'prefix':[
+ 'notBlank'
+ ],
+ 'defaultOriginServer':[
+
+ ],
+ 'backend_status':[
+ 'notBlank'
+ ],
+ 'id':[
+
+ ],
+ 'no_sync':[
+
+ ],
+ 'enacted':[
+
+ ]
+ },
+ 'id':1,
+ 'created':'2015-10-26T13:09:44.196Z',
+ 'updated':'2015-10-26T13:09:44.196Z',
+ 'enacted':null,
+ 'policed':null,
+ 'backend_register':'{}',
+ 'backend_status':'0 - Provisioning in progress',
+ 'deleted':false,
+ 'write_protect':false,
+ 'lazy_blocked':false,
+ 'no_sync':false,
+ 'cdn_prefix_id':null,
+ 'prefix':'downloads.onosproject.org',
+ 'contentProvider':'http://0.0.0.0:9000/hpcapi/contentproviders/2/',
+ 'description':null,
+ 'defaultOriginServer':'http://0.0.0.0:9000/hpcapi/originservers/1/',
+ 'enabled':true
+ }
+ ],
+ OSlist: [
+ {
+ 'humanReadableName':'another.it',
+ 'validators':{
+ 'updated':[
+
+ ],
+ 'contentProvider':[
+ 'notBlank'
+ ],
+ 'origin_server_id':[
+
+ ],
+ 'policed':[
+
+ ],
+ 'created':[
+
+ ],
+ 'deleted':[
+
+ ],
+ 'description':[
+
+ ],
+ 'enabled':[
+
+ ],
+ 'redirects':[
+
+ ],
+ 'protocol':[
+ 'notBlank'
+ ],
+ 'lazy_blocked':[
+
+ ],
+ 'backend_register':[
+ 'notBlank'
+ ],
+ 'write_protect':[
+
+ ],
+ 'url':[
+ 'notBlank'
+ ],
+ 'authenticated':[
+
+ ],
+ 'backend_status':[
+ 'notBlank'
+ ],
+ 'id':[
+
+ ],
+ 'no_sync':[
+
+ ],
+ 'enacted':[
+
+ ]
+ },
+ 'id':8,
+ 'created':'2015-10-26T13:40:36.878Z',
+ 'updated':'2015-10-26T13:40:36.878Z',
+ 'enacted':null,
+ 'policed':null,
+ 'backend_register':'{}',
+ 'backend_status':'0 - Provisioning in progress',
+ 'deleted':false,
+ 'write_protect':false,
+ 'lazy_blocked':false,
+ 'no_sync':false,
+ 'origin_server_id':null,
+ 'url':'another.it',
+ 'contentProvider':'http://0.0.0.0:9000/hpcapi/contentproviders/1/',
+ 'authenticated':false,
+ 'enabled':true,
+ 'protocol':'http',
+ 'redirects':true,
+ 'description':null
+ },
+ {
+ 'humanReadableName':'test.it',
+ 'validators':{
+ 'updated':[
+
+ ],
+ 'contentProvider':[
+ 'notBlank'
+ ],
+ 'origin_server_id':[
+
+ ],
+ 'policed':[
+
+ ],
+ 'created':[
+
+ ],
+ 'deleted':[
+
+ ],
+ 'description':[
+
+ ],
+ 'enabled':[
+
+ ],
+ 'redirects':[
+
+ ],
+ 'protocol':[
+ 'notBlank'
+ ],
+ 'lazy_blocked':[
+
+ ],
+ 'backend_register':[
+ 'notBlank'
+ ],
+ 'write_protect':[
+
+ ],
+ 'url':[
+ 'notBlank'
+ ],
+ 'authenticated':[
+
+ ],
+ 'backend_status':[
+ 'notBlank'
+ ],
+ 'id':[
+
+ ],
+ 'no_sync':[
+
+ ],
+ 'enacted':[
+
+ ]
+ },
+ 'id':7,
+ 'created':'2015-10-26T13:36:42.567Z',
+ 'updated':'2015-10-26T13:36:42.567Z',
+ 'enacted':null,
+ 'policed':null,
+ 'backend_register':'{}',
+ 'backend_status':'0 - Provisioning in progress',
+ 'deleted':false,
+ 'write_protect':false,
+ 'lazy_blocked':false,
+ 'no_sync':false,
+ 'origin_server_id':null,
+ 'url':'test.it',
+ 'contentProvider':'http://0.0.0.0:9000/hpcapi/contentproviders/1/',
+ 'authenticated':false,
+ 'enabled':true,
+ 'protocol':'http',
+ 'redirects':true,
+ 'description':null
+ },
+ {
+ 'humanReadableName':'onlab.vicci.org',
+ 'validators':{
+ 'updated':[
+
+ ],
+ 'contentProvider':[
+ 'notBlank'
+ ],
+ 'origin_server_id':[
+
+ ],
+ 'policed':[
+
+ ],
+ 'created':[
+
+ ],
+ 'deleted':[
+
+ ],
+ 'description':[
+
+ ],
+ 'enabled':[
+
+ ],
+ 'redirects':[
+
+ ],
+ 'protocol':[
+ 'notBlank'
+ ],
+ 'lazy_blocked':[
+
+ ],
+ 'backend_register':[
+ 'notBlank'
+ ],
+ 'write_protect':[
+
+ ],
+ 'url':[
+ 'notBlank'
+ ],
+ 'authenticated':[
+
+ ],
+ 'backend_status':[
+ 'notBlank'
+ ],
+ 'id':[
+
+ ],
+ 'no_sync':[
+
+ ],
+ 'enacted':[
+
+ ]
+ },
+ 'id':2,
+ 'created':'2015-10-26T13:09:44.286Z',
+ 'updated':'2015-10-26T13:09:44.286Z',
+ 'enacted':null,
+ 'policed':null,
+ 'backend_register':'{}',
+ 'backend_status':'0 - Provisioning in progress',
+ 'deleted':false,
+ 'write_protect':false,
+ 'lazy_blocked':false,
+ 'no_sync':false,
+ 'origin_server_id':null,
+ 'url':'onlab.vicci.org',
+ 'contentProvider':'http://0.0.0.0:9000/hpcapi/contentproviders/1/',
+ 'authenticated':false,
+ 'enabled':true,
+ 'protocol':'HTTP',
+ 'redirects':true,
+ 'description':null
+ },
+ {
+ 'humanReadableName':'downloads.onosproject.org',
+ 'validators':{
+ 'updated':[
+
+ ],
+ 'contentProvider':[
+ 'notBlank'
+ ],
+ 'origin_server_id':[
+
+ ],
+ 'policed':[
+
+ ],
+ 'created':[
+
+ ],
+ 'deleted':[
+
+ ],
+ 'description':[
+
+ ],
+ 'enabled':[
+
+ ],
+ 'redirects':[
+
+ ],
+ 'protocol':[
+ 'notBlank'
+ ],
+ 'lazy_blocked':[
+
+ ],
+ 'backend_register':[
+ 'notBlank'
+ ],
+ 'write_protect':[
+
+ ],
+ 'url':[
+ 'notBlank'
+ ],
+ 'authenticated':[
+
+ ],
+ 'backend_status':[
+ 'notBlank'
+ ],
+ 'id':[
+
+ ],
+ 'no_sync':[
+
+ ],
+ 'enacted':[
+
+ ]
+ },
+ 'id':1,
+ 'created':'2015-10-26T13:09:44.182Z',
+ 'updated':'2015-10-26T13:09:44.182Z',
+ 'enacted':null,
+ 'policed':null,
+ 'backend_register':'{}',
+ 'backend_status':'0 - Provisioning in progress',
+ 'deleted':false,
+ 'write_protect':false,
+ 'lazy_blocked':false,
+ 'no_sync':false,
+ 'origin_server_id':null,
+ 'url':'downloads.onosproject.org',
+ 'contentProvider':'http://0.0.0.0:9000/hpcapi/contentproviders/1/',
+ 'authenticated':false,
+ 'enabled':true,
+ 'protocol':'HTTP',
+ 'redirects':true,
+ 'description':null
+ }
]
};
\ No newline at end of file
diff --git a/xos/core/xoslib/static/css/xosLib.css b/xos/core/xoslib/static/css/xosLib.css
new file mode 100644
index 0000000..d2f9349
--- /dev/null
+++ b/xos/core/xoslib/static/css/xosLib.css
@@ -0,0 +1,13 @@
+/* Style helpers for xoslib*/
+
+
+/* gives an element the same spacing as it is inside a well*/
+.margin-wells {
+ margin: 12px;
+}
+
+/* if inside a form set margin-top to align with inputs instead of labels*/
+/* TODO once scss use a form like form [class^=span] > label + &[class^=span] > .margin-wells to match only if label is set*/
+form button.margin-wells {
+ margin-top: 20px;
+}
\ No newline at end of file
diff --git a/xos/core/xoslib/static/js/xosContentProvider.js b/xos/core/xoslib/static/js/xosContentProvider.js
index dfac97b..0c21fe3 100644
--- a/xos/core/xoslib/static/js/xosContentProvider.js
+++ b/xos/core/xoslib/static/js/xosContentProvider.js
@@ -54,7 +54,7 @@
}
};
})
-.service('ContentProvider', function($resource) {
+.service('ContentProvider', function($resource, $q, User) {
return $resource('/hpcapi/contentproviders/:id/', {id: '@id'}, {
'update': {method: 'PUT'}
});
@@ -137,6 +137,10 @@
};
});
}
+ else {
+ console.log('new');
+ _this.cp = new ContentProvider();
+ }
ServiceProvider.query().$promise
.then(function(sp) {
@@ -161,11 +165,11 @@
else {
isNew = true;
cp.name = cp.humanReadableName;
- p = new ContentProvider(cp).$save();
+ console.log('save');
+ p = cp.$save();
}
p.then(function(res) {
- console.log('save done', res);
_this.result = {
status: 1,
msg: 'Content Provider Saved'
@@ -188,6 +192,7 @@
return{
restrict: 'E',
controllerAs: 'vm',
+ scope: {},
templateUrl: '../../static/templates/contentProvider/cp_cdn_prefix.html',
controller: function() {
var _this = this;
@@ -263,10 +268,11 @@
return{
restrict: 'E',
controllerAs: 'vm',
+ scope: {},
templateUrl: '../../static/templates/contentProvider/cp_origin_server.html',
controller: function() {
this.pageName = 'server';
- this.protocols = ['HTTP', 'RTMP', 'RTP', 'SHOUTcast'];
+ this.protocols = {'http': 'HTTP', 'rtmp': 'RTMP', 'rtp': 'RTP','shout': 'SHOUTcast'};
var _this = this;
@@ -326,7 +332,7 @@
}
};
})
-.directive('contentProviderUsers', function($routeParams, ContentProvider, User, lodash) {
+.directive('contentProviderUsers', function($routeParams, ContentProvider, User, lodash, $q) {
return{
restrict: 'E',
controllerAs: 'vm',
@@ -339,7 +345,20 @@
this.cp_users = [];
if($routeParams.id) {
- ContentProvider.get({id: $routeParams.id}).$promise
+ User.query().$promise
+ .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});
+ }
+ return res;
+ })
.then(function(cp) {
_this.cp = cp;
}).catch(function(e) {
@@ -350,22 +369,30 @@
});
}
- User.query().$promise
- .then(function(users) {
- _this.users = users;
- }).catch(function(e) {
- _this.result = {
- status: 0,
- msg: e.data.detail
- };
- });
-
this.addUserToCp = function(user) {
- _this.cp_users.push(user);
+ _this.cp.users.push(user);
};
this.removeUserFromCp = function(user) {
- lodash.remove(_this.cp_users, user);
+ lodash.remove(_this.cp.users, user);
+ };
+
+ this.saveContentProvider = function(cp) {
+
+ cp.$update()
+ .then(function() {
+ _this.result = {
+ status: 1,
+ msg: 'Content Provider Saved'
+ };
+
+ })
+ .catch(function(e) {
+ _this.result = {
+ status: 0,
+ msg: e.data.detail
+ };
+ });
};
}
};
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 d989f90..7367f30 100644
--- a/xos/core/xoslib/static/templates/contentProvider/cp_cdn_prefix.html
+++ b/xos/core/xoslib/static/templates/contentProvider/cp_cdn_prefix.html
@@ -41,8 +41,8 @@
<option ng-repeat="prf in vm.prf" ng-value="'/hpcapi/originservers/' + prf.id + '/'">{$ prf.humanReadableName $}</option>
</select>
</div>
- <div class="span2">
- <button class="btn btn-success pull-right">
+ <div class="span2 text-right">
+ <button class="btn btn-success margin-wells">
<i class="icon icon-plus"></i>
</button>
</div>
diff --git a/xos/core/xoslib/static/templates/contentProvider/cp_list.html b/xos/core/xoslib/static/templates/contentProvider/cp_list.html
index ddb0aa7..3303564 100644
--- a/xos/core/xoslib/static/templates/contentProvider/cp_list.html
+++ b/xos/core/xoslib/static/templates/contentProvider/cp_list.html
@@ -19,7 +19,7 @@
<td>
{$ item.enabled $}
</td>
- <td>
+ <td class="text-right">
<a ng-click="vm.deleteCp(item.id)" class="btn btn-danger"><i class="icon icon-remove"></i></a></td>
</tr>
</table>
diff --git a/xos/core/xoslib/static/templates/contentProvider/cp_origin_server.html b/xos/core/xoslib/static/templates/contentProvider/cp_origin_server.html
index b2e1881..ff77864 100644
--- a/xos/core/xoslib/static/templates/contentProvider/cp_origin_server.html
+++ b/xos/core/xoslib/static/templates/contentProvider/cp_origin_server.html
@@ -33,14 +33,14 @@
<div class="row-fluid">
<div class="span4">
<label>Protocol</label>
- <select ng-model="vm.new_os.protocol" ng-options="p for p in vm.protocols" style="max-width: 100%;"></select>
+ <select ng-model="vm.new_os.protocol" ng-options="k as v for (k,v) in vm.protocols" style="max-width: 100%;"></select>
</div>
<div class="span6">
<label>Url</label>
<input type="text" ng-model="vm.new_os.url" required>
</div>
- <div class="span2">
- <button class="btn btn-success pull-right">
+ <div class="span2 text-right">
+ <button class="btn btn-success margin-wells">
<i class="icon icon-plus"></i>
</button>
</div>
diff --git a/xos/core/xoslib/static/templates/contentProvider/cp_user.html b/xos/core/xoslib/static/templates/contentProvider/cp_user.html
index 435e656..a8d12f6 100644
--- a/xos/core/xoslib/static/templates/contentProvider/cp_user.html
+++ b/xos/core/xoslib/static/templates/contentProvider/cp_user.html
@@ -12,7 +12,7 @@
<div ng-include="'../../static/templates/contentProvider/cp_side_nav.html'"></div>
</div>
<div class="span10">
- <div ng-repeat="item in vm.cp_users" class="well">
+ <div ng-repeat="item in vm.cp.users" class="well">
<div class="row-fluid">
<div class="span3">
{{item.firstname}}
@@ -31,8 +31,18 @@
</div>
</div>
<hr>
- <form>
- <select ng-model="vm.user" ng-options="u as u.username for u in vm.users" ng-change="vm.addUserToCp(vm.user)"></select>
+ <form ng-submit="vm.saveContentProvider(vm.cp)">
+ <div class="row-fluid">
+ <div class="span8">
+ <label>Select user:</label>
+ <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">
+ Save
+ </button>
+ </div>
+ </div>
</form>
</div>
</div>
\ No newline at end of file