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/";
21 CONTROLLER_API = "/plstackapi/controllers/";
23 /* changed as a side effect of the big rename
24 SLICEDEPLOYMENT_API = "/plstackapi/slice_deployments/";
25 USERDEPLOYMENT_API = "/plstackapi/user_deployments/";
28 SLICEDEPLOYMENT_API = "/plstackapi/slicedeployments/";
29 USERDEPLOYMENT_API = "/plstackapi/userdeployments/";
31 SLICEPLUS_API = "/xoslib/slicesplus/";
33 XOSModel = Backbone.Model.extend({
34 /* from backbone-tastypie.js */
35 //idAttribute: 'resource_uri',
37 /* from backbone-tastypie.js */
39 var url = this.attributes.resource_uri;
43 url = this.urlRoot + this.id;
45 // this happens when creating a new model.
51 // XXX I'm not sure this does anything useful
52 url = ( _.isFunction( this.collection.url ) ? this.collection.url() : this.collection.url );
53 url = url || this.urlRoot;
56 // remove any existing query parameters
57 url && ( url.indexOf("?") > -1 ) && ( url = url.split("?")[0] );
59 url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
61 url && ( url += "?no_hyperlinks=1" );
66 listMethods: function() {
68 for(var m in this) {
\r
69 if(typeof this[m] == "function") {
\r
76 save: function(attributes, options) {
80 return Backbone.Model.prototype.save.call(this, attributes, options);
83 getChoices: function(fieldName, excludeChosen) {
85 if (fieldName in this.m2mFields) {
86 for (index in xos[this.m2mFields[fieldName]].models) {
87 candidate = xos[this.m2mFields[fieldName]].models[index];
88 if (excludeChosen && idInArray(candidate.id, this.attributes[fieldName])) {
91 choices.push(candidate.id);
97 /* If a 'validate' method is supplied, then it will be called
98 automatically on save. Unfortunately, save calls neither the
99 'error' nor the 'success' callback if the validator fails.
101 For now, we're calling our validator 'xosValidate' so this
102 autoamtic validation doesn't occur.
105 xosValidate: function(attrs, options) {
108 _.each(this.validators, function(validatorList, fieldName) {
109 _.each(validatorList, function(validator) {
110 if (fieldName in attrs) {
111 validatorResult = validateField(validator, attrs[fieldName], this)
112 if (validatorResult != true) {
113 errors[fieldName] = validatorResult;
122 // backbone.js semantics -- on successful validate, return nothing
125 /* uncommenting this would make validate() call xosValidate()
126 validate: function(attrs, options) {
127 r = this.xosValidate(attrs, options);
128 console.log("validate");
134 XOSCollection = Backbone.Collection.extend({
135 objects: function() {
136 return this.models.map(function(element) { return element.attributes; });
139 initialize: function(){
140 this.isLoaded = false;
141 this.failedLoad = false;
142 this.startedLoad = false;
143 this.sortVar = 'name';
\r
144 this.sortOrder = 'asc';
\r
145 this.on( "sort", this.sorted );
\r
148 relatedCollections: [],
\r
149 foreignCollections: [],
\r
151 sorted: function() {
\r
152 //console.log("sorted " + this.modelName);
\r
155 simpleComparator: function( model ){
\r
156 parts=this.sortVar.split(".");
\r
157 result = model.get(parts[0]);
\r
158 for (index=1; index<parts.length; ++index) {
\r
159 result=result[parts[index]];
\r
164 comparator: function (left, right) {
\r
165 var l = this.simpleComparator(left);
\r
166 var r = this.simpleComparator(right);
\r
168 if (l === void 0) return -1;
\r
169 if (r === void 0) return 1;
\r
171 if (this.sortOrder=="desc") {
\r
172 return l < r ? 1 : l > r ? -1 : 0;
\r
174 return l < r ? -1 : l > r ? 1 : 0;
\r
178 fetchSuccess: function(collection, response, options) {
\r
179 //console.log("fetch succeeded " + collection.modelName);
\r
180 this.failedLoad = false;
\r
181 this.fetching = false;
\r
182 if (!this.isLoaded) {
\r
183 this.isLoaded = true;
\r
184 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
186 this.trigger("fetchStateChange");
\r
187 if (options["orig_success"]) {
\r
188 options["orig_success"](collection, response, options);
\r
192 fetchFailure: function(collection, response, options) {
\r
193 //console.log("fetch failed " + collection.modelName);
\r
194 this.fetching = false;
\r
195 if ((!this.isLoaded) && (!this.failedLoad)) {
\r
196 this.failedLoad=true;
\r
197 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
199 this.trigger("fetchStateChange");
\r
200 if (options["orig_failure"]) {
\r
201 options["orig_failure"](collection, response, options);
\r
205 fetch: function(options) {
\r
207 this.fetching=true;
\r
208 //console.log("fetch " + this.modelName);
\r
209 if (!this.startedLoad) {
\r
210 this.startedLoad=true;
\r
211 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
213 this.trigger("fetchStateChange");
\r
214 if (options == undefined) {
\r
217 options["orig_success"] = options["success"];
\r
218 options["orig_failure"] = options["failure"];
\r
219 options["success"] = function(collection, response, options) { self.fetchSuccess.call(self, collection, response, options); };
\r
220 options["failure"] = this.fetchFailure;
\r
221 Backbone.Collection.prototype.fetch.call(this, options);
\r
224 startPolling: function() {
\r
225 if (!this._polling) {
\r
227 setInterval(function() { collection.fetch(); }, 10000);
233 refresh: function(refreshRelated) {
234 if (!this.fetching) {
237 if (refreshRelated) {
238 for (related in this.relatedCollections) {
239 related = xos[related];
240 if (!related.fetching) {
247 maybeFetch: function(options){
248 // Helper function to fetch only if this collection has not been fetched before.
250 // If this has already been fetched, call the success, if it exists
251 options.success && options.success();
252 console.log("alreadyFetched");
256 // when the original success function completes mark this collection as fetched
258 successWrapper = function(success){
260 self._fetched = true;
261 success && success.apply(this, arguments);
264 options.success = successWrapper(options.success);
265 console.log("call fetch");
269 getOrFetch: function(id, options){
270 // Helper function to use this collection as a cache for models on the server
271 var model = this.get(id);
274 options.success && options.success(model);
278 model = new this.model({
282 model.fetch(options);
285 /* filterBy: note that this yields a new collection. If you pass that
286 collection to a CompositeView, then the CompositeView won't get
287 any events that trigger on the original collection.
289 Using this function is probably wrong, and I wrote
290 FilteredCompositeView() to replace it.
293 filterBy: function(fieldName, value) {
294 filtered = this.filter(function(obj) {
295 return obj.get(fieldName) == value;
297 return new this.constructor(filtered);
300 /* from backbone-tastypie.js */
301 url: function( models ) {
302 var url = this.urlRoot || ( models && models.length && models[0].urlRoot );
303 url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
305 // Build a url to retrieve a set of models. This assume the last part of each model's idAttribute
306 // (set to 'resource_uri') contains the model's id.
307 if ( models && models.length ) {
308 var ids = _.map( models, function( model ) {
309 var parts = _.compact( model.id.split('/') );
310 return parts[ parts.length - 1 ];
312 url += 'set/' + ids.join(';') + '/';
315 url && ( url += "?no_hyperlinks=1" );
320 listMethods: function() {
322 for(var m in this) {
\r
323 if(typeof this[m] == "function") {
\r
331 function define_model(lib, attrs) {
332 modelName = attrs.modelName;
333 modelClassName = modelName;
334 collectionClassName = modelName + "Collection";
336 if (!attrs.addFields) {
337 attrs.addFields = attrs.detailFields;
340 attrs.inputType = attrs.inputType || {};
341 attrs.foreignFields = attrs.foreignFields || {};
342 attrs.m2mFields = attrs.m2mFields || {};
343 attrs.readOnlyFields = attrs.readOnlyFields || [];
344 attrs.detailLinkFields = attrs.detailLinkFields || ["id","name"];
346 if (!attrs.collectionName) {
347 attrs.collectionName = modelName + "s";
349 collectionName = attrs.collectionName;
356 if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "listFields", "addFields", "detailFields", "detailLinkFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections"])>=0) {
357 modelAttrs[key] = value;
358 collectionAttrs[key] = value;
360 if ($.inArray(key, ["validate", "preSave", "readOnlyFields"])) {
361 modelAttrs[key] = value;
365 if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
366 modelAttrs["defaults"] = xosdefaults[modelName];
369 if ((typeof xosvalidators !== "undefined") && xosvalidators[modelName]) {
370 modelAttrs["validators"] = xosvalidators[modelName];
373 lib[modelName] = XOSModel.extend(modelAttrs);
375 collectionAttrs["model"] = lib[modelName];
377 lib[collectionClassName] = XOSCollection.extend(collectionAttrs);
378 lib[collectionName] = new lib[collectionClassName]();
380 lib.allCollectionNames.push(collectionName);
381 lib.allCollections.push(lib[collectionName]);
385 this.allCollectionNames = [];
386 this.allCollections = [];
388 /* Give an id, the name of a collection, and the name of a field for models
389 within that collection, lookup the id and return the value of the field.
392 this.idToName = function(id, collectionName, fieldName) {
393 linkedObject = xos[collectionName].get(id);
394 if (linkedObject == undefined) {
397 return linkedObject.attributes[fieldName];
401 /* defining the models
403 modelName - name of the model.
405 relatedCollections - collections which should be drawn as an inline
406 list when the detail view is displayed.
407 Format: <collection>:<collectionFieldName> where
408 <collectionFieldName> is the name of the field
409 in the collection that points back to the
410 collection in the detail view.
412 foreignCollections - collections which are used in idToName() calls
413 when presenting the data to the user. Used to
414 create a listento event. Somewhat
415 redundant with foreignFields.
417 foreignFields - <localFieldName>:<collection>. Used to
418 automatically map ids into humanReadableNames
419 when presenting data to the user.
421 m2mfields - <localFieldName>:<colleciton>. Used to
422 populate choices in picker lists. Simalar to
425 listFields - fields to display in lists
427 detailFields - fields to display in detail views
429 addFields - fields to display in popup add windows
431 inputType - by default, "detailFields" will be displayed
432 as text input controls. This will let you display
433 a checkbox or a picker instead.
436 define_model(this, {urlRoot: SLIVER_API,
437 relatedCollections: {"networkSlivers": "sliver"},
438 foreignCollections: ["slices", "deployments", "images", "nodes", "users", "flavors"],
439 foreignFields: {"creator": "users", "image": "images", "node": "nodes", "deploymentNetwork": "deployments", "slice": "slices", "flavor": "flavors"},
441 listFields: ["backend_status", "id", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "image", "node", "flavor"],
442 addFields: ["slice", "deploymentNetwork", "flavor", "image", "node"],
443 detailFields: ["backend_status", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "flavor", "image", "node", "creator"],
444 preSave: function() { if (!this.attributes.name && this.attributes.slice) { this.attributes.name = xos.idToName(this.attributes.slice, "slices", "name"); } },
447 define_model(this, {urlRoot: SLICE_API,
448 relatedCollections: {"slivers": "slice", "slicePrivileges": "slice", "networks": "owner"},
449 foreignCollections: ["services", "sites"],
450 foreignFields: {"service": "services", "site": "sites"},
451 listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
452 detailFields: ["backend_status", "name", "site", "enabled", "description", "slice_url", "max_slivers"],
453 inputType: {"enabled": "checkbox"},
455 xosValidate: function(attrs, options) {
456 errors = XOSModel.prototype.xosValidate(this, attrs, options);
457 // validate that slice.name starts with site.login_base
458 site = attrs.site || this.site;
459 if ((site!=undefined) && (attrs.name!=undefined)) {
460 site = xos.sites.get(site);
461 if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
462 errors = errors || {};
463 errors["name"] = "must start with " + site.attributes.login_base + "_";
470 define_model(this, {urlRoot: SLICEPRIVILEGE_API,
471 foreignCollections: ["slices", "users", "sliceRoles"],
472 modelName: "slicePrivilege",
473 foreignFields: {"user": "users", "slice": "slices", "role": "sliceRoles"},
474 listFields: ["backend_status", "id", "user", "slice", "role"],
475 detailFields: ["backend_status", "user", "slice", "role"],
478 define_model(this, {urlRoot: SLICEROLE_API,
479 modelName: "sliceRole",
480 listFields: ["backend_status", "id", "role"],
481 detailFields: ["backend_status", "role"],
484 define_model(this, {urlRoot: NODE_API,
485 foreignCollections: ["sites", "deployments"],
487 foreignFields: {"site": "sites", "deployment": "deployments"},
488 listFields: ["backend_status", "id", "name", "site", "deployment"],
489 detailFields: ["backend_status", "name", "site", "deployment"],
492 define_model(this, {urlRoot: SITE_API,
493 relatedCollections: {"users": "site", "slices": "site", "nodes": "site"},
495 listFields: ["backend_status", "id", "name", "site_url", "enabled", "login_base", "is_public", "abbreviated_name"],
496 detailFields: ["backend_status", "name", "abbreviated_name", "url", "enabled", "is_public", "login_base"],
497 inputType: {"enabled": "checkbox", "is_public": "checkbox"},
500 define_model(this, {urlRoot: USER_API,
501 relatedCollections: {"slicePrivileges": "user", "slices": "owner"},
502 foreignCollections: ["sites"],
504 foreignFields: {"site": "sites"},
505 listFields: ["backend_status", "id", "username", "firstname", "lastname", "phone", "user_url", "site"],
506 detailFields: ["backend_status", "username", "firstname", "lastname", "phone", "user_url", "site"],
509 define_model(this, { urlRoot: DEPLOYMENT_API,
510 relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork"},
511 m2mFields: {"flavors": "flavors", "sites": "sites", "images": "images"},
512 modelName: "deployment",
513 listFields: ["backend_status", "id", "name", "backend_type", "admin_tenant"],
514 detailFields: ["backend_status", "name", "backend_type", "admin_tenant", "flavors", "sites", "images"],
515 inputType: {"flavors": "picker", "sites": "picker", "images": "picker"},
518 define_model(this, {urlRoot: IMAGE_API,
521 listFields: ["backend_status", "id", "name", "disk_format", "container_format", "path"],
522 detailFields: ["backend_status", "name", "disk_format", "admin_tenant"],
525 define_model(this, {urlRoot: NETWORKTEMPLATE_API,
526 modelName: "networkTemplate",
527 listFields: ["backend_status", "id", "name", "visibility", "translation", "sharedNetworkName", "sharedNetworkId"],
528 detailFields: ["backend_status", "name", "description", "visibility", "translation", "sharedNetworkName", "sharedNetworkId"],
531 define_model(this, {urlRoot: NETWORK_API,
532 relatedCollections: {"networkSlivers": "network"},
533 foreignCollections: ["slices", "networkTemplates"],
534 modelName: "network",
535 foreignFields: {"template": "networkTemplates", "owner": "slices"},
536 listFields: ["backend_status", "id", "name", "template", "ports", "labels", "owner"],
537 detailFields: ["backend_status", "name", "template", "ports", "labels", "owner"],
540 define_model(this, {urlRoot: NETWORKSLIVER_API,
541 modelName: "networkSliver",
542 foreignFields: {"network": "networks", "sliver": "slivers"},
543 listFields: ["backend_status", "id", "network", "sliver", "ip", "port_id"],
544 detailFields: ["backend_status", "network", "sliver", "ip", "port_id"],
547 define_model(this, {urlRoot: SERVICE_API,
548 modelName: "service",
549 listFields: ["backend_status", "id", "name", "enabled", "versionNumber", "published"],
550 detailFields: ["backend_status", "name", "description", "versionNumber"],
553 define_model(this, {urlRoot: FLAVOR_API,
555 m2mFields: {"deployments": "deployments"},
556 listFields: ["backend_status", "id", "name", "flavor", "order", "default"],
557 detailFields: ["backend_status", "name", "description", "flavor", "order", "default", "deployments"],
558 inputType: {"default": "checkbox", "deployments": "picker"},
561 define_model(this, {urlRoot: CONTROLLER_API,
562 modelName: "controller",
563 listFields: ["backend_status", "id", "name", "version", "backend_type"],
564 detailFields: ["backend_status", "id", "name", "version", "backend_type", "auth_url", "admin_user", "admin_password", "admin_tenant"],
567 /* DELETED in site-controller branch
569 define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
570 modelName: "networkDeployment",
571 foreignFields: {"network": "networks", "deployment": "deployments"},
572 listFields: ["backend_status", "id", "network", "deployment", "net_id"],
573 detailFields: ["backend_status", "network", "deployment", "net_id"],
576 define_model(this, {urlRoot: SLICEDEPLOYMENT_API,
577 foreignCollections: ["slices", "deployments"],
578 modelName: "sliceDeployment",
579 foreignFields: {"slice": "slices", "deployment": "deployments"},
580 listFields: ["backend_status", "id", "slice", "deployment", "tenant_id"],
581 detailFields: ["backend_status", "slice", "deployment", "tenant_id"],
584 define_model(this, {urlRoot: USERDEPLOYMENT_API,
585 foreignCollections: ["users","deployments"],
586 modelName: "userDeployment",
587 foreignFields: {"deployment": "deployments", "user": "users"},
588 listFields: ["backend_status", "id", "user", "deployment", "kuser_id"],
589 detailFields: ["backend_status", "user", "deployment", "kuser_id"],
592 END stuff deleted in site-controller branch */
594 /* not deleted, but obsolete since it has degenerated to a ManyToMany with no other fields
596 define_model(this, {urlRoot: IMAGEDEPLOYMENTS_API,
597 modelName: "imageDeployment",
598 foreignCollections: ["images", "deployments"],
599 listFields: ["backend_status", "id", "image", "deployment", "glance_image_id"],
600 detailFields: ["backend_status", "image", "deployment", "glance_image_id"],
606 // XXX this really needs to somehow be combined with Slice, to avoid duplication
607 define_model(this, {urlRoot: SLICEPLUS_API,
608 relatedCollections: {'slivers': "slice"},
609 modelName: "slicePlus",
610 collectionName: "slicesPlus"});
612 this.listObjects = function() { return this.allCollectionNames; };
614 this.getCollectionStatus = function() {
615 stats = {isLoaded: 0, failedLoad: 0, startedLoad: 0};
616 for (index in this.allCollections) {
617 collection = this.allCollections[index];
618 if (collection.isLoaded) {
619 stats["isLoaded"] = stats["isLoaded"] + 1;
621 if (collection.failedLoad) {
622 stats["failedLoad"] = stats["failedLoad"] + 1;
624 if (collection.startedLoad) {
625 stats["startedLoad"] = stats["startedLoad"] + 1;
628 stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
635 function getCookie(name) {
636 var cookieValue = null;
\r
637 if (document.cookie && document.cookie != '') {
\r
638 var cookies = document.cookie.split(';');
\r
639 for (var i = 0; i < cookies.length; i++) {
\r
640 var cookie = jQuery.trim(cookies[i]);
\r
641 // Does this cookie string begin with the name we want?
\r
642 if (cookie.substring(0, name.length + 1) == (name + '=')) {
\r
643 cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
\r
648 return cookieValue;
\r
652 var _sync = Backbone.sync;
\r
653 Backbone.sync = function(method, model, options){
\r
654 options.beforeSend = function(xhr){
\r
655 var token = getCookie("csrftoken");
\r
656 xhr.setRequestHeader('X-CSRFToken', token);
\r
658 return _sync(method, model, options);
\r