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 getChoices: function(fieldName, excludeChosen) {
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])) {
90 choices.push(candidate.id);
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.
100 For now, we're calling our validator 'xosValidate' so this
101 autoamtic validation doesn't occur.
104 xosValidate: function(attrs, options) {
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;
121 // backbone.js semantics -- on successful validate, return nothing
124 /* uncommenting this would make validate() call xosValidate()
125 validate: function(attrs, options) {
126 r = this.xosValidate(attrs, options);
127 console.log("validate");
133 XOSCollection = Backbone.Collection.extend({
134 objects: function() {
135 return this.models.map(function(element) { return element.attributes; });
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
147 relatedCollections: [],
\r
148 foreignCollections: [],
\r
150 sorted: function() {
\r
151 //console.log("sorted " + this.modelName);
\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
163 comparator: function (left, right) {
\r
164 var l = this.simpleComparator(left);
\r
165 var r = this.simpleComparator(right);
\r
167 if (l === void 0) return -1;
\r
168 if (r === void 0) return 1;
\r
170 if (this.sortOrder=="desc") {
\r
171 return l < r ? 1 : l > r ? -1 : 0;
\r
173 return l < r ? -1 : l > r ? 1 : 0;
\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
185 this.trigger("fetchStateChange");
\r
186 if (options["orig_success"]) {
\r
187 options["orig_success"](collection, response, options);
\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
198 this.trigger("fetchStateChange");
\r
199 if (options["orig_failure"]) {
\r
200 options["orig_failure"](collection, response, options);
\r
204 fetch: function(options) {
\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
212 this.trigger("fetchStateChange");
\r
213 if (options == undefined) {
\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
223 startPolling: function() {
\r
224 if (!this._polling) {
\r
226 setInterval(function() { collection.fetch(); }, 10000);
232 refresh: function(refreshRelated) {
233 if (!this.fetching) {
236 if (refreshRelated) {
237 for (related in this.relatedCollections) {
238 related = xos[related];
239 if (!related.fetching) {
246 maybeFetch: function(options){
247 // Helper function to fetch only if this collection has not been fetched before.
249 // If this has already been fetched, call the success, if it exists
250 options.success && options.success();
251 console.log("alreadyFetched");
255 // when the original success function completes mark this collection as fetched
257 successWrapper = function(success){
259 self._fetched = true;
260 success && success.apply(this, arguments);
263 options.success = successWrapper(options.success);
264 console.log("call fetch");
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);
273 options.success && options.success(model);
277 model = new this.model({
281 model.fetch(options);
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.
288 Using this function is probably wrong, and I wrote
289 FilteredCompositeView() to replace it.
292 filterBy: function(fieldName, value) {
293 filtered = this.filter(function(obj) {
294 return obj.get(fieldName) == value;
296 return new this.constructor(filtered);
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 ) === '/' ) ? '' : '/' );
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 ];
311 url += 'set/' + ids.join(';') + '/';
314 url && ( url += "?no_hyperlinks=1" );
319 listMethods: function() {
321 for(var m in this) {
\r
322 if(typeof this[m] == "function") {
\r
330 function define_model(lib, attrs) {
331 modelName = attrs.modelName;
332 modelClassName = modelName;
333 collectionClassName = modelName + "Collection";
335 if (!attrs.addFields) {
336 attrs.addFields = attrs.detailFields;
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"];
345 if (!attrs.collectionName) {
346 attrs.collectionName = modelName + "s";
348 collectionName = attrs.collectionName;
355 if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "listFields", "addFields", "detailFields", "detailLinkFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections"])>=0) {
356 modelAttrs[key] = value;
357 collectionAttrs[key] = value;
359 if ($.inArray(key, ["validate", "preSave", "readOnlyFields"])) {
360 modelAttrs[key] = value;
364 if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
365 modelAttrs["defaults"] = xosdefaults[modelName];
368 if ((typeof xosvalidators !== "undefined") && xosvalidators[modelName]) {
369 modelAttrs["validators"] = xosvalidators[modelName];
372 lib[modelName] = XOSModel.extend(modelAttrs);
374 collectionAttrs["model"] = lib[modelName];
376 lib[collectionClassName] = XOSCollection.extend(collectionAttrs);
377 lib[collectionName] = new lib[collectionClassName]();
379 lib.allCollectionNames.push(collectionName);
380 lib.allCollections.push(lib[collectionName]);
384 this.allCollectionNames = [];
385 this.allCollections = [];
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.
391 this.idToName = function(id, collectionName, fieldName) {
392 linkedObject = xos[collectionName].get(id);
393 if (linkedObject == undefined) {
396 return linkedObject.attributes[fieldName];
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"},
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"); } },
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"},
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 + "_";
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"],
442 define_model(this, {urlRoot: SLICEROLE_API,
443 modelName: "sliceRole",
444 listFields: ["backend_status", "id", "role"],
445 detailFields: ["backend_status", "role"],
448 define_model(this, {urlRoot: NODE_API,
449 foreignCollections: ["sites", "deployments"],
451 foreignFields: {"site": "sites", "deployment": "deployments"},
452 listFields: ["backend_status", "id", "name", "site", "deployment"],
453 detailFields: ["backend_status", "name", "site", "deployment"],
456 define_model(this, {urlRoot: SITE_API,
457 relatedCollections: {"users": "site", "slices": "site", "nodes": "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"},
464 define_model(this, {urlRoot: USER_API,
465 relatedCollections: {"slicePrivileges": "user", "slices": "owner"},
466 foreignCollections: ["sites"],
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"],
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"},
482 define_model(this, {urlRoot: IMAGE_API,
485 listFields: ["backend_status", "id", "name", "disk_format", "container_format", "path"],
486 detailFields: ["backend_status", "name", "disk_format", "admin_tenant"],
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"],
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"],
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"],
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"],
517 define_model(this, {urlRoot: FLAVOR_API,
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"},
525 /* DELETED in site-controller branch
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"],
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"],
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"],
550 END stuff deleted in site-controller branch */
552 /* not deleted, but seems obsolete and should be replaced by ManyToMany
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"],
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"});
570 this.listObjects = function() { return this.allCollectionNames; };
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;
579 if (collection.failedLoad) {
580 stats["failedLoad"] = stats["failedLoad"] + 1;
582 if (collection.startedLoad) {
583 stats["startedLoad"] = stats["startedLoad"] + 1;
586 stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
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
606 return cookieValue;
\r
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
616 return _sync(method, model, options);
\r