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/";
38 XOSModel = Backbone.Model.extend({
39 /* from backbone-tastypie.js */
40 //idAttribute: 'resource_uri',
42 /* from backbone-tastypie.js */
44 var url = this.attributes.resource_uri;
48 url = this.urlRoot + this.id;
50 // this happens when creating a new model.
56 // XXX I'm not sure this does anything useful
57 url = ( _.isFunction( this.collection.url ) ? this.collection.url() : this.collection.url );
58 url = url || this.urlRoot;
61 // remove any existing query parameters
62 url && ( url.indexOf("?") > -1 ) && ( url = url.split("?")[0] );
64 url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
66 url && ( url += "?no_hyperlinks=1" );
71 listMethods: function() {
73 for(var m in this) {
\r
74 if(typeof this[m] == "function") {
\r
81 save: function(attributes, options) {
85 return Backbone.Model.prototype.save.call(this, attributes, options);
88 getChoices: function(fieldName, excludeChosen) {
90 if (fieldName in this.m2mFields) {
91 for (index in xos[this.m2mFields[fieldName]].models) {
92 candidate = xos[this.m2mFields[fieldName]].models[index];
93 if (excludeChosen && idInArray(candidate.id, this.attributes[fieldName])) {
96 choices.push(candidate.id);
102 /* If a 'validate' method is supplied, then it will be called
103 automatically on save. Unfortunately, save calls neither the
104 'error' nor the 'success' callback if the validator fails.
106 For now, we're calling our validator 'xosValidate' so this
107 autoamtic validation doesn't occur.
110 xosValidate: function(attrs, options) {
113 _.each(this.validators, function(validatorList, fieldName) {
114 _.each(validatorList, function(validator) {
115 if (fieldName in attrs) {
116 validatorResult = validateField(validator, attrs[fieldName], this)
117 if (validatorResult != true) {
118 errors[fieldName] = validatorResult;
127 // backbone.js semantics -- on successful validate, return nothing
130 /* uncommenting this would make validate() call xosValidate()
131 validate: function(attrs, options) {
132 r = this.xosValidate(attrs, options);
133 console.log("validate");
139 XOSCollection = Backbone.Collection.extend({
140 objects: function() {
141 return this.models.map(function(element) { return element.attributes; });
144 initialize: function(){
145 this.isLoaded = false;
146 this.failedLoad = false;
147 this.startedLoad = false;
148 this.sortVar = 'name';
\r
149 this.sortOrder = 'asc';
\r
150 this.on( "sort", this.sorted );
\r
153 relatedCollections: [],
\r
154 foreignCollections: [],
\r
156 sorted: function() {
\r
157 //console.log("sorted " + this.modelName);
\r
160 simpleComparator: function( model ){
\r
161 parts=this.sortVar.split(".");
\r
162 result = model.get(parts[0]);
\r
163 for (index=1; index<parts.length; ++index) {
\r
164 result=result[parts[index]];
\r
169 comparator: function (left, right) {
\r
170 var l = this.simpleComparator(left);
\r
171 var r = this.simpleComparator(right);
\r
173 if (l === void 0) return -1;
\r
174 if (r === void 0) return 1;
\r
176 if (this.sortOrder=="desc") {
\r
177 return l < r ? 1 : l > r ? -1 : 0;
\r
179 return l < r ? -1 : l > r ? 1 : 0;
\r
183 fetchSuccess: function(collection, response, options) {
\r
184 //console.log("fetch succeeded " + collection.modelName);
\r
185 this.failedLoad = false;
\r
186 this.fetching = false;
\r
187 if (!this.isLoaded) {
\r
188 this.isLoaded = true;
\r
189 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
191 this.trigger("fetchStateChange");
\r
192 if (options["orig_success"]) {
\r
193 options["orig_success"](collection, response, options);
\r
197 fetchFailure: function(collection, response, options) {
\r
198 //console.log("fetch failed " + collection.modelName);
\r
199 this.fetching = false;
\r
200 if ((!this.isLoaded) && (!this.failedLoad)) {
\r
201 this.failedLoad=true;
\r
202 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
204 this.trigger("fetchStateChange");
\r
205 if (options["orig_failure"]) {
\r
206 options["orig_failure"](collection, response, options);
\r
210 fetch: function(options) {
\r
212 this.fetching=true;
\r
213 //console.log("fetch " + this.modelName);
\r
214 if (!this.startedLoad) {
\r
215 this.startedLoad=true;
\r
216 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
218 this.trigger("fetchStateChange");
\r
219 if (options == undefined) {
\r
222 options["orig_success"] = options["success"];
\r
223 options["orig_failure"] = options["failure"];
\r
224 options["success"] = function(collection, response, options) { self.fetchSuccess.call(self, collection, response, options); };
\r
225 options["failure"] = this.fetchFailure;
\r
226 Backbone.Collection.prototype.fetch.call(this, options);
\r
229 startPolling: function() {
\r
230 if (!this._polling) {
\r
232 setInterval(function() { collection.fetch(); }, 10000);
238 refresh: function(refreshRelated) {
239 if (!this.fetching) {
242 if (refreshRelated) {
243 for (related in this.relatedCollections) {
244 related = xos[related];
245 if (!related.fetching) {
252 maybeFetch: function(options){
253 // Helper function to fetch only if this collection has not been fetched before.
255 // If this has already been fetched, call the success, if it exists
256 options.success && options.success();
257 console.log("alreadyFetched");
261 // when the original success function completes mark this collection as fetched
263 successWrapper = function(success){
265 self._fetched = true;
266 success && success.apply(this, arguments);
269 options.success = successWrapper(options.success);
270 console.log("call fetch");
274 getOrFetch: function(id, options){
275 // Helper function to use this collection as a cache for models on the server
276 var model = this.get(id);
279 options.success && options.success(model);
283 model = new this.model({
287 model.fetch(options);
290 /* filterBy: note that this yields a new collection. If you pass that
291 collection to a CompositeView, then the CompositeView won't get
292 any events that trigger on the original collection.
294 Using this function is probably wrong, and I wrote
295 FilteredCompositeView() to replace it.
298 filterBy: function(fieldName, value) {
299 filtered = this.filter(function(obj) {
300 return obj.get(fieldName) == value;
302 return new this.constructor(filtered);
305 /* from backbone-tastypie.js */
306 url: function( models ) {
307 var url = this.urlRoot || ( models && models.length && models[0].urlRoot );
308 url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
310 // Build a url to retrieve a set of models. This assume the last part of each model's idAttribute
311 // (set to 'resource_uri') contains the model's id.
312 if ( models && models.length ) {
313 var ids = _.map( models, function( model ) {
314 var parts = _.compact( model.id.split('/') );
315 return parts[ parts.length - 1 ];
317 url += 'set/' + ids.join(';') + '/';
320 url && ( url += "?no_hyperlinks=1" );
325 listMethods: function() {
327 for(var m in this) {
\r
328 if(typeof this[m] == "function") {
\r
336 function define_model(lib, attrs) {
337 modelName = attrs.modelName;
338 modelClassName = modelName;
339 collectionClassName = modelName + "Collection";
341 if (!attrs.addFields) {
342 attrs.addFields = attrs.detailFields;
345 attrs.inputType = attrs.inputType || {};
346 attrs.foreignFields = attrs.foreignFields || {};
347 attrs.m2mFields = attrs.m2mFields || {};
348 attrs.readOnlyFields = attrs.readOnlyFields || [];
349 attrs.detailLinkFields = attrs.detailLinkFields || ["id","name"];
351 if (!attrs.collectionName) {
352 attrs.collectionName = modelName + "s";
354 collectionName = attrs.collectionName;
361 if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "listFields", "addFields", "detailFields", "detailLinkFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections"])>=0) {
362 modelAttrs[key] = value;
363 collectionAttrs[key] = value;
365 if ($.inArray(key, ["validate", "preSave", "readOnlyFields"])) {
366 modelAttrs[key] = value;
370 if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
371 modelAttrs["defaults"] = xosdefaults[modelName];
374 if ((typeof xosvalidators !== "undefined") && xosvalidators[modelName]) {
375 modelAttrs["validators"] = xosvalidators[modelName];
378 lib[modelName] = XOSModel.extend(modelAttrs);
380 collectionAttrs["model"] = lib[modelName];
382 lib[collectionClassName] = XOSCollection.extend(collectionAttrs);
383 lib[collectionName] = new lib[collectionClassName]();
385 lib.allCollectionNames.push(collectionName);
386 lib.allCollections.push(lib[collectionName]);
390 this.allCollectionNames = [];
391 this.allCollections = [];
393 /* Give an id, the name of a collection, and the name of a field for models
394 within that collection, lookup the id and return the value of the field.
397 this.idToName = function(id, collectionName, fieldName) {
398 linkedObject = xos[collectionName].get(id);
399 if (linkedObject == undefined) {
402 return linkedObject.attributes[fieldName];
406 /* defining the models
408 modelName - name of the model.
410 relatedCollections - collections which should be drawn as an inline
411 list when the detail view is displayed.
412 Format: <collection>:<collectionFieldName> where
413 <collectionFieldName> is the name of the field
414 in the collection that points back to the
415 collection in the detail view.
417 foreignCollections - collections which are used in idToName() calls
418 when presenting the data to the user. Used to
419 create a listento event. Somewhat
420 redundant with foreignFields.
422 foreignFields - <localFieldName>:<collection>. Used to
423 automatically map ids into humanReadableNames
424 when presenting data to the user.
426 m2mfields - <localFieldName>:<colleciton>. Used to
427 populate choices in picker lists. Simalar to
430 listFields - fields to display in lists
432 detailFields - fields to display in detail views
434 addFields - fields to display in popup add windows
436 inputType - by default, "detailFields" will be displayed
437 as text input controls. This will let you display
438 a checkbox or a picker instead.
441 define_model(this, {urlRoot: SLIVER_API,
442 relatedCollections: {"networkSlivers": "sliver"},
443 foreignCollections: ["slices", "deployments", "images", "nodes", "users", "flavors"],
444 foreignFields: {"creator": "users", "image": "images", "node": "nodes", "deploymentNetwork": "deployments", "slice": "slices", "flavor": "flavors"},
446 listFields: ["backend_status", "id", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "image", "node", "flavor"],
447 addFields: ["slice", "deploymentNetwork", "flavor", "image", "node"],
448 detailFields: ["backend_status", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "flavor", "image", "node", "creator"],
449 preSave: function() { if (!this.attributes.name && this.attributes.slice) { this.attributes.name = xos.idToName(this.attributes.slice, "slices", "name"); } },
452 define_model(this, {urlRoot: SLICE_API,
453 relatedCollections: {"slivers": "slice", "slicePrivileges": "slice", "networks": "owner"},
454 foreignCollections: ["services", "sites"],
455 foreignFields: {"service": "services", "site": "sites"},
456 listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
457 detailFields: ["backend_status", "name", "site", "enabled", "description", "slice_url", "max_slivers"],
458 inputType: {"enabled": "checkbox"},
460 xosValidate: function(attrs, options) {
461 errors = XOSModel.prototype.xosValidate(this, attrs, options);
462 // validate that slice.name starts with site.login_base
463 site = attrs.site || this.site;
464 if ((site!=undefined) && (attrs.name!=undefined)) {
465 site = xos.sites.get(site);
466 if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
467 errors = errors || {};
468 errors["name"] = "must start with " + site.attributes.login_base + "_";
475 define_model(this, {urlRoot: SLICEPRIVILEGE_API,
476 foreignCollections: ["slices", "users", "sliceRoles"],
477 modelName: "slicePrivilege",
478 foreignFields: {"user": "users", "slice": "slices", "role": "sliceRoles"},
479 listFields: ["backend_status", "id", "user", "slice", "role"],
480 detailFields: ["backend_status", "user", "slice", "role"],
483 define_model(this, {urlRoot: SLICEROLE_API,
484 modelName: "sliceRole",
485 listFields: ["backend_status", "id", "role"],
486 detailFields: ["backend_status", "role"],
489 define_model(this, {urlRoot: NODE_API,
490 foreignCollections: ["sites", "deployments"],
492 foreignFields: {"site": "sites", "deployment": "deployments"},
493 listFields: ["backend_status", "id", "name", "site", "deployment"],
494 detailFields: ["backend_status", "name", "site", "deployment"],
497 define_model(this, {urlRoot: SITE_API,
498 relatedCollections: {"users": "site", "slices": "site", "nodes": "site", "siteDeployments": "site"},
500 listFields: ["backend_status", "id", "name", "site_url", "enabled", "login_base", "is_public", "abbreviated_name"],
501 detailFields: ["backend_status", "name", "abbreviated_name", "url", "enabled", "is_public", "login_base"],
502 inputType: {"enabled": "checkbox", "is_public": "checkbox"},
505 define_model(this, {urlRoot: SITEDEPLOYMENT_API,
506 foreignCollections: ["sites", "deployments", "controllers"],
507 foreignFields: {"site": "sites", "deployment": "deployments", "controller": "controllers"},
508 modelName: "siteDeployment",
509 listFields: ["backend_status", "id", "site", "deployment", "controller", "availability_zone"],
510 detailFields: ["backend_status", "site", "deployment", "controller", "availability_zone"],
511 inputType: {"enabled": "checkbox", "is_public": "checkbox"},
514 define_model(this, {urlRoot: USER_API,
515 relatedCollections: {"slicePrivileges": "user", "slices": "owner"},
516 foreignCollections: ["sites"],
518 foreignFields: {"site": "sites"},
519 listFields: ["backend_status", "id", "username", "firstname", "lastname", "phone", "user_url", "site"],
520 detailFields: ["backend_status", "username", "firstname", "lastname", "phone", "user_url", "site"],
523 define_model(this, { urlRoot: DEPLOYMENT_API,
524 relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork"},
525 m2mFields: {"flavors": "flavors", "sites": "sites", "images": "images"},
526 modelName: "deployment",
527 listFields: ["backend_status", "id", "name", "backend_type", "admin_tenant"],
528 detailFields: ["backend_status", "name", "backend_type", "admin_tenant", "flavors", "sites", "images"],
529 inputType: {"flavors": "picker", "sites": "picker", "images": "picker"},
532 define_model(this, {urlRoot: IMAGE_API,
535 listFields: ["backend_status", "id", "name", "disk_format", "container_format", "path"],
536 detailFields: ["backend_status", "name", "disk_format", "admin_tenant"],
539 define_model(this, {urlRoot: NETWORKTEMPLATE_API,
540 modelName: "networkTemplate",
541 listFields: ["backend_status", "id", "name", "visibility", "translation", "shared_network_name", "shared_network_id"],
542 detailFields: ["backend_status", "name", "description", "visibility", "translation", "shared_network_name", "shared_network_id"],
545 define_model(this, {urlRoot: NETWORK_API,
546 relatedCollections: {"networkSlivers": "network"},
547 foreignCollections: ["slices", "networkTemplates"],
548 modelName: "network",
549 foreignFields: {"template": "networkTemplates", "owner": "slices"},
550 listFields: ["backend_status", "id", "name", "template", "ports", "labels", "owner"],
551 detailFields: ["backend_status", "name", "template", "ports", "labels", "owner"],
554 define_model(this, {urlRoot: NETWORKSLIVER_API,
555 modelName: "networkSliver",
556 foreignFields: {"network": "networks", "sliver": "slivers"},
557 listFields: ["backend_status", "id", "network", "sliver", "ip", "port_id"],
558 detailFields: ["backend_status", "network", "sliver", "ip", "port_id"],
561 define_model(this, {urlRoot: SERVICE_API,
562 modelName: "service",
563 listFields: ["backend_status", "id", "name", "enabled", "versionNumber", "published"],
564 detailFields: ["backend_status", "name", "description", "versionNumber"],
567 define_model(this, {urlRoot: FLAVOR_API,
569 m2mFields: {"deployments": "deployments"},
570 listFields: ["backend_status", "id", "name", "flavor", "order", "default"],
571 detailFields: ["backend_status", "name", "description", "flavor", "order", "default", "deployments"],
572 inputType: {"default": "checkbox", "deployments": "picker"},
575 define_model(this, {urlRoot: CONTROLLER_API,
576 modelName: "controller",
577 listFields: ["backend_status", "id", "name", "version", "backend_type"],
578 detailFields: ["backend_status", "name", "version", "backend_type", "auth_url", "admin_user", "admin_password", "admin_tenant"],
582 define_model(this, {urlRoot: CONTROLLERSITEDEPLOYMENT_API,
583 modelName: "controllerSiteDeployment",
584 foreignCollections: ["site_deployments", "controllers"],
585 foreignFields: {"site_deployment": "siteDeployments", "controller": "controllers"},
586 listFields: ["backend_status", "id", "site_deployment", "controller", "tenant_id"],
587 detailFields: ["backend_status", "site_deployment", "controller", "tenant_id"],
591 /* DELETED in site-controller branch
593 define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
594 modelName: "networkDeployment",
595 foreignFields: {"network": "networks", "deployment": "deployments"},
596 listFields: ["backend_status", "id", "network", "deployment", "net_id"],
597 detailFields: ["backend_status", "network", "deployment", "net_id"],
600 define_model(this, {urlRoot: SLICEDEPLOYMENT_API,
601 foreignCollections: ["slices", "deployments"],
602 modelName: "sliceDeployment",
603 foreignFields: {"slice": "slices", "deployment": "deployments"},
604 listFields: ["backend_status", "id", "slice", "deployment", "tenant_id"],
605 detailFields: ["backend_status", "slice", "deployment", "tenant_id"],
608 define_model(this, {urlRoot: USERDEPLOYMENT_API,
609 foreignCollections: ["users","deployments"],
610 modelName: "userDeployment",
611 foreignFields: {"deployment": "deployments", "user": "users"},
612 listFields: ["backend_status", "id", "user", "deployment", "kuser_id"],
613 detailFields: ["backend_status", "user", "deployment", "kuser_id"],
616 END stuff deleted in site-controller branch */
618 /* not deleted, but obsolete since it has degenerated to a ManyToMany with no other fields
620 define_model(this, {urlRoot: IMAGEDEPLOYMENTS_API,
621 modelName: "imageDeployment",
622 foreignCollections: ["images", "deployments"],
623 listFields: ["backend_status", "id", "image", "deployment", "glance_image_id"],
624 detailFields: ["backend_status", "image", "deployment", "glance_image_id"],
630 // XXX this really needs to somehow be combined with Slice, to avoid duplication
631 define_model(this, {urlRoot: SLICEPLUS_API,
632 relatedCollections: {'slivers': "slice"},
633 modelName: "slicePlus",
634 collectionName: "slicesPlus"});
636 this.listObjects = function() { return this.allCollectionNames; };
638 this.getCollectionStatus = function() {
639 stats = {isLoaded: 0, failedLoad: 0, startedLoad: 0};
640 for (index in this.allCollections) {
641 collection = this.allCollections[index];
642 if (collection.isLoaded) {
643 stats["isLoaded"] = stats["isLoaded"] + 1;
645 if (collection.failedLoad) {
646 stats["failedLoad"] = stats["failedLoad"] + 1;
648 if (collection.startedLoad) {
649 stats["startedLoad"] = stats["startedLoad"] + 1;
652 stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
659 function getCookie(name) {
660 var cookieValue = null;
\r
661 if (document.cookie && document.cookie != '') {
\r
662 var cookies = document.cookie.split(';');
\r
663 for (var i = 0; i < cookies.length; i++) {
\r
664 var cookie = jQuery.trim(cookies[i]);
\r
665 // Does this cookie string begin with the name we want?
\r
666 if (cookie.substring(0, name.length + 1) == (name + '=')) {
\r
667 cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
\r
672 return cookieValue;
\r
676 var _sync = Backbone.sync;
\r
677 Backbone.sync = function(method, model, options){
\r
678 options.beforeSend = function(xhr){
\r
679 var token = getCookie("csrftoken");
\r
680 xhr.setRequestHeader('X-CSRFToken', token);
\r
682 return _sync(method, model, options);
\r