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 url && ( url += "?no_hyperlinks=1" );
324 if (this.currentUserCanSee) {
325 url && ( url += "¤t_user_can_see=1" );
331 listMethods: function() {
333 for(var m in this) {
\r
334 if(typeof this[m] == "function") {
\r
342 function get_defaults(modelName) {
343 if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
344 return xosdefaults[modelName];
349 function extend_defaults(modelName, stuff) {
350 defaults = get_defaults(modelName);
352 return $.extend({}, defaults, stuff);
358 function define_model(lib, attrs) {
359 modelName = attrs.modelName;
360 modelClassName = modelName;
361 collectionClass = attrs.collectionClass || XOSCollection;
362 collectionClassName = modelName + "Collection";
364 if (!attrs.addFields) {
365 attrs.addFields = attrs.detailFields;
368 attrs.inputType = attrs.inputType || {};
369 attrs.foreignFields = attrs.foreignFields || {};
370 attrs.m2mFields = attrs.m2mFields || {};
371 attrs.readOnlyFields = attrs.readOnlyFields || [];
372 attrs.detailLinkFields = attrs.detailLinkFields || ["id","name"];
374 if (!attrs.collectionName) {
375 attrs.collectionName = modelName + "s";
377 collectionName = attrs.collectionName;
384 if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "listFields", "addFields", "detailFields", "detailLinkFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections", "defaults"])>=0) {
385 modelAttrs[key] = value;
386 collectionAttrs[key] = value;
388 if ($.inArray(key, ["validate", "preSave", "readOnlyFields"])) {
389 modelAttrs[key] = value;
393 if (!modelAttrs.defaults) {
394 modelAttrs.defaults = get_defaults(modelName);
397 // if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
398 // modelAttrs["defaults"] = xosdefaults[modelName];
401 if ((typeof xosvalidators !== "undefined") && xosvalidators[modelName]) {
402 modelAttrs["validators"] = xosvalidators[modelName];
405 lib[modelName] = XOSModel.extend(modelAttrs);
407 collectionAttrs["model"] = lib[modelName];
409 lib[collectionClassName] = collectionClass.extend(collectionAttrs);
410 lib[collectionName] = new lib[collectionClassName]();
412 lib.allCollectionNames.push(collectionName);
413 lib.allCollections.push(lib[collectionName]);
417 this.allCollectionNames = [];
418 this.allCollections = [];
420 /* Give an id, the name of a collection, and the name of a field for models
421 within that collection, lookup the id and return the value of the field.
424 this.idToName = function(id, collectionName, fieldName) {
425 linkedObject = xos[collectionName].get(id);
426 if (linkedObject == undefined) {
429 return linkedObject.attributes[fieldName];
433 /* defining the models
435 modelName - name of the model.
437 relatedCollections - collections which should be drawn as an inline
438 list when the detail view is displayed.
439 Format: <collection>:<collectionFieldName> where
440 <collectionFieldName> is the name of the field
441 in the collection that points back to the
442 collection in the detail view.
444 foreignCollections - collections which are used in idToName() calls
445 when presenting the data to the user. Used to
446 create a listento event. Somewhat
447 redundant with foreignFields.
449 foreignFields - <localFieldName>:<collection>. Used to
450 automatically map ids into humanReadableNames
451 when presenting data to the user.
453 m2mfields - <localFieldName>:<colleciton>. Used to
454 populate choices in picker lists. Simalar to
457 listFields - fields to display in lists
459 detailFields - fields to display in detail views
461 addFields - fields to display in popup add windows
463 inputType - by default, "detailFields" will be displayed
464 as text input controls. This will let you display
465 a checkbox or a picker instead.
468 define_model(this, {urlRoot: SLIVER_API,
469 relatedCollections: {"networkSlivers": "sliver"},
470 foreignCollections: ["slices", "deployments", "images", "nodes", "users", "flavors"],
471 foreignFields: {"creator": "users", "image": "images", "node": "nodes", "deploymentNetwork": "deployments", "slice": "slices", "flavor": "flavors"},
473 listFields: ["backend_status", "id", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "image", "node", "flavor"],
474 addFields: ["slice", "deploymentNetwork", "flavor", "image", "node"],
475 detailFields: ["backend_status", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "flavor", "image", "node", "creator"],
476 preSave: function() { if (!this.attributes.name && this.attributes.slice) { this.attributes.name = xos.idToName(this.attributes.slice, "slices", "name"); } },
479 define_model(this, {urlRoot: SLICE_API,
480 relatedCollections: {"slivers": "slice", "slicePrivileges": "slice", "networks": "owner"},
481 foreignCollections: ["services", "sites"],
482 foreignFields: {"service": "services", "site": "sites"},
483 listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
484 detailFields: ["backend_status", "name", "site", "enabled", "description", "slice_url", "max_slivers"],
485 inputType: {"enabled": "checkbox"},
487 xosValidate: function(attrs, options) {
488 errors = XOSModel.prototype.xosValidate(this, attrs, options);
489 // validate that slice.name starts with site.login_base
490 site = attrs.site || this.site;
491 if ((site!=undefined) && (attrs.name!=undefined)) {
492 site = xos.sites.get(site);
493 if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
494 errors = errors || {};
495 errors["name"] = "must start with " + site.attributes.login_base + "_";
502 define_model(this, {urlRoot: SLICEPRIVILEGE_API,
503 foreignCollections: ["slices", "users", "sliceRoles"],
504 modelName: "slicePrivilege",
505 foreignFields: {"user": "users", "slice": "slices", "role": "sliceRoles"},
506 listFields: ["backend_status", "id", "user", "slice", "role"],
507 detailFields: ["backend_status", "user", "slice", "role"],
510 define_model(this, {urlRoot: SLICEROLE_API,
511 modelName: "sliceRole",
512 listFields: ["backend_status", "id", "role"],
513 detailFields: ["backend_status", "role"],
516 define_model(this, {urlRoot: NODE_API,
517 foreignCollections: ["sites", "deployments"],
519 foreignFields: {"site": "sites", "deployment": "deployments"},
520 listFields: ["backend_status", "id", "name", "site", "deployment"],
521 detailFields: ["backend_status", "name", "site", "deployment"],
524 define_model(this, {urlRoot: SITE_API,
525 relatedCollections: {"users": "site", "slices": "site", "nodes": "site", "siteDeployments": "site"},
527 listFields: ["backend_status", "id", "name", "site_url", "enabled", "login_base", "is_public", "abbreviated_name"],
528 detailFields: ["backend_status", "name", "abbreviated_name", "url", "enabled", "is_public", "login_base"],
529 inputType: {"enabled": "checkbox", "is_public": "checkbox"},
532 define_model(this, {urlRoot: SITEDEPLOYMENT_API,
533 foreignCollections: ["sites", "deployments", "controllers"],
534 foreignFields: {"site": "sites", "deployment": "deployments", "controller": "controllers"},
535 modelName: "siteDeployment",
536 listFields: ["backend_status", "id", "site", "deployment", "controller", "availability_zone"],
537 detailFields: ["backend_status", "site", "deployment", "controller", "availability_zone"],
538 inputType: {"enabled": "checkbox", "is_public": "checkbox"},
541 define_model(this, {urlRoot: USER_API,
542 relatedCollections: {"slicePrivileges": "user", "slices": "owner"},
543 foreignCollections: ["sites"],
545 foreignFields: {"site": "sites"},
546 listFields: ["backend_status", "id", "username", "firstname", "lastname", "phone", "user_url", "site"],
547 detailFields: ["backend_status", "username", "firstname", "lastname", "phone", "user_url", "site"],
550 define_model(this, { urlRoot: DEPLOYMENT_API,
551 relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork"},
552 m2mFields: {"flavors": "flavors", "sites": "sites", "images": "images"},
553 modelName: "deployment",
554 listFields: ["backend_status", "id", "name", "backend_type", "admin_tenant"],
555 detailFields: ["backend_status", "name", "backend_type", "admin_tenant", "flavors", "sites", "images"],
556 inputType: {"flavors": "picker", "sites": "picker", "images": "picker"},
559 define_model(this, {urlRoot: IMAGE_API,
562 listFields: ["backend_status", "id", "name", "disk_format", "container_format", "path"],
563 detailFields: ["backend_status", "name", "disk_format", "admin_tenant"],
566 define_model(this, {urlRoot: NETWORKTEMPLATE_API,
567 modelName: "networkTemplate",
568 listFields: ["backend_status", "id", "name", "visibility", "translation", "shared_network_name", "shared_network_id"],
569 detailFields: ["backend_status", "name", "description", "visibility", "translation", "shared_network_name", "shared_network_id"],
572 define_model(this, {urlRoot: NETWORK_API,
573 relatedCollections: {"networkSlivers": "network"},
574 foreignCollections: ["slices", "networkTemplates"],
575 modelName: "network",
576 foreignFields: {"template": "networkTemplates", "owner": "slices"},
577 listFields: ["backend_status", "id", "name", "template", "ports", "labels", "owner"],
578 detailFields: ["backend_status", "name", "template", "ports", "labels", "owner"],
581 define_model(this, {urlRoot: NETWORKSLIVER_API,
582 modelName: "networkSliver",
583 foreignFields: {"network": "networks", "sliver": "slivers"},
584 listFields: ["backend_status", "id", "network", "sliver", "ip", "port_id"],
585 detailFields: ["backend_status", "network", "sliver", "ip", "port_id"],
588 define_model(this, {urlRoot: SERVICE_API,
589 modelName: "service",
590 listFields: ["backend_status", "id", "name", "enabled", "versionNumber", "published"],
591 detailFields: ["backend_status", "name", "description", "versionNumber"],
594 define_model(this, {urlRoot: FLAVOR_API,
596 m2mFields: {"deployments": "deployments"},
597 listFields: ["backend_status", "id", "name", "flavor", "order", "default"],
598 detailFields: ["backend_status", "name", "description", "flavor", "order", "default", "deployments"],
599 inputType: {"default": "checkbox", "deployments": "picker"},
602 define_model(this, {urlRoot: CONTROLLER_API,
603 modelName: "controller",
604 listFields: ["backend_status", "id", "name", "version", "backend_type"],
605 detailFields: ["backend_status", "name", "version", "backend_type", "auth_url", "admin_user", "admin_password", "admin_tenant"],
609 define_model(this, {urlRoot: CONTROLLERSITEDEPLOYMENT_API,
610 modelName: "controllerSiteDeployment",
611 foreignCollections: ["site_deployments", "controllers"],
612 foreignFields: {"site_deployment": "siteDeployments", "controller": "controllers"},
613 listFields: ["backend_status", "id", "site_deployment", "controller", "tenant_id"],
614 detailFields: ["backend_status", "site_deployment", "controller", "tenant_id"],
618 /* DELETED in site-controller branch
620 define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
621 modelName: "networkDeployment",
622 foreignFields: {"network": "networks", "deployment": "deployments"},
623 listFields: ["backend_status", "id", "network", "deployment", "net_id"],
624 detailFields: ["backend_status", "network", "deployment", "net_id"],
627 define_model(this, {urlRoot: SLICEDEPLOYMENT_API,
628 foreignCollections: ["slices", "deployments"],
629 modelName: "sliceDeployment",
630 foreignFields: {"slice": "slices", "deployment": "deployments"},
631 listFields: ["backend_status", "id", "slice", "deployment", "tenant_id"],
632 detailFields: ["backend_status", "slice", "deployment", "tenant_id"],
635 define_model(this, {urlRoot: USERDEPLOYMENT_API,
636 foreignCollections: ["users","deployments"],
637 modelName: "userDeployment",
638 foreignFields: {"deployment": "deployments", "user": "users"},
639 listFields: ["backend_status", "id", "user", "deployment", "kuser_id"],
640 detailFields: ["backend_status", "user", "deployment", "kuser_id"],
643 END stuff deleted in site-controller branch */
645 /* not deleted, but obsolete since it has degenerated to a ManyToMany with no other fields
647 define_model(this, {urlRoot: IMAGEDEPLOYMENTS_API,
648 modelName: "imageDeployment",
649 foreignCollections: ["images", "deployments"],
650 listFields: ["backend_status", "id", "image", "deployment", "glance_image_id"],
651 detailFields: ["backend_status", "image", "deployment", "glance_image_id"],
657 // XXX this really needs to somehow be combined with Slice, to avoid duplication
658 define_model(this, {urlRoot: SLICEPLUS_API,
659 relatedCollections: {"slivers": "slice", "slicePrivileges": "slice", "networks": "owner"},
660 foreignCollections: ["services", "sites"],
661 foreignFields: {"service": "services", "site": "sites"},
662 listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
663 detailFields: ["backend_status", "name", "site", "enabled", "description", "slice_url", "max_slivers"],
664 inputType: {"enabled": "checkbox"},
665 modelName: "slicePlus",
666 collectionName: "slicesPlus",
667 defaults: extend_defaults("slice", {"network_ports": "", "site_allocation": []}),
668 xosValidate: function(attrs, options) {
669 errors = XOSModel.prototype.xosValidate(this, attrs, options);
670 // validate that slice.name starts with site.login_base
671 site = attrs.site || this.site;
672 if ((site!=undefined) && (attrs.name!=undefined)) {
673 site = xos.sites.get(site);
674 if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
675 errors = errors || {};
676 errors["name"] = "must start with " + site.attributes.login_base + "_";
683 define_model(this, {urlRoot: TENANTVIEW_API,
684 modelName: "tenantview",
685 collectionName: "tenantview",
690 /* by default, have slicePlus only fetch the slices the user can see */
691 this.slicesPlus.currentUserCanSee = true;
693 this.tenant = function() { return this.tenantview.models[0].attributes; };
695 this.listObjects = function() { return this.allCollectionNames; };
697 this.getCollectionStatus = function() {
698 stats = {isLoaded: 0, failedLoad: 0, startedLoad: 0};
699 for (index in this.allCollections) {
700 collection = this.allCollections[index];
701 if (collection.isLoaded) {
702 stats["isLoaded"] = stats["isLoaded"] + 1;
704 if (collection.failedLoad) {
705 stats["failedLoad"] = stats["failedLoad"] + 1;
707 if (collection.startedLoad) {
708 stats["startedLoad"] = stats["startedLoad"] + 1;
711 stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
718 function getCookie(name) {
719 var cookieValue = null;
\r
720 if (document.cookie && document.cookie != '') {
\r
721 var cookies = document.cookie.split(';');
\r
722 for (var i = 0; i < cookies.length; i++) {
\r
723 var cookie = jQuery.trim(cookies[i]);
\r
724 // Does this cookie string begin with the name we want?
\r
725 if (cookie.substring(0, name.length + 1) == (name + '=')) {
\r
726 cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
\r
731 return cookieValue;
\r
735 var _sync = Backbone.sync;
\r
736 Backbone.sync = function(method, model, options){
\r
737 options.beforeSend = function(xhr){
\r
738 var token = getCookie("csrftoken");
\r
739 xhr.setRequestHeader('X-CSRFToken', token);
\r
741 return _sync(method, model, options);
\r