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 SITEDEPLOYMENT_API = "/plstackapi/sitedeployments/";
10 USER_API = "/plstackapi/users/";
11 USERDEPLOYMENT_API = "/plstackapi/user_deployments/";
12 DEPLOYMENT_API = "/plstackapi/deployments/";
13 IMAGE_API = "/plstackapi/images/";
14 IMAGEDEPLOYMENTS_API = "/plstackapi/imagedeployments/";
15 NETWORKTEMPLATE_API = "/plstackapi/networktemplates/";
16 NETWORK_API = "/plstackapi/networks/";
17 NETWORKSLIVER_API = "/plstackapi/networkslivers/";
18 SERVICE_API = "/plstackapi/services/";
19 SLICEPRIVILEGE_API = "/plstackapi/slice_privileges/";
20 NETWORKDEPLOYMENT_API = "/plstackapi/networkdeployments/";
21 FLAVOR_API = "/plstackapi/flavors/";
22 CONTROLLER_API = "/plstackapi/controllers/";
23 CONTROLLERSITEDEPLOYMENT_API = "/plstackapi/controllersitedeploymentses";
25 /* changed as a side effect of the big rename
26 SLICEDEPLOYMENT_API = "/plstackapi/slice_deployments/";
27 USERDEPLOYMENT_API = "/plstackapi/user_deployments/";
30 SLICEDEPLOYMENT_API = "/plstackapi/slicedeployments/";
31 USERDEPLOYMENT_API = "/plstackapi/userdeployments/";
33 SLICEPLUS_API = "/xoslib/slicesplus/";
35 XOSModel = Backbone.Model.extend({
36 /* from backbone-tastypie.js */
37 //idAttribute: 'resource_uri',
39 /* from backbone-tastypie.js */
41 var url = this.attributes.resource_uri;
45 url = this.urlRoot + this.id;
47 // this happens when creating a new model.
53 // XXX I'm not sure this does anything useful
54 url = ( _.isFunction( this.collection.url ) ? this.collection.url() : this.collection.url );
55 url = url || this.urlRoot;
58 // remove any existing query parameters
59 url && ( url.indexOf("?") > -1 ) && ( url = url.split("?")[0] );
61 url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
63 url && ( url += "?no_hyperlinks=1" );
68 listMethods: function() {
70 for(var m in this) {
\r
71 if(typeof this[m] == "function") {
\r
78 save: function(attributes, options) {
82 return Backbone.Model.prototype.save.call(this, attributes, options);
85 getChoices: function(fieldName, excludeChosen) {
87 if (fieldName in this.m2mFields) {
88 for (index in xos[this.m2mFields[fieldName]].models) {
89 candidate = xos[this.m2mFields[fieldName]].models[index];
90 if (excludeChosen && idInArray(candidate.id, this.attributes[fieldName])) {
93 choices.push(candidate.id);
99 /* If a 'validate' method is supplied, then it will be called
100 automatically on save. Unfortunately, save calls neither the
101 'error' nor the 'success' callback if the validator fails.
103 For now, we're calling our validator 'xosValidate' so this
104 autoamtic validation doesn't occur.
107 xosValidate: function(attrs, options) {
110 _.each(this.validators, function(validatorList, fieldName) {
111 _.each(validatorList, function(validator) {
112 if (fieldName in attrs) {
113 validatorResult = validateField(validator, attrs[fieldName], this)
114 if (validatorResult != true) {
115 errors[fieldName] = validatorResult;
124 // backbone.js semantics -- on successful validate, return nothing
127 /* uncommenting this would make validate() call xosValidate()
128 validate: function(attrs, options) {
129 r = this.xosValidate(attrs, options);
130 console.log("validate");
136 XOSCollection = Backbone.Collection.extend({
137 objects: function() {
138 return this.models.map(function(element) { return element.attributes; });
141 initialize: function(){
142 this.isLoaded = false;
143 this.failedLoad = false;
144 this.startedLoad = false;
145 this.sortVar = 'name';
\r
146 this.sortOrder = 'asc';
\r
147 this.on( "sort", this.sorted );
\r
150 relatedCollections: [],
\r
151 foreignCollections: [],
\r
153 sorted: function() {
\r
154 //console.log("sorted " + this.modelName);
\r
157 simpleComparator: function( model ){
\r
158 parts=this.sortVar.split(".");
\r
159 result = model.get(parts[0]);
\r
160 for (index=1; index<parts.length; ++index) {
\r
161 result=result[parts[index]];
\r
166 comparator: function (left, right) {
\r
167 var l = this.simpleComparator(left);
\r
168 var r = this.simpleComparator(right);
\r
170 if (l === void 0) return -1;
\r
171 if (r === void 0) return 1;
\r
173 if (this.sortOrder=="desc") {
\r
174 return l < r ? 1 : l > r ? -1 : 0;
\r
176 return l < r ? -1 : l > r ? 1 : 0;
\r
180 fetchSuccess: function(collection, response, options) {
\r
181 //console.log("fetch succeeded " + collection.modelName);
\r
182 this.failedLoad = false;
\r
183 this.fetching = false;
\r
184 if (!this.isLoaded) {
\r
185 this.isLoaded = true;
\r
186 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
188 this.trigger("fetchStateChange");
\r
189 if (options["orig_success"]) {
\r
190 options["orig_success"](collection, response, options);
\r
194 fetchFailure: function(collection, response, options) {
\r
195 //console.log("fetch failed " + collection.modelName);
\r
196 this.fetching = false;
\r
197 if ((!this.isLoaded) && (!this.failedLoad)) {
\r
198 this.failedLoad=true;
\r
199 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
201 this.trigger("fetchStateChange");
\r
202 if (options["orig_failure"]) {
\r
203 options["orig_failure"](collection, response, options);
\r
207 fetch: function(options) {
\r
209 this.fetching=true;
\r
210 //console.log("fetch " + this.modelName);
\r
211 if (!this.startedLoad) {
\r
212 this.startedLoad=true;
\r
213 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
215 this.trigger("fetchStateChange");
\r
216 if (options == undefined) {
\r
219 options["orig_success"] = options["success"];
\r
220 options["orig_failure"] = options["failure"];
\r
221 options["success"] = function(collection, response, options) { self.fetchSuccess.call(self, collection, response, options); };
\r
222 options["failure"] = this.fetchFailure;
\r
223 Backbone.Collection.prototype.fetch.call(this, options);
\r
226 startPolling: function() {
\r
227 if (!this._polling) {
\r
229 setInterval(function() { collection.fetch(); }, 10000);
235 refresh: function(refreshRelated) {
236 if (!this.fetching) {
239 if (refreshRelated) {
240 for (related in this.relatedCollections) {
241 related = xos[related];
242 if (!related.fetching) {
249 maybeFetch: function(options){
250 // Helper function to fetch only if this collection has not been fetched before.
252 // If this has already been fetched, call the success, if it exists
253 options.success && options.success();
254 console.log("alreadyFetched");
258 // when the original success function completes mark this collection as fetched
260 successWrapper = function(success){
262 self._fetched = true;
263 success && success.apply(this, arguments);
266 options.success = successWrapper(options.success);
267 console.log("call fetch");
271 getOrFetch: function(id, options){
272 // Helper function to use this collection as a cache for models on the server
273 var model = this.get(id);
276 options.success && options.success(model);
280 model = new this.model({
284 model.fetch(options);
287 /* filterBy: note that this yields a new collection. If you pass that
288 collection to a CompositeView, then the CompositeView won't get
289 any events that trigger on the original collection.
291 Using this function is probably wrong, and I wrote
292 FilteredCompositeView() to replace it.
295 filterBy: function(fieldName, value) {
296 filtered = this.filter(function(obj) {
297 return obj.get(fieldName) == value;
299 return new this.constructor(filtered);
302 /* from backbone-tastypie.js */
303 url: function( models ) {
304 var url = this.urlRoot || ( models && models.length && models[0].urlRoot );
305 url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
307 // Build a url to retrieve a set of models. This assume the last part of each model's idAttribute
308 // (set to 'resource_uri') contains the model's id.
309 if ( models && models.length ) {
310 var ids = _.map( models, function( model ) {
311 var parts = _.compact( model.id.split('/') );
312 return parts[ parts.length - 1 ];
314 url += 'set/' + ids.join(';') + '/';
317 url && ( url += "?no_hyperlinks=1" );
322 listMethods: function() {
324 for(var m in this) {
\r
325 if(typeof this[m] == "function") {
\r
333 function define_model(lib, attrs) {
334 modelName = attrs.modelName;
335 modelClassName = modelName;
336 collectionClassName = modelName + "Collection";
338 if (!attrs.addFields) {
339 attrs.addFields = attrs.detailFields;
342 attrs.inputType = attrs.inputType || {};
343 attrs.foreignFields = attrs.foreignFields || {};
344 attrs.m2mFields = attrs.m2mFields || {};
345 attrs.readOnlyFields = attrs.readOnlyFields || [];
346 attrs.detailLinkFields = attrs.detailLinkFields || ["id","name"];
348 if (!attrs.collectionName) {
349 attrs.collectionName = modelName + "s";
351 collectionName = attrs.collectionName;
358 if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "listFields", "addFields", "detailFields", "detailLinkFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections"])>=0) {
359 modelAttrs[key] = value;
360 collectionAttrs[key] = value;
362 if ($.inArray(key, ["validate", "preSave", "readOnlyFields"])) {
363 modelAttrs[key] = value;
367 if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
368 modelAttrs["defaults"] = xosdefaults[modelName];
371 if ((typeof xosvalidators !== "undefined") && xosvalidators[modelName]) {
372 modelAttrs["validators"] = xosvalidators[modelName];
375 lib[modelName] = XOSModel.extend(modelAttrs);
377 collectionAttrs["model"] = lib[modelName];
379 lib[collectionClassName] = XOSCollection.extend(collectionAttrs);
380 lib[collectionName] = new lib[collectionClassName]();
382 lib.allCollectionNames.push(collectionName);
383 lib.allCollections.push(lib[collectionName]);
387 this.allCollectionNames = [];
388 this.allCollections = [];
390 /* Give an id, the name of a collection, and the name of a field for models
391 within that collection, lookup the id and return the value of the field.
394 this.idToName = function(id, collectionName, fieldName) {
395 linkedObject = xos[collectionName].get(id);
396 if (linkedObject == undefined) {
399 return linkedObject.attributes[fieldName];
403 /* defining the models
405 modelName - name of the model.
407 relatedCollections - collections which should be drawn as an inline
408 list when the detail view is displayed.
409 Format: <collection>:<collectionFieldName> where
410 <collectionFieldName> is the name of the field
411 in the collection that points back to the
412 collection in the detail view.
414 foreignCollections - collections which are used in idToName() calls
415 when presenting the data to the user. Used to
416 create a listento event. Somewhat
417 redundant with foreignFields.
419 foreignFields - <localFieldName>:<collection>. Used to
420 automatically map ids into humanReadableNames
421 when presenting data to the user.
423 m2mfields - <localFieldName>:<colleciton>. Used to
424 populate choices in picker lists. Simalar to
427 listFields - fields to display in lists
429 detailFields - fields to display in detail views
431 addFields - fields to display in popup add windows
433 inputType - by default, "detailFields" will be displayed
434 as text input controls. This will let you display
435 a checkbox or a picker instead.
438 define_model(this, {urlRoot: SLIVER_API,
439 relatedCollections: {"networkSlivers": "sliver"},
440 foreignCollections: ["slices", "deployments", "images", "nodes", "users", "flavors"],
441 foreignFields: {"creator": "users", "image": "images", "node": "nodes", "deploymentNetwork": "deployments", "slice": "slices", "flavor": "flavors"},
443 listFields: ["backend_status", "id", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "image", "node", "flavor"],
444 addFields: ["slice", "deploymentNetwork", "flavor", "image", "node"],
445 detailFields: ["backend_status", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "flavor", "image", "node", "creator"],
446 preSave: function() { if (!this.attributes.name && this.attributes.slice) { this.attributes.name = xos.idToName(this.attributes.slice, "slices", "name"); } },
449 define_model(this, {urlRoot: SLICE_API,
450 relatedCollections: {"slivers": "slice", "slicePrivileges": "slice", "networks": "owner"},
451 foreignCollections: ["services", "sites"],
452 foreignFields: {"service": "services", "site": "sites"},
453 listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
454 detailFields: ["backend_status", "name", "site", "enabled", "description", "slice_url", "max_slivers"],
455 inputType: {"enabled": "checkbox"},
457 xosValidate: function(attrs, options) {
458 errors = XOSModel.prototype.xosValidate(this, attrs, options);
459 // validate that slice.name starts with site.login_base
460 site = attrs.site || this.site;
461 if ((site!=undefined) && (attrs.name!=undefined)) {
462 site = xos.sites.get(site);
463 if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
464 errors = errors || {};
465 errors["name"] = "must start with " + site.attributes.login_base + "_";
472 define_model(this, {urlRoot: SLICEPRIVILEGE_API,
473 foreignCollections: ["slices", "users", "sliceRoles"],
474 modelName: "slicePrivilege",
475 foreignFields: {"user": "users", "slice": "slices", "role": "sliceRoles"},
476 listFields: ["backend_status", "id", "user", "slice", "role"],
477 detailFields: ["backend_status", "user", "slice", "role"],
480 define_model(this, {urlRoot: SLICEROLE_API,
481 modelName: "sliceRole",
482 listFields: ["backend_status", "id", "role"],
483 detailFields: ["backend_status", "role"],
486 define_model(this, {urlRoot: NODE_API,
487 foreignCollections: ["sites", "deployments"],
489 foreignFields: {"site": "sites", "deployment": "deployments"},
490 listFields: ["backend_status", "id", "name", "site", "deployment"],
491 detailFields: ["backend_status", "name", "site", "deployment"],
494 define_model(this, {urlRoot: SITE_API,
495 relatedCollections: {"users": "site", "slices": "site", "nodes": "site", "siteDeployments": "site"},
497 listFields: ["backend_status", "id", "name", "site_url", "enabled", "login_base", "is_public", "abbreviated_name"],
498 detailFields: ["backend_status", "name", "abbreviated_name", "url", "enabled", "is_public", "login_base"],
499 inputType: {"enabled": "checkbox", "is_public": "checkbox"},
502 define_model(this, {urlRoot: SITEDEPLOYMENT_API,
503 relatedCollections: {"controllerSiteDeployments": "site_deployment"},
504 foreignCollections: ["sites", "deployments", "controllers"],
505 foreignFields: {"site": "sites", "deployment": "deployments", "controller": "controllers"},
506 modelName: "siteDeployment",
507 listFields: ["backend_status", "id", "site", "deployment", "controller", "availability_zone"],
508 detailFields: ["backend_status", "site", "deployment", "controller", "availability_zone"],
509 inputType: {"enabled": "checkbox", "is_public": "checkbox"},
512 define_model(this, {urlRoot: USER_API,
513 relatedCollections: {"slicePrivileges": "user", "slices": "owner"},
514 foreignCollections: ["sites"],
516 foreignFields: {"site": "sites"},
517 listFields: ["backend_status", "id", "username", "firstname", "lastname", "phone", "user_url", "site"],
518 detailFields: ["backend_status", "username", "firstname", "lastname", "phone", "user_url", "site"],
521 define_model(this, { urlRoot: DEPLOYMENT_API,
522 relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork"},
523 m2mFields: {"flavors": "flavors", "sites": "sites", "images": "images"},
524 modelName: "deployment",
525 listFields: ["backend_status", "id", "name", "backend_type", "admin_tenant"],
526 detailFields: ["backend_status", "name", "backend_type", "admin_tenant", "flavors", "sites", "images"],
527 inputType: {"flavors": "picker", "sites": "picker", "images": "picker"},
530 define_model(this, {urlRoot: IMAGE_API,
533 listFields: ["backend_status", "id", "name", "disk_format", "container_format", "path"],
534 detailFields: ["backend_status", "name", "disk_format", "admin_tenant"],
537 define_model(this, {urlRoot: NETWORKTEMPLATE_API,
538 modelName: "networkTemplate",
539 listFields: ["backend_status", "id", "name", "visibility", "translation", "shared_network_name", "shared_network_id"],
540 detailFields: ["backend_status", "name", "description", "visibility", "translation", "shared_network_name", "shared_network_id"],
543 define_model(this, {urlRoot: NETWORK_API,
544 relatedCollections: {"networkSlivers": "network"},
545 foreignCollections: ["slices", "networkTemplates"],
546 modelName: "network",
547 foreignFields: {"template": "networkTemplates", "owner": "slices"},
548 listFields: ["backend_status", "id", "name", "template", "ports", "labels", "owner"],
549 detailFields: ["backend_status", "name", "template", "ports", "labels", "owner"],
552 define_model(this, {urlRoot: NETWORKSLIVER_API,
553 modelName: "networkSliver",
554 foreignFields: {"network": "networks", "sliver": "slivers"},
555 listFields: ["backend_status", "id", "network", "sliver", "ip", "port_id"],
556 detailFields: ["backend_status", "network", "sliver", "ip", "port_id"],
559 define_model(this, {urlRoot: SERVICE_API,
560 modelName: "service",
561 listFields: ["backend_status", "id", "name", "enabled", "versionNumber", "published"],
562 detailFields: ["backend_status", "name", "description", "versionNumber"],
565 define_model(this, {urlRoot: FLAVOR_API,
567 m2mFields: {"deployments": "deployments"},
568 listFields: ["backend_status", "id", "name", "flavor", "order", "default"],
569 detailFields: ["backend_status", "name", "description", "flavor", "order", "default", "deployments"],
570 inputType: {"default": "checkbox", "deployments": "picker"},
573 define_model(this, {urlRoot: CONTROLLER_API,
574 modelName: "controller",
575 listFields: ["backend_status", "id", "name", "version", "backend_type"],
576 detailFields: ["backend_status", "name", "version", "backend_type", "auth_url", "admin_user", "admin_password", "admin_tenant"],
579 define_model(this, {urlRoot: CONTROLLERSITEDEPLOYMENT_API,
580 modelName: "controllerSiteDeployment",
581 foreignCollections: ["site_deployments", "controllers"],
582 foreignFields: {"site_deployment": "siteDeployments", "controller": "controllers"},
583 listFields: ["backend_status", "id", "site_deployment", "controller", "tenant_id"],
584 detailFields: ["backend_status", "site_deployment", "controller", "tenant_id"],
587 /* DELETED in site-controller branch
589 define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
590 modelName: "networkDeployment",
591 foreignFields: {"network": "networks", "deployment": "deployments"},
592 listFields: ["backend_status", "id", "network", "deployment", "net_id"],
593 detailFields: ["backend_status", "network", "deployment", "net_id"],
596 define_model(this, {urlRoot: SLICEDEPLOYMENT_API,
597 foreignCollections: ["slices", "deployments"],
598 modelName: "sliceDeployment",
599 foreignFields: {"slice": "slices", "deployment": "deployments"},
600 listFields: ["backend_status", "id", "slice", "deployment", "tenant_id"],
601 detailFields: ["backend_status", "slice", "deployment", "tenant_id"],
604 define_model(this, {urlRoot: USERDEPLOYMENT_API,
605 foreignCollections: ["users","deployments"],
606 modelName: "userDeployment",
607 foreignFields: {"deployment": "deployments", "user": "users"},
608 listFields: ["backend_status", "id", "user", "deployment", "kuser_id"],
609 detailFields: ["backend_status", "user", "deployment", "kuser_id"],
612 END stuff deleted in site-controller branch */
614 /* not deleted, but obsolete since it has degenerated to a ManyToMany with no other fields
616 define_model(this, {urlRoot: IMAGEDEPLOYMENTS_API,
617 modelName: "imageDeployment",
618 foreignCollections: ["images", "deployments"],
619 listFields: ["backend_status", "id", "image", "deployment", "glance_image_id"],
620 detailFields: ["backend_status", "image", "deployment", "glance_image_id"],
626 // XXX this really needs to somehow be combined with Slice, to avoid duplication
627 define_model(this, {urlRoot: SLICEPLUS_API,
628 relatedCollections: {'slivers': "slice"},
629 modelName: "slicePlus",
630 collectionName: "slicesPlus"});
632 this.listObjects = function() { return this.allCollectionNames; };
634 this.getCollectionStatus = function() {
635 stats = {isLoaded: 0, failedLoad: 0, startedLoad: 0};
636 for (index in this.allCollections) {
637 collection = this.allCollections[index];
638 if (collection.isLoaded) {
639 stats["isLoaded"] = stats["isLoaded"] + 1;
641 if (collection.failedLoad) {
642 stats["failedLoad"] = stats["failedLoad"] + 1;
644 if (collection.startedLoad) {
645 stats["startedLoad"] = stats["startedLoad"] + 1;
648 stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
655 function getCookie(name) {
656 var cookieValue = null;
\r
657 if (document.cookie && document.cookie != '') {
\r
658 var cookies = document.cookie.split(';');
\r
659 for (var i = 0; i < cookies.length; i++) {
\r
660 var cookie = jQuery.trim(cookies[i]);
\r
661 // Does this cookie string begin with the name we want?
\r
662 if (cookie.substring(0, name.length + 1) == (name + '=')) {
\r
663 cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
\r
668 return cookieValue;
\r
672 var _sync = Backbone.sync;
\r
673 Backbone.sync = function(method, model, options){
\r
674 options.beforeSend = function(xhr){
\r
675 var token = getCookie("csrftoken");
\r
676 xhr.setRequestHeader('X-CSRFToken', token);
\r
678 return _sync(method, model, options);
\r