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 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/";
20 /* changed as a side effect of the big rename
21 SLICEDEPLOYMENT_API = "/plstackapi/slice_deployments/";
22 USERDEPLOYMENT_API = "/plstackapi/user_deployments/";
25 SLICEDEPLOYMENT_API = "/plstackapi/slicedeployments/";
26 USERDEPLOYMENT_API = "/plstackapi/userdeployments/";
28 SLICEPLUS_API = "/xoslib/slicesplus/";
30 XOSModel = Backbone.Model.extend({
31 /* from backbone-tastypie.js */
32 //idAttribute: 'resource_uri',
34 /* from backbone-tastypie.js */
36 var url = this.attributes.resource_uri;
40 url = this.urlRoot + this.id;
42 // this happens when creating a new model.
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;
53 // remove any existing query parameters
54 url && ( url.indexOf("?") > -1 ) && ( url = url.split("?")[0] );
56 url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
58 url && ( url += "?no_hyperlinks=1" );
63 listMethods: function() {
65 for(var m in this) {
\r
66 if(typeof this[m] == "function") {
\r
73 save: function(attributes, options) {
77 return Backbone.Model.prototype.save.call(this, attributes, options);
80 /* If a 'validate' method is supplied, then it will be called
81 automatically on save. Unfortunately, save calls neither the
82 'error' nor the 'success' callback if the validator fails.
84 For now, we're calling our validator 'xosValidate' so this
85 autoamtic validation doesn't occur.
88 xosValidate: function(attrs, options) {
91 _.each(this.validators, function(validatorList, fieldName) {
92 _.each(validatorList, function(validator) {
93 if (fieldName in attrs) {
94 validatorResult = validateField(validator, attrs[fieldName], this)
95 if (validatorResult != true) {
96 errors[fieldName] = validatorResult;
105 // backbone.js semantics -- on successful validate, return nothing
108 /* uncommenting this would make validate() call xosValidate()
109 validate: function(attrs, options) {
110 r = this.xosValidate(attrs, options);
111 console.log("validate");
117 XOSCollection = Backbone.Collection.extend({
118 objects: function() {
119 return this.models.map(function(element) { return element.attributes; });
122 initialize: function(){
123 this.isLoaded = false;
124 this.failedLoad = false;
125 this.startedLoad = false;
126 this.sortVar = 'name';
\r
127 this.sortOrder = 'asc';
\r
128 this.on( "sort", this.sorted );
\r
131 relatedCollections: [],
\r
132 foreignCollections: [],
\r
134 sorted: function() {
\r
135 //console.log("sorted " + this.modelName);
\r
138 simpleComparator: function( model ){
\r
139 parts=this.sortVar.split(".");
\r
140 result = model.get(parts[0]);
\r
141 for (index=1; index<parts.length; ++index) {
\r
142 result=result[parts[index]];
\r
147 comparator: function (left, right) {
\r
148 var l = this.simpleComparator(left);
\r
149 var r = this.simpleComparator(right);
\r
151 if (l === void 0) return -1;
\r
152 if (r === void 0) return 1;
\r
154 if (this.sortOrder=="desc") {
\r
155 return l < r ? 1 : l > r ? -1 : 0;
\r
157 return l < r ? -1 : l > r ? 1 : 0;
\r
161 fetchSuccess: function(collection, response, options) {
\r
162 //console.log("fetch succeeded " + collection.modelName);
\r
163 this.failedLoad = false;
\r
164 this.fetching = false;
\r
165 if (!this.isLoaded) {
\r
166 this.isLoaded = true;
\r
167 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
169 this.trigger("fetchStateChange");
\r
170 if (options["orig_success"]) {
\r
171 options["orig_success"](collection, response, options);
\r
175 fetchFailure: function(collection, response, options) {
\r
176 //console.log("fetch failed " + collection.modelName);
\r
177 this.fetching = false;
\r
178 if ((!this.isLoaded) && (!this.failedLoad)) {
\r
179 this.failedLoad=true;
\r
180 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
182 this.trigger("fetchStateChange");
\r
183 if (options["orig_failure"]) {
\r
184 options["orig_failure"](collection, response, options);
\r
188 fetch: function(options) {
\r
190 this.fetching=true;
\r
191 //console.log("fetch " + this.modelName);
\r
192 if (!this.startedLoad) {
\r
193 this.startedLoad=true;
\r
194 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
196 this.trigger("fetchStateChange");
\r
197 if (options == undefined) {
\r
200 options["orig_success"] = options["success"];
\r
201 options["orig_failure"] = options["failure"];
\r
202 options["success"] = function(collection, response, options) { self.fetchSuccess.call(self, collection, response, options); };
\r
203 options["failure"] = this.fetchFailure;
\r
204 Backbone.Collection.prototype.fetch.call(this, options);
\r
207 startPolling: function() {
\r
208 if (!this._polling) {
\r
210 setInterval(function() { collection.fetch(); }, 10000);
216 refresh: function(refreshRelated) {
217 if (!this.fetching) {
220 if (refreshRelated) {
221 for (related in this.relatedCollections) {
222 related = xos[related];
223 if (!related.fetching) {
230 maybeFetch: function(options){
231 // Helper function to fetch only if this collection has not been fetched before.
233 // If this has already been fetched, call the success, if it exists
234 options.success && options.success();
235 console.log("alreadyFetched");
239 // when the original success function completes mark this collection as fetched
241 successWrapper = function(success){
243 self._fetched = true;
244 success && success.apply(this, arguments);
247 options.success = successWrapper(options.success);
248 console.log("call fetch");
252 getOrFetch: function(id, options){
253 // Helper function to use this collection as a cache for models on the server
254 var model = this.get(id);
257 options.success && options.success(model);
261 model = new this.model({
265 model.fetch(options);
268 /* filterBy: note that this yields a new collection. If you pass that
269 collection to a CompositeView, then the CompositeView won't get
270 any events that trigger on the original collection.
272 Using this function is probably wrong, and I wrote
273 FilteredCompositeView() to replace it.
276 filterBy: function(fieldName, value) {
277 filtered = this.filter(function(obj) {
278 return obj.get(fieldName) == value;
280 return new this.constructor(filtered);
283 /* from backbone-tastypie.js */
284 url: function( models ) {
285 var url = this.urlRoot || ( models && models.length && models[0].urlRoot );
286 url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
288 // Build a url to retrieve a set of models. This assume the last part of each model's idAttribute
289 // (set to 'resource_uri') contains the model's id.
290 if ( models && models.length ) {
291 var ids = _.map( models, function( model ) {
292 var parts = _.compact( model.id.split('/') );
293 return parts[ parts.length - 1 ];
295 url += 'set/' + ids.join(';') + '/';
298 url && ( url += "?no_hyperlinks=1" );
303 listMethods: function() {
305 for(var m in this) {
\r
306 if(typeof this[m] == "function") {
\r
314 function define_model(lib, attrs) {
315 modelName = attrs.modelName;
316 modelClassName = modelName;
317 collectionClassName = modelName + "Collection";
319 if (!attrs.addFields) {
320 attrs.addFields = attrs.detailFields;
323 if (!attrs.inputType) {
324 attrs.inputType = {};
327 if (!attrs.foreignFields) {
328 attrs.foreignFields = {};
331 if (!attrs.collectionName) {
332 attrs.collectionName = modelName + "s";
334 collectionName = attrs.collectionName;
341 if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "addFields", "detailFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections"])>=0) {
342 modelAttrs[key] = value;
343 collectionAttrs[key] = value;
345 if ($.inArray(key, ["validate", "preSave"])) {
346 modelAttrs[key] = value;
350 if (xosdefaults && xosdefaults[modelName]) {
351 modelAttrs["defaults"] = xosdefaults[modelName];
354 if (xosvalidators && xosvalidators[modelName]) {
355 modelAttrs["validators"] = xosvalidators[modelName];
358 lib[modelName] = XOSModel.extend(modelAttrs);
360 collectionAttrs["model"] = lib[modelName];
362 lib[collectionClassName] = XOSCollection.extend(collectionAttrs);
363 lib[collectionName] = new lib[collectionClassName]();
365 lib.allCollectionNames.push(collectionName);
366 lib.allCollections.push(lib[collectionName]);
370 this.allCollectionNames = [];
371 this.allCollections = [];
373 /* Give an id, the name of a collection, and the name of a field for models
374 within that collection, lookup the id and return the value of the field.
377 this.idToName = function(id, collectionName, fieldName) {
378 linkedObject = xos[collectionName].get(id);
379 if (linkedObject == undefined) {
382 return linkedObject.attributes[fieldName];
386 define_model(this, {urlRoot: SLIVER_API,
387 relatedCollections: {"networkSlivers": "sliver"},
388 foreignCollections: ["slices", "deployments", "images", "nodes", "users"],
389 foreignFields: {"creator": "users", "image": "images", "node": "nodes", "deploymentNetwork": "deployments", "slice": "slices"},
391 addFields: ["slice", "deploymentNetwork", "image", "node"],
392 detailFields: ["name", "instance_id", "instance_name", "slice", "deploymentNetwork", "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 detailFields: ["name", "site", "enabled", "description", "url", "max_slivers"],
401 inputType: {"enabled": "checkbox"},
403 xosValidate: function(attrs, options) {
404 errors = XOSModel.prototype.xosValidate(this, attrs, options);
405 // validate that slice.name starts with site.login_base
406 site = attrs.site || this.site;
407 if ((site!=undefined) && (attrs.name!=undefined)) {
408 site = xos.sites.get(site);
409 if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
410 errors = errors || {};
411 errors["name"] = "must start with " + site.attributes.login_base + "_";
418 define_model(this, {urlRoot: SLICEDEPLOYMENT_API,
419 foreignCollections: ["slices", "deployments"],
420 modelName: "sliceDeployment",
421 foreignFields: {"slice": "slices", "deployment": "deployments"},
422 detailFields: ["slice", "deployment", "tenant_id"],
425 define_model(this, {urlRoot: SLICEPRIVILEGE_API,
426 foreignCollections: ["slices", "users", "sliceRoles"],
427 modelName: "slicePrivilege",
428 foreignFields: {"user": "users", "slice": "slices", "role": "sliceRoles"},
429 detailFields: ["user", "slice", "role"],
432 define_model(this, {urlRoot: SLICEROLE_API,
433 modelName: "sliceRole",
434 detailFields: ["role"],
437 define_model(this, {urlRoot: NODE_API,
438 foreignCollections: ["sites", "deployments"],
440 foreignFields: {"site": "sites", "deployment": "deployments"},
441 detailFields: ["name", "site", "deployment"],
444 define_model(this, {urlRoot: SITE_API,
445 relatedCollections: {"users": "site", "slices": "site", "nodes": "site"},
447 detailFields: ["name", "abbreviated_name", "url", "enabled", "is_public", "login_base"],
448 inputType: {"enabled": "checkbox", "is_public": "checkbox"},
451 define_model(this, {urlRoot: USER_API,
452 relatedCollections: {"slicePrivileges": "user", "slices": "owner", "userDeployments": "user"},
453 foreignCollections: ["sites"],
455 foreignFields: {"site": "sites"},
456 detailFields: ["username", "firstname", "lastname", "phone", "user_url", "site"],
459 define_model(this, {urlRoot: USERDEPLOYMENT_API,
460 foreignCollections: ["users","deployments"],
461 modelName: "userDeployment",
462 foreignFields: {"deployment": "deployments", "user": "users"},
463 detailFields: ["user", "deployment", "kuser_id"],
466 define_model(this, { urlRoot: DEPLOYMENT_API,
467 relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork", "networkDeployments": "deployment", "userDeployments": "deployment"},
468 modelName: "deployment",
469 detailFields: ["name", "backend_type", "admin_tenant"],
472 define_model(this, {urlRoot: IMAGE_API,
475 detailFields: ["name", "disk_format", "admin_tenant"],
478 define_model(this, {urlRoot: NETWORKTEMPLATE_API,
479 modelName: "networkTemplate",
480 detailFields: ["name", "description", "visibility", "translation", "sharedNetworkName", "sharedNetworkId"],
483 define_model(this, {urlRoot: NETWORK_API,
484 relatedCollections: {"networkDeployments": "network", "networkSlivers": "network"},
485 foreignCollections: ["slices", "networkTemplates"],
486 modelName: "network",
487 foreignFields: {"template": "networkTemplates", "owner": "slices"},
488 detailFields: ["name", "template", "ports", "labels", "owner"],
491 define_model(this, {urlRoot: NETWORKSLIVER_API,
492 modelName: "networkSliver",
493 foreignFields: {"network": "networks", "sliver": "slivers"},
494 detailFields: ["network", "sliver", "ip", "port_id"],
497 define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
498 modelName: "networkDeployment",
499 foreignFields: {"network": "networks", "deployment": "deployments"},
500 detailFields: ["network", "deployment", "net_id"],
503 define_model(this, {urlRoot: SERVICE_API,
504 modelName: "service",
505 detailFields: ["name", "description", "versionNumber"],
509 define_model(this, {urlRoot: SLICEPLUS_API,
510 relatedCollections: {'slivers': "slice"},
511 modelName: "slicePlus",
512 collectionName: "slicesPlus"});
514 this.listObjects = function() { return this.allCollectionNames; };
516 this.getCollectionStatus = function() {
517 stats = {isLoaded: 0, failedLoad: 0, startedLoad: 0};
518 for (index in this.allCollections) {
519 collection = this.allCollections[index];
520 if (collection.isLoaded) {
521 stats["isLoaded"] = stats["isLoaded"] + 1;
523 if (collection.failedLoad) {
524 stats["failedLoad"] = stats["failedLoad"] + 1;
526 if (collection.startedLoad) {
527 stats["startedLoad"] = stats["startedLoad"] + 1;
530 stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
537 function getCookie(name) {
538 var cookieValue = null;
\r
539 if (document.cookie && document.cookie != '') {
\r
540 var cookies = document.cookie.split(';');
\r
541 for (var i = 0; i < cookies.length; i++) {
\r
542 var cookie = jQuery.trim(cookies[i]);
\r
543 // Does this cookie string begin with the name we want?
\r
544 if (cookie.substring(0, name.length + 1) == (name + '=')) {
\r
545 cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
\r
550 return cookieValue;
\r
554 var _sync = Backbone.sync;
\r
555 Backbone.sync = function(method, model, options){
\r
556 options.beforeSend = function(xhr){
\r
557 var token = getCookie("csrftoken");
\r
558 xhr.setRequestHeader('X-CSRFToken', token);
\r
560 return _sync(method, model, options);
\r