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", "sliceDeployments": "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: SLICEDEPLOYMENT_API,
435 foreignCollections: ["slices", "deployments"],
436 modelName: "sliceDeployment",
437 foreignFields: {"slice": "slices", "deployment": "deployments"},
438 listFields: ["backend_status", "id", "slice", "deployment", "tenant_id"],
439 detailFields: ["backend_status", "slice", "deployment", "tenant_id"],
442 define_model(this, {urlRoot: SLICEPRIVILEGE_API,
443 foreignCollections: ["slices", "users", "sliceRoles"],
444 modelName: "slicePrivilege",
445 foreignFields: {"user": "users", "slice": "slices", "role": "sliceRoles"},
446 listFields: ["backend_status", "id", "user", "slice", "role"],
447 detailFields: ["backend_status", "user", "slice", "role"],
450 define_model(this, {urlRoot: SLICEROLE_API,
451 modelName: "sliceRole",
452 listFields: ["backend_status", "id", "role"],
453 detailFields: ["backend_status", "role"],
456 define_model(this, {urlRoot: NODE_API,
457 foreignCollections: ["sites", "deployments"],
459 foreignFields: {"site": "sites", "deployment": "deployments"},
460 listFields: ["backend_status", "id", "name", "site", "deployment"],
461 detailFields: ["backend_status", "name", "site", "deployment"],
464 define_model(this, {urlRoot: SITE_API,
465 relatedCollections: {"users": "site", "slices": "site", "nodes": "site"},
467 listFields: ["backend_status", "id", "name", "site_url", "enabled", "login_base", "is_public", "abbreviated_name"],
468 detailFields: ["backend_status", "name", "abbreviated_name", "url", "enabled", "is_public", "login_base"],
469 inputType: {"enabled": "checkbox", "is_public": "checkbox"},
472 define_model(this, {urlRoot: USER_API,
473 relatedCollections: {"slicePrivileges": "user", "slices": "owner", "userDeployments": "user"},
474 foreignCollections: ["sites"],
476 foreignFields: {"site": "sites"},
477 listFields: ["backend_status", "id", "username", "firstname", "lastname", "phone", "user_url", "site"],
478 detailFields: ["backend_status", "username", "firstname", "lastname", "phone", "user_url", "site"],
481 define_model(this, {urlRoot: USERDEPLOYMENT_API,
482 foreignCollections: ["users","deployments"],
483 modelName: "userDeployment",
484 foreignFields: {"deployment": "deployments", "user": "users"},
485 listFields: ["backend_status", "id", "user", "deployment", "kuser_id"],
486 detailFields: ["backend_status", "user", "deployment", "kuser_id"],
489 define_model(this, { urlRoot: DEPLOYMENT_API,
490 relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork", "networkDeployments": "deployment", "userDeployments": "deployment"},
491 m2mFields: {"flavors": "flavors", "sites": "sites", "images": "images"},
492 modelName: "deployment",
493 listFields: ["backend_status", "id", "name", "backend_type", "admin_tenant"],
494 detailFields: ["backend_status", "name", "backend_type", "admin_tenant", "flavors", "sites", "images"],
495 inputType: {"flavors": "picker", "sites": "picker", "images": "picker"},
498 define_model(this, {urlRoot: IMAGE_API,
501 listFields: ["backend_status", "id", "name", "disk_format", "container_format", "path"],
502 detailFields: ["backend_status", "name", "disk_format", "admin_tenant"],
505 define_model(this, {urlRoot: IMAGEDEPLOYMENTS_API,
506 modelName: "imageDeployment",
507 foreignCollections: ["images", "deployments"],
508 listFields: ["backend_status", "id", "image", "deployment", "glance_image_id"],
509 detailFields: ["backend_status", "image", "deployment", "glance_image_id"],
512 define_model(this, {urlRoot: NETWORKTEMPLATE_API,
513 modelName: "networkTemplate",
514 listFields: ["backend_status", "id", "name", "visibility", "translation", "sharedNetworkName", "sharedNetworkId"],
515 detailFields: ["backend_status", "name", "description", "visibility", "translation", "sharedNetworkName", "sharedNetworkId"],
518 define_model(this, {urlRoot: NETWORK_API,
519 relatedCollections: {"networkDeployments": "network", "networkSlivers": "network"},
520 foreignCollections: ["slices", "networkTemplates"],
521 modelName: "network",
522 foreignFields: {"template": "networkTemplates", "owner": "slices"},
523 listFields: ["backend_status", "id", "name", "template", "ports", "labels", "owner"],
524 detailFields: ["backend_status", "name", "template", "ports", "labels", "owner"],
527 define_model(this, {urlRoot: NETWORKSLIVER_API,
528 modelName: "networkSliver",
529 foreignFields: {"network": "networks", "sliver": "slivers"},
530 listFields: ["backend_status", "id", "network", "sliver", "ip", "port_id"],
531 detailFields: ["backend_status", "network", "sliver", "ip", "port_id"],
534 define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
535 modelName: "networkDeployment",
536 foreignFields: {"network": "networks", "deployment": "deployments"},
537 listFields: ["backend_status", "id", "network", "deployment", "net_id"],
538 detailFields: ["backend_status", "network", "deployment", "net_id"],
541 define_model(this, {urlRoot: SERVICE_API,
542 modelName: "service",
543 listFields: ["backend_status", "id", "name", "enabled", "versionNumber", "published"],
544 detailFields: ["backend_status", "name", "description", "versionNumber"],
547 define_model(this, {urlRoot: FLAVOR_API,
549 m2mFields: {"deployments": "deployments"},
550 listFields: ["backend_status", "id", "name", "flavor", "order", "default"],
551 detailFields: ["backend_status", "name", "description", "flavor", "order", "default", "deployments"],
552 inputType: {"default": "checkbox", "deployments": "picker"},
556 // XXX this really needs to somehow be combined with Slice, to avoid duplication
557 define_model(this, {urlRoot: SLICEPLUS_API,
558 relatedCollections: {'slivers': "slice"},
559 modelName: "slicePlus",
560 collectionName: "slicesPlus"});
562 this.listObjects = function() { return this.allCollectionNames; };
564 this.getCollectionStatus = function() {
565 stats = {isLoaded: 0, failedLoad: 0, startedLoad: 0};
566 for (index in this.allCollections) {
567 collection = this.allCollections[index];
568 if (collection.isLoaded) {
569 stats["isLoaded"] = stats["isLoaded"] + 1;
571 if (collection.failedLoad) {
572 stats["failedLoad"] = stats["failedLoad"] + 1;
574 if (collection.startedLoad) {
575 stats["startedLoad"] = stats["startedLoad"] + 1;
578 stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
585 function getCookie(name) {
586 var cookieValue = null;
\r
587 if (document.cookie && document.cookie != '') {
\r
588 var cookies = document.cookie.split(';');
\r
589 for (var i = 0; i < cookies.length; i++) {
\r
590 var cookie = jQuery.trim(cookies[i]);
\r
591 // Does this cookie string begin with the name we want?
\r
592 if (cookie.substring(0, name.length + 1) == (name + '=')) {
\r
593 cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
\r
598 return cookieValue;
\r
602 var _sync = Backbone.sync;
\r
603 Backbone.sync = function(method, model, options){
\r
604 options.beforeSend = function(xhr){
\r
605 var token = getCookie("csrftoken");
\r
606 xhr.setRequestHeader('X-CSRFToken', token);
\r
608 return _sync(method, model, options);
\r