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/";
24 /* changed as a side effect of the big rename
25 SLICEDEPLOYMENT_API = "/plstackapi/slice_deployments/";
26 USERDEPLOYMENT_API = "/plstackapi/user_deployments/";
29 SLICEDEPLOYMENT_API = "/plstackapi/slicedeployments/";
30 USERDEPLOYMENT_API = "/plstackapi/userdeployments/";
32 SLICEPLUS_API = "/xoslib/slicesplus/";
34 XOSModel = Backbone.Model.extend({
35 /* from backbone-tastypie.js */
36 //idAttribute: 'resource_uri',
38 /* from backbone-tastypie.js */
40 var url = this.attributes.resource_uri;
44 url = this.urlRoot + this.id;
46 // this happens when creating a new model.
52 // XXX I'm not sure this does anything useful
53 url = ( _.isFunction( this.collection.url ) ? this.collection.url() : this.collection.url );
54 url = url || this.urlRoot;
57 // remove any existing query parameters
58 url && ( url.indexOf("?") > -1 ) && ( url = url.split("?")[0] );
60 url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
62 url && ( url += "?no_hyperlinks=1" );
67 listMethods: function() {
69 for(var m in this) {
\r
70 if(typeof this[m] == "function") {
\r
77 save: function(attributes, options) {
81 return Backbone.Model.prototype.save.call(this, attributes, options);
84 getChoices: function(fieldName, excludeChosen) {
86 if (fieldName in this.m2mFields) {
87 for (index in xos[this.m2mFields[fieldName]].models) {
88 candidate = xos[this.m2mFields[fieldName]].models[index];
89 if (excludeChosen && idInArray(candidate.id, this.attributes[fieldName])) {
92 choices.push(candidate.id);
98 /* If a 'validate' method is supplied, then it will be called
99 automatically on save. Unfortunately, save calls neither the
100 'error' nor the 'success' callback if the validator fails.
102 For now, we're calling our validator 'xosValidate' so this
103 autoamtic validation doesn't occur.
106 xosValidate: function(attrs, options) {
109 _.each(this.validators, function(validatorList, fieldName) {
110 _.each(validatorList, function(validator) {
111 if (fieldName in attrs) {
112 validatorResult = validateField(validator, attrs[fieldName], this)
113 if (validatorResult != true) {
114 errors[fieldName] = validatorResult;
123 // backbone.js semantics -- on successful validate, return nothing
126 /* uncommenting this would make validate() call xosValidate()
127 validate: function(attrs, options) {
128 r = this.xosValidate(attrs, options);
129 console.log("validate");
135 XOSCollection = Backbone.Collection.extend({
136 objects: function() {
137 return this.models.map(function(element) { return element.attributes; });
140 initialize: function(){
141 this.isLoaded = false;
142 this.failedLoad = false;
143 this.startedLoad = false;
144 this.sortVar = 'name';
\r
145 this.sortOrder = 'asc';
\r
146 this.on( "sort", this.sorted );
\r
149 relatedCollections: [],
\r
150 foreignCollections: [],
\r
152 sorted: function() {
\r
153 //console.log("sorted " + this.modelName);
\r
156 simpleComparator: function( model ){
\r
157 parts=this.sortVar.split(".");
\r
158 result = model.get(parts[0]);
\r
159 for (index=1; index<parts.length; ++index) {
\r
160 result=result[parts[index]];
\r
165 comparator: function (left, right) {
\r
166 var l = this.simpleComparator(left);
\r
167 var r = this.simpleComparator(right);
\r
169 if (l === void 0) return -1;
\r
170 if (r === void 0) return 1;
\r
172 if (this.sortOrder=="desc") {
\r
173 return l < r ? 1 : l > r ? -1 : 0;
\r
175 return l < r ? -1 : l > r ? 1 : 0;
\r
179 fetchSuccess: function(collection, response, options) {
\r
180 //console.log("fetch succeeded " + collection.modelName);
\r
181 this.failedLoad = false;
\r
182 this.fetching = false;
\r
183 if (!this.isLoaded) {
\r
184 this.isLoaded = true;
\r
185 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
187 this.trigger("fetchStateChange");
\r
188 if (options["orig_success"]) {
\r
189 options["orig_success"](collection, response, options);
\r
193 fetchFailure: function(collection, response, options) {
\r
194 //console.log("fetch failed " + collection.modelName);
\r
195 this.fetching = false;
\r
196 if ((!this.isLoaded) && (!this.failedLoad)) {
\r
197 this.failedLoad=true;
\r
198 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
200 this.trigger("fetchStateChange");
\r
201 if (options["orig_failure"]) {
\r
202 options["orig_failure"](collection, response, options);
\r
206 fetch: function(options) {
\r
208 this.fetching=true;
\r
209 //console.log("fetch " + this.modelName);
\r
210 if (!this.startedLoad) {
\r
211 this.startedLoad=true;
\r
212 Backbone.trigger("xoslib:collectionLoadChange", this);
\r
214 this.trigger("fetchStateChange");
\r
215 if (options == undefined) {
\r
218 options["orig_success"] = options["success"];
\r
219 options["orig_failure"] = options["failure"];
\r
220 options["success"] = function(collection, response, options) { self.fetchSuccess.call(self, collection, response, options); };
\r
221 options["failure"] = this.fetchFailure;
\r
222 Backbone.Collection.prototype.fetch.call(this, options);
\r
225 startPolling: function() {
\r
226 if (!this._polling) {
\r
228 setInterval(function() { collection.fetch(); }, 10000);
234 refresh: function(refreshRelated) {
235 if (!this.fetching) {
238 if (refreshRelated) {
239 for (related in this.relatedCollections) {
240 related = xos[related];
241 if (!related.fetching) {
248 maybeFetch: function(options){
249 // Helper function to fetch only if this collection has not been fetched before.
251 // If this has already been fetched, call the success, if it exists
252 options.success && options.success();
253 console.log("alreadyFetched");
257 // when the original success function completes mark this collection as fetched
259 successWrapper = function(success){
261 self._fetched = true;
262 success && success.apply(this, arguments);
265 options.success = successWrapper(options.success);
266 console.log("call fetch");
270 getOrFetch: function(id, options){
271 // Helper function to use this collection as a cache for models on the server
272 var model = this.get(id);
275 options.success && options.success(model);
279 model = new this.model({
283 model.fetch(options);
286 /* filterBy: note that this yields a new collection. If you pass that
287 collection to a CompositeView, then the CompositeView won't get
288 any events that trigger on the original collection.
290 Using this function is probably wrong, and I wrote
291 FilteredCompositeView() to replace it.
294 filterBy: function(fieldName, value) {
295 filtered = this.filter(function(obj) {
296 return obj.get(fieldName) == value;
298 return new this.constructor(filtered);
301 /* from backbone-tastypie.js */
302 url: function( models ) {
303 var url = this.urlRoot || ( models && models.length && models[0].urlRoot );
304 url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
306 // Build a url to retrieve a set of models. This assume the last part of each model's idAttribute
307 // (set to 'resource_uri') contains the model's id.
308 if ( models && models.length ) {
309 var ids = _.map( models, function( model ) {
310 var parts = _.compact( model.id.split('/') );
311 return parts[ parts.length - 1 ];
313 url += 'set/' + ids.join(';') + '/';
316 url && ( url += "?no_hyperlinks=1" );
321 listMethods: function() {
323 for(var m in this) {
\r
324 if(typeof this[m] == "function") {
\r
332 function define_model(lib, attrs) {
333 modelName = attrs.modelName;
334 modelClassName = modelName;
335 collectionClassName = modelName + "Collection";
337 if (!attrs.addFields) {
338 attrs.addFields = attrs.detailFields;
341 attrs.inputType = attrs.inputType || {};
342 attrs.foreignFields = attrs.foreignFields || {};
343 attrs.m2mFields = attrs.m2mFields || {};
344 attrs.readOnlyFields = attrs.readOnlyFields || [];
345 attrs.detailLinkFields = attrs.detailLinkFields || ["id","name"];
347 if (!attrs.collectionName) {
348 attrs.collectionName = modelName + "s";
350 collectionName = attrs.collectionName;
357 if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "listFields", "addFields", "detailFields", "detailLinkFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections"])>=0) {
358 modelAttrs[key] = value;
359 collectionAttrs[key] = value;
361 if ($.inArray(key, ["validate", "preSave", "readOnlyFields"])) {
362 modelAttrs[key] = value;
366 if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
367 modelAttrs["defaults"] = xosdefaults[modelName];
370 if ((typeof xosvalidators !== "undefined") && xosvalidators[modelName]) {
371 modelAttrs["validators"] = xosvalidators[modelName];
374 lib[modelName] = XOSModel.extend(modelAttrs);
376 collectionAttrs["model"] = lib[modelName];
378 lib[collectionClassName] = XOSCollection.extend(collectionAttrs);
379 lib[collectionName] = new lib[collectionClassName]();
381 lib.allCollectionNames.push(collectionName);
382 lib.allCollections.push(lib[collectionName]);
386 this.allCollectionNames = [];
387 this.allCollections = [];
389 /* Give an id, the name of a collection, and the name of a field for models
390 within that collection, lookup the id and return the value of the field.
393 this.idToName = function(id, collectionName, fieldName) {
394 linkedObject = xos[collectionName].get(id);
395 if (linkedObject == undefined) {
398 return linkedObject.attributes[fieldName];
402 /* defining the models
404 modelName - name of the model.
406 relatedCollections - collections which should be drawn as an inline
407 list when the detail view is displayed.
408 Format: <collection>:<collectionFieldName> where
409 <collectionFieldName> is the name of the field
410 in the collection that points back to the
411 collection in the detail view.
413 foreignCollections - collections which are used in idToName() calls
414 when presenting the data to the user. Used to
415 create a listento event. Somewhat
416 redundant with foreignFields.
418 foreignFields - <localFieldName>:<collection>. Used to
419 automatically map ids into humanReadableNames
420 when presenting data to the user.
422 m2mfields - <localFieldName>:<colleciton>. Used to
423 populate choices in picker lists. Simalar to
426 listFields - fields to display in lists
428 detailFields - fields to display in detail views
430 addFields - fields to display in popup add windows
432 inputType - by default, "detailFields" will be displayed
433 as text input controls. This will let you display
434 a checkbox or a picker instead.
437 define_model(this, {urlRoot: SLIVER_API,
438 relatedCollections: {"networkSlivers": "sliver"},
439 foreignCollections: ["slices", "deployments", "images", "nodes", "users", "flavors"],
440 foreignFields: {"creator": "users", "image": "images", "node": "nodes", "deploymentNetwork": "deployments", "slice": "slices", "flavor": "flavors"},
442 listFields: ["backend_status", "id", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "image", "node", "flavor"],
443 addFields: ["slice", "deploymentNetwork", "flavor", "image", "node"],
444 detailFields: ["backend_status", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "flavor", "image", "node", "creator"],
445 preSave: function() { if (!this.attributes.name && this.attributes.slice) { this.attributes.name = xos.idToName(this.attributes.slice, "slices", "name"); } },
448 define_model(this, {urlRoot: SLICE_API,
449 relatedCollections: {"slivers": "slice", "slicePrivileges": "slice", "networks": "owner"},
450 foreignCollections: ["services", "sites"],
451 foreignFields: {"service": "services", "site": "sites"},
452 listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
453 detailFields: ["backend_status", "name", "site", "enabled", "description", "slice_url", "max_slivers"],
454 inputType: {"enabled": "checkbox"},
456 xosValidate: function(attrs, options) {
457 errors = XOSModel.prototype.xosValidate(this, attrs, options);
458 // validate that slice.name starts with site.login_base
459 site = attrs.site || this.site;
460 if ((site!=undefined) && (attrs.name!=undefined)) {
461 site = xos.sites.get(site);
462 if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
463 errors = errors || {};
464 errors["name"] = "must start with " + site.attributes.login_base + "_";
471 define_model(this, {urlRoot: SLICEPRIVILEGE_API,
472 foreignCollections: ["slices", "users", "sliceRoles"],
473 modelName: "slicePrivilege",
474 foreignFields: {"user": "users", "slice": "slices", "role": "sliceRoles"},
475 listFields: ["backend_status", "id", "user", "slice", "role"],
476 detailFields: ["backend_status", "user", "slice", "role"],
479 define_model(this, {urlRoot: SLICEROLE_API,
480 modelName: "sliceRole",
481 listFields: ["backend_status", "id", "role"],
482 detailFields: ["backend_status", "role"],
485 define_model(this, {urlRoot: NODE_API,
486 foreignCollections: ["sites", "deployments"],
488 foreignFields: {"site": "sites", "deployment": "deployments"},
489 listFields: ["backend_status", "id", "name", "site", "deployment"],
490 detailFields: ["backend_status", "name", "site", "deployment"],
493 define_model(this, {urlRoot: SITE_API,
494 relatedCollections: {"users": "site", "slices": "site", "nodes": "site", "siteDeployments": "site"},
496 listFields: ["backend_status", "id", "name", "site_url", "enabled", "login_base", "is_public", "abbreviated_name"],
497 detailFields: ["backend_status", "name", "abbreviated_name", "url", "enabled", "is_public", "login_base"],
498 inputType: {"enabled": "checkbox", "is_public": "checkbox"},
501 define_model(this, {urlRoot: SITEDEPLOYMENT_API,
502 foreignCollections: ["sites", "deployments", "controllers"],
503 foreignFields: {"site": "sites", "deployment": "deployments", "controller": "controllers"},
504 modelName: "siteDeployment",
505 listFields: ["backend_status", "id", "site", "deployment", "controller", "availability_zone"],
506 detailFields: ["backend_status", "site", "deployment", "controller", "availability_zone"],
507 inputType: {"enabled": "checkbox", "is_public": "checkbox"},
510 define_model(this, {urlRoot: USER_API,
511 relatedCollections: {"slicePrivileges": "user", "slices": "owner"},
512 foreignCollections: ["sites"],
514 foreignFields: {"site": "sites"},
515 listFields: ["backend_status", "id", "username", "firstname", "lastname", "phone", "user_url", "site"],
516 detailFields: ["backend_status", "username", "firstname", "lastname", "phone", "user_url", "site"],
519 define_model(this, { urlRoot: DEPLOYMENT_API,
520 relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork"},
521 m2mFields: {"flavors": "flavors", "sites": "sites", "images": "images"},
522 modelName: "deployment",
523 listFields: ["backend_status", "id", "name", "backend_type", "admin_tenant"],
524 detailFields: ["backend_status", "name", "backend_type", "admin_tenant", "flavors", "sites", "images"],
525 inputType: {"flavors": "picker", "sites": "picker", "images": "picker"},
528 define_model(this, {urlRoot: IMAGE_API,
531 listFields: ["backend_status", "id", "name", "disk_format", "container_format", "path"],
532 detailFields: ["backend_status", "name", "disk_format", "admin_tenant"],
535 define_model(this, {urlRoot: NETWORKTEMPLATE_API,
536 modelName: "networkTemplate",
537 listFields: ["backend_status", "id", "name", "visibility", "translation", "sharedNetworkName", "sharedNetworkId"],
538 detailFields: ["backend_status", "name", "description", "visibility", "translation", "sharedNetworkName", "sharedNetworkId"],
541 define_model(this, {urlRoot: NETWORK_API,
542 relatedCollections: {"networkSlivers": "network"},
543 foreignCollections: ["slices", "networkTemplates"],
544 modelName: "network",
545 foreignFields: {"template": "networkTemplates", "owner": "slices"},
546 listFields: ["backend_status", "id", "name", "template", "ports", "labels", "owner"],
547 detailFields: ["backend_status", "name", "template", "ports", "labels", "owner"],
550 define_model(this, {urlRoot: NETWORKSLIVER_API,
551 modelName: "networkSliver",
552 foreignFields: {"network": "networks", "sliver": "slivers"},
553 listFields: ["backend_status", "id", "network", "sliver", "ip", "port_id"],
554 detailFields: ["backend_status", "network", "sliver", "ip", "port_id"],
557 define_model(this, {urlRoot: SERVICE_API,
558 modelName: "service",
559 listFields: ["backend_status", "id", "name", "enabled", "versionNumber", "published"],
560 detailFields: ["backend_status", "name", "description", "versionNumber"],
563 define_model(this, {urlRoot: FLAVOR_API,
565 m2mFields: {"deployments": "deployments"},
566 listFields: ["backend_status", "id", "name", "flavor", "order", "default"],
567 detailFields: ["backend_status", "name", "description", "flavor", "order", "default", "deployments"],
568 inputType: {"default": "checkbox", "deployments": "picker"},
571 define_model(this, {urlRoot: CONTROLLER_API,
572 modelName: "controller",
573 listFields: ["backend_status", "id", "name", "version", "backend_type"],
574 detailFields: ["backend_status", "id", "name", "version", "backend_type", "auth_url", "admin_user", "admin_password", "admin_tenant"],
577 /* DELETED in site-controller branch
579 define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
580 modelName: "networkDeployment",
581 foreignFields: {"network": "networks", "deployment": "deployments"},
582 listFields: ["backend_status", "id", "network", "deployment", "net_id"],
583 detailFields: ["backend_status", "network", "deployment", "net_id"],
586 define_model(this, {urlRoot: SLICEDEPLOYMENT_API,
587 foreignCollections: ["slices", "deployments"],
588 modelName: "sliceDeployment",
589 foreignFields: {"slice": "slices", "deployment": "deployments"},
590 listFields: ["backend_status", "id", "slice", "deployment", "tenant_id"],
591 detailFields: ["backend_status", "slice", "deployment", "tenant_id"],
594 define_model(this, {urlRoot: USERDEPLOYMENT_API,
595 foreignCollections: ["users","deployments"],
596 modelName: "userDeployment",
597 foreignFields: {"deployment": "deployments", "user": "users"},
598 listFields: ["backend_status", "id", "user", "deployment", "kuser_id"],
599 detailFields: ["backend_status", "user", "deployment", "kuser_id"],
602 END stuff deleted in site-controller branch */
604 /* not deleted, but obsolete since it has degenerated to a ManyToMany with no other fields
606 define_model(this, {urlRoot: IMAGEDEPLOYMENTS_API,
607 modelName: "imageDeployment",
608 foreignCollections: ["images", "deployments"],
609 listFields: ["backend_status", "id", "image", "deployment", "glance_image_id"],
610 detailFields: ["backend_status", "image", "deployment", "glance_image_id"],
616 // XXX this really needs to somehow be combined with Slice, to avoid duplication
617 define_model(this, {urlRoot: SLICEPLUS_API,
618 relatedCollections: {'slivers': "slice"},
619 modelName: "slicePlus",
620 collectionName: "slicesPlus"});
622 this.listObjects = function() { return this.allCollectionNames; };
624 this.getCollectionStatus = function() {
625 stats = {isLoaded: 0, failedLoad: 0, startedLoad: 0};
626 for (index in this.allCollections) {
627 collection = this.allCollections[index];
628 if (collection.isLoaded) {
629 stats["isLoaded"] = stats["isLoaded"] + 1;
631 if (collection.failedLoad) {
632 stats["failedLoad"] = stats["failedLoad"] + 1;
634 if (collection.startedLoad) {
635 stats["startedLoad"] = stats["startedLoad"] + 1;
638 stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
645 function getCookie(name) {
646 var cookieValue = null;
\r
647 if (document.cookie && document.cookie != '') {
\r
648 var cookies = document.cookie.split(';');
\r
649 for (var i = 0; i < cookies.length; i++) {
\r
650 var cookie = jQuery.trim(cookies[i]);
\r
651 // Does this cookie string begin with the name we want?
\r
652 if (cookie.substring(0, name.length + 1) == (name + '=')) {
\r
653 cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
\r
658 return cookieValue;
\r
662 var _sync = Backbone.sync;
\r
663 Backbone.sync = function(method, model, options){
\r
664 options.beforeSend = function(xhr){
\r
665 var token = getCookie("csrftoken");
\r
666 xhr.setRequestHeader('X-CSRFToken', token);
\r
668 return _sync(method, model, options);
\r