1 if (! window.XOSLIB_LOADED ) {
2 window.XOSLIB_LOADED=true;
4 SLIVER_API = "/plstackapi/slivers/";
5 SLICE_API = "/plstackapi/slices/";
6 SLICEROLE_API = "/plstackapi/slice_roles/";
7 NODE_API = "/plstackapi/nodes/";
8 SITE_API = "/plstackapi/sites/";
9 SITEDEPLOYMENT_API = "/plstackapi/sitedeployments/";
10 USER_API = "/plstackapi/users/";
11 USERDEPLOYMENT_API = "/plstackapi/user_deployments/";
12 DEPLOYMENT_API = "/plstackapi/deployments/";
13 IMAGE_API = "/plstackapi/images/";
14 IMAGEDEPLOYMENTS_API = "/plstackapi/imagedeployments/";
15 NETWORKTEMPLATE_API = "/plstackapi/networktemplates/";
16 NETWORK_API = "/plstackapi/networks/";
17 NETWORKSLIVER_API = "/plstackapi/networkslivers/";
18 SERVICE_API = "/plstackapi/services/";
19 SLICEPRIVILEGE_API = "/plstackapi/slice_privileges/";
20 NETWORKDEPLOYMENT_API = "/plstackapi/networkdeployments/";
21 FLAVOR_API = "/plstackapi/flavors/";
22 CONTROLLER_API = "/plstackapi/controllers/";
25 CONTROLLERSITEDEPLOYMENT_API = "/plstackapi/controllersitedeploymentses";
28 /* changed as a side effect of the big rename
29 SLICEDEPLOYMENT_API = "/plstackapi/slice_deployments/";
30 USERDEPLOYMENT_API = "/plstackapi/user_deployments/";
33 SLICEDEPLOYMENT_API = "/plstackapi/slicedeployments/";
34 USERDEPLOYMENT_API = "/plstackapi/userdeployments/";
36 SLICEPLUS_API = "/xoslib/slicesplus/";
37 TENANTVIEW_API = "/xoslib/tenantview/"
39 XOSModel = Backbone.Model.extend({
40 relatedCollections: [],
41 foreignCollections: [],
47 /* from backbone-tastypie.js */
48 //idAttribute: 'resource_uri',
50 /* from backbone-tastypie.js */
52 var url = this.attributes.resource_uri;
56 url = this.urlRoot + this.id;
58 // this happens when creating a new model.
64 // XXX I'm not sure this does anything useful
65 url = ( _.isFunction( this.collection.url ) ? this.collection.url() : this.collection.url );
66 url = url || this.urlRoot;
69 // remove any existing query parameters
70 url && ( url.indexOf("?") > -1 ) && ( url = url.split("?")[0] );
72 url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
74 url && ( url += "?no_hyperlinks=1" );
79 listMethods: function() {
81 for(var m in this) {
\r
82 if(typeof this[m] == "function") {
\r
89 save: function(attributes, options) {
93 return Backbone.Model.prototype.save.call(this, attributes, options);
96 getChoices: function(fieldName, excludeChosen) {
98 if (fieldName in this.m2mFields) {
99 for (index in xos[this.m2mFields[fieldName]].models) {
100 candidate = xos[this.m2mFields[fieldName]].models[index];
101 if (excludeChosen && idInArray(candidate.id, this.attributes[fieldName])) {
104 choices.push(candidate.id);
110 /* If a 'validate' method is supplied, then it will be called
111 automatically on save. Unfortunately, save calls neither the
112 'error' nor the 'success' callback if the validator fails.
114 For now, we're calling our validator 'xosValidate' so this
115 autoamtic validation doesn't occur.
118 xosValidate: function(attrs, options) {
121 _.each(this.validators, function(validatorList, fieldName) {
122 _.each(validatorList, function(validator) {
123 if (fieldName in attrs) {
124 validatorResult = validateField(validator, attrs[fieldName], this)
125 if (validatorResult != true) {
126 errors[fieldName] = validatorResult;
135 // backbone.js semantics -- on successful validate, return nothing
138 /* uncommenting this would make validate() call xosValidate()
139 validate: function(attrs, options) {
140 r = this.xosValidate(attrs, options);
141 console.log("validate");
147 XOSCollection = Backbone.Collection.extend({
148 objects: function() {
149 return this.models.map(function(element) { return element.attributes; });
152 initialize: function(){
153 this.isLoaded = false;
154 this.failedLoad = false;
155 this.startedLoad = false;
156 this.sortVar = 'name';
\r
157 this.sortOrder = 'asc';
\r
158 this.on( "sort", this.sorted );
\r
161 relatedCollections: [],
\r
162 foreignCollections: [],
\r
166 detailLinkFields: [],
\r
168 sorted: function() {
\r
169 //console.log("sorted " + this.modelName);
\r
172 simpleComparator: function( model ){
\r
173 parts=this.sortVar.split(".");
\r
174 result = model.get(parts[0]);
\r
175 for (index=1; index<parts.length; ++index) {
\r
176 result=result[parts[index]];
\r
181 comparator: function (left, right) {
\r
182 var l = this.simpleComparator(left);
\r
183 var r = this.simpleComparator(right);
\r
185 if (l === void 0) return -1;
\r
186 if (r === void 0) return 1;
\r
188 if (this.sortOrder=="desc") {
\r
189 return l < r ? 1 : l > r ? -1 : 0;
\r
191 return l < r ? -1 : l > r ? 1 : 0;
\r
195 fetchSuccess: function(collection, response, options) {
\r
196 //console.log("fetch succeeded " + collection.modelName);
\r
197 this.failedLoad = false;
\r
198 this.fetching = false;
\r
199 if (!this.isLoaded) {
\r
200 this.isLoaded = true;
\r
201 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
203 this.trigger("fetchStateChange");
\r
204 if (options["orig_success"]) {
\r
205 options["orig_success"](collection, response, options);
\r
209 fetchFailure: function(collection, response, options) {
\r
210 //console.log("fetch failed " + collection.modelName);
\r
211 this.fetching = false;
\r
212 if ((!this.isLoaded) && (!this.failedLoad)) {
\r
213 this.failedLoad=true;
\r
214 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
216 this.trigger("fetchStateChange");
\r
217 if (options["orig_failure"]) {
\r
218 options["orig_failure"](collection, response, options);
\r
222 fetch: function(options) {
\r
224 this.fetching=true;
\r
225 //console.log("fetch " + this.modelName);
\r
226 if (!this.startedLoad) {
\r
227 this.startedLoad=true;
\r
228 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
230 this.trigger("fetchStateChange");
\r
231 if (options == undefined) {
\r
234 options["orig_success"] = options["success"];
\r
235 options["orig_failure"] = options["failure"];
\r
236 options["success"] = function(collection, response, options) { self.fetchSuccess.call(self, collection, response, options); };
\r
237 options["failure"] = this.fetchFailure;
\r
238 Backbone.Collection.prototype.fetch.call(this, options);
\r
241 startPolling: function() {
\r
242 if (!this._polling) {
\r
244 setInterval(function() { collection.fetch(); }, 10000);
250 refresh: function(refreshRelated) {
251 if (!this.fetching) {
254 if (refreshRelated) {
255 for (related in this.relatedCollections) {
256 related = xos[related];
257 if (!related.fetching) {
264 maybeFetch: function(options){
265 // Helper function to fetch only if this collection has not been fetched before.
267 // If this has already been fetched, call the success, if it exists
268 options.success && options.success();
269 console.log("alreadyFetched");
273 // when the original success function completes mark this collection as fetched
275 successWrapper = function(success){
277 self._fetched = true;
278 success && success.apply(this, arguments);
281 options.success = successWrapper(options.success);
282 console.log("call fetch");
286 getOrFetch: function(id, options){
287 // Helper function to use this collection as a cache for models on the server
288 var model = this.get(id);
291 options.success && options.success(model);
295 model = new this.model({
299 model.fetch(options);
302 /* filterBy: note that this yields a new collection. If you pass that
303 collection to a CompositeView, then the CompositeView won't get
304 any events that trigger on the original collection.
306 Using this function is probably wrong, and I wrote
307 FilteredCompositeView() to replace it.
310 filterBy: function(fieldName, value) {
311 filtered = this.filter(function(obj) {
312 return obj.get(fieldName) == value;
314 return new this.constructor(filtered);
317 /* from backbone-tastypie.js */
318 url: function( models ) {
319 var url = this.urlRoot || ( models && models.length && models[0].urlRoot );
320 url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
322 // Build a url to retrieve a set of models. This assume the last part of each model's idAttribute
323 // (set to 'resource_uri') contains the model's id.
324 if ( models && models.length ) {
325 var ids = _.map( models, function( model ) {
326 var parts = _.compact( model.id.split('/') );
327 return parts[ parts.length - 1 ];
329 url += 'set/' + ids.join(';') + '/';
332 url && ( url += "?no_hyperlinks=1" );
337 listMethods: function() {
339 for(var m in this) {
\r
340 if(typeof this[m] == "function") {
\r
348 function get_defaults(modelName) {
349 if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
350 return xosdefaults[modelName];
355 function extend_defaults(modelName, stuff) {
356 defaults = get_defaults(modelName);
358 return $.extend({}, defaults, stuff);
364 function define_model(lib, attrs) {
365 modelName = attrs.modelName;
366 modelClassName = modelName;
367 collectionClassName = modelName + "Collection";
369 if (!attrs.addFields) {
370 attrs.addFields = attrs.detailFields;
373 attrs.inputType = attrs.inputType || {};
374 attrs.foreignFields = attrs.foreignFields || {};
375 attrs.m2mFields = attrs.m2mFields || {};
376 attrs.readOnlyFields = attrs.readOnlyFields || [];
377 attrs.detailLinkFields = attrs.detailLinkFields || ["id","name"];
379 if (!attrs.collectionName) {
380 attrs.collectionName = modelName + "s";
382 collectionName = attrs.collectionName;
389 if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "listFields", "addFields", "detailFields", "detailLinkFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections", "defaults"])>=0) {
390 modelAttrs[key] = value;
391 collectionAttrs[key] = value;
393 if ($.inArray(key, ["validate", "preSave", "readOnlyFields"])) {
394 modelAttrs[key] = value;
398 if (!modelAttrs.defaults) {
399 modelAttrs.defaults = get_defaults(modelName);
401 console.log(modelName);
402 console.log(modelAttrs);
404 // if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
405 // modelAttrs["defaults"] = xosdefaults[modelName];
408 if ((typeof xosvalidators !== "undefined") && xosvalidators[modelName]) {
409 modelAttrs["validators"] = xosvalidators[modelName];
412 lib[modelName] = XOSModel.extend(modelAttrs);
414 collectionAttrs["model"] = lib[modelName];
416 lib[collectionClassName] = XOSCollection.extend(collectionAttrs);
417 lib[collectionName] = new lib[collectionClassName]();
419 lib.allCollectionNames.push(collectionName);
420 lib.allCollections.push(lib[collectionName]);
424 this.allCollectionNames = [];
425 this.allCollections = [];
427 /* Give an id, the name of a collection, and the name of a field for models
428 within that collection, lookup the id and return the value of the field.
431 this.idToName = function(id, collectionName, fieldName) {
432 linkedObject = xos[collectionName].get(id);
433 if (linkedObject == undefined) {
436 return linkedObject.attributes[fieldName];
440 /* defining the models
442 modelName - name of the model.
444 relatedCollections - collections which should be drawn as an inline
445 list when the detail view is displayed.
446 Format: <collection>:<collectionFieldName> where
447 <collectionFieldName> is the name of the field
448 in the collection that points back to the
449 collection in the detail view.
451 foreignCollections - collections which are used in idToName() calls
452 when presenting the data to the user. Used to
453 create a listento event. Somewhat
454 redundant with foreignFields.
456 foreignFields - <localFieldName>:<collection>. Used to
457 automatically map ids into humanReadableNames
458 when presenting data to the user.
460 m2mfields - <localFieldName>:<colleciton>. Used to
461 populate choices in picker lists. Simalar to
464 listFields - fields to display in lists
466 detailFields - fields to display in detail views
468 addFields - fields to display in popup add windows
470 inputType - by default, "detailFields" will be displayed
471 as text input controls. This will let you display
472 a checkbox or a picker instead.
475 define_model(this, {urlRoot: SLIVER_API,
476 relatedCollections: {"networkSlivers": "sliver"},
477 foreignCollections: ["slices", "deployments", "images", "nodes", "users", "flavors"],
478 foreignFields: {"creator": "users", "image": "images", "node": "nodes", "deploymentNetwork": "deployments", "slice": "slices", "flavor": "flavors"},
480 listFields: ["backend_status", "id", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "image", "node", "flavor"],
481 addFields: ["slice", "deploymentNetwork", "flavor", "image", "node"],
482 detailFields: ["backend_status", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "flavor", "image", "node", "creator"],
483 preSave: function() { if (!this.attributes.name && this.attributes.slice) { this.attributes.name = xos.idToName(this.attributes.slice, "slices", "name"); } },
486 define_model(this, {urlRoot: SLICE_API,
487 relatedCollections: {"slivers": "slice", "slicePrivileges": "slice", "networks": "owner"},
488 foreignCollections: ["services", "sites"],
489 foreignFields: {"service": "services", "site": "sites"},
490 listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
491 detailFields: ["backend_status", "name", "site", "enabled", "description", "slice_url", "max_slivers"],
492 inputType: {"enabled": "checkbox"},
494 xosValidate: function(attrs, options) {
495 errors = XOSModel.prototype.xosValidate(this, attrs, options);
496 // validate that slice.name starts with site.login_base
497 site = attrs.site || this.site;
498 if ((site!=undefined) && (attrs.name!=undefined)) {
499 site = xos.sites.get(site);
500 if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
501 errors = errors || {};
502 errors["name"] = "must start with " + site.attributes.login_base + "_";
509 define_model(this, {urlRoot: SLICEPRIVILEGE_API,
510 foreignCollections: ["slices", "users", "sliceRoles"],
511 modelName: "slicePrivilege",
512 foreignFields: {"user": "users", "slice": "slices", "role": "sliceRoles"},
513 listFields: ["backend_status", "id", "user", "slice", "role"],
514 detailFields: ["backend_status", "user", "slice", "role"],
517 define_model(this, {urlRoot: SLICEROLE_API,
518 modelName: "sliceRole",
519 listFields: ["backend_status", "id", "role"],
520 detailFields: ["backend_status", "role"],
523 define_model(this, {urlRoot: NODE_API,
524 foreignCollections: ["sites", "deployments"],
526 foreignFields: {"site": "sites", "deployment": "deployments"},
527 listFields: ["backend_status", "id", "name", "site", "deployment"],
528 detailFields: ["backend_status", "name", "site", "deployment"],
531 define_model(this, {urlRoot: SITE_API,
532 relatedCollections: {"users": "site", "slices": "site", "nodes": "site", "siteDeployments": "site"},
534 listFields: ["backend_status", "id", "name", "site_url", "enabled", "login_base", "is_public", "abbreviated_name"],
535 detailFields: ["backend_status", "name", "abbreviated_name", "url", "enabled", "is_public", "login_base"],
536 inputType: {"enabled": "checkbox", "is_public": "checkbox"},
539 define_model(this, {urlRoot: SITEDEPLOYMENT_API,
540 foreignCollections: ["sites", "deployments", "controllers"],
541 foreignFields: {"site": "sites", "deployment": "deployments", "controller": "controllers"},
542 modelName: "siteDeployment",
543 listFields: ["backend_status", "id", "site", "deployment", "controller", "availability_zone"],
544 detailFields: ["backend_status", "site", "deployment", "controller", "availability_zone"],
545 inputType: {"enabled": "checkbox", "is_public": "checkbox"},
548 define_model(this, {urlRoot: USER_API,
549 relatedCollections: {"slicePrivileges": "user", "slices": "owner"},
550 foreignCollections: ["sites"],
552 foreignFields: {"site": "sites"},
553 listFields: ["backend_status", "id", "username", "firstname", "lastname", "phone", "user_url", "site"],
554 detailFields: ["backend_status", "username", "firstname", "lastname", "phone", "user_url", "site"],
557 define_model(this, { urlRoot: DEPLOYMENT_API,
558 relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork"},
559 m2mFields: {"flavors": "flavors", "sites": "sites", "images": "images"},
560 modelName: "deployment",
561 listFields: ["backend_status", "id", "name", "backend_type", "admin_tenant"],
562 detailFields: ["backend_status", "name", "backend_type", "admin_tenant", "flavors", "sites", "images"],
563 inputType: {"flavors": "picker", "sites": "picker", "images": "picker"},
566 define_model(this, {urlRoot: IMAGE_API,
569 listFields: ["backend_status", "id", "name", "disk_format", "container_format", "path"],
570 detailFields: ["backend_status", "name", "disk_format", "admin_tenant"],
573 define_model(this, {urlRoot: NETWORKTEMPLATE_API,
574 modelName: "networkTemplate",
575 listFields: ["backend_status", "id", "name", "visibility", "translation", "shared_network_name", "shared_network_id"],
576 detailFields: ["backend_status", "name", "description", "visibility", "translation", "shared_network_name", "shared_network_id"],
579 define_model(this, {urlRoot: NETWORK_API,
580 relatedCollections: {"networkSlivers": "network"},
581 foreignCollections: ["slices", "networkTemplates"],
582 modelName: "network",
583 foreignFields: {"template": "networkTemplates", "owner": "slices"},
584 listFields: ["backend_status", "id", "name", "template", "ports", "labels", "owner"],
585 detailFields: ["backend_status", "name", "template", "ports", "labels", "owner"],
588 define_model(this, {urlRoot: NETWORKSLIVER_API,
589 modelName: "networkSliver",
590 foreignFields: {"network": "networks", "sliver": "slivers"},
591 listFields: ["backend_status", "id", "network", "sliver", "ip", "port_id"],
592 detailFields: ["backend_status", "network", "sliver", "ip", "port_id"],
595 define_model(this, {urlRoot: SERVICE_API,
596 modelName: "service",
597 listFields: ["backend_status", "id", "name", "enabled", "versionNumber", "published"],
598 detailFields: ["backend_status", "name", "description", "versionNumber"],
601 define_model(this, {urlRoot: FLAVOR_API,
603 m2mFields: {"deployments": "deployments"},
604 listFields: ["backend_status", "id", "name", "flavor", "order", "default"],
605 detailFields: ["backend_status", "name", "description", "flavor", "order", "default", "deployments"],
606 inputType: {"default": "checkbox", "deployments": "picker"},
609 define_model(this, {urlRoot: CONTROLLER_API,
610 modelName: "controller",
611 listFields: ["backend_status", "id", "name", "version", "backend_type"],
612 detailFields: ["backend_status", "name", "version", "backend_type", "auth_url", "admin_user", "admin_password", "admin_tenant"],
616 define_model(this, {urlRoot: CONTROLLERSITEDEPLOYMENT_API,
617 modelName: "controllerSiteDeployment",
618 foreignCollections: ["site_deployments", "controllers"],
619 foreignFields: {"site_deployment": "siteDeployments", "controller": "controllers"},
620 listFields: ["backend_status", "id", "site_deployment", "controller", "tenant_id"],
621 detailFields: ["backend_status", "site_deployment", "controller", "tenant_id"],
625 /* DELETED in site-controller branch
627 define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
628 modelName: "networkDeployment",
629 foreignFields: {"network": "networks", "deployment": "deployments"},
630 listFields: ["backend_status", "id", "network", "deployment", "net_id"],
631 detailFields: ["backend_status", "network", "deployment", "net_id"],
634 define_model(this, {urlRoot: SLICEDEPLOYMENT_API,
635 foreignCollections: ["slices", "deployments"],
636 modelName: "sliceDeployment",
637 foreignFields: {"slice": "slices", "deployment": "deployments"},
638 listFields: ["backend_status", "id", "slice", "deployment", "tenant_id"],
639 detailFields: ["backend_status", "slice", "deployment", "tenant_id"],
642 define_model(this, {urlRoot: USERDEPLOYMENT_API,
643 foreignCollections: ["users","deployments"],
644 modelName: "userDeployment",
645 foreignFields: {"deployment": "deployments", "user": "users"},
646 listFields: ["backend_status", "id", "user", "deployment", "kuser_id"],
647 detailFields: ["backend_status", "user", "deployment", "kuser_id"],
650 END stuff deleted in site-controller branch */
652 /* not deleted, but obsolete since it has degenerated to a ManyToMany with no other fields
654 define_model(this, {urlRoot: IMAGEDEPLOYMENTS_API,
655 modelName: "imageDeployment",
656 foreignCollections: ["images", "deployments"],
657 listFields: ["backend_status", "id", "image", "deployment", "glance_image_id"],
658 detailFields: ["backend_status", "image", "deployment", "glance_image_id"],
664 // XXX this really needs to somehow be combined with Slice, to avoid duplication
665 define_model(this, {urlRoot: SLICEPLUS_API,
666 relatedCollections: {"slivers": "slice", "slicePrivileges": "slice", "networks": "owner"},
667 foreignCollections: ["services", "sites"],
668 foreignFields: {"service": "services", "site": "sites"},
669 listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
670 detailFields: ["backend_status", "name", "site", "enabled", "description", "slice_url", "max_slivers"],
671 inputType: {"enabled": "checkbox"},
672 modelName: "slicePlus",
673 collectionName: "slicesPlus",
674 defaults: extend_defaults("slice", {"network_ports": "", "site_allocation": []}),
675 xosValidate: function(attrs, options) {
676 errors = XOSModel.prototype.xosValidate(this, attrs, options);
677 // validate that slice.name starts with site.login_base
678 site = attrs.site || this.site;
679 if ((site!=undefined) && (attrs.name!=undefined)) {
680 site = xos.sites.get(site);
681 if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
682 errors = errors || {};
683 errors["name"] = "must start with " + site.attributes.login_base + "_";
690 define_model(this, {urlRoot: TENANTVIEW_API,
691 modelName: "tenantview",
692 collectionName: "tenantview",
697 this.listObjects = function() { return this.allCollectionNames; };
699 this.getCollectionStatus = function() {
700 stats = {isLoaded: 0, failedLoad: 0, startedLoad: 0};
701 for (index in this.allCollections) {
702 collection = this.allCollections[index];
703 if (collection.isLoaded) {
704 stats["isLoaded"] = stats["isLoaded"] + 1;
706 if (collection.failedLoad) {
707 stats["failedLoad"] = stats["failedLoad"] + 1;
709 if (collection.startedLoad) {
710 stats["startedLoad"] = stats["startedLoad"] + 1;
713 stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
720 function getCookie(name) {
721 var cookieValue = null;
\r
722 if (document.cookie && document.cookie != '') {
\r
723 var cookies = document.cookie.split(';');
\r
724 for (var i = 0; i < cookies.length; i++) {
\r
725 var cookie = jQuery.trim(cookies[i]);
\r
726 // Does this cookie string begin with the name we want?
\r
727 if (cookie.substring(0, name.length + 1) == (name + '=')) {
\r
728 cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
\r
733 return cookieValue;
\r
737 var _sync = Backbone.sync;
\r
738 Backbone.sync = function(method, model, options){
\r
739 options.beforeSend = function(xhr){
\r
740 var token = getCookie("csrftoken");
\r
741 xhr.setRequestHeader('X-CSRFToken', token);
\r
743 return _sync(method, model, options);
\r