PageRenderTime 51ms CodeModel.GetById 2ms app.highlight 42ms RepoModel.GetById 1ms app.codeStats 0ms

/nailgun/static/js/models.js

https://github.com/aedocw/fuel-web
JavaScript | 761 lines | 699 code | 44 blank | 18 comment | 124 complexity | c087c947ce29b113b0976a20c0d6d16f MD5 | raw file
  1/*
  2 * Copyright 2013 Mirantis, Inc.
  3 *
  4 * Licensed under the Apache License, Version 2.0 (the "License"); you may
  5 * not use this file except in compliance with the License. You may obtain
  6 * a copy of the License at
  7 *
  8 *     http://www.apache.org/licenses/LICENSE-2.0
  9 *
 10 * Unless required by applicable law or agreed to in writing, software
 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 13 * License for the specific language governing permissions and limitations
 14 * under the License.
 15**/
 16define(['utils', 'deepModel'], function(utils) {
 17    'use strict';
 18
 19    var models = {};
 20
 21    var cacheMixin = {
 22        fetch: function(options) {
 23            if (this.cacheFor && options && options.cache && this.lastSyncTime && (this.cacheFor > (new Date() - this.lastSyncTime))) {
 24                return $.Deferred().resolve();
 25            }
 26            return this.constructor.__super__.fetch.apply(this, arguments);
 27        },
 28        sync: function(options) {
 29            if (this.cacheFor) {
 30                this.lastSyncTime = new Date();
 31            }
 32            return this.constructor.__super__.sync.apply(this, arguments);
 33        }
 34    };
 35
 36    models.Release = Backbone.Model.extend({
 37        constructorName: 'Release',
 38        urlRoot: '/api/releases'
 39    });
 40
 41    models.Releases = Backbone.Collection.extend({
 42        constructorName: 'Releases',
 43        model: models.Release,
 44        url: '/api/releases',
 45        comparator: function(release) {
 46            return release.id;
 47        }
 48    });
 49
 50    models.Cluster = Backbone.Model.extend({
 51        constructorName: 'Cluster',
 52        urlRoot: '/api/clusters',
 53        defaults: function() {
 54            var defaults = {
 55                nodes: new models.Nodes(),
 56                tasks: new models.Tasks()
 57            };
 58            defaults.nodes.cluster = defaults.tasks.cluster = this;
 59            return defaults;
 60        },
 61        validate: function(attrs) {
 62            var errors = {};
 63            if (!$.trim(attrs.name) || $.trim(attrs.name).length == 0) {
 64                errors.name = 'Environment name cannot be empty';
 65            }
 66            if (!attrs.release) {
 67                errors.release = 'Please choose OpenStack release';
 68            }
 69            return _.isEmpty(errors) ? null : errors;
 70        },
 71        groupings: function() {
 72            return {roles: $.t('cluster_page.nodes_tab.roles'), hardware: $.t('cluster_page.nodes_tab.hardware_info'), both: $.t('cluster_page.nodes_tab.roles_and_hardware_info')};
 73        },
 74        task: function(filter1, filter2) {
 75            var filters = _.isPlainObject(filter1) ? filter1 : {name: filter1, status: filter2};
 76            return this.get('tasks') && this.get('tasks').findTask(filters);
 77        },
 78        tasks: function(filter1, filter2) {
 79            var filters = _.isPlainObject(filter1) ? filter1 : {name: filter1, status: filter2};
 80            return this.get('tasks') && this.get('tasks').filterTasks(filters);
 81        },
 82        hasChanges: function() {
 83            return this.get('nodes').hasChanges() || (this.get('changes').length && this.get('nodes').currentNodes().length);
 84        },
 85        needsRedeployment: function() {
 86            return this.get('nodes').where({pending_addition: false, status: 'error'}).length;
 87        },
 88        availableModes: function() {
 89            return ['ha_compact', 'multinode'];
 90        },
 91        fetchRelated: function(related, options) {
 92            return this.get(related).fetch(_.extend({data: {cluster_id: this.id}}, options));
 93        },
 94        isAvailableForSettingsChanges: function() {
 95            return this.get('status') == 'new' || (this.get('status') == 'stopped' && !this.get('nodes').where({status: 'ready'}).length);
 96        }
 97    });
 98
 99    models.Clusters = Backbone.Collection.extend({
100        constructorName: 'Clusters',
101        model: models.Cluster,
102        url: '/api/clusters',
103        comparator: function(cluster) {
104            return cluster.id;
105        }
106    });
107
108    models.Node = Backbone.Model.extend({
109        constructorName: 'Node',
110        urlRoot: '/api/nodes',
111        resource: function(resourceName) {
112            var resource = 0;
113            try {
114                if (resourceName == 'cores') {
115                    resource = this.get('meta').cpu.total;
116                } else if (resourceName == 'hdd') {
117                    resource = _.reduce(this.get('meta').disks, function(hdd, disk) {return _.isNumber(disk.size) ?  hdd + disk.size : hdd;}, 0);
118                } else if (resourceName == 'ram') {
119                    resource = this.get('meta').memory.total;
120                } else if (resourceName == 'disks') {
121                    resource = _.pluck(this.get('meta').disks, 'size').sort(function(a, b) {return a - b;});
122                } else if (resourceName == 'interfaces') {
123                    resource = this.get('meta').interfaces.length;
124                }
125            } catch (ignore) {}
126            if (_.isNaN(resource)) {
127                resource = 0;
128            }
129            return resource;
130        },
131        sortedRoles: function() {
132            var preferredOrder = this.collection.cluster.get('release').get('roles');
133            return _.union(this.get('roles'), this.get('pending_roles')).sort(function(a, b) {
134                return _.indexOf(preferredOrder, a) - _.indexOf(preferredOrder, b);
135            });
136        },
137        toJSON: function(options) {
138            var result = this.constructor.__super__.toJSON.call(this, options);
139            return _.omit(result, 'checked');
140        },
141        isSelectable: function() {
142            return this.get('status') != 'error' || this.get('cluster');
143        },
144        hasRole: function(role, onlyDeployedRoles) {
145            var roles = onlyDeployedRoles ? this.get('roles') : _.union(this.get('roles'), this.get('pending_roles'));
146            return _.contains(roles, role);
147        },
148        getRolesSummary: function() {
149            var rolesMetaData = this.collection.cluster.get('release').get('roles_metadata');
150            return _.map(this.sortedRoles(), function(role) {return rolesMetaData[role].name;}).join(', ');
151        },
152        getHardwareSummary: function() {
153            return $.t('node_details.hdd') + ': ' + utils.showDiskSize(this.resource('hdd')) + ' \u00A0 ' + $.t('node_details.ram') + ': ' + utils.showMemorySize(this.resource('ram'));
154        }
155    });
156
157    models.Nodes = Backbone.Collection.extend({
158        constructorName: 'Nodes',
159        model: models.Node,
160        url: '/api/nodes',
161        comparator: function(node) {
162            return node.id;
163        },
164        hasChanges: function() {
165            return !!this.filter(function(node) {
166                return node.get('pending_addition') || node.get('pending_deletion') || node.get('pending_roles').length;
167            }).length;
168        },
169        currentNodes: function() {
170            return this.filter(function(node) {return !node.get('pending_addition');});
171        },
172        nodesAfterDeployment: function() {
173            return this.filter(function(node) {return node.get('pending_addition') || !node.get('pending_deletion');});
174        },
175        nodesAfterDeploymentWithRole: function(role) {
176            return _.filter(this.nodesAfterDeployment(), function(node) {return _.contains(_.union(node.get('roles'), node.get('pending_roles')), role);}).length;
177        },
178        resources: function(resourceName) {
179            var resources = this.map(function(node) {return node.resource(resourceName);});
180            return _.reduce(resources, function(sum, n) {return sum + n;}, 0);
181        },
182        getByIds: function(ids) {
183            return this.filter(function(node) {return _.contains(ids, node.id);});
184        },
185        groupByAttribute: function(attr) {
186            if (attr == 'roles') {
187                return this.groupBy(function(node) {return node.getRolesSummary();});
188            }
189            if (attr == 'hardware') {
190                return this.groupBy(function(node) {return node.getHardwareSummary();});
191            }
192            return this.groupBy(function(node) {return node.getRolesSummary() + '; \u00A0' + node.getHardwareSummary();});
193        }
194    });
195
196    models.NodesStatistics = Backbone.Model.extend({
197        constructorName: 'NodesStatistics',
198        urlRoot: '/api/nodes/allocation/stats'
199    });
200
201    models.Task = Backbone.Model.extend({
202        constructorName: 'Task',
203        urlRoot: '/api/tasks',
204        releaseId: function() {
205            var id;
206            try {
207                id = this.get('result').release_info.release_id;
208            } catch (ignore) {}
209            return id;
210        },
211        groups: {
212            release_setup: ['redhat_setup'],
213            network: ['verify_networks', 'check_networks'],
214            deployment: ['stop_deployment', 'deploy', 'reset_environment']
215        },
216        extendGroups: function(filters) {
217            return _.union(utils.composeList(filters.name), _.flatten(_.map(utils.composeList(filters.group), _.bind(function(group) {return this.groups[group];}, this))));
218        },
219        match: function(filters) {
220            filters = filters || {};
221            var result = false;
222            if (filters.group || filters.name) {
223                if (_.contains(this.extendGroups(filters), this.get('name'))) {
224                    result = true;
225                    if (filters.status) {
226                        result = _.contains(utils.composeList(filters.status), this.get('status'));
227                    }
228                    if (filters.release) {
229                        result = result && this.releaseId() == filters.release;
230                    }
231                }
232            } else if (filters.status) {
233                result = _.contains(utils.composeList(filters.status), this.get('status'));
234            }
235            return result;
236        }
237    });
238
239    models.Tasks = Backbone.Collection.extend({
240        constructorName: 'Tasks',
241        model: models.Task,
242        url: '/api/tasks',
243        toJSON: function(options) {
244            return this.pluck('id');
245        },
246        comparator: function(task) {
247            return task.id;
248        },
249        filterTasks: function(filters) {
250            return _.flatten(_.map(this.model.prototype.extendGroups(filters), function(name) {
251                return this.filter(function(task) {
252                    return task.match(_.extend(_.omit(filters, 'group'), {name: name}));
253                });
254            }, this));
255        },
256        findTask: function(filters) {
257            return this.filterTasks(filters)[0];
258        },
259        bindToView: function(view, filters, bindCallback, addCallback, removeCallback) {
260            bindCallback = _.bind(bindCallback, view);
261            addCallback = _.bind(addCallback || view.render, view);
262            removeCallback = _.bind(removeCallback || view.render, view);
263            function taskMatchesFilters(task) {
264                return _.any(filters, task.match, task);
265            }
266            function onTaskAdd(task) {
267                if (taskMatchesFilters(task)) {
268                    bindCallback(task);
269                    addCallback();
270                }
271            }
272            function onTaskRemove(task) {
273                if (taskMatchesFilters(task)) {
274                    removeCallback();
275                }
276            }
277            this.each(function(task) {
278                if (taskMatchesFilters(task)) {
279                    bindCallback(task);
280                }
281            });
282            this.on('add', onTaskAdd, view);
283            this.on('remove', onTaskRemove, view);
284        }
285    });
286
287    models.Notification = Backbone.Model.extend({
288        constructorName: 'Notification',
289        urlRoot: '/api/notifications'
290    });
291
292    models.Notifications = Backbone.Collection.extend({
293        constructorName: 'Notifications',
294        model: models.Notification,
295        url: '/api/notifications',
296        comparator: function(notification) {
297            return notification.id;
298        }
299    });
300
301    models.Settings = Backbone.DeepModel.extend({
302        constructorName: 'Settings',
303        urlRoot: '/api/clusters/',
304        cacheFor: 60 * 1000,
305        isNew: function() {
306            return false;
307        },
308        parse: function(response) {
309            return response.editable;
310        },
311        toJSON: function(options) {
312            var result = this.constructor.__super__.toJSON.call(this, options);
313            _.each(result, function(group, groupName) {
314                result[groupName].metadata = _.omit(group.metadata, 'disabled', 'visible');
315                _.each(group, function(setting, settingName) {
316                    group[settingName] = _.omit(setting, 'disabled');
317                    _.each(setting.values, function(option, index) {
318                        setting.values[index] = _.omit(option, 'disabled');
319                    });
320                });
321            }, this);
322            return {editable: result};
323        },
324        validate: function(attrs) {
325            var errors = [];
326            _.each(attrs, function(group, groupName) {
327                _.each(group, function(setting, settingName) {
328                    if (setting.regex && setting.regex.source) {
329                        var regExp = new RegExp(setting.regex.source);
330                        if (!setting.value.match(regExp)) {
331                            errors.push({
332                                field: groupName + '.' + settingName,
333                                message: setting.regex.error
334                            });
335                        }
336                    }
337                });
338            });
339            return errors.length ? errors : null;
340        }
341    });
342    _.extend(models.Settings.prototype, cacheMixin);
343
344    models.Disk = Backbone.Model.extend({
345        constructorName: 'Disk',
346        urlRoot: '/api/nodes/',
347        parse: function(response) {
348            response.volumes = new models.Volumes(response.volumes);
349            response.volumes.disk = this;
350            return response;
351        },
352        toJSON: function(options) {
353            return _.extend(this.constructor.__super__.toJSON.call(this, options), {volumes: this.get('volumes').toJSON()});
354        },
355        getUnallocatedSpace: function(options) {
356            options = options || {};
357            var volumes = options.volumes || this.get('volumes');
358            var allocatedSpace = volumes.reduce(function(sum, volume) {return volume.get('name') == options.skip ? sum : sum + volume.get('size');}, 0);
359            return this.get('size') - allocatedSpace;
360        },
361        validate: function(attrs) {
362            var error;
363            var unallocatedSpace = this.getUnallocatedSpace({volumes: attrs.volumes});
364            if (unallocatedSpace < 0) {
365                error = 'Volume groups total size exceeds available space of ' + utils.formatNumber(unallocatedSpace * -1) + ' MB';
366            }
367            return error;
368        }
369    });
370
371    models.Disks = Backbone.Collection.extend({
372        constructorName: 'Disks',
373        model: models.Disk,
374        url: '/api/nodes/',
375        comparator: function(disk) {
376            return disk.get('name');
377        }
378    });
379
380    models.Volume = Backbone.Model.extend({
381        constructorName: 'Volume',
382        urlRoot: '/api/volumes/',
383        getMinimalSize: function(minimum) {
384            var currentDisk = this.collection.disk;
385            var groupAllocatedSpace = currentDisk.collection.reduce(function(sum, disk) {return disk.id == currentDisk.id ? sum : sum + disk.get('volumes').findWhere({name: this.get('name')}).get('size');}, 0, this);
386            return minimum - groupAllocatedSpace;
387        },
388        validate: function(attrs, options) {
389            var error;
390            var min = this.getMinimalSize(options.minimum);
391            if (_.isNaN(attrs.size)) {
392                error = 'Invalid size';
393            } else if (attrs.size < min) {
394                error = 'The value is too low. You must allocate at least ' + utils.formatNumber(min) + ' MB';
395            }
396            return error;
397        }
398    });
399
400    models.Volumes = Backbone.Collection.extend({
401        constructorName: 'Volumes',
402        model: models.Volume,
403        url: '/api/volumes/'
404    });
405
406    models.Interface = Backbone.Model.extend({
407        constructorName: 'Interface',
408        bondingModes: ['active-backup', 'balance-slb', 'lacp-balance-tcp'],
409        parse: function(response) {
410            response.assigned_networks = new models.InterfaceNetworks(response.assigned_networks);
411            response.assigned_networks.interface = this;
412            return response;
413        },
414        toJSON: function(options) {
415            return _.omit(_.extend(this.constructor.__super__.toJSON.call(this, options), {
416                assigned_networks: this.get('assigned_networks').toJSON()
417            }), 'checked');
418        },
419        isBond: function() {
420            return this.get('type') == 'bond';
421        },
422        getSlaveInterfaces: function() {
423            if (!this.isBond()) {return [this];}
424            var slaveInterfaceNames = _.pluck(this.get('slaves'), 'name');
425            return this.collection.filter(function(slaveInterface) {
426                return _.contains(slaveInterfaceNames, slaveInterface.get('name'));
427            });
428        },
429        validate: function() {
430            var errors = [];
431            var networks = new models.Networks(this.get('assigned_networks').invoke('getFullNetwork'));
432            var untaggedNetworks = networks.filter(function(network) { return _.isNull(network.getVlanRange()); });
433            // public and floating networks are allowed to be assigned to the same interface
434            var maxUntaggedNetworksCount = networks.where({name: 'public'}).length && networks.where({name: 'floating'}).length ? 2 : 1;
435            if (untaggedNetworks.length > maxUntaggedNetworksCount) {
436                errors.push($.t('cluster_page.nodes_tab.configure_interfaces.validation.too_many_untagged_networks'));
437            }
438            return errors;
439        }
440    });
441
442    models.Interfaces = Backbone.Collection.extend({
443        constructorName: 'Interfaces',
444        model: models.Interface,
445        generateBondName: function() {
446            var index, proposedName, base = 'ovs-bond';
447            for (index = 0; true; index += 1) {
448                proposedName = base + index;
449                if (!this.where({name: proposedName}).length) {
450                    return proposedName;
451                }
452            }
453        },
454        comparator: function(ifc) {
455            return [!ifc.isBond(), ifc.get('name')];
456        }
457    });
458
459    models.InterfaceNetwork = Backbone.Model.extend({
460        constructorName: 'InterfaceNetwork'
461    });
462
463    models.InterfaceNetworks = Backbone.Collection.extend({
464        constructorName: 'InterfaceNetworks',
465        model: models.InterfaceNetwork,
466        preferredOrder: ['public', 'floating', 'storage', 'management', 'fixed'],
467        comparator: function(network) {
468            return _.indexOf(this.preferredOrder, network.get('name'));
469        }
470    });
471
472    models.Network = Backbone.Model.extend({
473        constructorName: 'Network'
474    });
475
476    models.Networks = Backbone.Collection.extend({
477        constructorName: 'Networks',
478        model: models.Network
479    });
480
481    models.NetworkingParameters = Backbone.Model.extend({
482        constructorName: 'NetworkingParameters'
483    });
484
485    models.NetworkConfiguration = Backbone.Model.extend({
486        constructorName: 'NetworkConfiguration',
487        cacheFor: 60 * 1000,
488        parse: function(response) {
489            response.networks = new models.Networks(response.networks);
490            response.networking_parameters = new models.NetworkingParameters(response.networking_parameters);
491            return response;
492        },
493        toJSON: function() {
494            return {
495                networks: this.get('networks').toJSON(),
496                networking_parameters: this.get('networking_parameters').toJSON()
497            };
498        },
499        isNew: function() {
500            return false;
501        },
502        validate: function(attrs) {
503            var errors = {};
504            var networksErrors = {};
505            var networkingParametersErrors = {};
506
507            // validate networks
508            attrs.networks.each(function(network) {
509                if (network.get('meta').configurable) {
510                    var networkErrors = {};
511                    if (network.get('meta').notation == 'ip_ranges') {
512                        var ipRangesErrors = utils.validateIpRanges(network.get('ip_ranges'), network.get('cidr'));
513                        if (ipRangesErrors.length) {
514                            networkErrors.ip_ranges = ipRangesErrors;
515                        }
516                    }
517                    _.extend(networkErrors, utils.validateCidr(network.get('cidr')));
518                    if (network.get('meta').use_gateway) {
519                        if (utils.validateIP(network.get('gateway'))) {
520                            networkErrors.gateway = $.t('cluster_page.network_tab.validation.invalid_gateway');
521                        } else if (!utils.validateIpCorrespondsToCIDR(network.get('cidr'), network.get('gateway'))) {
522                            networkErrors.gateway = $.t('cluster_page.network_tab.validation.gateway_is_out_of_ip_range');
523                        }
524                    }
525                    var forbiddenVlans = attrs.networks.map(function(net) {return net.id != network.id ? net.get('vlan_start') : null;});
526                    _.extend(networkErrors, utils.validateVlan(network.get('vlan_start'), forbiddenVlans, 'vlan_start'));
527                    if (!_.isEmpty(networkErrors)) {
528                        networksErrors[network.id] = networkErrors;
529                    }
530                }
531            }, this);
532            if (!_.isEmpty(networksErrors)) {
533                errors.networks = networksErrors;
534            }
535
536            // validate networking parameters
537            var novaNetManager = attrs.networking_parameters.get('net_manager');
538            if (novaNetManager) {
539                networkingParametersErrors = _.extend(networkingParametersErrors, utils.validateCidr(attrs.networking_parameters.get('fixed_networks_cidr'), 'fixed_networks_cidr'));
540                var fixedAmount = attrs.networking_parameters.get('fixed_networks_amount');
541                var fixedVlan = attrs.networking_parameters.get('fixed_networks_vlan_start');
542                if (!utils.isNaturalNumber(fixedAmount)) {
543                    networkingParametersErrors.fixed_networks_amount = $.t('cluster_page.network_tab.validation.invalid_amount');
544                }
545                var vlanErrors = utils.validateVlan(fixedVlan, attrs.networks.pluck('vlan_start'), 'fixed_networks_vlan_start', novaNetManager == 'VlanManager');
546                _.extend(networkingParametersErrors, vlanErrors);
547                if (_.isEmpty(vlanErrors)) {
548                    if (!networkingParametersErrors.fixed_networks_amount && fixedAmount > 4095 - fixedVlan) {
549                        networkingParametersErrors.fixed_networks_amount = $.t('cluster_page.network_tab.validation.need_more_vlan');
550                    }
551                    var vlanIntersection = false;
552                    _.each(_.compact(attrs.networks.pluck('vlan_start')), function(vlan) {
553                        if (utils.validateVlanRange(fixedVlan, fixedVlan + fixedAmount - 1, vlan)) {
554                            vlanIntersection = true;
555                        }
556                    });
557                    if (vlanIntersection) {
558                        networkingParametersErrors.fixed_networks_vlan_start = $.t('cluster_page.network_tab.validation.vlan_intersection');
559                    }
560                }
561            } else {
562                var idRangeErrors = ['', ''];
563                var segmentation = attrs.networking_parameters.get('segmentation_type');
564                var idRangeAttr = segmentation == 'gre' ? 'gre_id_range' : 'vlan_range';
565                var maxId = segmentation == 'gre' ? 65535 : 4094;
566                var idRange = attrs.networking_parameters.get(idRangeAttr);
567                var idStart = Number(idRange[0]), idEnd = Number(idRange[1]);
568                if (!utils.isNaturalNumber(idStart) || idStart < 2 || idStart > maxId) {
569                    idRangeErrors[0] = $.t('cluster_page.network_tab.validation.invalid_id_start');
570                } else if (!utils.isNaturalNumber(idEnd) || idEnd < 2 || idEnd > maxId) {
571                    idRangeErrors[1 ] = $.t('cluster_page.network_tab.validation.invalid_id_end');
572                } else if (idStart > idEnd) {
573                    idRangeErrors[0] = idRangeErrors[1] = $.t('cluster_page.network_tab.validation.invalid_id_range');
574                } else if (segmentation == 'vlan') {
575                    _.each(_.compact(attrs.networks.pluck('vlan_start')), function(vlan) {
576                        if (utils.validateVlanRange(idStart, idEnd, vlan)) {
577                            idRangeErrors[0] = $.t('cluster_page.network_tab.validation.vlan_intersection');
578                        }
579                        return idRangeErrors[0];
580                    });
581                }
582                if (_.compact(idRangeErrors).length) {
583                    networkingParametersErrors[idRangeAttr] = idRangeErrors;
584                }
585                if (!attrs.networking_parameters.get('base_mac').match(utils.regexes.mac)) {
586                    networkingParametersErrors.base_mac = $.t('cluster_page.network_tab.validation.invalid_mac');
587                }
588                var cidr = attrs.networking_parameters.get('internal_cidr');
589                networkingParametersErrors = _.extend(networkingParametersErrors, utils.validateCidr(cidr, 'internal_cidr'));
590                var gateway = attrs.networking_parameters.get('internal_gateway');
591                if (utils.validateIP(gateway)) {
592                    networkingParametersErrors.internal_gateway = $.t('cluster_page.network_tab.validation.invalid_gateway');
593                } else if (!utils.validateIpCorrespondsToCIDR(cidr, gateway)) {
594                    networkingParametersErrors.internal_gateway = $.t('cluster_page.network_tab.validation.gateway_is_out_of_internal_ip_range');
595                }
596            }
597            var networkWithFloatingRange = attrs.networks.filter(function(network){ return network.get('meta').floating_range_var; })[0];
598            var floatingRangesErrors = utils.validateIpRanges(attrs.networking_parameters.get('floating_ranges'), networkWithFloatingRange ? networkWithFloatingRange.get('cidr') : null);
599            if (floatingRangesErrors.length) {
600                networkingParametersErrors.floating_ranges = floatingRangesErrors;
601            }
602            var nameserverErrors = [];
603            _.each(attrs.networking_parameters.get('dns_nameservers'), function(nameserver, i) {
604                nameserverErrors.push(utils.validateIP(nameserver) ? $.t('cluster_page.network_tab.validation.invalid_nameserver') : null);
605            });
606            if (_.compact(nameserverErrors).length) {
607                networkingParametersErrors.dns_nameservers = nameserverErrors;
608            }
609            if (!_.isEmpty(networkingParametersErrors)) {
610                errors.networking_parameters = networkingParametersErrors;
611            }
612            return _.isEmpty(errors) ? null : errors;
613        }
614    });
615    _.extend(models.NetworkConfiguration.prototype, cacheMixin);
616
617    models.LogSource = Backbone.Model.extend({
618        constructorName: 'LogSource',
619        urlRoot: '/api/logs/sources'
620    });
621
622    models.LogSources = Backbone.Collection.extend({
623        constructorName: 'LogSources',
624        model: models.LogSource,
625        url: '/api/logs/sources'
626    });
627
628    models.RedHatAccount = Backbone.Model.extend({
629        constructorName: 'RedHatAccount',
630        urlRoot: '/api/redhat/account',
631        validate: function(attrs) {
632            var errors = {};
633            var regexes = {
634                username: /^[A-z0-9\._%\+\-@]+$/,
635                password: /^[\x21-\x7E]+$/,
636                satellite: /(^(?:(?!\d+\.)[a-zA-Z0-9_\-]{1,63}\.?)+(?:[a-zA-Z]{2,})$)/,
637                activation_key: /^[A-z0-9\*\.\+\-]+$/
638            };
639            var messages = {
640                username: 'Invalid username',
641                password: 'Invalid password',
642                satellite: 'Only valid fully qualified domain name is allowed for the hostname field',
643                activation_key: 'Invalid activation key'
644            };
645            var fields = ['username', 'password'];
646            if (attrs.license_type == 'rhn') {
647                fields = _.union(fields, ['satellite', 'activation_key']);
648            }
649            _.each(fields, function(attr) {
650                if (!regexes[attr].test(attrs[attr])) {
651                    errors[attr] = messages[attr];
652                }
653            });
654            return _.isEmpty(errors) ? null : errors;
655        }
656    });
657
658    models.TestSet = Backbone.Model.extend({
659        constructorName: 'TestSet',
660        urlRoot: '/ostf/testsets'
661    });
662
663    models.TestSets = Backbone.Collection.extend({
664        constructorName: 'TestSets',
665        model: models.TestSet,
666        url: '/ostf/testsets'
667    });
668
669    models.Test = Backbone.Model.extend({
670        constructorName: 'Test',
671        urlRoot: '/ostf/tests'
672    });
673
674    models.Tests = Backbone.Collection.extend({
675        constructorName: 'Tests',
676        model: models.Test,
677        url: '/ostf/tests'
678    });
679
680    models.TestRun = Backbone.Model.extend({
681        constructorName: 'TestRun',
682        urlRoot: '/ostf/testruns'
683    });
684
685    models.TestRuns = Backbone.Collection.extend({
686        constructorName: 'TestRuns',
687        model: models.TestRun,
688        url: '/ostf/testruns'
689    });
690
691    models.OSTFClusterMetadata = Backbone.Model.extend({
692        constructorName: 'TestRun',
693        urlRoot: '/api/ostf'
694    });
695
696    models.FuelKey = Backbone.Model.extend({
697        constructorName: 'FuelKey',
698        urlRoot: '/api/registration/key'
699    });
700
701    models.FuelVersion = Backbone.Model.extend({
702        constructorName: 'FuelVersion',
703        urlRoot: '/api/version'
704    });
705
706    models.LogsPackage = Backbone.Model.extend({
707        constructorName: 'LogsPackage',
708        urlRoot: '/api/logs/package'
709    });
710
711    models.CapacityLog = Backbone.Model.extend({
712        constructorName: 'CapacityLog',
713        urlRoot: '/api/capacity'
714    });
715
716    models.WizardModel = Backbone.DeepModel.extend({
717        constructorName: 'WizardModel',
718        parseConfig: function(config) {
719             var result = {};
720            _.each(config, _.bind(function(paneConfig, paneName) {
721                result[paneName] = {};
722                _.each(paneConfig, function(attributeConfig, attribute) {
723                    var attributeConfigValue = attributeConfig.value;
724                    if (_.isUndefined(attributeConfigValue)) {
725                        switch (attributeConfig.type) {
726                            case 'checkbox':
727                                attributeConfigValue = false;
728                                break;
729                            case 'radio':
730                                attributeConfigValue = _.first(attributeConfig.values).data;
731                                break;
732                            case 'password':
733                            case 'text':
734                                attributeConfigValue = "";
735                                break;
736                        }
737                    }
738                    result[paneName][attribute] = attributeConfigValue;
739                });
740            }, this));
741            return result;
742        },
743        processConfig: function(config) {
744            this.set(this.parseConfig(config));
745        },
746        restoreDefaultValues: function(panesToRestore) {
747            var result = {};
748            _.each(this.defaults, _.bind(function(paneConfig, paneName) {
749                if (_.contains(panesToRestore, paneName)) {
750                    result[paneName] = this.defaults[paneName];
751                }
752            }, this));
753            this.set(result);
754        },
755        initialize: function(config) {
756            this.defaults = this.parseConfig(config);
757        }
758    });
759
760    return models;
761});