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/";
25 CONTROLLERSITEDEPLOYMENT_API = "/plstackapi/controllersitedeploymentses";
28 /* changed as a side effect of the big rename
29 SLICEDEPLOYMENT_API = "/plstackapi/slice_deployments/";
30 USERDEPLOYMENT_API = "/plstackapi/user_deployments/";
33 SLICEDEPLOYMENT_API = "/plstackapi/slicedeployments/";
34 USERDEPLOYMENT_API = "/plstackapi/userdeployments/";
36 SLICEPLUS_API = "/xoslib/slicesplus/";
37 TENANTVIEW_API = "/xoslib/tenantview/"
39 XOSModel = Backbone.Model.extend({
40 relatedCollections: [],
41 foreignCollections: [],
47 /* from backbone-tastypie.js */
48 //idAttribute: 'resource_uri',
50 /* from backbone-tastypie.js */
52 var url = this.attributes.resource_uri;
56 url = this.urlRoot + this.id;
58 // this happens when creating a new model.
64 // XXX I'm not sure this does anything useful
65 url = ( _.isFunction( this.collection.url ) ? this.collection.url() : this.collection.url );
66 url = url || this.urlRoot;
69 // remove any existing query parameters
70 url && ( url.indexOf("?") > -1 ) && ( url = url.split("?")[0] );
72 url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
74 url && ( url += "?no_hyperlinks=1" );
79 listMethods: function() {
81 for(var m in this) {
\r
82 if(typeof this[m] == "function") {
\r
89 save: function(attributes, options) {
93 return Backbone.Model.prototype.save.call(this, attributes, options);
96 getChoices: function(fieldName, excludeChosen) {
98 if (fieldName in this.m2mFields) {
99 for (index in xos[this.m2mFields[fieldName]].models) {
100 candidate = xos[this.m2mFields[fieldName]].models[index];
101 if (excludeChosen && idInArray(candidate.id, this.attributes[fieldName])) {
104 choices.push(candidate.id);
110 /* If a 'validate' method is supplied, then it will be called
111 automatically on save. Unfortunately, save calls neither the
112 'error' nor the 'success' callback if the validator fails.
114 For now, we're calling our validator 'xosValidate' so this
115 autoamtic validation doesn't occur.
118 xosValidate: function(attrs, options) {
121 _.each(this.validators, function(validatorList, fieldName) {
122 _.each(validatorList, function(validator) {
123 if (fieldName in attrs) {
124 validatorResult = validateField(validator, attrs[fieldName], this)
125 if (validatorResult != true) {
126 errors[fieldName] = validatorResult;
135 // backbone.js semantics -- on successful validate, return nothing
138 /* uncommenting this would make validate() call xosValidate()
139 validate: function(attrs, options) {
140 r = this.xosValidate(attrs, options);
141 console.log("validate");
147 XOSCollection = Backbone.Collection.extend({
148 objects: function() {
149 return this.models.map(function(element) { return element.attributes; });
152 initialize: function(){
153 this.isLoaded = false;
154 this.failedLoad = false;
155 this.startedLoad = false;
156 this.sortVar = 'name';
\r
157 this.sortOrder = 'asc';
\r
158 this.on( "sort", this.sorted );
\r
161 relatedCollections: [],
\r
162 foreignCollections: [],
\r
166 detailLinkFields: [],
\r
168 sorted: function() {
\r
169 //console.log("sorted " + this.modelName);
\r
172 simpleComparator: function( model ){
\r
173 parts=this.sortVar.split(".");
\r
174 result = model.get(parts[0]);
\r
175 for (index=1; index<parts.length; ++index) {
\r
176 result=result[parts[index]];
\r
181 comparator: function (left, right) {
\r
182 var l = this.simpleComparator(left);
\r
183 var r = this.simpleComparator(right);
\r
185 if (l === void 0) return -1;
\r
186 if (r === void 0) return 1;
\r
188 if (this.sortOrder=="desc") {
\r
189 return l < r ? 1 : l > r ? -1 : 0;
\r
191 return l < r ? -1 : l > r ? 1 : 0;
\r
195 fetchSuccess: function(collection, response, options) {
\r
196 //console.log("fetch succeeded " + collection.modelName);
\r
197 this.failedLoad = false;
\r
198 this.fetching = false;
\r
199 if (!this.isLoaded) {
\r
200 this.isLoaded = true;
\r
201 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
203 this.trigger("fetchStateChange");
\r
204 if (options["orig_success"]) {
\r
205 options["orig_success"](collection, response, options);
\r
209 fetchFailure: function(collection, response, options) {
\r
210 //console.log("fetch failed " + collection.modelName);
\r
211 this.fetching = false;
\r
212 if ((!this.isLoaded) && (!this.failedLoad)) {
\r
213 this.failedLoad=true;
\r
214 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
216 this.trigger("fetchStateChange");
\r
217 if (options["orig_failure"]) {
\r
218 options["orig_failure"](collection, response, options);
\r
222 fetch: function(options) {
\r
224 this.fetching=true;
\r
225 //console.log("fetch " + this.modelName);
\r
226 if (!this.startedLoad) {
\r
227 this.startedLoad=true;
\r
228 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
230 this.trigger("fetchStateChange");
\r
231 if (options == undefined) {
\r
234 options["orig_success"] = options["success"];
\r
235 options["orig_failure"] = options["failure"];
\r
236 options["success"] = function(collection, response, options) { self.fetchSuccess.call(self, collection, response, options); };
\r
237 options["failure"] = this.fetchFailure;
\r
238 Backbone.Collection.prototype.fetch.call(this, options);
\r
241 startPolling: function() {
\r
242 if (!this._polling) {
\r
244 setInterval(function() { collection.fetch(); }, 10000);
250 refresh: function(refreshRelated) {
251 if (!this.fetching) {
254 if (refreshRelated) {
255 for (related in this.relatedCollections) {
256 related = xos[related];
257 if (!related.fetching) {
264 maybeFetch: function(options){
265 // Helper function to fetch only if this collection has not been fetched before.
267 // If this has already been fetched, call the success, if it exists
268 options.success && options.success();
269 console.log("alreadyFetched");
273 // when the original success function completes mark this collection as fetched
275 successWrapper = function(success){
277 self._fetched = true;
278 success && success.apply(this, arguments);
281 options.success = successWrapper(options.success);
282 console.log("call fetch");
286 getOrFetch: function(id, options){
287 // Helper function to use this collection as a cache for models on the server
288 var model = this.get(id);
291 options.success && options.success(model);
295 model = new this.model({
299 model.fetch(options);
302 /* filterBy: note that this yields a new collection. If you pass that
303 collection to a CompositeView, then the CompositeView won't get
304 any events that trigger on the original collection.
306 Using this function is probably wrong, and I wrote
307 FilteredCompositeView() to replace it.
310 filterBy: function(fieldName, value) {
311 filtered = this.filter(function(obj) {
312 return obj.get(fieldName) == value;
314 return new this.constructor(filtered);
317 /* from backbone-tastypie.js */
318 url: function( models ) {
319 var url = this.urlRoot || ( models && models.length && models[0].urlRoot );
320 url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
322 // Build a url to retrieve a set of models. This assume the last part of each model's idAttribute
323 // (set to 'resource_uri') contains the model's id.
324 if ( models && models.length ) {
325 var ids = _.map( models, function( model ) {
326 var parts = _.compact( model.id.split('/') );
327 return parts[ parts.length - 1 ];
329 url += 'set/' + ids.join(';') + '/';
332 url && ( url += "?no_hyperlinks=1" );
337 listMethods: function() {
339 for(var m in this) {
\r
340 if(typeof this[m] == "function") {
\r
348 function get_defaults(modelName) {
349 if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
350 return xosdefaults[modelName];
355 function extend_defaults(modelName, stuff) {
356 defaults = get_defaults(modelName);
358 return $.extend({}, defaults, stuff);
364 function define_model(lib, attrs) {
365 modelName = attrs.modelName;
366 modelClassName = modelName;
367 collectionClassName = modelName + "Collection";
369 if (!attrs.addFields) {
370 attrs.addFields = attrs.detailFields;
373 attrs.inputType = attrs.inputType || {};
374 attrs.foreignFields = attrs.foreignFields || {};
375 attrs.m2mFields = attrs.m2mFields || {};
376 attrs.readOnlyFields = attrs.readOnlyFields || [];
377 attrs.detailLinkFields = attrs.detailLinkFields || ["id","name"];
379 if (!attrs.collectionName) {
380 attrs.collectionName = modelName + "s";
382 collectionName = attrs.collectionName;
389 if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "listFields", "addFields", "detailFields", "detailLinkFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections", "defaults"])>=0) {
390 modelAttrs[key] = value;
391 collectionAttrs[key] = value;
393 if ($.inArray(key, ["validate", "preSave", "readOnlyFields"])) {
394 modelAttrs[key] = value;
398 if (!modelAttrs.defaults) {
399 modelAttrs.defaults = get_defaults(modelName);
402 // if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
403 // modelAttrs["defaults"] = xosdefaults[modelName];
406 if ((typeof xosvalidators !== "undefined") && xosvalidators[modelName]) {
407 modelAttrs["validators"] = xosvalidators[modelName];
410 lib[modelName] = XOSModel.extend(modelAttrs);
412 collectionAttrs["model"] = lib[modelName];
414 lib[collectionClassName] = XOSCollection.extend(collectionAttrs);
415 lib[collectionName] = new lib[collectionClassName]();
417 lib.allCollectionNames.push(collectionName);
418 lib.allCollections.push(lib[collectionName]);
422 this.allCollectionNames = [];
423 this.allCollections = [];
425 /* Give an id, the name of a collection, and the name of a field for models
426 within that collection, lookup the id and return the value of the field.
429 this.idToName = function(id, collectionName, fieldName) {
430 linkedObject = xos[collectionName].get(id);
431 if (linkedObject == undefined) {
434 return linkedObject.attributes[fieldName];
438 /* defining the models
440 modelName - name of the model.
442 relatedCollections - collections which should be drawn as an inline
443 list when the detail view is displayed.
444 Format: <collection>:<collectionFieldName> where
445 <collectionFieldName> is the name of the field
446 in the collection that points back to the
447 collection in the detail view.
449 foreignCollections - collections which are used in idToName() calls
450 when presenting the data to the user. Used to
451 create a listento event. Somewhat
452 redundant with foreignFields.
454 foreignFields - <localFieldName>:<collection>. Used to
455 automatically map ids into humanReadableNames
456 when presenting data to the user.
458 m2mfields - <localFieldName>:<colleciton>. Used to
459 populate choices in picker lists. Simalar to
462 listFields - fields to display in lists
464 detailFields - fields to display in detail views
466 addFields - fields to display in popup add windows
468 inputType - by default, "detailFields" will be displayed
469 as text input controls. This will let you display
470 a checkbox or a picker instead.
473 define_model(this, {urlRoot: SLIVER_API,
474 relatedCollections: {"networkSlivers": "sliver"},
475 foreignCollections: ["slices", "deployments", "images", "nodes", "users", "flavors"],
476 foreignFields: {"creator": "users", "image": "images", "node": "nodes", "deploymentNetwork": "deployments", "slice": "slices", "flavor": "flavors"},
478 listFields: ["backend_status", "id", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "image", "node", "flavor"],
479 addFields: ["slice", "deploymentNetwork", "flavor", "image", "node"],
480 detailFields: ["backend_status", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "flavor", "image", "node", "creator"],
481 preSave: function() { if (!this.attributes.name && this.attributes.slice) { this.attributes.name = xos.idToName(this.attributes.slice, "slices", "name"); } },
484 define_model(this, {urlRoot: SLICE_API,
485 relatedCollections: {"slivers": "slice", "slicePrivileges": "slice", "networks": "owner"},
486 foreignCollections: ["services", "sites"],
487 foreignFields: {"service": "services", "site": "sites"},
488 listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
489 detailFields: ["backend_status", "name", "site", "enabled", "description", "slice_url", "max_slivers"],
490 inputType: {"enabled": "checkbox"},
492 xosValidate: function(attrs, options) {
493 errors = XOSModel.prototype.xosValidate(this, attrs, options);
494 // validate that slice.name starts with site.login_base
495 site = attrs.site || this.site;
496 if ((site!=undefined) && (attrs.name!=undefined)) {
497 site = xos.sites.get(site);
498 if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
499 errors = errors || {};
500 errors["name"] = "must start with " + site.attributes.login_base + "_";
507 define_model(this, {urlRoot: SLICEPRIVILEGE_API,
508 foreignCollections: ["slices", "users", "sliceRoles"],
509 modelName: "slicePrivilege",
510 foreignFields: {"user": "users", "slice": "slices", "role": "sliceRoles"},
511 listFields: ["backend_status", "id", "user", "slice", "role"],
512 detailFields: ["backend_status", "user", "slice", "role"],
515 define_model(this, {urlRoot: SLICEROLE_API,
516 modelName: "sliceRole",
517 listFields: ["backend_status", "id", "role"],
518 detailFields: ["backend_status", "role"],
521 define_model(this, {urlRoot: NODE_API,
522 foreignCollections: ["sites", "deployments"],
524 foreignFields: {"site": "sites", "deployment": "deployments"},
525 listFields: ["backend_status", "id", "name", "site", "deployment"],
526 detailFields: ["backend_status", "name", "site", "deployment"],
529 define_model(this, {urlRoot: SITE_API,
530 relatedCollections: {"users": "site", "slices": "site", "nodes": "site", "siteDeployments": "site"},
532 listFields: ["backend_status", "id", "name", "site_url", "enabled", "login_base", "is_public", "abbreviated_name"],
533 detailFields: ["backend_status", "name", "abbreviated_name", "url", "enabled", "is_public", "login_base"],
534 inputType: {"enabled": "checkbox", "is_public": "checkbox"},
537 define_model(this, {urlRoot: SITEDEPLOYMENT_API,
538 foreignCollections: ["sites", "deployments", "controllers"],
539 foreignFields: {"site": "sites", "deployment": "deployments", "controller": "controllers"},
540 modelName: "siteDeployment",
541 listFields: ["backend_status", "id", "site", "deployment", "controller", "availability_zone"],
542 detailFields: ["backend_status", "site", "deployment", "controller", "availability_zone"],
543 inputType: {"enabled": "checkbox", "is_public": "checkbox"},
546 define_model(this, {urlRoot: USER_API,
547 relatedCollections: {"slicePrivileges": "user", "slices": "owner"},
548 foreignCollections: ["sites"],
550 foreignFields: {"site": "sites"},
551 listFields: ["backend_status", "id", "username", "firstname", "lastname", "phone", "user_url", "site"],
552 detailFields: ["backend_status", "username", "firstname", "lastname", "phone", "user_url", "site"],
555 define_model(this, { urlRoot: DEPLOYMENT_API,
556 relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork"},
557 m2mFields: {"flavors": "flavors", "sites": "sites", "images": "images"},
558 modelName: "deployment",
559 listFields: ["backend_status", "id", "name", "backend_type", "admin_tenant"],
560 detailFields: ["backend_status", "name", "backend_type", "admin_tenant", "flavors", "sites", "images"],
561 inputType: {"flavors": "picker", "sites": "picker", "images": "picker"},
564 define_model(this, {urlRoot: IMAGE_API,
567 listFields: ["backend_status", "id", "name", "disk_format", "container_format", "path"],
568 detailFields: ["backend_status", "name", "disk_format", "admin_tenant"],
571 define_model(this, {urlRoot: NETWORKTEMPLATE_API,
572 modelName: "networkTemplate",
573 listFields: ["backend_status", "id", "name", "visibility", "translation", "shared_network_name", "shared_network_id"],
574 detailFields: ["backend_status", "name", "description", "visibility", "translation", "shared_network_name", "shared_network_id"],
577 define_model(this, {urlRoot: NETWORK_API,
578 relatedCollections: {"networkSlivers": "network"},
579 foreignCollections: ["slices", "networkTemplates"],
580 modelName: "network",
581 foreignFields: {"template": "networkTemplates", "owner": "slices"},
582 listFields: ["backend_status", "id", "name", "template", "ports", "labels", "owner"],
583 detailFields: ["backend_status", "name", "template", "ports", "labels", "owner"],
586 define_model(this, {urlRoot: NETWORKSLIVER_API,
587 modelName: "networkSliver",
588 foreignFields: {"network": "networks", "sliver": "slivers"},
589 listFields: ["backend_status", "id", "network", "sliver", "ip", "port_id"],
590 detailFields: ["backend_status", "network", "sliver", "ip", "port_id"],
593 define_model(this, {urlRoot: SERVICE_API,
594 modelName: "service",
595 listFields: ["backend_status", "id", "name", "enabled", "versionNumber", "published"],
596 detailFields: ["backend_status", "name", "description", "versionNumber"],
599 define_model(this, {urlRoot: FLAVOR_API,
601 m2mFields: {"deployments": "deployments"},
602 listFields: ["backend_status", "id", "name", "flavor", "order", "default"],
603 detailFields: ["backend_status", "name", "description", "flavor", "order", "default", "deployments"],
604 inputType: {"default": "checkbox", "deployments": "picker"},
607 define_model(this, {urlRoot: CONTROLLER_API,
608 modelName: "controller",
609 listFields: ["backend_status", "id", "name", "version", "backend_type"],
610 detailFields: ["backend_status", "name", "version", "backend_type", "auth_url", "admin_user", "admin_password", "admin_tenant"],
614 define_model(this, {urlRoot: CONTROLLERSITEDEPLOYMENT_API,
615 modelName: "controllerSiteDeployment",
616 foreignCollections: ["site_deployments", "controllers"],
617 foreignFields: {"site_deployment": "siteDeployments", "controller": "controllers"},
618 listFields: ["backend_status", "id", "site_deployment", "controller", "tenant_id"],
619 detailFields: ["backend_status", "site_deployment", "controller", "tenant_id"],
623 /* DELETED in site-controller branch
625 define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
626 modelName: "networkDeployment",
627 foreignFields: {"network": "networks", "deployment": "deployments"},
628 listFields: ["backend_status", "id", "network", "deployment", "net_id"],
629 detailFields: ["backend_status", "network", "deployment", "net_id"],
632 define_model(this, {urlRoot: SLICEDEPLOYMENT_API,
633 foreignCollections: ["slices", "deployments"],
634 modelName: "sliceDeployment",
635 foreignFields: {"slice": "slices", "deployment": "deployments"},
636 listFields: ["backend_status", "id", "slice", "deployment", "tenant_id"],
637 detailFields: ["backend_status", "slice", "deployment", "tenant_id"],
640 define_model(this, {urlRoot: USERDEPLOYMENT_API,
641 foreignCollections: ["users","deployments"],
642 modelName: "userDeployment",
643 foreignFields: {"deployment": "deployments", "user": "users"},
644 listFields: ["backend_status", "id", "user", "deployment", "kuser_id"],
645 detailFields: ["backend_status", "user", "deployment", "kuser_id"],
648 END stuff deleted in site-controller branch */
650 /* not deleted, but obsolete since it has degenerated to a ManyToMany with no other fields
652 define_model(this, {urlRoot: IMAGEDEPLOYMENTS_API,
653 modelName: "imageDeployment",
654 foreignCollections: ["images", "deployments"],
655 listFields: ["backend_status", "id", "image", "deployment", "glance_image_id"],
656 detailFields: ["backend_status", "image", "deployment", "glance_image_id"],
662 // XXX this really needs to somehow be combined with Slice, to avoid duplication
663 define_model(this, {urlRoot: SLICEPLUS_API,
664 relatedCollections: {"slivers": "slice", "slicePrivileges": "slice", "networks": "owner"},
665 foreignCollections: ["services", "sites"],
666 foreignFields: {"service": "services", "site": "sites"},
667 listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
668 detailFields: ["backend_status", "name", "site", "enabled", "description", "slice_url", "max_slivers"],
669 inputType: {"enabled": "checkbox"},
670 modelName: "slicePlus",
671 collectionName: "slicesPlus",
672 defaults: extend_defaults("slice", {"network_ports": "", "site_allocation": []}),
673 xosValidate: function(attrs, options) {
674 errors = XOSModel.prototype.xosValidate(this, attrs, options);
675 // validate that slice.name starts with site.login_base
676 site = attrs.site || this.site;
677 if ((site!=undefined) && (attrs.name!=undefined)) {
678 site = xos.sites.get(site);
679 if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
680 errors = errors || {};
681 errors["name"] = "must start with " + site.attributes.login_base + "_";
688 define_model(this, {urlRoot: TENANTVIEW_API,
689 modelName: "tenantview",
690 collectionName: "tenantview",
695 this.tenant = function() { return this.tenantview.models[0].attributes; }
697 this.listObjects = function() { return this.allCollectionNames; };
699 this.getCollectionStatus = function() {
700 stats = {isLoaded: 0, failedLoad: 0, startedLoad: 0};
701 for (index in this.allCollections) {
702 collection = this.allCollections[index];
703 if (collection.isLoaded) {
704 stats["isLoaded"] = stats["isLoaded"] + 1;
706 if (collection.failedLoad) {
707 stats["failedLoad"] = stats["failedLoad"] + 1;
709 if (collection.startedLoad) {
710 stats["startedLoad"] = stats["startedLoad"] + 1;
713 stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
720 function getCookie(name) {
721 var cookieValue = null;
\r
722 if (document.cookie && document.cookie != '') {
\r
723 var cookies = document.cookie.split(';');
\r
724 for (var i = 0; i < cookies.length; i++) {
\r
725 var cookie = jQuery.trim(cookies[i]);
\r
726 // Does this cookie string begin with the name we want?
\r
727 if (cookie.substring(0, name.length + 1) == (name + '=')) {
\r
728 cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
\r
733 return cookieValue;
\r
737 var _sync = Backbone.sync;
\r
738 Backbone.sync = function(method, model, options){
\r
739 options.beforeSend = function(xhr){
\r
740 var token = getCookie("csrftoken");
\r
741 xhr.setRequestHeader('X-CSRFToken', token);
\r
743 return _sync(method, model, options);
\r