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