/* * Copyright 2015 IBM Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ (function () { 'use strict'; /** * @ngdoc controller * @name LaunchInstanceSourceController * @description * The `LaunchInstanceSourceController` controller provides functions for * configuring the source step of the Launch Instance Wizard. * */ var push = [].push; angular .module('horizon.dashboard.project.workflow.launch-instance') .controller('LaunchInstanceSourceController', LaunchInstanceSourceController); LaunchInstanceSourceController.$inject = [ '$scope', 'horizon.dashboard.project.workflow.launch-instance.boot-source-types', 'bytesFilter', 'dateFilter', 'decodeFilter', 'diskFormatFilter', 'gbFilter', 'horizon.dashboard.project.workflow.launch-instance.basePath' ]; function LaunchInstanceSourceController($scope, bootSourceTypes, bytesFilter, dateFilter, decodeFilter, diskFormatFilter, gbFilter, basePath ) { var ctrl = this; // Error text for invalid fields /*eslint-disable max-len */ ctrl.bootSourceTypeError = gettext('Volumes can only be attached to 1 active instance at a time. Please either set your instance count to 1 or select a different source type.'); /*eslint-enable max-len */ ctrl.volumeSizeError = gettext('Volume size is required and must be an integer'); // toggle button label/value defaults ctrl.toggleButtonOptions = [ { label: gettext('Yes'), value: true }, { label: gettext('No'), value: false } ]; /* * Boot Sources */ ctrl.bootSourcesOptions = [ { type: bootSourceTypes.IMAGE, label: gettext('Image') }, { type: bootSourceTypes.INSTANCE_SNAPSHOT, label: gettext('Instance Snapshot') }, { type: bootSourceTypes.VOLUME, label: gettext('Volume') }, { type: bootSourceTypes.VOLUME_SNAPSHOT, label: gettext('Volume Snapshot') } ]; ctrl.updateBootSourceSelection = updateBootSourceSelection; /* * Transfer table */ ctrl.tableHeadCells = []; ctrl.tableBodyCells = []; ctrl.tableData = {}; ctrl.helpText = {}; ctrl.sourceDetails = basePath + 'source/source-details.html'; var selection = ctrl.selection = $scope.model.newInstanceSpec.source; var bootSources = { image: { available: $scope.model.images, allocated: selection, displayedAvailable: $scope.model.images, displayedAllocated: selection }, snapshot: { available: $scope.model.imageSnapshots, allocated: selection, displayedAvailable: [], displayedAllocated: selection }, volume: { available: $scope.model.volumes, allocated: selection, displayedAvailable: [], displayedAllocated: selection }, volume_snapshot: { available: $scope.model.volumeSnapshots, allocated: selection, displayedAvailable: [], displayedAllocated: selection } }; // Mapping for dynamic table headers var tableHeadCellsMap = { image: [ { text: gettext('Name'), style: { width: '30%' }, sortable: true, sortDefault: true }, { text: gettext('Updated'), style: { width: '15%' }, sortable: true }, { text: gettext('Size'), style: { width: '15%' }, classList: ['number'], sortable: true }, { text: gettext('Type'), sortable: true }, { text: gettext('Visibility'), sortable: true } ], snapshot: [ { text: gettext('Name'), style: { width: '30%' }, sortable: true, sortDefault: true }, { text: gettext('Updated'), style: { width: '15%' }, sortable: true }, { text: gettext('Size'), style: { width: '15%' }, classList: ['number'], sortable: true }, { text: gettext('Type'), sortable: true }, { text: gettext('Visibility'), sortable: true } ], volume: [ { text: gettext('Name'), style: { width: '25%' }, sortable: true, sortDefault: true }, { text: gettext('Description'), style: { width: '20%' }, sortable: true }, { text: gettext('Size'), style: { width: '15%' }, classList: ['number'], sortable: true }, { text: gettext('Type'), style: { width: '20%' }, sortable: true }, { text: gettext('Availability Zone'), style: { width: '20%' }, sortable: true } ], volume_snapshot: [ { text: gettext('Name'), style: { width: '25%' }, sortable: true, sortDefault: true }, { text: gettext('Description'), style: { width: '20%' }, sortable: true }, { text: gettext('Size'), style: { width: '15%' }, classList: ['number'], sortable: true }, { text: gettext('Created'), style: { width: '15%' }, sortable: true }, { text: gettext('Status'), style: { width: '20%' }, sortable: true } ] }; // Map Visibility data so we can decode true/false to Public/Private var _visibilitymap = { true: gettext('Public'), false: gettext('Private') }; // Mapping for dynamic table data var tableBodyCellsMap = { image: [ { key: 'name', classList: ['hi-light'] }, { key: 'updated_at', filter: dateFilter, filterArg: 'short' }, { key: 'size', filter: bytesFilter, classList: ['number'] }, { key: 'disk_format', style: { 'text-transform': 'uppercase' } }, { key: 'is_public', filter: decodeFilter, filterArg: _visibilitymap, style: { 'text-transform': 'capitalize' } } ], snapshot: [ { key: 'name', classList: ['hi-light'] }, { key: 'updated_at', filter: dateFilter, filterArg: 'short' }, { key: 'size', filter: bytesFilter, classList: ['number'] }, { key: 'disk_format', style: { 'text-transform': 'uppercase' } }, { key: 'is_public', filter: decodeFilter, filterArg: _visibilitymap, style: { 'text-transform': 'capitalize' } } ], volume: [ { key: 'name', classList: ['hi-light'] }, { key: 'description' }, { key: 'size', filter: gbFilter, classList: ['number'] }, { key: 'volume_image_metadata', filter: diskFormatFilter, style: { 'text-transform': 'uppercase' } }, { key: 'availability_zone' } ], volume_snapshot: [ { key: 'name', classList: ['hi-light'] }, { key: 'description' }, { key: 'size', filter: gbFilter, classList: ['number'] }, { key: 'created_at', filter: dateFilter, filterArg: 'short' }, { key: 'status', style: { 'text-transform': 'capitalize' } } ] }; var newSpecWatcher = $scope.$watch( function () { return $scope.model.newInstanceSpec.instance_count; }, function (newValue, oldValue) { if (newValue !== oldValue) { validateBootSourceType(); } } ); var allocatedWatcher = $scope.$watch( function () { return ctrl.tableData.allocated.length; }, function (newValue) { checkVolumeForImage(newValue); } ); var imagesWatcher = $scope.$watchCollection( function () { return $scope.model.images; }, function () { $scope.initPromise.then(function () { $scope.$applyAsync(function () { if ($scope.launchContext.imageId) { setSourceImageWithId($scope.launchContext.imageId); } }); }); } ); // Explicitly remove watchers on desruction of this controller $scope.$on('$destroy', function() { newSpecWatcher(); allocatedWatcher(); imagesWatcher(); }); // Initialize changeBootSource(ctrl.bootSourcesOptions[0].type); if (!$scope.model.newInstanceSpec.source_type) { $scope.model.newInstanceSpec.source_type = ctrl.bootSourcesOptions[0]; ctrl.currentBootSource = ctrl.bootSourcesOptions[0].type; } //////////////////// function updateBootSourceSelection(selectedSource) { ctrl.currentBootSource = selectedSource; $scope.model.newInstanceSpec.vol_create = false; $scope.model.newInstanceSpec.vol_delete_on_instance_delete = false; changeBootSource(selectedSource); validateBootSourceType(); } // Dynamically update page based on boot source selection function changeBootSource(key, preSelection) { updateDataSource(key, preSelection); updateHelpText(key); updateTableHeadCells(key); updateTableBodyCells(key); } function updateDataSource(key, preSelection) { selection.length = 0; if (preSelection) { push.apply(selection, preSelection); } angular.extend(ctrl.tableData, bootSources[key]); } function updateHelpText() { angular.extend(ctrl.helpText, { noneAllocText: gettext('Select a source from those listed below.'), availHelpText: gettext('Select one'), /*eslint-disable max-len */ volumeAZHelpText: gettext('When selecting volume as boot source, please ensure the instance\'s availability zone is compatible with your volume\'s availability zone.') /*eslint-enable max-len */ }); } function updateTableHeadCells(key) { refillArray(ctrl.tableHeadCells, tableHeadCellsMap[key]); } function updateTableBodyCells(key) { refillArray(ctrl.tableBodyCells, tableBodyCellsMap[key]); } function refillArray(arrayToRefill, contentArray) { arrayToRefill.length = 0; Array.prototype.push.apply(arrayToRefill, contentArray); } /* * Validation */ /* * If boot source type is 'image' and 'Create New Volume' is checked, set the minimum volume * size for validating vol_size field */ function checkVolumeForImage() { var source = selection ? selection[0] : undefined; if (source && ctrl.currentBootSource === bootSourceTypes.IMAGE) { var imageGb = source.size * 1e-9; var imageDisk = source.min_disk; ctrl.minVolumeSize = Math.ceil(Math.max(imageGb, imageDisk)); var volumeSizeText = gettext('The volume size must be at least %(minVolumeSize)s GB'); var volumeSizeObj = { minVolumeSize: ctrl.minVolumeSize }; ctrl.minVolumeSizeError = interpolate(volumeSizeText, volumeSizeObj, true); } else { ctrl.minVolumeSize = undefined; } } // Validator for boot source type. Instance count must to be 1 if volume selected function validateBootSourceType() { var bootSourceType = ctrl.currentBootSource; var instanceCount = $scope.model.newInstanceSpec.instance_count; /* * Field is valid if boot source type is not volume, instance count is blank/undefined * (this is an error with instance count) or instance count is 1 */ var isValid = bootSourceType !== bootSourceTypes.VOLUME || !instanceCount || instanceCount === 1; $scope.launchInstanceSourceForm['boot-source-type'] .$setValidity('bootSourceType', isValid); } function findSourceById(sources, id) { var len = sources.length; var source; for (var i = 0; i < len; i++) { source = sources[i]; if (source.id === id) { return source; } } } function setSourceImageWithId(id) { var pre = findSourceById($scope.model.images, id); if (pre) { changeBootSource(bootSourceTypes.IMAGE, [pre]); $scope.model.newInstanceSpec.source_type = ctrl.bootSourcesOptions[0]; ctrl.currentBootSource = ctrl.bootSourcesOptions[0].type; } } } })();