47ea66a4f00a6036546db6c4b18106fae86b04d0
[plstackapi.git] / planetstack / core / xoslib / static / js / xoslib / xos-backbone.js
1 if (! window.XOSLIB_LOADED ) {
2     window.XOSLIB_LOADED=true;
3
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/";
23
24     /* removed
25     CONTROLLERSITEDEPLOYMENT_API = "/plstackapi/controllersitedeploymentses";
26     */
27
28     /* changed as a side effect of the big rename
29     SLICEDEPLOYMENT_API = "/plstackapi/slice_deployments/";
30     USERDEPLOYMENT_API = "/plstackapi/user_deployments/";
31     */
32
33     SLICEDEPLOYMENT_API = "/plstackapi/slicedeployments/";
34     USERDEPLOYMENT_API = "/plstackapi/userdeployments/";
35
36     SLICEPLUS_API = "/xoslib/slicesplus/";
37
38     XOSModel = Backbone.Model.extend({
39         /* from backbone-tastypie.js */
40         //idAttribute: 'resource_uri',
41
42         /* from backbone-tastypie.js */
43         url: function() {
44                     var url = this.attributes.resource_uri;
45
46                     if (!url) {
47                         if (this.id) {
48                             url = this.urlRoot + this.id;
49                         } else {
50                             // this happens when creating a new model.
51                             url = this.urlRoot;
52                         }
53                     }
54
55                     if (!url) {
56                         // XXX I'm not sure this does anything useful
57                         url = ( _.isFunction( this.collection.url ) ? this.collection.url() : this.collection.url );
58                         url = url || this.urlRoot;
59                     }
60
61                     // remove any existing query parameters
62                     url && ( url.indexOf("?") > -1 ) && ( url = url.split("?")[0] );
63
64                     url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
65
66                     url && ( url += "?no_hyperlinks=1" );
67
68                     return url;
69             },
70
71             listMethods: function() {
72                 var res = [];\r
73                 for(var m in this) {\r
74                     if(typeof this[m] == "function") {\r
75                         res.push(m)\r
76                     }\r
77                 }\r
78                 return res;\r
79             },
80
81             save: function(attributes, options) {
82                 if (this.preSave) {
83                     this.preSave();
84                 }
85                 return Backbone.Model.prototype.save.call(this, attributes, options);
86             },
87
88             getChoices: function(fieldName, excludeChosen) {
89                 choices=[];
90                 if (fieldName in this.m2mFields) {
91                     for (index in xos[this.m2mFields[fieldName]].models) {
92                         candidate = xos[this.m2mFields[fieldName]].models[index];
93                         if (excludeChosen && idInArray(candidate.id, this.attributes[fieldName])) {
94                             continue;
95                         }
96                         choices.push(candidate.id);
97                     }
98                 }
99                 return choices;
100             },
101
102             /* If a 'validate' method is supplied, then it will be called
103                automatically on save. Unfortunately, save calls neither the
104                'error' nor the 'success' callback if the validator fails.
105
106                For now, we're calling our validator 'xosValidate' so this
107                autoamtic validation doesn't occur.
108             */
109
110             xosValidate: function(attrs, options) {
111                 errors = {};
112                 foundErrors = false;
113                 _.each(this.validators, function(validatorList, fieldName) {
114                     _.each(validatorList, function(validator) {
115                         if (fieldName in attrs) {
116                             validatorResult = validateField(validator, attrs[fieldName], this)
117                             if (validatorResult != true) {
118                                 errors[fieldName] = validatorResult;
119                                 foundErrors = true;
120                             }
121                         }
122                     });
123                 });
124                 if (foundErrors) {
125                     return errors;
126                 }
127                 // backbone.js semantics -- on successful validate, return nothing
128             },
129
130             /* uncommenting this would make validate() call xosValidate()
131             validate: function(attrs, options) {
132                 r = this.xosValidate(attrs, options);
133                 console.log("validate");
134                 console.log(r);
135                 return r;
136             }, */
137     });
138
139     XOSCollection = Backbone.Collection.extend({
140         objects: function() {
141                     return this.models.map(function(element) { return element.attributes; });
142                  },
143
144         initialize: function(){
145           this.isLoaded = false;
146           this.failedLoad = false;
147           this.startedLoad = false;
148           this.sortVar = 'name';\r
149           this.sortOrder = 'asc';\r
150           this.on( "sort", this.sorted );\r
151         },\r
152 \r
153         relatedCollections: [],\r
154         foreignCollections: [],\r
155 \r
156         sorted: function() {\r
157             //console.log("sorted " + this.modelName);\r
158         },\r
159 \r
160         simpleComparator: function( model ){\r
161           parts=this.sortVar.split(".");\r
162           result = model.get(parts[0]);\r
163           for (index=1; index<parts.length; ++index) {\r
164               result=result[parts[index]];\r
165           }\r
166           return result;\r
167         },\r
168 \r
169         comparator: function (left, right) {\r
170             var l = this.simpleComparator(left);\r
171             var r = this.simpleComparator(right);\r
172 \r
173             if (l === void 0) return -1;\r
174             if (r === void 0) return 1;\r
175 \r
176             if (this.sortOrder=="desc") {\r
177                 return l < r ? 1 : l > r ? -1 : 0;\r
178             } else {\r
179                 return l < r ? -1 : l > r ? 1 : 0;\r
180             }\r
181         },\r
182 \r
183         fetchSuccess: function(collection, response, options) {\r
184             //console.log("fetch succeeded " + collection.modelName);\r
185             this.failedLoad = false;\r
186             this.fetching = false;\r
187             if (!this.isLoaded) {\r
188                 this.isLoaded = true;\r
189                 Backbone.trigger("xoslib:collectionLoadChange", this);\r
190             }\r
191             this.trigger("fetchStateChange");\r
192             if (options["orig_success"]) {\r
193                 options["orig_success"](collection, response, options);\r
194             }\r
195         },\r
196 \r
197         fetchFailure: function(collection, response, options) {\r
198             //console.log("fetch failed " + collection.modelName);\r
199             this.fetching = false;\r
200             if ((!this.isLoaded) && (!this.failedLoad)) {\r
201                 this.failedLoad=true;\r
202                 Backbone.trigger("xoslib:collectionLoadChange", this);\r
203             }\r
204             this.trigger("fetchStateChange");\r
205             if (options["orig_failure"]) {\r
206                 options["orig_failure"](collection, response, options);\r
207             }\r
208         },\r
209 \r
210         fetch: function(options) {\r
211             var self=this;\r
212             this.fetching=true;\r
213             //console.log("fetch " + this.modelName);\r
214             if (!this.startedLoad) {\r
215                 this.startedLoad=true;\r
216                 Backbone.trigger("xoslib:collectionLoadChange", this);\r
217             }\r
218             this.trigger("fetchStateChange");\r
219             if (options == undefined) {\r
220                 options = {};\r
221             }\r
222             options["orig_success"] = options["success"];\r
223             options["orig_failure"] = options["failure"];\r
224             options["success"] = function(collection, response, options) { self.fetchSuccess.call(self, collection, response, options); };\r
225             options["failure"] = this.fetchFailure;\r
226             Backbone.Collection.prototype.fetch.call(this, options);\r
227         },\r
228 \r
229         startPolling: function() {\r
230             if (!this._polling) {\r
231                 var collection=this;
232                 setInterval(function() { collection.fetch(); }, 10000);
233                 this._polling=true;
234                 this.fetch();
235             }
236         },
237
238         refresh: function(refreshRelated) {
239             if (!this.fetching) {
240                 this.fetch();
241             }
242             if (refreshRelated) {
243                 for (related in this.relatedCollections) {
244                     related = xos[related];
245                     if (!related.fetching) {
246                         related.fetch();
247                     }
248                 }
249             }
250         },
251
252         maybeFetch: function(options){
253                 // Helper function to fetch only if this collection has not been fetched before.
254             if(this._fetched){
255                     // If this has already been fetched, call the success, if it exists
256                 options.success && options.success();
257                 console.log("alreadyFetched");
258                 return;
259             }
260
261                 // when the original success function completes mark this collection as fetched
262             var self = this,
263             successWrapper = function(success){
264                 return function(){
265                     self._fetched = true;
266                     success && success.apply(this, arguments);
267                 };
268             };
269             options.success = successWrapper(options.success);
270             console.log("call fetch");
271             this.fetch(options);
272         },
273
274         getOrFetch: function(id, options){
275                 // Helper function to use this collection as a cache for models on the server
276             var model = this.get(id);
277
278             if(model){
279                 options.success && options.success(model);
280                 return;
281             }
282
283             model = new this.model({
284                 resource_uri: id
285             });
286
287             model.fetch(options);
288         },
289
290         /* filterBy: note that this yields a new collection. If you pass that
291               collection to a CompositeView, then the CompositeView won't get
292               any events that trigger on the original collection.
293
294               Using this function is probably wrong, and I wrote
295               FilteredCompositeView() to replace it.
296         */
297
298         filterBy: function(fieldName, value) {
299              filtered = this.filter(function(obj) {
300                  return obj.get(fieldName) == value;
301                  });
302              return new this.constructor(filtered);
303         },
304
305         /* from backbone-tastypie.js */
306         url: function( models ) {
307                     var url = this.urlRoot || ( models && models.length && models[0].urlRoot );
308                     url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
309
310                     // Build a url to retrieve a set of models. This assume the last part of each model's idAttribute
311                     // (set to 'resource_uri') contains the model's id.
312                     if ( models && models.length ) {
313                             var ids = _.map( models, function( model ) {
314                                             var parts = _.compact( model.id.split('/') );
315                                             return parts[ parts.length - 1 ];
316                                     });
317                             url += 'set/' + ids.join(';') + '/';
318                     }
319
320                     url && ( url += "?no_hyperlinks=1" );
321
322                     return url;
323             },
324
325         listMethods: function() {
326                 var res = [];\r
327                 for(var m in this) {\r
328                     if(typeof this[m] == "function") {\r
329                         res.push(m)\r
330                     }\r
331                 }\r
332                 return res;\r
333             },
334     });
335
336     function define_model(lib, attrs) {
337         modelName = attrs.modelName;
338         modelClassName = modelName;
339         collectionClassName = modelName + "Collection";
340
341         if (!attrs.addFields) {
342             attrs.addFields = attrs.detailFields;
343         }
344
345         attrs.inputType = attrs.inputType || {};
346         attrs.foreignFields = attrs.foreignFields || {};
347         attrs.m2mFields = attrs.m2mFields || {};
348         attrs.readOnlyFields = attrs.readOnlyFields || [];
349         attrs.detailLinkFields = attrs.detailLinkFields || ["id","name"];
350
351         if (!attrs.collectionName) {
352             attrs.collectionName = modelName + "s";
353         }
354         collectionName = attrs.collectionName;
355
356         modelAttrs = {}
357         collectionAttrs = {}
358
359         for (key in attrs) {
360             value = attrs[key];
361             if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "listFields", "addFields", "detailFields", "detailLinkFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections"])>=0) {
362                 modelAttrs[key] = value;
363                 collectionAttrs[key] = value;
364             }
365             if ($.inArray(key, ["validate", "preSave", "readOnlyFields"])) {
366                 modelAttrs[key] = value;
367             }
368         }
369
370         if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
371             modelAttrs["defaults"] = xosdefaults[modelName];
372         }
373
374         if ((typeof xosvalidators !== "undefined") && xosvalidators[modelName]) {
375             modelAttrs["validators"] = xosvalidators[modelName];
376         }
377
378         lib[modelName] = XOSModel.extend(modelAttrs);
379
380         collectionAttrs["model"] = lib[modelName];
381
382         lib[collectionClassName] = XOSCollection.extend(collectionAttrs);
383         lib[collectionName] = new lib[collectionClassName]();
384
385         lib.allCollectionNames.push(collectionName);
386         lib.allCollections.push(lib[collectionName]);
387     };
388
389     function xoslib() {
390         this.allCollectionNames = [];
391         this.allCollections = [];
392
393         /* Give an id, the name of a collection, and the name of a field for models
394            within that collection, lookup the id and return the value of the field.
395         */
396
397         this.idToName = function(id, collectionName, fieldName) {
398             linkedObject = xos[collectionName].get(id);
399             if (linkedObject == undefined) {
400                 return "#" + id;
401             } else {
402                 return linkedObject.attributes[fieldName];
403             }
404         };
405
406         /* defining the models
407
408            modelName          - name of the model.
409
410            relatedCollections - collections which should be drawn as an inline
411                                 list when the detail view is displayed.
412                                 Format: <collection>:<collectionFieldName> where
413                                 <collectionFieldName> is the name of the field
414                                 in the collection that points back to the
415                                 collection in the detail view.
416
417            foreignCollections - collections which are used in idToName() calls
418                                 when presenting the data to the user. Used to
419                                 create a listento event. Somewhat
420                                 redundant with foreignFields.
421
422            foreignFields -      <localFieldName>:<collection>. Used to
423                                 automatically map ids into humanReadableNames
424                                 when presenting data to the user.
425
426            m2mfields -          <localFieldName>:<colleciton>. Used to
427                                 populate choices in picker lists. Simalar to
428                                 foreignFields.
429
430            listFields -         fields to display in lists
431
432            detailFields -       fields to display in detail views
433
434            addFields -          fields to display in popup add windows
435
436            inputType -          by default, "detailFields" will be displayed
437                                 as text input controls. This will let you display
438                                 a checkbox or a picker instead.
439         */
440
441         define_model(this, {urlRoot: SLIVER_API,
442                             relatedCollections: {"networkSlivers": "sliver"},
443                             foreignCollections: ["slices", "deployments", "images", "nodes", "users", "flavors"],
444                             foreignFields: {"creator": "users", "image": "images", "node": "nodes", "deploymentNetwork": "deployments", "slice": "slices", "flavor": "flavors"},
445                             modelName: "sliver",
446                             listFields: ["backend_status", "id", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "image", "node", "flavor"],
447                             addFields: ["slice", "deploymentNetwork", "flavor", "image", "node"],
448                             detailFields: ["backend_status", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "flavor", "image", "node", "creator"],
449                             preSave: function() { if (!this.attributes.name && this.attributes.slice) { this.attributes.name = xos.idToName(this.attributes.slice, "slices", "name"); } },
450                             });
451
452         define_model(this, {urlRoot: SLICE_API,
453                            relatedCollections: {"slivers": "slice", "slicePrivileges": "slice", "networks": "owner"},
454                            foreignCollections: ["services", "sites"],
455                            foreignFields: {"service": "services", "site": "sites"},
456                            listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
457                            detailFields: ["backend_status", "name", "site", "enabled", "description", "slice_url", "max_slivers"],
458                            inputType: {"enabled": "checkbox"},
459                            modelName: "slice",
460                            xosValidate: function(attrs, options) {
461                                errors = XOSModel.prototype.xosValidate(this, attrs, options);
462                                // validate that slice.name starts with site.login_base
463                                site = attrs.site || this.site;
464                                if ((site!=undefined) && (attrs.name!=undefined)) {
465                                    site = xos.sites.get(site);
466                                    if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
467                                         errors = errors || {};
468                                         errors["name"] = "must start with " + site.attributes.login_base + "_";
469                                    }
470                                }
471                                return errors;
472                              },
473                            });
474
475         define_model(this, {urlRoot: SLICEPRIVILEGE_API,
476                             foreignCollections: ["slices", "users", "sliceRoles"],
477                             modelName: "slicePrivilege",
478                             foreignFields: {"user": "users", "slice": "slices", "role": "sliceRoles"},
479                             listFields: ["backend_status", "id", "user", "slice", "role"],
480                             detailFields: ["backend_status", "user", "slice", "role"],
481                             });
482
483         define_model(this, {urlRoot: SLICEROLE_API,
484                             modelName: "sliceRole",
485                             listFields: ["backend_status", "id", "role"],
486                             detailFields: ["backend_status", "role"],
487                             });
488
489         define_model(this, {urlRoot: NODE_API,
490                             foreignCollections: ["sites", "deployments"],
491                             modelName: "node",
492                             foreignFields: {"site": "sites", "deployment": "deployments"},
493                             listFields: ["backend_status", "id", "name", "site", "deployment"],
494                             detailFields: ["backend_status", "name", "site", "deployment"],
495                             });
496
497         define_model(this, {urlRoot: SITE_API,
498                             relatedCollections: {"users": "site", "slices": "site", "nodes": "site", "siteDeployments": "site"},
499                             modelName: "site",
500                             listFields: ["backend_status", "id", "name", "site_url", "enabled", "login_base", "is_public", "abbreviated_name"],
501                             detailFields: ["backend_status", "name", "abbreviated_name", "url", "enabled", "is_public", "login_base"],
502                             inputType: {"enabled": "checkbox", "is_public": "checkbox"},
503                             });
504
505         define_model(this, {urlRoot: SITEDEPLOYMENT_API,
506                             foreignCollections: ["sites", "deployments", "controllers"],
507                             foreignFields: {"site": "sites", "deployment": "deployments", "controller": "controllers"},
508                             modelName: "siteDeployment",
509                             listFields: ["backend_status", "id", "site", "deployment", "controller", "availability_zone"],
510                             detailFields: ["backend_status", "site", "deployment", "controller", "availability_zone"],
511                             inputType: {"enabled": "checkbox", "is_public": "checkbox"},
512                             });
513
514         define_model(this, {urlRoot: USER_API,
515                             relatedCollections: {"slicePrivileges": "user", "slices": "owner"},
516                             foreignCollections: ["sites"],
517                             modelName: "user",
518                             foreignFields: {"site": "sites"},
519                             listFields: ["backend_status", "id", "username", "firstname", "lastname", "phone", "user_url", "site"],
520                             detailFields: ["backend_status", "username", "firstname", "lastname", "phone", "user_url", "site"],
521                             });
522
523         define_model(this, { urlRoot: DEPLOYMENT_API,
524                              relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork"},
525                              m2mFields: {"flavors": "flavors", "sites": "sites", "images": "images"},
526                              modelName: "deployment",
527                              listFields: ["backend_status", "id", "name", "backend_type", "admin_tenant"],
528                              detailFields: ["backend_status", "name", "backend_type", "admin_tenant", "flavors", "sites", "images"],
529                              inputType: {"flavors": "picker", "sites": "picker", "images": "picker"},
530                              });
531
532         define_model(this, {urlRoot: IMAGE_API,
533                             model: this.image,
534                             modelName: "image",
535                             listFields: ["backend_status", "id", "name", "disk_format", "container_format", "path"],
536                             detailFields: ["backend_status", "name", "disk_format", "admin_tenant"],
537                             });
538
539         define_model(this, {urlRoot: NETWORKTEMPLATE_API,
540                             modelName: "networkTemplate",
541                             listFields: ["backend_status", "id", "name", "visibility", "translation", "shared_network_name", "shared_network_id"],
542                             detailFields: ["backend_status", "name", "description", "visibility", "translation", "shared_network_name", "shared_network_id"],
543                             });
544
545         define_model(this, {urlRoot: NETWORK_API,
546                             relatedCollections: {"networkSlivers": "network"},
547                             foreignCollections: ["slices", "networkTemplates"],
548                             modelName: "network",
549                             foreignFields: {"template": "networkTemplates", "owner": "slices"},
550                             listFields: ["backend_status", "id", "name", "template", "ports", "labels", "owner"],
551                             detailFields: ["backend_status", "name", "template", "ports", "labels", "owner"],
552                             });
553
554         define_model(this, {urlRoot: NETWORKSLIVER_API,
555                             modelName: "networkSliver",
556                             foreignFields: {"network": "networks", "sliver": "slivers"},
557                             listFields: ["backend_status", "id", "network", "sliver", "ip", "port_id"],
558                             detailFields: ["backend_status", "network", "sliver", "ip", "port_id"],
559                             });
560
561         define_model(this, {urlRoot: SERVICE_API,
562                             modelName: "service",
563                             listFields: ["backend_status", "id", "name", "enabled", "versionNumber", "published"],
564                             detailFields: ["backend_status", "name", "description", "versionNumber"],
565                             });
566
567         define_model(this, {urlRoot: FLAVOR_API,
568                             modelName: "flavor",
569                             m2mFields: {"deployments": "deployments"},
570                             listFields: ["backend_status", "id", "name", "flavor", "order", "default"],
571                             detailFields: ["backend_status", "name", "description", "flavor", "order", "default", "deployments"],
572                             inputType: {"default": "checkbox", "deployments": "picker"},
573                             });
574
575         define_model(this, {urlRoot: CONTROLLER_API,
576                             modelName: "controller",
577                             listFields: ["backend_status", "id", "name", "version", "backend_type"],
578                             detailFields: ["backend_status", "name", "version", "backend_type", "auth_url", "admin_user", "admin_password", "admin_tenant"],
579                             });
580
581         /* removed
582         define_model(this, {urlRoot: CONTROLLERSITEDEPLOYMENT_API,
583                             modelName: "controllerSiteDeployment",
584                             foreignCollections: ["site_deployments", "controllers"],
585                             foreignFields: {"site_deployment": "siteDeployments", "controller": "controllers"},
586                             listFields: ["backend_status", "id", "site_deployment", "controller", "tenant_id"],
587                             detailFields: ["backend_status", "site_deployment", "controller", "tenant_id"],
588                             });
589         */
590
591         /* DELETED in site-controller branch
592
593         define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
594                             modelName: "networkDeployment",
595                             foreignFields: {"network": "networks", "deployment": "deployments"},
596                             listFields: ["backend_status", "id", "network", "deployment", "net_id"],
597                             detailFields: ["backend_status", "network", "deployment", "net_id"],
598                             });
599
600         define_model(this, {urlRoot: SLICEDEPLOYMENT_API,
601                            foreignCollections: ["slices", "deployments"],
602                            modelName: "sliceDeployment",
603                            foreignFields: {"slice": "slices", "deployment": "deployments"},
604                            listFields: ["backend_status", "id", "slice", "deployment", "tenant_id"],
605                            detailFields: ["backend_status", "slice", "deployment", "tenant_id"],
606                            });
607
608         define_model(this, {urlRoot: USERDEPLOYMENT_API,
609                             foreignCollections: ["users","deployments"],
610                             modelName: "userDeployment",
611                             foreignFields: {"deployment": "deployments", "user": "users"},
612                             listFields: ["backend_status", "id", "user", "deployment", "kuser_id"],
613                             detailFields: ["backend_status", "user", "deployment", "kuser_id"],
614                             });
615
616         END stuff deleted in site-controller branch */
617
618         /* not deleted, but obsolete since it has degenerated to a ManyToMany with no other fields
619
620         define_model(this, {urlRoot: IMAGEDEPLOYMENTS_API,
621                             modelName: "imageDeployment",
622                             foreignCollections: ["images", "deployments"],
623                             listFields: ["backend_status", "id", "image", "deployment", "glance_image_id"],
624                             detailFields: ["backend_status", "image", "deployment", "glance_image_id"],
625                             });
626
627         */
628
629         // enhanced REST
630         // XXX this really needs to somehow be combined with Slice, to avoid duplication
631         define_model(this, {urlRoot: SLICEPLUS_API,
632                             relatedCollections: {'slivers': "slice"},
633                             modelName: "slicePlus",
634                             collectionName: "slicesPlus"});
635
636         this.listObjects = function() { return this.allCollectionNames; };
637
638         this.getCollectionStatus = function() {
639             stats = {isLoaded: 0, failedLoad: 0, startedLoad: 0};
640             for (index in this.allCollections) {
641                 collection = this.allCollections[index];
642                 if (collection.isLoaded) {
643                     stats["isLoaded"] = stats["isLoaded"] + 1;
644                 }
645                 if (collection.failedLoad) {
646                     stats["failedLoad"] = stats["failedLoad"] + 1;
647                 }
648                 if (collection.startedLoad) {
649                     stats["startedLoad"] = stats["startedLoad"] + 1;
650                 }
651             }
652             stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
653             return stats;
654         };
655     };
656
657     xos = new xoslib();
658
659     function getCookie(name) {
660         var cookieValue = null;\r
661         if (document.cookie && document.cookie != '') {\r
662             var cookies = document.cookie.split(';');\r
663             for (var i = 0; i < cookies.length; i++) {\r
664                 var cookie = jQuery.trim(cookies[i]);\r
665                 // Does this cookie string begin with the name we want?\r
666                 if (cookie.substring(0, name.length + 1) == (name + '=')) {\r
667                     cookieValue = decodeURIComponent(cookie.substring(name.length + 1));\r
668                     break;\r
669                 }\r
670             }\r
671         }\r
672         return cookieValue;\r
673     }
674
675     (function() {
676       var _sync = Backbone.sync;\r
677       Backbone.sync = function(method, model, options){\r
678         options.beforeSend = function(xhr){\r
679           var token = getCookie("csrftoken");\r
680           xhr.setRequestHeader('X-CSRFToken', token);\r
681         };\r
682         return _sync(method, model, options);\r
683       };\r
684     })();
685 }