all detail templates replaced by generic
[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: function(fieldName, value) {
262              filtered = this.filter(function(obj) {
263                  return obj.get(fieldName) == value;
264                  });
265              return new this.constructor(filtered);
266         },
267
268         /* from backbone-tastypie.js */
269         url: function( models ) {
270                     var url = this.urlRoot || ( models && models.length && models[0].urlRoot );
271                     url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
272
273                     // Build a url to retrieve a set of models. This assume the last part of each model's idAttribute
274                     // (set to 'resource_uri') contains the model's id.
275                     if ( models && models.length ) {
276                             var ids = _.map( models, function( model ) {
277                                             var parts = _.compact( model.id.split('/') );
278                                             return parts[ parts.length - 1 ];
279                                     });
280                             url += 'set/' + ids.join(';') + '/';
281                     }
282
283                     url && ( url += "?no_hyperlinks=1" );
284
285                     return url;
286             },
287
288         listMethods: function() {
289                 var res = [];\r
290                 for(var m in this) {\r
291                     if(typeof this[m] == "function") {\r
292                         res.push(m)\r
293                     }\r
294                 }\r
295                 return res;\r
296             },
297     });
298
299     function define_model(lib, attrs) {
300         modelName = attrs.modelName;
301         modelClassName = modelName;
302         collectionClassName = modelName + "Collection";
303
304         if (!attrs.addFields) {
305             attrs.addFields = attrs.detailFields;
306         }
307
308         if (!attrs.inputType) {
309             attrs.inputType = {};
310         }
311
312         if (!attrs.foreignFields) {
313             attrs.foreignFields = {};
314         }
315
316         if (!attrs.collectionName) {
317             attrs.collectionName = modelName + "s";
318         }
319         collectionName = attrs.collectionName;
320
321         modelAttrs = {}
322         collectionAttrs = {}
323
324         for (key in attrs) {
325             value = attrs[key];
326             if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "addFields", "detailFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections"])>=0) {
327                 modelAttrs[key] = value;
328                 collectionAttrs[key] = value;
329             }
330             if ($.inArray(key, ["validate"])) {
331                 modelAttrs[key] = value;
332             }
333         }
334
335         if (xosdefaults && xosdefaults[modelName]) {
336             modelAttrs["defaults"] = xosdefaults[modelName];
337         }
338
339         if (xosvalidators && xosvalidators[modelName]) {
340             modelAttrs["validators"] = xosvalidators[modelName];
341         }
342
343         lib[modelName] = XOSModel.extend(modelAttrs);
344
345         collectionAttrs["model"] = lib[modelName];
346
347         lib[collectionClassName] = XOSCollection.extend(collectionAttrs);
348         lib[collectionName] = new lib[collectionClassName]();
349
350         lib.allCollectionNames.push(collectionName);
351         lib.allCollections.push(lib[collectionName]);
352     };
353
354     function xoslib() {
355         this.allCollectionNames = [];
356         this.allCollections = [];
357
358         define_model(this, {urlRoot: SLIVER_API,
359                             relatedCollections: {"networkSlivers": "sliver"},
360                             foreignCollections: ["slices", "deployments", "images", "nodes", "users"],
361                             foreignFields: {"creator": "users", "image": "images", "node": "nodes", "deploymentNetwork": "deployments", "slice": "slices"},
362                             modelName: "sliver",
363                             addFields: ["slice", "deploymentNetwork", "image", "node"],
364                             detailFields: ["name", "instance_id", "instance_name", "slice", "deploymentNetwork", "image", "node", "creator"],
365                             });
366
367         define_model(this, {urlRoot: SLICE_API,
368                            relatedCollections: {"slivers": "slice", "sliceDeployments": "slice", "slicePrivileges": "slice", "networks": "owner"},
369                            foreignCollections: ["services", "sites"],
370                            foreignFields: {"service": "services", "site": "sites"},
371                            detailFields: ["name", "site", "enabled", "description", "url", "max_slivers"],
372                            inputType: {"enabled": "checkbox"},
373                            modelName: "slice",
374                            xosValidate: function(attrs, options) {
375                                errors = XOSModel.prototype.xosValidate(this, attrs, options);
376                                // validate that slice.name starts with site.login_base
377                                site = attrs.site || this.site;
378                                if ((site!=undefined) && (attrs.name!=undefined)) {
379                                    site = xos.sites.get(site);
380                                    if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
381                                         errors = errors || {};
382                                         errors["name"] = "must start with " + site.attributes.login_base + "_";
383                                    }
384                                }
385                                return errors;
386                              },
387                            });
388
389         define_model(this, {urlRoot: SLICEDEPLOYMENT_API,
390                            foreignCollections: ["slices", "deployments"],
391                            modelName: "sliceDeployment",
392                            foreignFields: {"slice": "slices", "deployment": "deployments"},
393                            detailFields: ["slice", "deployment", "tenant_id"],
394                            });
395
396         define_model(this, {urlRoot: SLICEPRIVILEGE_API,
397                             foreignCollections: ["slices", "users", "sliceRoles"],
398                             modelName: "slicePrivilege",
399                             foreignFields: {"user": "users", "slice": "slices", "role": "sliceRoles"},
400                             detailFields: ["user", "slice", "role"],
401                             });
402
403         define_model(this, {urlRoot: SLICEROLE_API,
404                             modelName: "sliceRole",
405                             detailFields: ["role"],
406                             });
407
408         define_model(this, {urlRoot: NODE_API,
409                             foreignCollections: ["sites", "deployments"],
410                             modelName: "node",
411                             foreignFields: {"site": "sites", "deployment": "deployments"},
412                             detailFields: ["name", "site", "deployment"],
413                             });
414
415         define_model(this, {urlRoot: SITE_API,
416                             relatedCollections: {"users": "site", "slices": "site", "nodes": "site"},
417                             modelName: "site",
418                             detailFields: ["name", "abbreviated_name", "url", "enabled", "is_public", "login_base"],
419                             inputType: {"enabled": "checkbox", "is_public": "checkbox"},
420                             });
421
422         define_model(this, {urlRoot: USER_API,
423                             relatedCollections: {"slicePrivileges": "user", "slices": "owner", "userDeployments": "user"},
424                             foreignCollections: ["sites"],
425                             modelName: "user",
426                             foreignFields: {"site": "sites"},
427                             detailFields: ["username", "firstname", "lastname", "phone", "user_url", "site"],
428                             });
429
430         define_model(this, {urlRoot: USERDEPLOYMENT_API,
431                             foreignCollections: ["users","deployments"],
432                             modelName: "userDeployment",
433                             foreignFields: {"deployment": "deployments", "user": "users"},
434                             detailFields: ["user", "deployment", "kuser_id"],
435                             });
436
437         define_model(this, { urlRoot: DEPLOYMENT_API,
438                              relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork", "networkDeployments": "deployment", "userDeployments": "deployment"},
439                              modelName: "deployment",
440                              detailFields: ["name", "backend_type", "admin_tenant"],
441                              });
442
443         define_model(this, {urlRoot: IMAGE_API,
444                             model: this.image,
445                             modelName: "image",
446                             detailFields: ["name", "disk_format", "admin_tenant"],
447                             });
448
449         define_model(this, {urlRoot: NETWORKTEMPLATE_API,
450                             modelName: "networkTemplate",
451                             detailFields: ["name", "description", "visibility", "translation", "sharedNetworkName", "sharedNetworkId"],
452                             });
453
454         define_model(this, {urlRoot: NETWORK_API,
455                             relatedCollections: {"networkDeployments": "network", "networkSlivers": "network"},
456                             foreignCollections: ["slices", "networkTemplates"],
457                             modelName: "network",
458                             foreignFields: {"template": "networkTemplates", "owner": "slices"},
459                             detailFields: ["name", "template", "ports", "labels", "owner"],
460                             });
461
462         define_model(this, {urlRoot: NETWORKSLIVER_API,
463                             modelName: "networkSliver",
464                             foreignFields: {"network": "networks", "sliver": "slivers"},
465                             detailFields: ["network", "sliver", "ip", "port_id"],
466                             });
467
468         define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
469                             modelName: "networkDeployment",
470                             foreignFields: {"network": "networks", "deployment": "deployments"},
471                             detailFields: ["network", "deployment", "net_id"],
472                             });
473
474         define_model(this, {urlRoot: SERVICE_API,
475                             modelName: "service",
476                             detailFields: ["name", "description", "versionNumber"],
477                             });
478
479         // enhanced REST
480         define_model(this, {urlRoot: SLICEPLUS_API,
481                             relatedCollections: {'slivers': "slice"},
482                             modelName: "slicePlus",
483                             collectionName: "slicesPlus"});
484
485         this.listObjects = function() { return this.allCollectionNames; };
486
487         this.getCollectionStatus = function() {
488             stats = {isLoaded: 0, failedLoad: 0, startedLoad: 0};
489             for (index in this.allCollections) {
490                 collection = this.allCollections[index];
491                 if (collection.isLoaded) {
492                     stats["isLoaded"] = stats["isLoaded"] + 1;
493                 }
494                 if (collection.failedLoad) {
495                     stats["failedLoad"] = stats["failedLoad"] + 1;
496                 }
497                 if (collection.startedLoad) {
498                     stats["startedLoad"] = stats["startedLoad"] + 1;
499                 }
500             }
501             stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
502             return stats;
503         };
504     };
505
506     xos = new xoslib();
507
508     function getCookie(name) {
509         var cookieValue = null;\r
510         if (document.cookie && document.cookie != '') {\r
511             var cookies = document.cookie.split(';');\r
512             for (var i = 0; i < cookies.length; i++) {\r
513                 var cookie = jQuery.trim(cookies[i]);\r
514                 // Does this cookie string begin with the name we want?\r
515                 if (cookie.substring(0, name.length + 1) == (name + '=')) {\r
516                     cookieValue = decodeURIComponent(cookie.substring(name.length + 1));\r
517                     break;\r
518                 }\r
519             }\r
520         }\r
521         return cookieValue;\r
522     }
523
524     (function() {
525       var _sync = Backbone.sync;\r
526       Backbone.sync = function(method, model, options){\r
527         options.beforeSend = function(xhr){\r
528           var token = getCookie("csrftoken");\r
529           xhr.setRequestHeader('X-CSRFToken', token);\r
530         };\r
531         return _sync(method, model, options);\r
532       };\r
533     })();
534 }