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 || [];
326 attrs.detailLinkFields = attrs.detailLinkFields || ["id","name"];
328 if (!attrs.collectionName) {
329 attrs.collectionName = modelName + "s";
331 collectionName = attrs.collectionName;
338 if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "listFields", "addFields", "detailFields", "detailLinkFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections"])>=0) {
339 modelAttrs[key] = value;
340 collectionAttrs[key] = value;
342 if ($.inArray(key, ["validate", "preSave", "readOnlyFields"])) {
343 modelAttrs[key] = value;
347 if (xosdefaults && xosdefaults[modelName]) {
348 modelAttrs["defaults"] = xosdefaults[modelName];
351 if (xosvalidators && xosvalidators[modelName]) {
352 modelAttrs["validators"] = xosvalidators[modelName];
355 lib[modelName] = XOSModel.extend(modelAttrs);
357 collectionAttrs["model"] = lib[modelName];
359 lib[collectionClassName] = XOSCollection.extend(collectionAttrs);
360 lib[collectionName] = new lib[collectionClassName]();
362 lib.allCollectionNames.push(collectionName);
363 lib.allCollections.push(lib[collectionName]);
367 this.allCollectionNames = [];
368 this.allCollections = [];
370 /* Give an id, the name of a collection, and the name of a field for models
371 within that collection, lookup the id and return the value of the field.
374 this.idToName = function(id, collectionName, fieldName) {
375 linkedObject = xos[collectionName].get(id);
376 if (linkedObject == undefined) {
379 return linkedObject.attributes[fieldName];
383 define_model(this, {urlRoot: SLIVER_API,
384 relatedCollections: {"networkSlivers": "sliver"},
385 foreignCollections: ["slices", "deployments", "images", "nodes", "users"],
386 foreignFields: {"creator": "users", "image": "images", "node": "nodes", "deploymentNetwork": "deployments", "slice": "slices"},
388 listFields: ["id", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "image", "node", "flavor"],
389 addFields: ["slice", "deploymentNetwork", "image", "node"],
390 detailFields: ["name", "instance_id", "instance_name", "slice", "deploymentNetwork", "image", "node", "creator"],
391 preSave: function() { if (!this.attributes.name && this.attributes.slice) { this.attributes.name = xos.idToName(this.attributes.slice, "slices", "name"); } },
394 define_model(this, {urlRoot: SLICE_API,
395 relatedCollections: {"slivers": "slice", "sliceDeployments": "slice", "slicePrivileges": "slice", "networks": "owner"},
396 foreignCollections: ["services", "sites"],
397 foreignFields: {"service": "services", "site": "sites"},
398 listFields: ["id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
399 detailFields: ["name", "site", "enabled", "description", "slice_url", "max_slivers"],
400 inputType: {"enabled": "checkbox"},
402 xosValidate: function(attrs, options) {
403 errors = XOSModel.prototype.xosValidate(this, attrs, options);
404 // validate that slice.name starts with site.login_base
405 site = attrs.site || this.site;
406 if ((site!=undefined) && (attrs.name!=undefined)) {
407 site = xos.sites.get(site);
408 if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
409 errors = errors || {};
410 errors["name"] = "must start with " + site.attributes.login_base + "_";
417 define_model(this, {urlRoot: SLICEDEPLOYMENT_API,
418 foreignCollections: ["slices", "deployments"],
419 modelName: "sliceDeployment",
420 foreignFields: {"slice": "slices", "deployment": "deployments"},
421 listFields: ["id", "slice", "deployment", "tenant_id"],
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 listFields: ["id", "user", "slice", "role"],
430 detailFields: ["user", "slice", "role"],
433 define_model(this, {urlRoot: SLICEROLE_API,
434 modelName: "sliceRole",
435 listFields: ["id", "role"],
436 detailFields: ["role"],
439 define_model(this, {urlRoot: NODE_API,
440 foreignCollections: ["sites", "deployments"],
442 foreignFields: {"site": "sites", "deployment": "deployments"},
443 listFields: ["id", "name", "site", "deployment"],
444 detailFields: ["name", "site", "deployment"],
447 define_model(this, {urlRoot: SITE_API,
448 relatedCollections: {"users": "site", "slices": "site", "nodes": "site"},
450 listFields: ["id", "name", "site_url", "enabled", "login_base", "is_public", "abbreviated_name"],
451 detailFields: ["name", "abbreviated_name", "url", "enabled", "is_public", "login_base"],
452 inputType: {"enabled": "checkbox", "is_public": "checkbox"},
455 define_model(this, {urlRoot: USER_API,
456 relatedCollections: {"slicePrivileges": "user", "slices": "owner", "userDeployments": "user"},
457 foreignCollections: ["sites"],
459 foreignFields: {"site": "sites"},
460 listFields: ["id", "username", "firstname", "lastname", "phone", "user_url", "site"],
461 detailFields: ["username", "firstname", "lastname", "phone", "user_url", "site"],
464 define_model(this, {urlRoot: USERDEPLOYMENT_API,
465 foreignCollections: ["users","deployments"],
466 modelName: "userDeployment",
467 foreignFields: {"deployment": "deployments", "user": "users"},
468 listFields: ["id", "user", "deployment", "kuser_id"],
469 detailFields: ["user", "deployment", "kuser_id"],
472 define_model(this, { urlRoot: DEPLOYMENT_API,
473 relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork", "networkDeployments": "deployment", "userDeployments": "deployment"},
474 modelName: "deployment",
475 listFields: ["id", "name", "backend_type", "admin_tenant"],
476 detailFields: ["name", "backend_type", "admin_tenant"],
479 define_model(this, {urlRoot: IMAGE_API,
482 listFields: ["id", "name", "disk_format", "container_format", "path"],
483 detailFields: ["name", "disk_format", "admin_tenant"],
486 define_model(this, {urlRoot: NETWORKTEMPLATE_API,
487 modelName: "networkTemplate",
488 listFields: ["id", "name", "visibility", "translation", "sharedNetworkName", "sharedNetworkId"],
489 detailFields: ["name", "description", "visibility", "translation", "sharedNetworkName", "sharedNetworkId"],
492 define_model(this, {urlRoot: NETWORK_API,
493 relatedCollections: {"networkDeployments": "network", "networkSlivers": "network"},
494 foreignCollections: ["slices", "networkTemplates"],
495 modelName: "network",
496 foreignFields: {"template": "networkTemplates", "owner": "slices"},
497 listFields: ["id", "name", "template", "ports", "labels", "owner"],
498 detailFields: ["name", "template", "ports", "labels", "owner"],
501 define_model(this, {urlRoot: NETWORKSLIVER_API,
502 modelName: "networkSliver",
503 foreignFields: {"network": "networks", "sliver": "slivers"},
504 listFields: ["id", "network", "sliver", "ip", "port_id"],
505 detailFields: ["network", "sliver", "ip", "port_id"],
508 define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
509 modelName: "networkDeployment",
510 foreignFields: {"network": "networks", "deployment": "deployments"},
511 listFields: ["id", "network", "deployment", "net_id"],
512 detailFields: ["network", "deployment", "net_id"],
515 define_model(this, {urlRoot: SERVICE_API,
516 modelName: "service",
517 listFields: ["id", "name", "enabled", "versionNumber", "published"],
518 detailFields: ["name", "description", "versionNumber"],
522 // XXX this really needs to somehow be combined with Slice, to avoid duplication
523 define_model(this, {urlRoot: SLICEPLUS_API,
524 relatedCollections: {'slivers': "slice"},
525 modelName: "slicePlus",
526 collectionName: "slicesPlus"});
528 this.listObjects = function() { return this.allCollectionNames; };
530 this.getCollectionStatus = function() {
531 stats = {isLoaded: 0, failedLoad: 0, startedLoad: 0};
532 for (index in this.allCollections) {
533 collection = this.allCollections[index];
534 if (collection.isLoaded) {
535 stats["isLoaded"] = stats["isLoaded"] + 1;
537 if (collection.failedLoad) {
538 stats["failedLoad"] = stats["failedLoad"] + 1;
540 if (collection.startedLoad) {
541 stats["startedLoad"] = stats["startedLoad"] + 1;
544 stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
551 function getCookie(name) {
552 var cookieValue = null;
\r
553 if (document.cookie && document.cookie != '') {
\r
554 var cookies = document.cookie.split(';');
\r
555 for (var i = 0; i < cookies.length; i++) {
\r
556 var cookie = jQuery.trim(cookies[i]);
\r
557 // Does this cookie string begin with the name we want?
\r
558 if (cookie.substring(0, name.length + 1) == (name + '=')) {
\r
559 cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
\r
564 return cookieValue;
\r
568 var _sync = Backbone.sync;
\r
569 Backbone.sync = function(method, model, options){
\r
570 options.beforeSend = function(xhr){
\r
571 var token = getCookie("csrftoken");
\r
572 xhr.setRequestHeader('X-CSRFToken', token);
\r
574 return _sync(method, model, options);
\r