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 attrs.inputType = attrs.inputType || {};
324 attrs.foreignFields = attrs.foreignFields || {};
325 attrs.readOnlyFields = attrs.readOnlyFields || [];
327 if (!attrs.collectionName) {
328 attrs.collectionName = modelName + "s";
330 collectionName = attrs.collectionName;
337 if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "addFields", "detailFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections"])>=0) {
338 modelAttrs[key] = value;
339 collectionAttrs[key] = value;
341 if ($.inArray(key, ["validate", "preSave", "readOnlyFields"])) {
342 modelAttrs[key] = value;
346 if (xosdefaults && xosdefaults[modelName]) {
347 modelAttrs["defaults"] = xosdefaults[modelName];
350 if (xosvalidators && xosvalidators[modelName]) {
351 modelAttrs["validators"] = xosvalidators[modelName];
354 lib[modelName] = XOSModel.extend(modelAttrs);
356 collectionAttrs["model"] = lib[modelName];
358 lib[collectionClassName] = XOSCollection.extend(collectionAttrs);
359 lib[collectionName] = new lib[collectionClassName]();
361 lib.allCollectionNames.push(collectionName);
362 lib.allCollections.push(lib[collectionName]);
366 this.allCollectionNames = [];
367 this.allCollections = [];
369 /* Give an id, the name of a collection, and the name of a field for models
370 within that collection, lookup the id and return the value of the field.
373 this.idToName = function(id, collectionName, fieldName) {
374 linkedObject = xos[collectionName].get(id);
375 if (linkedObject == undefined) {
378 return linkedObject.attributes[fieldName];
382 define_model(this, {urlRoot: SLIVER_API,
383 relatedCollections: {"networkSlivers": "sliver"},
384 foreignCollections: ["slices", "deployments", "images", "nodes", "users"],
385 foreignFields: {"creator": "users", "image": "images", "node": "nodes", "deploymentNetwork": "deployments", "slice": "slices"},
387 addFields: ["slice", "deploymentNetwork", "image", "node"],
388 detailFields: ["name", "instance_id", "instance_name", "slice", "deploymentNetwork", "image", "node", "creator"],
389 preSave: function() { if (!this.attributes.name && this.attributes.slice) { this.attributes.name = xos.idToName(this.attributes.slice, "slices", "name"); } },
392 define_model(this, {urlRoot: SLICE_API,
393 relatedCollections: {"slivers": "slice", "sliceDeployments": "slice", "slicePrivileges": "slice", "networks": "owner"},
394 foreignCollections: ["services", "sites"],
395 foreignFields: {"service": "services", "site": "sites"},
396 detailFields: ["name", "site", "enabled", "description", "url", "max_slivers"],
397 inputType: {"enabled": "checkbox"},
399 xosValidate: function(attrs, options) {
400 errors = XOSModel.prototype.xosValidate(this, attrs, options);
401 // validate that slice.name starts with site.login_base
402 site = attrs.site || this.site;
403 if ((site!=undefined) && (attrs.name!=undefined)) {
404 site = xos.sites.get(site);
405 if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
406 errors = errors || {};
407 errors["name"] = "must start with " + site.attributes.login_base + "_";
414 define_model(this, {urlRoot: SLICEDEPLOYMENT_API,
415 foreignCollections: ["slices", "deployments"],
416 modelName: "sliceDeployment",
417 foreignFields: {"slice": "slices", "deployment": "deployments"},
418 detailFields: ["slice", "deployment", "tenant_id"],
421 define_model(this, {urlRoot: SLICEPRIVILEGE_API,
422 foreignCollections: ["slices", "users", "sliceRoles"],
423 modelName: "slicePrivilege",
424 foreignFields: {"user": "users", "slice": "slices", "role": "sliceRoles"},
425 detailFields: ["user", "slice", "role"],
428 define_model(this, {urlRoot: SLICEROLE_API,
429 modelName: "sliceRole",
430 detailFields: ["role"],
433 define_model(this, {urlRoot: NODE_API,
434 foreignCollections: ["sites", "deployments"],
436 foreignFields: {"site": "sites", "deployment": "deployments"},
437 detailFields: ["name", "site", "deployment"],
440 define_model(this, {urlRoot: SITE_API,
441 relatedCollections: {"users": "site", "slices": "site", "nodes": "site"},
443 detailFields: ["name", "abbreviated_name", "url", "enabled", "is_public", "login_base"],
444 inputType: {"enabled": "checkbox", "is_public": "checkbox"},
447 define_model(this, {urlRoot: USER_API,
448 relatedCollections: {"slicePrivileges": "user", "slices": "owner", "userDeployments": "user"},
449 foreignCollections: ["sites"],
451 foreignFields: {"site": "sites"},
452 detailFields: ["username", "firstname", "lastname", "phone", "user_url", "site"],
455 define_model(this, {urlRoot: USERDEPLOYMENT_API,
456 foreignCollections: ["users","deployments"],
457 modelName: "userDeployment",
458 foreignFields: {"deployment": "deployments", "user": "users"},
459 detailFields: ["user", "deployment", "kuser_id"],
462 define_model(this, { urlRoot: DEPLOYMENT_API,
463 relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork", "networkDeployments": "deployment", "userDeployments": "deployment"},
464 modelName: "deployment",
465 detailFields: ["name", "backend_type", "admin_tenant"],
468 define_model(this, {urlRoot: IMAGE_API,
471 detailFields: ["name", "disk_format", "admin_tenant"],
474 define_model(this, {urlRoot: NETWORKTEMPLATE_API,
475 modelName: "networkTemplate",
476 detailFields: ["name", "description", "visibility", "translation", "sharedNetworkName", "sharedNetworkId"],
479 define_model(this, {urlRoot: NETWORK_API,
480 relatedCollections: {"networkDeployments": "network", "networkSlivers": "network"},
481 foreignCollections: ["slices", "networkTemplates"],
482 modelName: "network",
483 foreignFields: {"template": "networkTemplates", "owner": "slices"},
484 detailFields: ["name", "template", "ports", "labels", "owner"],
487 define_model(this, {urlRoot: NETWORKSLIVER_API,
488 modelName: "networkSliver",
489 foreignFields: {"network": "networks", "sliver": "slivers"},
490 detailFields: ["network", "sliver", "ip", "port_id"],
493 define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
494 modelName: "networkDeployment",
495 foreignFields: {"network": "networks", "deployment": "deployments"},
496 detailFields: ["network", "deployment", "net_id"],
499 define_model(this, {urlRoot: SERVICE_API,
500 modelName: "service",
501 detailFields: ["name", "description", "versionNumber"],
505 define_model(this, {urlRoot: SLICEPLUS_API,
506 relatedCollections: {'slivers': "slice"},
507 modelName: "slicePlus",
508 collectionName: "slicesPlus"});
510 this.listObjects = function() { return this.allCollectionNames; };
512 this.getCollectionStatus = function() {
513 stats = {isLoaded: 0, failedLoad: 0, startedLoad: 0};
514 for (index in this.allCollections) {
515 collection = this.allCollections[index];
516 if (collection.isLoaded) {
517 stats["isLoaded"] = stats["isLoaded"] + 1;
519 if (collection.failedLoad) {
520 stats["failedLoad"] = stats["failedLoad"] + 1;
522 if (collection.startedLoad) {
523 stats["startedLoad"] = stats["startedLoad"] + 1;
526 stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
533 function getCookie(name) {
534 var cookieValue = null;
\r
535 if (document.cookie && document.cookie != '') {
\r
536 var cookies = document.cookie.split(';');
\r
537 for (var i = 0; i < cookies.length; i++) {
\r
538 var cookie = jQuery.trim(cookies[i]);
\r
539 // Does this cookie string begin with the name we want?
\r
540 if (cookie.substring(0, name.length + 1) == (name + '=')) {
\r
541 cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
\r
546 return cookieValue;
\r
550 var _sync = Backbone.sync;
\r
551 Backbone.sync = function(method, model, options){
\r
552 options.beforeSend = function(xhr){
\r
553 var token = getCookie("csrftoken");
\r
554 xhr.setRequestHeader('X-CSRFToken', token);
\r
556 return _sync(method, model, options);
\r