1 if (! window.XOSLIB_LOADED ) {
2 window.XOSLIB_LOADED=true;
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/";
22 /* changed as a side effect of the big rename
23 SLICEDEPLOYMENT_API = "/plstackapi/slice_deployments/";
24 USERDEPLOYMENT_API = "/plstackapi/user_deployments/";
27 SLICEDEPLOYMENT_API = "/plstackapi/slicedeployments/";
28 USERDEPLOYMENT_API = "/plstackapi/userdeployments/";
30 SLICEPLUS_API = "/xoslib/slicesplus/";
32 XOSModel = Backbone.Model.extend({
33 /* from backbone-tastypie.js */
34 //idAttribute: 'resource_uri',
36 /* from backbone-tastypie.js */
38 var url = this.attributes.resource_uri;
42 url = this.urlRoot + this.id;
44 // this happens when creating a new model.
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;
55 // remove any existing query parameters
56 url && ( url.indexOf("?") > -1 ) && ( url = url.split("?")[0] );
58 url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
60 url && ( url += "?no_hyperlinks=1" );
65 listMethods: function() {
67 for(var m in this) {
\r
68 if(typeof this[m] == "function") {
\r
75 save: function(attributes, options) {
79 return Backbone.Model.prototype.save.call(this, attributes, options);
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.
86 For now, we're calling our validator 'xosValidate' so this
87 autoamtic validation doesn't occur.
90 xosValidate: function(attrs, options) {
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;
107 // backbone.js semantics -- on successful validate, return nothing
110 /* uncommenting this would make validate() call xosValidate()
111 validate: function(attrs, options) {
112 r = this.xosValidate(attrs, options);
113 console.log("validate");
119 XOSCollection = Backbone.Collection.extend({
120 objects: function() {
121 return this.models.map(function(element) { return element.attributes; });
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
133 relatedCollections: [],
\r
134 foreignCollections: [],
\r
136 sorted: function() {
\r
137 //console.log("sorted " + this.modelName);
\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
149 comparator: function (left, right) {
\r
150 var l = this.simpleComparator(left);
\r
151 var r = this.simpleComparator(right);
\r
153 if (l === void 0) return -1;
\r
154 if (r === void 0) return 1;
\r
156 if (this.sortOrder=="desc") {
\r
157 return l < r ? 1 : l > r ? -1 : 0;
\r
159 return l < r ? -1 : l > r ? 1 : 0;
\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
171 this.trigger("fetchStateChange");
\r
172 if (options["orig_success"]) {
\r
173 options["orig_success"](collection, response, options);
\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
184 this.trigger("fetchStateChange");
\r
185 if (options["orig_failure"]) {
\r
186 options["orig_failure"](collection, response, options);
\r
190 fetch: function(options) {
\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
198 this.trigger("fetchStateChange");
\r
199 if (options == undefined) {
\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
209 startPolling: function() {
\r
210 if (!this._polling) {
\r
212 setInterval(function() { collection.fetch(); }, 10000);
218 refresh: function(refreshRelated) {
219 if (!this.fetching) {
222 if (refreshRelated) {
223 for (related in this.relatedCollections) {
224 related = xos[related];
225 if (!related.fetching) {
232 maybeFetch: function(options){
233 // Helper function to fetch only if this collection has not been fetched before.
235 // If this has already been fetched, call the success, if it exists
236 options.success && options.success();
237 console.log("alreadyFetched");
241 // when the original success function completes mark this collection as fetched
243 successWrapper = function(success){
245 self._fetched = true;
246 success && success.apply(this, arguments);
249 options.success = successWrapper(options.success);
250 console.log("call fetch");
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);
259 options.success && options.success(model);
263 model = new this.model({
267 model.fetch(options);
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.
274 Using this function is probably wrong, and I wrote
275 FilteredCompositeView() to replace it.
278 filterBy: function(fieldName, value) {
279 filtered = this.filter(function(obj) {
280 return obj.get(fieldName) == value;
282 return new this.constructor(filtered);
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 ) === '/' ) ? '' : '/' );
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 ];
297 url += 'set/' + ids.join(';') + '/';
300 url && ( url += "?no_hyperlinks=1" );
305 listMethods: function() {
307 for(var m in this) {
\r
308 if(typeof this[m] == "function") {
\r
316 function define_model(lib, attrs) {
317 modelName = attrs.modelName;
318 modelClassName = modelName;
319 collectionClassName = modelName + "Collection";
321 if (!attrs.addFields) {
322 attrs.addFields = attrs.detailFields;
325 attrs.inputType = attrs.inputType || {};
326 attrs.foreignFields = attrs.foreignFields || {};
327 attrs.readOnlyFields = attrs.readOnlyFields || [];
328 attrs.detailLinkFields = attrs.detailLinkFields || ["id","name"];
330 if (!attrs.collectionName) {
331 attrs.collectionName = modelName + "s";
333 collectionName = attrs.collectionName;
340 if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "listFields", "addFields", "detailFields", "detailLinkFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections"])>=0) {
341 modelAttrs[key] = value;
342 collectionAttrs[key] = value;
344 if ($.inArray(key, ["validate", "preSave", "readOnlyFields"])) {
345 modelAttrs[key] = value;
349 if (xosdefaults && xosdefaults[modelName]) {
350 modelAttrs["defaults"] = xosdefaults[modelName];
353 if (xosvalidators && xosvalidators[modelName]) {
354 modelAttrs["validators"] = xosvalidators[modelName];
357 lib[modelName] = XOSModel.extend(modelAttrs);
359 collectionAttrs["model"] = lib[modelName];
361 lib[collectionClassName] = XOSCollection.extend(collectionAttrs);
362 lib[collectionName] = new lib[collectionClassName]();
364 lib.allCollectionNames.push(collectionName);
365 lib.allCollections.push(lib[collectionName]);
369 this.allCollectionNames = [];
370 this.allCollections = [];
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.
376 this.idToName = function(id, collectionName, fieldName) {
377 linkedObject = xos[collectionName].get(id);
378 if (linkedObject == undefined) {
381 return linkedObject.attributes[fieldName];
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"},
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"); } },
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"},
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 + "_";
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"],
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"],
435 define_model(this, {urlRoot: SLICEROLE_API,
436 modelName: "sliceRole",
437 listFields: ["backend_status", "id", "role"],
438 detailFields: ["backend_status", "role"],
441 define_model(this, {urlRoot: NODE_API,
442 foreignCollections: ["sites", "deployments"],
444 foreignFields: {"site": "sites", "deployment": "deployments"},
445 listFields: ["backend_status", "id", "name", "site", "deployment"],
446 detailFields: ["backend_status", "name", "site", "deployment"],
449 define_model(this, {urlRoot: SITE_API,
450 relatedCollections: {"users": "site", "slices": "site", "nodes": "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"},
457 define_model(this, {urlRoot: USER_API,
458 relatedCollections: {"slicePrivileges": "user", "slices": "owner", "userDeployments": "user"},
459 foreignCollections: ["sites"],
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"],
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"],
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"],
481 define_model(this, {urlRoot: IMAGE_API,
484 listFields: ["backend_status", "id", "name", "disk_format", "container_format", "path"],
485 detailFields: ["backend_status", "name", "disk_format", "admin_tenant"],
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"],
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"],
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"],
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"],
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"],
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"],
530 define_model(this, {urlRoot: FLAVOR_API,
532 listFields: ["backend_status", "id", "name", "flavor", "order", "default"],
533 detailFields: ["backend_status", "name", "description", "flavor", "order", "default"],
534 inputType: {"default": "checkbox"},
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"});
544 this.listObjects = function() { return this.allCollectionNames; };
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;
553 if (collection.failedLoad) {
554 stats["failedLoad"] = stats["failedLoad"] + 1;
556 if (collection.startedLoad) {
557 stats["startedLoad"] = stats["startedLoad"] + 1;
560 stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
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
580 return cookieValue;
\r
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
590 return _sync(method, model, options);
\r