filtering the collection was the very very wrong way to go about it
[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     USER_API = "/plstackapi/users/";
10     USERDEPLOYMENT_API = "/plstackapi/user_deployments/";
11     DEPLOYMENT_API = "/plstackapi/deployments/";
12     IMAGE_API = "/plstackapi/images/";
13     NETWORKTEMPLATE_API = "/plstackapi/networktemplates/";
14     NETWORK_API = "/plstackapi/networks/";
15     NETWORKSLIVER_API = "/plstackapi/networkslivers/";
16     SERVICE_API = "/plstackapi/services/";
17     SLICEPRIVILEGE_API = "/plstackapi/slice_privileges/";
18     NETWORKDEPLOYMENT_API = "/plstackapi/networkdeployments/";
19
20     /* changed as a side effect of the big rename
21     SLICEDEPLOYMENT_API = "/plstackapi/slice_deployments/";
22     USERDEPLOYMENT_API = "/plstackapi/user_deployments/";
23     */
24
25     SLICEDEPLOYMENT_API = "/plstackapi/slicedeployments/";
26     USERDEPLOYMENT_API = "/plstackapi/userdeployments/";
27
28     SLICEPLUS_API = "/xoslib/slicesplus/";
29
30     XOSModel = Backbone.Model.extend({
31         /* from backbone-tastypie.js */
32         //idAttribute: 'resource_uri',
33
34         /* from backbone-tastypie.js */
35         url: function() {
36                     var url = this.attributes.resource_uri;
37
38                     if (!url) {
39                         if (this.id) {
40                             url = this.urlRoot + this.id;
41                         } else {
42                             // this happens when creating a new model.
43                             url = this.urlRoot;
44                         }
45                     }
46
47                     if (!url) {
48                         // XXX I'm not sure this does anything useful
49                         url = ( _.isFunction( this.collection.url ) ? this.collection.url() : this.collection.url );
50                         url = url || this.urlRoot;
51                     }
52
53                     // remove any existing query parameters
54                     url && ( url.indexOf("?") > -1 ) && ( url = url.split("?")[0] );
55
56                     url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
57
58                     url && ( url += "?no_hyperlinks=1" );
59
60                     return url;
61             },
62
63             listMethods: function() {
64                 var res = [];\r
65                 for(var m in this) {\r
66                     if(typeof this[m] == "function") {\r
67                         res.push(m)\r
68                     }\r
69                 }\r
70                 return res;\r
71             },
72
73             /* If a 'validate' method is supplied, then it will be called
74                automatically on save. Unfortunately, save calls neither the
75                'error' nor the 'success' callback if the validator fails.
76
77                For now, we're calling our validator 'xosValidate' so this
78                autoamtic validation doesn't occur.
79             */
80
81             xosValidate: function(attrs, options) {
82                 errors = {};
83                 foundErrors = false;
84                 _.each(this.validators, function(validatorList, fieldName) {
85                     _.each(validatorList, function(validator) {
86                         if (fieldName in attrs) {
87                             validatorResult = validateField(validator, attrs[fieldName], this)
88                             if (validatorResult != true) {
89                                 errors[fieldName] = validatorResult;
90                                 foundErrors = true;
91                             }
92                         }
93                     });
94                 });
95                 if (foundErrors) {
96                     return errors;
97                 }
98                 // backbone.js semantics -- on successful validate, return nothing
99             },
100
101             /* uncommenting this would make validate() call xosValidate()
102             validate: function(attrs, options) {
103                 r = this.xosValidate(attrs, options);
104                 console.log("validate");
105                 console.log(r);
106                 return r;
107             }, */
108     });
109
110     XOSCollection = Backbone.Collection.extend({
111         objects: function() {
112                     return this.models.map(function(element) { return element.attributes; });
113                  },
114
115         initialize: function(){
116           this.isLoaded = false;
117           this.failedLoad = false;
118           this.startedLoad = false;
119           this.sortVar = 'name';\r
120           this.sortOrder = 'asc';\r
121           this.on( "sort", this.sorted );\r
122         },\r
123 \r
124         relatedCollections: [],\r
125         foreignCollections: [],\r
126 \r
127         sorted: function() {\r
128             //console.log("sorted " + this.modelName);\r
129         },\r
130 \r
131         simpleComparator: function( model ){\r
132           parts=this.sortVar.split(".");\r
133           result = model.get(parts[0]);\r
134           for (index=1; index<parts.length; ++index) {\r
135               result=result[parts[index]];\r
136           }\r
137           return result;\r
138         },\r
139 \r
140         comparator: function (left, right) {\r
141             var l = this.simpleComparator(left);\r
142             var r = this.simpleComparator(right);\r
143 \r
144             if (l === void 0) return -1;\r
145             if (r === void 0) return 1;\r
146 \r
147             if (this.sortOrder=="desc") {\r
148                 return l < r ? 1 : l > r ? -1 : 0;\r
149             } else {\r
150                 return l < r ? -1 : l > r ? 1 : 0;\r
151             }\r
152         },\r
153 \r
154         fetchSuccess: function(collection, response, options) {\r
155             //console.log("fetch succeeded " + collection.modelName);\r
156             this.failedLoad = false;\r
157             this.fetching = false;\r
158             if (!this.isLoaded) {\r
159                 this.isLoaded = true;\r
160                 Backbone.trigger("xoslib:collectionLoadChange", this);\r
161             }\r
162             this.trigger("fetchStateChange");\r
163             if (options["orig_success"]) {\r
164                 options["orig_success"](collection, response, options);\r
165             }\r
166         },\r
167 \r
168         fetchFailure: function(collection, response, options) {\r
169             //console.log("fetch failed " + collection.modelName);\r
170             this.fetching = false;\r
171             if ((!this.isLoaded) && (!this.failedLoad)) {\r
172                 this.failedLoad=true;\r
173                 Backbone.trigger("xoslib:collectionLoadChange", this);\r
174             }\r
175             this.trigger("fetchStateChange");\r
176             if (options["orig_failure"]) {\r
177                 options["orig_failure"](collection, response, options);\r
178             }\r
179         },\r
180 \r
181         fetch: function(options) {\r
182             var self=this;\r
183             this.fetching=true;\r
184             //console.log("fetch " + this.modelName);\r
185             if (!this.startedLoad) {\r
186                 this.startedLoad=true;\r
187                 Backbone.trigger("xoslib:collectionLoadChange", this);\r
188             }\r
189             this.trigger("fetchStateChange");\r
190             if (options == undefined) {\r
191                 options = {};\r
192             }\r
193             options["orig_success"] = options["success"];\r
194             options["orig_failure"] = options["failure"];\r
195             options["success"] = function(collection, response, options) { self.fetchSuccess.call(self, collection, response, options); };\r
196             options["failure"] = this.fetchFailure;\r
197             Backbone.Collection.prototype.fetch.call(this, options);\r
198         },\r
199 \r
200         startPolling: function() {\r
201             if (!this._polling) {\r
202                 var collection=this;
203                 setInterval(function() { collection.fetch(); }, 10000);
204                 this._polling=true;
205                 this.fetch();
206             }
207         },
208
209         refresh: function(refreshRelated) {
210             if (!this.fetching) {
211                 this.fetch();
212             }
213             if (refreshRelated) {
214                 for (related in this.relatedCollections) {
215                     related = xos[related];
216                     if (!related.fetching) {
217                         related.fetch();
218                     }
219                 }
220             }
221         },
222
223         maybeFetch: function(options){
224                 // Helper function to fetch only if this collection has not been fetched before.
225             if(this._fetched){
226                     // If this has already been fetched, call the success, if it exists
227                 options.success && options.success();
228                 console.log("alreadyFetched");
229                 return;
230             }
231
232                 // when the original success function completes mark this collection as fetched
233             var self = this,
234             successWrapper = function(success){
235                 return function(){
236                     self._fetched = true;
237                     success && success.apply(this, arguments);
238                 };
239             };
240             options.success = successWrapper(options.success);
241             console.log("call fetch");
242             this.fetch(options);
243         },
244
245         getOrFetch: function(id, options){
246                 // Helper function to use this collection as a cache for models on the server
247             var model = this.get(id);
248
249             if(model){
250                 options.success && options.success(model);
251                 return;
252             }
253
254             model = new this.model({
255                 resource_uri: id
256             });
257
258             model.fetch(options);
259         },
260
261         /* filterBy: note that this yields a new collection. If you pass that
262               collection to a CompositeView, then the CompositeView won't get
263               any events that trigger on the original collection.
264
265               Using this function is probably wrong, and I wrote
266               FilteredCompositeView() to replace it.
267         */
268
269         filterBy: function(fieldName, value) {
270              filtered = this.filter(function(obj) {
271                  return obj.get(fieldName) == value;
272                  });
273              return new this.constructor(filtered);
274         },
275
276         /* from backbone-tastypie.js */
277         url: function( models ) {
278                     var url = this.urlRoot || ( models && models.length && models[0].urlRoot );
279                     url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
280
281                     // Build a url to retrieve a set of models. This assume the last part of each model's idAttribute
282                     // (set to 'resource_uri') contains the model's id.
283                     if ( models && models.length ) {
284                             var ids = _.map( models, function( model ) {
285                                             var parts = _.compact( model.id.split('/') );
286                                             return parts[ parts.length - 1 ];
287                                     });
288                             url += 'set/' + ids.join(';') + '/';
289                     }
290
291                     url && ( url += "?no_hyperlinks=1" );
292
293                     return url;
294             },
295
296         listMethods: function() {
297                 var res = [];\r
298                 for(var m in this) {\r
299                     if(typeof this[m] == "function") {\r
300                         res.push(m)\r
301                     }\r
302                 }\r
303                 return res;\r
304             },
305     });
306
307     function define_model(lib, attrs) {
308         modelName = attrs.modelName;
309         modelClassName = modelName;
310         collectionClassName = modelName + "Collection";
311
312         if (!attrs.addFields) {
313             attrs.addFields = attrs.detailFields;
314         }
315
316         if (!attrs.inputType) {
317             attrs.inputType = {};
318         }
319
320         if (!attrs.foreignFields) {
321             attrs.foreignFields = {};
322         }
323
324         if (!attrs.collectionName) {
325             attrs.collectionName = modelName + "s";
326         }
327         collectionName = attrs.collectionName;
328
329         modelAttrs = {}
330         collectionAttrs = {}
331
332         for (key in attrs) {
333             value = attrs[key];
334             if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "addFields", "detailFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections"])>=0) {
335                 modelAttrs[key] = value;
336                 collectionAttrs[key] = value;
337             }
338             if ($.inArray(key, ["validate"])) {
339                 modelAttrs[key] = value;
340             }
341         }
342
343         if (xosdefaults && xosdefaults[modelName]) {
344             modelAttrs["defaults"] = xosdefaults[modelName];
345         }
346
347         if (xosvalidators && xosvalidators[modelName]) {
348             modelAttrs["validators"] = xosvalidators[modelName];
349         }
350
351         lib[modelName] = XOSModel.extend(modelAttrs);
352
353         collectionAttrs["model"] = lib[modelName];
354
355         lib[collectionClassName] = XOSCollection.extend(collectionAttrs);
356         lib[collectionName] = new lib[collectionClassName]();
357
358         lib.allCollectionNames.push(collectionName);
359         lib.allCollections.push(lib[collectionName]);
360     };
361
362     function xoslib() {
363         this.allCollectionNames = [];
364         this.allCollections = [];
365
366         define_model(this, {urlRoot: SLIVER_API,
367                             relatedCollections: {"networkSlivers": "sliver"},
368                             foreignCollections: ["slices", "deployments", "images", "nodes", "users"],
369                             foreignFields: {"creator": "users", "image": "images", "node": "nodes", "deploymentNetwork": "deployments", "slice": "slices"},
370                             modelName: "sliver",
371                             addFields: ["slice", "deploymentNetwork", "image", "node"],
372                             detailFields: ["name", "instance_id", "instance_name", "slice", "deploymentNetwork", "image", "node", "creator"],
373                             });
374
375         define_model(this, {urlRoot: SLICE_API,
376                            relatedCollections: {"slivers": "slice", "sliceDeployments": "slice", "slicePrivileges": "slice", "networks": "owner"},
377                            foreignCollections: ["services", "sites"],
378                            foreignFields: {"service": "services", "site": "sites"},
379                            detailFields: ["name", "site", "enabled", "description", "url", "max_slivers"],
380                            inputType: {"enabled": "checkbox"},
381                            modelName: "slice",
382                            xosValidate: function(attrs, options) {
383                                errors = XOSModel.prototype.xosValidate(this, attrs, options);
384                                // validate that slice.name starts with site.login_base
385                                site = attrs.site || this.site;
386                                if ((site!=undefined) && (attrs.name!=undefined)) {
387                                    site = xos.sites.get(site);
388                                    if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
389                                         errors = errors || {};
390                                         errors["name"] = "must start with " + site.attributes.login_base + "_";
391                                    }
392                                }
393                                return errors;
394                              },
395                            });
396
397         define_model(this, {urlRoot: SLICEDEPLOYMENT_API,
398                            foreignCollections: ["slices", "deployments"],
399                            modelName: "sliceDeployment",
400                            foreignFields: {"slice": "slices", "deployment": "deployments"},
401                            detailFields: ["slice", "deployment", "tenant_id"],
402                            });
403
404         define_model(this, {urlRoot: SLICEPRIVILEGE_API,
405                             foreignCollections: ["slices", "users", "sliceRoles"],
406                             modelName: "slicePrivilege",
407                             foreignFields: {"user": "users", "slice": "slices", "role": "sliceRoles"},
408                             detailFields: ["user", "slice", "role"],
409                             });
410
411         define_model(this, {urlRoot: SLICEROLE_API,
412                             modelName: "sliceRole",
413                             detailFields: ["role"],
414                             });
415
416         define_model(this, {urlRoot: NODE_API,
417                             foreignCollections: ["sites", "deployments"],
418                             modelName: "node",
419                             foreignFields: {"site": "sites", "deployment": "deployments"},
420                             detailFields: ["name", "site", "deployment"],
421                             });
422
423         define_model(this, {urlRoot: SITE_API,
424                             relatedCollections: {"users": "site", "slices": "site", "nodes": "site"},
425                             modelName: "site",
426                             detailFields: ["name", "abbreviated_name", "url", "enabled", "is_public", "login_base"],
427                             inputType: {"enabled": "checkbox", "is_public": "checkbox"},
428                             });
429
430         define_model(this, {urlRoot: USER_API,
431                             relatedCollections: {"slicePrivileges": "user", "slices": "owner", "userDeployments": "user"},
432                             foreignCollections: ["sites"],
433                             modelName: "user",
434                             foreignFields: {"site": "sites"},
435                             detailFields: ["username", "firstname", "lastname", "phone", "user_url", "site"],
436                             });
437
438         define_model(this, {urlRoot: USERDEPLOYMENT_API,
439                             foreignCollections: ["users","deployments"],
440                             modelName: "userDeployment",
441                             foreignFields: {"deployment": "deployments", "user": "users"},
442                             detailFields: ["user", "deployment", "kuser_id"],
443                             });
444
445         define_model(this, { urlRoot: DEPLOYMENT_API,
446                              relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork", "networkDeployments": "deployment", "userDeployments": "deployment"},
447                              modelName: "deployment",
448                              detailFields: ["name", "backend_type", "admin_tenant"],
449                              });
450
451         define_model(this, {urlRoot: IMAGE_API,
452                             model: this.image,
453                             modelName: "image",
454                             detailFields: ["name", "disk_format", "admin_tenant"],
455                             });
456
457         define_model(this, {urlRoot: NETWORKTEMPLATE_API,
458                             modelName: "networkTemplate",
459                             detailFields: ["name", "description", "visibility", "translation", "sharedNetworkName", "sharedNetworkId"],
460                             });
461
462         define_model(this, {urlRoot: NETWORK_API,
463                             relatedCollections: {"networkDeployments": "network", "networkSlivers": "network"},
464                             foreignCollections: ["slices", "networkTemplates"],
465                             modelName: "network",
466                             foreignFields: {"template": "networkTemplates", "owner": "slices"},
467                             detailFields: ["name", "template", "ports", "labels", "owner"],
468                             });
469
470         define_model(this, {urlRoot: NETWORKSLIVER_API,
471                             modelName: "networkSliver",
472                             foreignFields: {"network": "networks", "sliver": "slivers"},
473                             detailFields: ["network", "sliver", "ip", "port_id"],
474                             });
475
476         define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
477                             modelName: "networkDeployment",
478                             foreignFields: {"network": "networks", "deployment": "deployments"},
479                             detailFields: ["network", "deployment", "net_id"],
480                             });
481
482         define_model(this, {urlRoot: SERVICE_API,
483                             modelName: "service",
484                             detailFields: ["name", "description", "versionNumber"],
485                             });
486
487         // enhanced REST
488         define_model(this, {urlRoot: SLICEPLUS_API,
489                             relatedCollections: {'slivers': "slice"},
490                             modelName: "slicePlus",
491                             collectionName: "slicesPlus"});
492
493         this.listObjects = function() { return this.allCollectionNames; };
494
495         this.getCollectionStatus = function() {
496             stats = {isLoaded: 0, failedLoad: 0, startedLoad: 0};
497             for (index in this.allCollections) {
498                 collection = this.allCollections[index];
499                 if (collection.isLoaded) {
500                     stats["isLoaded"] = stats["isLoaded"] + 1;
501                 }
502                 if (collection.failedLoad) {
503                     stats["failedLoad"] = stats["failedLoad"] + 1;
504                 }
505                 if (collection.startedLoad) {
506                     stats["startedLoad"] = stats["startedLoad"] + 1;
507                 }
508             }
509             stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
510             return stats;
511         };
512     };
513
514     xos = new xoslib();
515
516     function getCookie(name) {
517         var cookieValue = null;\r
518         if (document.cookie && document.cookie != '') {\r
519             var cookies = document.cookie.split(';');\r
520             for (var i = 0; i < cookies.length; i++) {\r
521                 var cookie = jQuery.trim(cookies[i]);\r
522                 // Does this cookie string begin with the name we want?\r
523                 if (cookie.substring(0, name.length + 1) == (name + '=')) {\r
524                     cookieValue = decodeURIComponent(cookie.substring(name.length + 1));\r
525                     break;\r
526                 }\r
527             }\r
528         }\r
529         return cookieValue;\r
530     }
531
532     (function() {
533       var _sync = Backbone.sync;\r
534       Backbone.sync = function(method, model, options){\r
535         options.beforeSend = function(xhr){\r
536           var token = getCookie("csrftoken");\r
537           xhr.setRequestHeader('X-CSRFToken', token);\r
538         };\r
539         return _sync(method, model, options);\r
540       };\r
541     })();
542 }