Merge branch 'feature/tenant_view' into develop
diff --git a/.gitignore b/.gitignore
index 5408220..e91dffb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,5 @@
*.moved-aside
xos/configurations/frontend/Dockerfile
xos/core/xoslib/karma-*
+xos/core/xoslib/docs
node_modules
diff --git a/xos/configurations/common/admin-openrc.sh b/xos/configurations/common/admin-openrc.sh
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/xos/configurations/common/admin-openrc.sh
diff --git a/xos/core/xoslib/jsdoc.conf.json b/xos/core/xoslib/jsdoc.conf.json
new file mode 100644
index 0000000..3352b79
--- /dev/null
+++ b/xos/core/xoslib/jsdoc.conf.json
@@ -0,0 +1,30 @@
+{
+ "tags": {
+ "allowUnknownTags": true
+ },
+ "plugins": ["plugins/markdown"],
+ "templates": {
+ "cleverLinks": false,
+ "monospaceLinks": false,
+ "dateFormat": "ddd MMM Do YYYY",
+ "outputSourceFiles": true,
+ "outputSourcePath": true,
+ "systemName": "XOS Lib",
+ "footer": "",
+ "copyright": "DocStrap Copyright © 2012-2014 The contributors to the JSDoc3 and DocStrap projects.",
+ "navType": "vertical",
+ "theme": "cosmo",
+ "linenums": true,
+ "collapseSymbols": false,
+ "inverseNav": true,
+ "highlightTutorialCode": true,
+ "protocol": "html://"
+ },
+ "markdown": {
+ "parser": "gfm",
+ "hardwrap": true
+ },
+ "opts": {
+ "template": "./node_modules/ink-docstrap/template/"
+ }
+}
diff --git a/xos/core/xoslib/package.json b/xos/core/xoslib/package.json
index cc23dbf..d2ed905 100644
--- a/xos/core/xoslib/package.json
+++ b/xos/core/xoslib/package.json
@@ -6,14 +6,17 @@
"scripts": {
"pretest": "npm install",
"test": "karma start",
- "lint": "eslint ."
+ "lint": "eslint .",
+ "docs": "jsdoc static/js -r -c ./jsdoc.conf.json -d docs"
},
"author": "",
"license": "BSD-2-Clause",
"devDependencies": {
"eslint": "~1.6.0",
"eslint-config-defaults": "^7.0.1",
+ "ink-docstrap": "^0.5.2",
"jasmine-core": "~2.3.4",
+ "jsdoc": "^3.3.3",
"karma": "~0.13.10",
"karma-babel-preprocessor": "~5.2.2",
"karma-jasmine": "~0.3.6",
diff --git a/xos/core/xoslib/spec/xoslib/xos-backbone.test.js b/xos/core/xoslib/spec/xoslib/xos-backbone.test.js
index f14275d..f97cc07 100644
--- a/xos/core/xoslib/spec/xoslib/xos-backbone.test.js
+++ b/xos/core/xoslib/spec/xoslib/xos-backbone.test.js
@@ -3,9 +3,9 @@
describe('The Xos Backbone', () => {
beforeEach(() => {
- xosdefaults = {
+ $.extend(xosdefaults,{
test: {config: true}
- };
+ });
});
describe('get_defaults mehod', () => {
@@ -36,6 +36,193 @@
});
+ describe('The define_model method', () => {
+
+ var testLib;
+
+ beforeEach(() => {
+ var TestLibDefinition = function() {
+ /* eslint-disable no-invalid-this*/
+ this.allCollectionNames = [];
+ this.allCollections = [];
+ /* eslint-enable no-invalid-this*/
+ };
+
+ testLib = new TestLibDefinition();
+ });
+
+ it('should create a model and attach it to xos lib', () => {
+ define_model(testLib, {
+ urlRoot: 'testUrl',
+ modelName: 'testModel'
+ });
+
+ expect(testLib.allCollectionNames[0]).toEqual('testModels');
+ expect(typeof testLib['testModel']).toBe('function');
+
+ });
+
+ describe('when a model is created', () => {
+ var model;
+ beforeEach(() => {
+ define_model(testLib, {
+ urlRoot: 'testUrl',
+ modelName: 'testModel',
+ // collectionName: 'testCollection',
+ relatedCollections: {instances: 'slices'},
+ foreignCollections: ['sites'],
+ foreignFields: {slice: 'slices'},
+ m2mFields: {sites: 'sites'},
+ listFields: ['name'],
+ detailFields: ['name', 'age'],
+ addFields: ['add'],
+ inputType: {add: 'checkbox'}
+ });
+ /*eslint-disable new-cap*/
+ model = new testLib.testModel();
+ /*eslint-enable new-cap*/
+
+ // add defaults and validator for `testModel`
+ xosdefaults['testModel'] = {name: 'Scott'};
+ xosvalidators['testModel'] = {network_ports: ['portspec']};
+ });
+
+ it('should have a name', () => {
+ expect(model.modelName).toEqual('testModel');
+ });
+
+ it('should have a default collectionName', () => {
+ expect(model.collectionName).toEqual('testModels');
+ });
+
+ describe('whith a custom collectionName', () => {
+ var customCollectionName;
+ beforeEach(() => {
+ define_model(testLib, {
+ urlRoot: 'collUrl',
+ modelName: 'customCollectionName',
+ collectionName: 'myCollection'
+ });
+
+ /*eslint-disable new-cap*/
+ customCollectionName = new testLib.customCollectionName();
+ /*eslint-enable new-cap*/
+ });
+
+ it('should have the custom collectionName', () => {
+ expect(customCollectionName.collectionName).toBe('myCollection');
+ });
+
+ afterEach(() => {
+ customCollectionName = null;
+ });
+ });
+
+ it('should have a valid url', () => {
+ expect(model.url()).toEqual('testUrl/?no_hyperlinks=1');
+ });
+
+ it('should have related collections', () => {
+ expect(model.relatedCollections).toEqual({instances: 'slices'});
+ });
+
+ it('should have foreign collections', () => {
+ expect(model.foreignCollections).toEqual(['sites']);
+ });
+
+ it('should have foreign fields', () => {
+ expect(model.foreignFields).toEqual({slice: 'slices'});
+ });
+
+ it('should have m2m fields', () => {
+ expect(model.m2mFields).toEqual({sites: 'sites'});
+ });
+
+ it('should have list field', () => {
+ expect(model.listFields).toEqual(['name']);
+ });
+
+ it('should have deatil field', () => {
+ expect(model.detailFields).toEqual(['name', 'age']);
+ });
+
+ it('should have add field', () => {
+ expect(model.addFields).toEqual(['add']);
+ });
+
+ it('should have input types defined', () => {
+ expect(model.inputType).toEqual({add: 'checkbox'});
+ });
+
+ it('should have standard defaults', () => {
+ expect(model.defaults).toEqual({name: 'Scott'});
+ });
+
+ describe('when default are defined', () => {
+
+ var extendDefault;
+ beforeEach(() => {
+ define_model(testLib, {
+ urlRoot: 'collUrl',
+ modelName: 'extendDefault',
+ defaults: extend_defaults('testModel', {surname: 'Baker'})
+ });
+
+ /*eslint-disable new-cap*/
+ extendDefault = new testLib.extendDefault();
+ /*eslint-enable new-cap*/
+ });
+
+ it('should return new defaults', () => {
+ expect(extendDefault.defaults).toEqual({name: 'Scott', surname: 'Baker'});
+ });
+
+ afterEach(() => {
+ extendDefault = null;
+ });
+ });
+
+ it('should add default validators', () => {
+ expect(model.validators).toEqual({network_ports: ['portspec']});
+ });
+
+ describe('when validators are defined', () => {
+
+ var extendValidators;
+ beforeEach(() => {
+ define_model(testLib, {
+ urlRoot: 'collUrl',
+ modelName: 'testModel',
+ validators: {site: ['notBlank']}
+ });
+
+ /*eslint-disable new-cap*/
+ extendValidators = new testLib.testModel();
+ /*eslint-enable new-cap*/
+ });
+
+ it('should return extended validators', () => {
+ expect(extendValidators.validators).toEqual({network_ports: ['portspec'], site: ['notBlank']});
+ });
+
+ afterEach(() => {
+ extendValidators = null;
+ });
+ });
+
+ it('should have a xosValidate method', () => {
+ console.log(model.xosValidate);
+ expect(typeof model.xosValidate).toEqual('function');
+ });
+
+ // TBT
+ // - xosValidate
+ // - Test the default
+ // - Test override
+
+ });
+ });
+
describe('getCookie method with no cookie', () => {
beforeEach(() => {
diff --git a/xos/core/xoslib/static/js/xosTenant.js b/xos/core/xoslib/static/js/xosTenant.js
index d2a3b4e..f933c89 100644
--- a/xos/core/xoslib/static/js/xosTenant.js
+++ b/xos/core/xoslib/static/js/xosTenant.js
@@ -1,456 +1,524 @@
-/* eslint-disable */
-XOSTenantSite = XOSModel.extend( {
- listFields: ["name", "allocated"],
- modelName: "tenantSite",
- collectionName: "tenantSites"
+/* globals XOSModel, XOSCollection */
+/* eslint-disable no-undef, guard-for-in, new-cap */
+
+XOSTenantSite = XOSModel.extend({
+ listFields: ['name', 'allocated'],
+ modelName: 'tenantSite',
+ collectionName: 'tenantSites'
});
-XOSTenantSiteCollection = XOSCollection.extend( {
- listFields: ["name", "allocated", "ready"],
- modelName: "tenantSite",
- collectionName: "tenantSites",
+XOSTenantSiteCollection = XOSCollection.extend({
+ listFields: ['name', 'allocated', 'ready'],
+ modelName: 'tenantSite',
+ collectionName: 'tenantSites',
- getFromSlice: function(slice) {
- var tenantSites = [];
- var id = 0;
- for (siteName in slice.attributes.site_allocation) {
- allocated = slice.attributes.site_allocation[siteName];
- ready = slice.attributes.site_ready[siteName] || 0;
- tenantSites.push(new XOSTenantSite( { name: siteName, allocated: allocated, ready: ready, id: id} ));
- id = id + 1;
- }
- for (index in xos.tenantview.models[0].attributes.blessed_site_names) {
- siteName = xos.tenantview.models[0].attributes.blessed_site_names[index];
- if (! (siteName in slice.attributes.site_allocation)) {
- tenantSites.push(new XOSTenantSite( { name: siteName, allocated: 0, ready: 0, id: id} ));
- id = id + 1;
- }
- }
- this.set(tenantSites);
+ getFromSlice: function(slice) {
+ var tenantSites = [];
+ var id = 0;
+ var that = this;
- var that=this;
- this.listenTo(slice, 'change', function() { that.getReadyFromSlice(slice); })
- },
+ for (siteName in slice.attributes.site_allocation) {
+ allocated = slice.attributes.site_allocation[siteName];
+ ready = slice.attributes.site_ready[siteName] || 0;
+ tenantSites.push(new XOSTenantSite({name: siteName, allocated: allocated, ready: ready, id: id}));
+ id = id + 1;
+ }
- getReadyFromSlice: function(slice) {
- for (siteName in slice.attributes.site_ready) {
- ready = slice.attributes.site_ready[siteName];
- for (index in this.models) {
- tenantSite = this.models[index];
- if (tenantSite.attributes.name == siteName) {
- tenantSite.set("ready", ready);
- }
- }
- }
- },
+ for (index in xos.tenantview.models[0].attributes.blessed_site_names) {
+ siteName = xos.tenantview.models[0].attributes.blessed_site_names[index];
+ if (! (siteName in slice.attributes.site_allocation)) {
+ tenantSites.push(new XOSTenantSite({name: siteName, allocated: 0, ready: 0, id: id}));
+ id = id + 1;
+ }
+ }
+ this.set(tenantSites);
- putToSlice: function(slice) {
- slice.attributes.site_allocation = {};
- for (index in this.models) {
- var model = this.models[index];
- slice.attributes.site_allocation[ model.attributes.name ] = model.attributes.allocated;
+ this.listenTo(slice, 'change', function() {
+ that.getReadyFromSlice(slice);
+ });
+ },
+
+ getReadyFromSlice: function(slice) {
+ for (siteName in slice.attributes.site_ready) {
+ ready = slice.attributes.site_ready[siteName];
+ for (index in this.models) {
+ tenantSite = this.models[index];
+ if (tenantSite.attributes.name == siteName) {
+ tenantSite.set('ready', ready);
}
- },
+ }
+ }
+ },
+
+ putToSlice: function(slice) {
+ slice.attributes.site_allocation = {};
+ for (index in this.models) {
+ var model = this.models[index];
+
+ slice.attributes.site_allocation[ model.attributes.name ] = model.attributes.allocated;
+ }
+ },
});
XOSEditUsersView = Marionette.ItemView.extend({
- template: "#tenant-edit-users",
- viewInitializers: [],
+ template: '#tenant-edit-users',
+ viewInitializers: [],
- onShow: function() {
- _.each(this.viewInitializers, function(initializer) {
- initializer();
- });
- },
+ onShow: function() {
+ _.each(this.viewInitializers, function(initializer) {
+ initializer();
+ });
+ },
- templateHelpers: function() { return { detailView: this, model: this.model }; },
+ templateHelpers: function() {
+ return {detailView: this, model: this.model};
+ }
- });
+});
XOSTenantSummaryView = XOSDetailView.extend({
- events: {"change": "onChange"},
+ events: {'change': 'onChange'},
- onChange: function(e) {
- XOSTenantApp.setDirty(true);
- },
+ onChange: function(e) {
+ XOSTenantApp.setDirty(true);
+ },
- saveSuccess: function() {
- console.log("saveSuccess!");
- XOSTenantApp.setDirty(false);
- },
+ saveSuccess: function() {
+ XOSTenantApp.setDirty(false);
+ },
- });
+});
XOSTenantButtonView = Marionette.ItemView.extend({
- template: "#xos-tenant-buttons-template",
+ template: '#xos-tenant-buttons-template',
- events: {"click button.btn-tenant-create": "createClicked",
- "click button.btn-tenant-delete": "deleteClicked",
- "click button.btn-tenant-add-user": "addUserClicked",
- "click button.btn-tenant-save": "saveClicked",
- "click button.btn-tenant-download-ssh": "downloadClicked",
- },
+ events: {'click button.btn-tenant-create': 'createClicked',
+ 'click button.btn-tenant-delete': 'deleteClicked',
+ 'click button.btn-tenant-add-user': 'addUserClicked',
+ 'click button.btn-tenant-save': 'saveClicked',
+ 'click button.btn-tenant-download-ssh': 'downloadClicked',
+ },
- createClicked: function(e) {
- XOSTenantApp.addSlice();
- },
+ createClicked: function() {
+ XOSTenantApp.addSlice();
+ },
- deleteClicked: function(e) {
- XOSTenantApp.deleteSlice(this.options.linkedView.model);
- },
+ deleteClicked: function() {
+ XOSTenantApp.deleteSlice(this.options.linkedView.model);
+ },
- addUserClicked: function(e) {
- XOSTenantApp.editUsers(this.options.linkedView.model);
- },
+ addUserClicked: function() {
+ XOSTenantApp.editUsers(this.options.linkedView.model);
+ },
- downloadClicked: function(e) {
- XOSTenantApp.downloadSSH(this.options.linkedView.model);
- },
+ downloadClicked: function() {
+ XOSTenantApp.downloadSSH(this.options.linkedView.model);
+ },
- saveClicked: function(e) {
- model = this.options.linkedView.model;
- model.tenantSiteCollection.putToSlice(model);
- model.attributes.users = model.usersBuffer;
+ saveClicked: function(e) {
+ var model = this.options.linkedView.model;
- e.preventDefault();
- this.options.linkedView.save();
- //this.options.linkedView.submitContinueClicked.call(this.options.linkedView, e);
- //XOSTenantApp.setDirty(false);
- },
- });
+ model.tenantSiteCollection.putToSlice(model);
+ model.attributes.users = model.usersBuffer;
+
+ e.preventDefault();
+ this.options.linkedView.save();
+ //this.options.linkedView.submitContinueClicked.call(this.options.linkedView, e);
+ //XOSTenantApp.setDirty(false);
+ }
+});
XOSTenantApp = new XOSApplication({
- logTableId: "#logTable",
- statusMsgId: "#statusMsg",
- hideTabsByDefault: true,
- dirty: false,
- varName: "XOSTenantApp",
+ logTableId: '#logTable',
+ statusMsgId: '#statusMsg',
+ hideTabsByDefault: true,
+ dirty: false,
+ varName: 'XOSTenantApp',
});
XOSTenantApp.addRegions({
- tenantSliceSelector: "#tenantSliceSelector",
- tenantSummary: "#tenantSummary",
- tenantSiteList: "#tenantSiteList",
- tenantButtons: "#tenantButtons",
- tenantAddSliceInterior: "#tenant-addslice-interior",
- tenantEditUsersInterior: "#tenant-edit-users-interior",
- tenantSSHCommandsInterior: "#tenant-ssh-commands-interior",
+ tenantSliceSelector: '#tenantSliceSelector',
+ tenantSummary: '#tenantSummary',
+ tenantSiteList: '#tenantSiteList',
+ tenantButtons: '#tenantButtons',
+ tenantAddSliceInterior: '#tenant-addslice-interior',
+ tenantEditUsersInterior: '#tenant-edit-users-interior',
+ tenantSSHCommandsInterior: '#tenant-ssh-commands-interior',
});
XOSTenantApp.setDirty = function(dirty) {
- XOSTenantApp.dirty = dirty;
- if (dirty) {
- $("button.btn-tenant-save").addClass("btn-success");
- } else {
- $("button.btn-tenant-save").removeClass("btn-success");
- }
+ XOSTenantApp.dirty = dirty;
+ if (dirty) {
+ $('button.btn-tenant-save').addClass('btn-success');
+ }
+ else {
+ $('button.btn-tenant-save').removeClass('btn-success');
+ }
};
XOSTenantApp.buildViews = function() {
- XOSTenantApp.tenantSites = new XOSTenantSiteCollection();
+ XOSTenantApp.tenantSites = new XOSTenantSiteCollection();
- tenantSummaryClass = XOSTenantSummaryView.extend({template: "#xos-detail-template",
- app: XOSTenantApp,
- detailFields: ["serviceClass", "default_image", "default_flavor", "network_ports"],
- fieldDisplayNames: {serviceClass: "Service Level", "default_flavor": "Flavor", "default_image": "Image", "mount_data_sets": "Data Sets"},
- helpText: {"serviceClass": "Existing instances will be re-instantiated if changed",
- "default_image": "Existing instances will be re-instantiated if changed",
- "default_flavor": "Existing instances will be re-instantiated if changed"},
+ tenantSummaryClass = XOSTenantSummaryView.extend({
+ template: '#xos-detail-template',
+ app: XOSTenantApp,
+ detailFields: ['serviceClass', 'default_image', 'default_flavor', 'network_ports'],
+ fieldDisplayNames: {
+ serviceClass: 'Service Level',
+ default_flavor: 'Flavor',
+ default_image: 'Image',
+ mount_data_sets: 'Data Sets'
+ },
+ helpText: {
+ 'serviceClass': 'Existing instances will be re-instantiated if changed',
+ 'default_image': 'Existing instances will be re-instantiated if changed',
+ 'default_flavor': 'Existing instances will be re-instantiated if changed'
+ },
+ onShow: function() {
+ // the slice selector is in a different table, so make every label cell the maximal width
+ make_same_width('#xos-tenant-view-panel', '.xos-label-cell');
+ },
+ });
- onShow: function() {
- // the slice selector is in a different table, so make every label cell the maximal width
- make_same_width("#xos-tenant-view-panel", ".xos-label-cell");
- },
- });
+ XOSTenantApp.tenantSummaryView = tenantSummaryClass;
- XOSTenantApp.tenantSummaryView = tenantSummaryClass;
+ tenantAddClass = XOSDetailView.extend({
+ template: '#xos-detail-template',
+ app: XOSTenantApp,
+ detailFields: ['name', 'description']
+ });
- tenantAddClass = XOSDetailView.extend({template: "#xos-detail-template",
- app: XOSTenantApp,
- detailFields: ["name", "description"]});
+ XOSTenantApp.tenantAddView = tenantAddClass;
- XOSTenantApp.tenantAddView = tenantAddClass;
+ tenantSiteItemClass = XOSItemView.extend({
+ template: '#xos-listitem-template',
+ app: XOSTenantApp
+ });
- tenantSiteItemClass = XOSItemView.extend({template: "#xos-listitem-template",
- app: XOSTenantApp});
+ XOSTenantApp.tenantSiteItemView = tenantSiteItemClass;
- XOSTenantApp.tenantSiteItemView = tenantSiteItemClass;
+ tenantSiteListClass = XOSDataTableView.extend({
+ template: '#xos-list-template',
+ app: XOSTenantApp,
+ childView: tenantSiteItemClass,
+ collection: XOSTenantApp.tenantSites,
+ title: 'sites',
+ inputType: {allocated: 'spinner'},
+ noDeleteColumn: true,
+ disablePaginate: true,
+ disableFilter: true,
+ fieldDisplayNames: {name: 'Site'},
+ });
- tenantSiteListClass = XOSDataTableView.extend({template: "#xos-list-template",
- app: XOSTenantApp,
- childView: tenantSiteItemClass,
- collection: XOSTenantApp.tenantSites,
- title: "sites",
- inputType: {allocated: "spinner"},
- noDeleteColumn: true,
- disablePaginate: true,
- disableFilter: true,
- fieldDisplayNames: {"name": "Site"},
- });
+ XOSTenantApp.tenantSiteListView = tenantSiteListClass;
- XOSTenantApp.tenantSiteListView = tenantSiteListClass;
+ XOSTenantApp.tenantSliceSelectorView = SliceSelectorView.extend({
+ sliceChanged: function(id) {
+ XOSTenantApp.navToSlice(id);
+ },
+ filter: function(slice) {
+ return slice.attributes.current_user_can_see;
+ },
+ });
- XOSTenantApp.tenantSliceSelectorView = SliceSelectorView.extend( {
- sliceChanged: function(id) {
- XOSTenantApp.navToSlice(id);
- },
- filter: function(slice) {
- return slice.attributes.current_user_can_see;
- },
- });
-
- xos.sites.fetch();
- xos.slicesPlus.fetch();
- xos.tenantview.fetch();
+ xos.sites.fetch();
+ xos.slicesPlus.fetch();
+ xos.tenantview.fetch();
};
make_choices = function(list_of_names, list_of_values) {
- result = [];
- if (!list_of_values) {
- for (index in list_of_names) {
- displayName = list_of_names[index];
- result.push( [displayName, displayName] );
- }
- } else {
- for (index in list_of_names) {
- displayName = list_of_names[index];
- id = list_of_values[index];
- result.push( [displayName, id] );
- }
+ var result = [];
+ var displayName;
+
+ if (!list_of_values) {
+ for (var index in list_of_names) {
+ displayName = list_of_names[index];
+ result.push([displayName, displayName]);
}
- return result;
+ }
+ else {
+ for (var index in list_of_names) {
+ displayName = list_of_names[index];
+ id = list_of_values[index];
+ result.push([displayName, id]);
+ }
+ }
+ return result;
};
XOSTenantApp.navToSlice = function(id) {
- XOSTenantApp.viewSlice(xos.slicesPlus.get(id));
+ XOSTenantApp.viewSlice(xos.slicesPlus.get(id));
};
XOSTenantApp.adjustCollectionField = function(collectionName, id, fieldName, amount) {
- model = XOSTenantApp[collectionName].get(id);
- model.set(fieldName, Math.max(model.get(fieldName) + amount, 0));
- XOSTenantApp.setDirty(true);
+ model = XOSTenantApp[collectionName].get(id);
+ model.set(fieldName, Math.max(model.get(fieldName) + amount, 0));
+ XOSTenantApp.setDirty(true);
};
XOSTenantApp.addSlice = function() {
- var app=this;
+ var app = this;
- if (!xos.tenant().current_user_can_create_slice) {
- window.alert("You do not have sufficient rights to create a slice on your site");
- return;
+ if (!xos.tenant().current_user_can_create_slice) {
+ window.alert('You do not have sufficient rights to create a slice on your site');
+ return;
+ }
+
+ model = new xos.slicesPlus.model({
+ site: xos.tenant().current_user_site_id,
+ name: xos.tenant().current_user_login_base + '_',
+ creator: xos.tenant().current_user_id
+ });
+
+ var detailView = new XOSTenantApp.tenantAddView({
+ model: model,
+ collection: xos.slicesPlus,
+ noSubmitButton: true,
+ });
+
+ detailView.dialog = $('#tenant-addslice-dialog');
+ app.tenantAddSliceInterior.show(detailView);
+
+ $('#tenant-addslice-dialog').dialog({
+ autoOpen: false,
+ modal: true,
+ width: 640,
+ buttons: {
+ 'Create Slice': function() {
+ var addDialog = this;
+
+ detailView.synchronous = true;
+ detailView.afterSave = function() {
+ $(addDialog).dialog('close');
+ XOSTenantApp.navToSlice(detailView.model.id);
+ };
+ detailView.save();
+ },
+ 'Cancel': function() {
+ $(this).dialog('close');
+ }
}
-
- model = new xos.slicesPlus.model({site: xos.tenant().current_user_site_id,
- name: xos.tenant().current_user_login_base + "_",
- creator: xos.tenant().current_user_id});
- console.log(model);
- var detailView = new XOSTenantApp.tenantAddView({model: model,
- collection: xos.slicesPlus,
- noSubmitButton: true,
- });
- detailView.dialog = $("#tenant-addslice-dialog");
- app.tenantAddSliceInterior.show(detailView);
- $("#tenant-addslice-dialog").dialog({
- autoOpen: false,
- modal: true,
- width: 640,
- buttons : {
- "Create Slice" : function() {
- var addDialog = this;
- console.log("SAVE!!!");
- detailView.synchronous = true;
- detailView.afterSave = function() { $(addDialog).dialog("close"); XOSTenantApp.navToSlice(detailView.model.id); }
- detailView.save();
- },
- "Cancel" : function() {
- $(this).dialog("close");
- }
- }
- });
- $("#tenant-addslice-dialog").dialog("open");
+ });
+ $('#tenant-addslice-dialog').dialog('open');
};
XOSTenantApp.editUsers = function(model) {
- var app=this;
- var detailView = new XOSEditUsersView({model: model, collection: xos.slicesPlus});
- detailView.dialog = $("#tenant-edit-users-dialog");
- app.tenantEditUsersInterior.show(detailView);
- $("#tenant-edit-users-dialog").dialog({
- autoOpen: false,
- modal: true,
- width: 640,
- buttons : {
- "Ok" : function() {
- var editDialog = this;
- user_ids = all_options($("#tenant-edit-users-dialog").find(".select-picker-to"));
- user_ids = user_ids.map( function(x) { return parseInt(x,10); } );
- if (!array_same_elements(user_ids, model.usersBuffer)) {
- XOSTenantApp.setDirty(true);
- }
- model.usersBuffer = user_ids;
- $(editDialog).dialog("close");
- },
- "Cancel" : function() {
- $(this).dialog("close");
- }
- }
+ var app = this;
+ var detailView = new XOSEditUsersView({model: model, collection: xos.slicesPlus});
+
+ detailView.dialog = $('#tenant-edit-users-dialog');
+ app.tenantEditUsersInterior.show(detailView);
+
+ $('#tenant-edit-users-dialog').dialog({
+ autoOpen: false,
+ modal: true,
+ width: 640,
+ buttons: {
+ 'Ok': function() {
+ var editDialog = this;
+ var user_ids = all_options($('#tenant-edit-users-dialog').find('.select-picker-to'));
+
+ user_ids = user_ids.map(function(x) {
+ return parseInt(x,10);
});
- $("#tenant-edit-users-dialog").dialog("open");
+
+ if (!array_same_elements(user_ids, model.usersBuffer)) {
+ XOSTenantApp.setDirty(true);
+ }
+ model.usersBuffer = user_ids;
+ $(editDialog).dialog('close');
+ },
+ 'Cancel': function() {
+ $(this).dialog('close');
+ }
+ }
+ });
+ $('#tenant-edit-users-dialog').dialog('open');
};
XOSTenantApp.downloadSSH = function(model) {
- var sshCommands = "";
- for (index in model.attributes.sliceInfo.sshCommands) {
- sshCommand = model.attributes.sliceInfo.sshCommands[index];
- sshCommands = sshCommands + sshCommand + "\n";
+ var sshCommands = '';
+
+ for (index in model.attributes.sliceInfo.sshCommands) {
+ sshCommand = model.attributes.sliceInfo.sshCommands[index];
+ sshCommands = sshCommands + sshCommand + '\n';
+ }
+
+ if (sshCommands.length == 0) {
+ alert('this slice has no instantiated instances yet');
+ return;
+ }
+
+ var htmlView = new HTMLView({
+ html: '<pre style="overflow: auto; word-wrap: normal; white-space: pre; word-wrap: normal;">' +
+ sshCommands + '</pre>'
+ });
+
+ XOSTenantApp.tenantSSHCommandsInterior.show(htmlView);
+
+ $('#tenant-ssh-commands-dialog').dialog({
+ autoOpen: false,
+ modal: true,
+ width: 640,
+ buttons: {
+ 'Download': function() {
+ var dlLink = document.createElement('a');
+
+ dlLink.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(sshCommands));
+ dlLink.setAttribute('download', 'sshcommands.txt');
+ dlLink.click();
+
+ //window.open('data:text/text,' + encodeURIComponent(sshCommands));
+ },
+ 'Close': function() {
+ $(this).dialog('close');
+ },
}
-
- if (sshCommands.length == 0) {
- alert("this slice has no instantiated instances yet");
- return;
- }
-
- var htmlView = new HTMLView({html: '<pre style="overflow: auto; word-wrap: normal; white-space: pre; word-wrap: normal;">' + sshCommands + '</pre>'});
- XOSTenantApp.tenantSSHCommandsInterior.show(htmlView);
-
- $("#tenant-ssh-commands-dialog").dialog({
- autoOpen: false,
- modal: true,
- width: 640,
- buttons : {
- "Download": function() {
- var dlLink = document.createElement('a');
- dlLink.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(sshCommands));
- dlLink.setAttribute('download', 'sshcommands.txt');
- dlLink.click();
-
- //window.open('data:text/text,' + encodeURIComponent(sshCommands));
- },
- "Close" : function() {
- $(this).dialog("close");
- },
- }
- });
- $("#tenant-ssh-commands-dialog").dialog("open");
+ });
+ $('#tenant-ssh-commands-dialog').dialog('open');
};
XOSTenantApp.deleteSlice = function(model) {
- var app=this;
- app.deleteDialog(model, function() { console.log("afterDelete"); app.viewSlice(undefined); });
+ var app = this;
+
+ app.deleteDialog(model, function() {
+ app.viewSlice(undefined);
+ });
};
XOSTenantApp.viewSlice = function(model) {
- if (XOSTenantApp.dirty) {
- if (!confirm("The current instance has unsaved data -- view new instance anyway ?")) {
- $("#tenantSliceSelector select").val(XOSTenantApp.currentSlice.id);
- return;
- }
+ if (XOSTenantApp.dirty) {
+ if (!confirm('The current instance has unsaved data -- view new instance anyway ?')) {
+ $('#tenantSliceSelector select').val(XOSTenantApp.currentSlice.id);
+ return;
}
+ }
- XOSTenantApp.setDirty(false);
+ XOSTenantApp.setDirty(false);
- if (!model && xos.slicesPlus.models.length > 0) {
- model = xos.slicesPlus.models[0];
- }
+ if (!model && xos.slicesPlus.models.length > 0) {
+ model = xos.slicesPlus.models[0];
+ }
- if (model) {
- sliceSelector = new XOSTenantApp.tenantSliceSelectorView({collection: xos.slicesPlus,
- selectedID: model ? model.id : null,
- } );
- XOSTenantApp.sliceSelector = sliceSelector;
- XOSTenantApp.tenantSliceSelector.show(sliceSelector);
+ if (model) {
+ sliceSelector = new XOSTenantApp.tenantSliceSelectorView({
+ collection: xos.slicesPlus,
+ selectedID: model ? model.id : null,
+ });
- tenantSummary = new XOSTenantApp.tenantSummaryView({model: model,
- choices: {mount_data_sets: make_choices(xos.tenant().public_volume_names, null),
- serviceClass: make_choices(xos.tenant().blessed_service_class_names, xos.tenant().blessed_service_classes),
- default_image: make_choices(xos.tenant().blessed_image_names, xos.tenant().blessed_images),
- default_flavor: make_choices(xos.tenant().blessed_flavor_names, xos.tenant().blessed_flavors),},
- });
- XOSTenantApp.tenantSummary.show(tenantSummary);
+ XOSTenantApp.sliceSelector = sliceSelector;
+ XOSTenantApp.tenantSliceSelector.show(sliceSelector);
- tenantSites = new XOSTenantSiteCollection();
- tenantSites.getFromSlice(model);
- model.usersBuffer = model.attributes.users; /* save a copy of 'users' that we can edit. This prevents another view (developer) from overwriting our copy with a fetch from the server */
- model.usersOrig = model.attributes.users; /* save an immutable copy that we'll use for username lookups */
- model.user_namesOrig = model.attributes.user_names;
- model.tenantSiteCollection = tenantSites;
- XOSTenantApp.tenantSites = tenantSites;
+ tenantSummary = new XOSTenantApp.tenantSummaryView({
+ model: model,
+ choices: {
+ mount_data_sets: make_choices(xos.tenant().public_volume_names, null),
+ serviceClass: make_choices(xos.tenant().blessed_service_class_names, xos.tenant().blessed_service_classes),
+ default_image: make_choices(xos.tenant().blessed_image_names, xos.tenant().blessed_images),
+ default_flavor: make_choices(xos.tenant().blessed_flavor_names, xos.tenant().blessed_flavors)
+ },
+ });
- tenantSiteList = new XOSTenantApp.tenantSiteListView({collection: tenantSites });
- XOSTenantApp.tenantSiteList.show(tenantSiteList);
- // on xos.slicePlus.sort, need to update xostenantapp.tenantSites
+ XOSTenantApp.tenantSummary.show(tenantSummary);
- XOSTenantApp.tenantButtons.show( new XOSTenantButtonView( { app: XOSTenantApp,
- linkedView: tenantSummary } ) );
+ tenantSites = new XOSTenantSiteCollection();
+ tenantSites.getFromSlice(model);
+ model.usersBuffer = model.attributes.users; /* save a copy of 'users' that we can edit. This prevents another view (developer) from overwriting our copy with a fetch from the server */
+ model.usersOrig = model.attributes.users; /* save an immutable copy that we'll use for username lookups */
+ model.user_namesOrig = model.attributes.user_names;
+ model.tenantSiteCollection = tenantSites;
+ XOSTenantApp.tenantSites = tenantSites;
- XOSTenantApp.currentSlice = model;
- } else {
- XOSTenantApp.tenantSliceSelector.show(new HTMLView({html: ""}));
- XOSTenantApp.tenantSummary.show(new HTMLView({html: "You have no slices"}));
- XOSTenantApp.tenantSiteList.show(new HTMLView({html: ""}));
- XOSTenantApp.tenantButtons.show( new XOSTenantButtonView( { template: "#xos-tenant-buttons-noslice-template",
- app: XOSTenantApp,
- linkedView: tenantSummary } ) );
- }
+ tenantSiteList = new XOSTenantApp.tenantSiteListView({collection: tenantSites});
+ XOSTenantApp.tenantSiteList.show(tenantSiteList);
+ // on xos.slicePlus.sort, need to update xostenantapp.tenantSites
+
+ XOSTenantApp.tenantButtons.show(
+ new XOSTenantButtonView({
+ app: XOSTenantApp,
+ linkedView: tenantSummary
+ })
+ );
+
+ XOSTenantApp.currentSlice = model;
+ }
+ else {
+ XOSTenantApp.tenantSliceSelector.show(new HTMLView({html: ''}));
+ XOSTenantApp.tenantSummary.show(new HTMLView({html: 'You have no slices'}));
+ XOSTenantApp.tenantSiteList.show(new HTMLView({html: ''}));
+ XOSTenantApp.tenantButtons.show(
+ new XOSTenantButtonView({
+ template: '#xos-tenant-buttons-noslice-template',
+ app: XOSTenantApp,
+ linkedView: tenantSummary
+ })
+ );
+ }
};
XOSTenantApp.sanityCheck = function() {
- errors = [];
- if (xos.tenant().blessed_deployments && xos.tenant().blessed_deployments.length == 0) {
- errors.push("no blessed deployments");
- }
- if (xos.tenant().blessed_service_classes.length == 0) {
- errors.push("no blessed service classes");
- }
- if (xos.tenant().blessed_flavors.length == 0) {
- errors.push("no blessed flavors");
- }
- if (xos.tenant().blessed_images.length == 0) {
- errors.push("no blessed images");
- }
- if (xos.tenant().blessed_sites.length == 0) {
- errors.push("no blessed sites");
- }
- if (xos.tenant().current_user_site_id == null) {
- errors.push("current user does not have a site");
- }
+ errors = [];
+ if (xos.tenant().blessed_deployments && xos.tenant().blessed_deployments.length == 0) {
+ errors.push('no blessed deployments');
+ }
+ if (xos.tenant().blessed_service_classes.length == 0) {
+ errors.push('no blessed service classes');
+ }
+ if (xos.tenant().blessed_flavors.length == 0) {
+ errors.push('no blessed flavors');
+ }
+ if (xos.tenant().blessed_images.length == 0) {
+ errors.push('no blessed images');
+ }
+ if (xos.tenant().blessed_sites.length == 0) {
+ errors.push('no blessed sites');
+ }
+ if (xos.tenant().current_user_site_id == null) {
+ errors.push('current user does not have a site');
+ }
- if (errors.length > 0) {
- t = templateFromId("#tenant-sanity-check")
- $("#tenantSummary").html( t({errors: errors, blessed_deployment_names: xos.tenant().blessed_deployment_names}) );
- return false;
- }
+ if (errors.length > 0) {
+ t = templateFromId('#tenant-sanity-check');
+ $('#tenantSummary').html(t({
+ errors: errors,
+ blessed_deployment_names:
+ xos.tenant().blessed_deployment_names
+ }));
+ return false;
+ }
- return true;
-}
-
-XOSTenantApp.collectionLoadChange = function() {
- stats = xos.getCollectionStatus();
-
- if (!XOSTenantApp.navigationStarted) {
- if (stats["isLoaded"] + stats["failedLoad"] >= stats["startedLoad"]) {
- if (XOSTenantApp.sanityCheck()) {
- XOSTenantApp.viewSlice(undefined);
- }
- } else {
- $("#tenantSummary").html("<h3>Loading...</h3><div id='xos-startup-progress'></div>");
- $("#xos-startup-progress").progressbar({value: stats["completedLoad"], max: stats["startedLoad"]});
- }
- }
+ return true;
};
-XOSTenantApp.on("start", function() {
- XOSTenantApp.buildViews();
+XOSTenantApp.collectionLoadChange = function() {
+ stats = xos.getCollectionStatus();
- // fire it once to initially show the progress bar
- XOSTenantApp.collectionLoadChange();
+ if (!XOSTenantApp.navigationStarted) {
+ if (stats['isLoaded'] + stats['failedLoad'] >= stats['startedLoad']) {
+ if (XOSTenantApp.sanityCheck()) {
+ XOSTenantApp.viewSlice(undefined);
+ }
+ }
+ else {
+ $('#tenantSummary').html('<h3>Loading...</h3><div id="xos-startup-progress"></div>');
+ $('#xos-startup-progress').progressbar({value: stats['completedLoad'], max: stats['startedLoad']});
+ }
+ }
+};
- // fire it each time the collection load status is updated
- Backbone.on("xoslib:collectionLoadChange", XOSTenantApp.collectionLoadChange);
+XOSTenantApp.on('start', function() {
+ XOSTenantApp.buildViews();
+
+ // fire it once to initially show the progress bar
+ XOSTenantApp.collectionLoadChange();
+
+ // fire it each time the collection load status is updated
+ Backbone.on('xoslib:collectionLoadChange', XOSTenantApp.collectionLoadChange);
});
-$(document).ready(function(){
- XOSTenantApp.start();
+$(document).ready(function() {
+ XOSTenantApp.start();
});
/* eslint-enable */
diff --git a/xos/core/xoslib/static/js/xoslib/xos-backbone.js b/xos/core/xoslib/static/js/xoslib/xos-backbone.js
index 6fb6cc1..a677d88 100644
--- a/xos/core/xoslib/static/js/xoslib/xos-backbone.js
+++ b/xos/core/xoslib/static/js/xoslib/xos-backbone.js
@@ -1,5 +1,5 @@
-/* eslint-disable*/
+/* eslint-disable*/
if (! window.XOSLIB_LOADED) {
window.XOSLIB_LOADED=true;
@@ -134,6 +134,7 @@
xosValidate: function(attrs, options) {
errors = {};
foundErrors = false;
+
_.each(this.validators, function(validatorList, fieldName) {
_.each(validatorList, function(validator) {
if (fieldName in attrs) {
@@ -385,7 +386,50 @@
}
}
+ /**
+ * This is an helper function to define XOS model.
+ *
+ * @param {object} lib A backbone collection library (eg: xos)
+ * @param {object} attrs The model attributes
+ * @param {string} attrs.modelName The name of the model
+ * @param {string} attrs.urlRoot The base url for the collection
+ * @param {object} [attrs.relatedCollections] collections which should be drawn as an inline
+ list when the detail view is displayed.
+ Format: **collection:collectionFieldName**
+ where **collectionFieldName** is the name of the field
+ in the collection that points back to the collection
+ in the detail view.
+ * @param {array} [attrs.foreignCollections] collections which are used in idToName() calls
+ when presenting the data to the user. Used to
+ create a listento event. Somewhat
+ redundant with foreignFields.
+ * @param {object} [attrs.foreignFields] **localFieldName:collection**. Used to
+ automatically map ids into humanReadableNames
+ when presenting data to the user.
+ * @param {object} [attrs.m2mFields] **localFieldName:collection**. Used to
+ populate choices in picker lists. Similar to
+ foreignFields.
+ * @param {Array} [attrs.listFields] Fields to display in lists
+ * @param {Array} {attrs.detailFields} Fields to display in detail views
+ * @param {Array} [attrs.addFields] Fields to display in popup add windows
+ * @param {Object} [attrs.inputType] by default, "detailFields" will be displayed
+ as text input controls. This will let you display
+ a checkbox or a picker instead.
+ * @param {Object} [attrs.defaults] Override the model defaults, `extend_defaults` can be used.
+ * @param {Object} [attrs.validators] Add validators to this model. Use `validateField` method in xos-util.js
+ * @param {function} [attrs.preSave] A pre-save method
+ * @param {function} [attrs.xosValidate] Override the default xosValidate.
+ * If you want to call it either start the function with
+ * `errors = XOSModel.prototype.xosValidate.call(this, attrs, options);`
+ * @returns void
+ */
+
function define_model(lib, attrs) {
+
+ // NOTE shouldn't we trhow an error if no:
+ // - attrs.urlRoot
+ // - attrs.modelName
+
modelName = attrs.modelName;
modelClassName = modelName;
collectionClass = attrs.collectionClass || XOSCollection;
@@ -397,6 +441,8 @@
attrs.inputType = attrs.inputType || {};
attrs.foreignFields = attrs.foreignFields || {};
+ // NOTE m2mFields are not set in modelAttr,
+ // see list in for loop
attrs.m2mFields = attrs.m2mFields || {};
attrs.readOnlyFields = attrs.readOnlyFields || [];
attrs.detailLinkFields = attrs.detailLinkFields || ["id","name"];
@@ -415,7 +461,9 @@
modelAttrs[key] = value;
collectionAttrs[key] = value;
}
- if ($.inArray(key, ["validate", "preSave", "readOnlyFields"]) >= 0) {
+ // NOTE xosValidate added by Matteo Scandolo
+ // check with Scott
+ if ($.inArray(key, ["validate", "preSave", "readOnlyFields", "xosValidate"]) >= 0) {
modelAttrs[key] = value;
}
}
@@ -424,17 +472,18 @@
modelAttrs.defaults = get_defaults(modelName);
}
-// if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
-// modelAttrs["defaults"] = xosdefaults[modelName];
-// }
-
+ // if there are default validators for this model
+ // extend with customs
if ((typeof xosvalidators !== "undefined") && xosvalidators[modelName]) {
modelAttrs["validators"] = $.extend({}, xosvalidators[modelName], attrs["validators"] || {});
- } else if (attrs["validators"]) {
- modelAttrs["validators"] = attrs["validators"];
- console.log(attrs);
- console.log(modelAttrs);
}
+ // else use custom
+ else if (attrs["validators"]) {
+ modelAttrs["validators"] = attrs["validators"];
+ // console.log(attrs);
+ // console.log(modelAttrs);
+ }
+ // NOTE Why define validators in multiple places?
lib[modelName] = XOSModel.extend(modelAttrs);
@@ -737,14 +786,15 @@
xosValidate: function(attrs, options) {
errors = XOSModel.prototype.xosValidate.call(this, attrs, options);
// validate that slice.name starts with site.login_base
- site = attrs.site || this.site;
+
+ var site = xos.tenant().current_user_login_base;
if ((site!=undefined) && (attrs.name!=undefined)) {
- site = xos.sites.get(site);
- if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
+ if (attrs.name.indexOf(site + "_") < 0) {
errors = errors || {};
- errors["name"] = "must start with " + site.attributes.login_base + "_";
+ errors["name"] = "must start with " + site + "_";
}
}
+
return errors;
},
});
diff --git a/xos/core/xoslib/static/js/xoslib/xos-defaults.js b/xos/core/xoslib/static/js/xoslib/xos-defaults.js
index 0f4ab27..06ee0d9 100644
--- a/xos/core/xoslib/static/js/xoslib/xos-defaults.js
+++ b/xos/core/xoslib/static/js/xoslib/xos-defaults.js
@@ -1,5 +1,6 @@
/* eslint-disable quotes, no-undef, max-len, new-cap*/
/* eslint indent: [2, 2]*/
+console.warn('**** XOS DEFAULT ****');
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};
diff --git a/xos/core/xoslib/static/js/xoslib/xos-validators.js b/xos/core/xoslib/static/js/xoslib/xos-validators.js
index ecee144..f762ef2 100644
--- a/xos/core/xoslib/static/js/xoslib/xos-validators.js
+++ b/xos/core/xoslib/static/js/xoslib/xos-validators.js
@@ -1,3 +1,9 @@
+/**
+* List of model validator
+*
+* @constructor
+*/
+
/* eslint-disable quotes, no-undef, max-len, new-cap*/
/* eslint indent: [2, 2]*/
function xos_get_validators() {
diff --git a/xos/core/xoslib/static/js/xoslib/xosHelper.js b/xos/core/xoslib/static/js/xoslib/xosHelper.js
index cad242f..16046ae 100644
--- a/xos/core/xoslib/static/js/xoslib/xosHelper.js
+++ b/xos/core/xoslib/static/js/xoslib/xosHelper.js
@@ -573,7 +573,9 @@
model.save, we call it ourselves, so we can throw up our
validation error before creating the infoMsg in the log
*/
+
errors = this.model.xosValidate(data);
+
if (errors) {
this.onFormDataInvalid(errors);
return;