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