/* * Copyright 2015 Mirantis, Inc. * * 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. **/ import $ from 'jquery'; import _ from 'underscore'; import Backbone from 'backbone'; import i18n from 'i18n'; import React from 'react'; import ReactDOM from 'react-dom'; import utils from 'utils'; import models from 'models'; import {backboneMixin, unsavedChangesMixin} from 'component_mixins'; import {Input} from 'views/controls'; var EditNodeDisksScreen = React.createClass({ mixins: [ backboneMixin('cluster', 'change:status change:nodes sync'), backboneMixin('nodes', 'change sync'), backboneMixin('disks', 'reset change'), unsavedChangesMixin ], statics: { fetchData(options) { var nodes = utils.getNodeListFromTabOptions(options); if (!nodes || !nodes.areDisksConfigurable()) { return $.Deferred().reject(); } var volumes = new models.Volumes(); volumes.url = _.result(nodes.at(0), 'url') + '/volumes'; return $.when(...nodes.map((node) => { node.disks = new models.Disks(); return node.disks.fetch({url: _.result(node, 'url') + '/disks'}); }, this).concat(volumes.fetch())) .then(() => { var disks = new models.Disks(_.cloneDeep(nodes.at(0).disks.toJSON()), {parse: true}); return { disks: disks, nodes: nodes, volumes: volumes }; }); } }, getInitialState() { return {actionInProgress: false}; }, componentWillMount() { this.updateInitialData(); }, isLocked() { return !!this.props.cluster.task({group: 'deployment', active: true}) || !_.all(this.props.nodes.invoke('areDisksConfigurable')); }, updateInitialData() { this.setState({initialDisks: _.cloneDeep(this.props.nodes.at(0).disks.toJSON())}); }, hasChanges() { return !this.isLocked() && !_.isEqual( _.pluck(this.props.disks.toJSON(), 'volumes'), _.pluck(this.state.initialDisks, 'volumes') ); }, loadDefaults() { this.setState({actionInProgress: true}); this.props.disks.fetch({url: _.result(this.props.nodes.at(0), 'url') + '/disks/defaults/'}) .fail((response) => { var ns = 'cluster_page.nodes_tab.configure_disks.configuration_error.'; utils.showErrorDialog({ title: i18n(ns + 'title'), message: utils.getResponseText(response) || i18n(ns + 'load_defaults_warning') }); }) .always(() => { this.setState({actionInProgress: false}); }); }, revertChanges() { this.props.disks.reset(_.cloneDeep(this.state.initialDisks), {parse: true}); }, applyChanges() { if (!this.isSavingPossible()) return $.Deferred().reject(); this.setState({actionInProgress: true}); return $.when(...this.props.nodes.map((node) => { node.disks.each((disk, index) => { disk.set({volumes: new models.Volumes(this.props.disks.at(index).get('volumes').toJSON())}); }); return Backbone.sync('update', node.disks, {url: _.result(node, 'url') + '/disks'}); })) .done(this.updateInitialData) .fail((response) => { var ns = 'cluster_page.nodes_tab.configure_disks.configuration_error.'; utils.showErrorDialog({ title: i18n(ns + 'title'), message: utils.getResponseText(response) || i18n(ns + 'saving_warning') }); }) .always(() => { this.setState({actionInProgress: false}); }); }, getDiskMetaData(disk) { var result; var disksMetaData = this.props.nodes.at(0).get('meta').disks; // try to find disk metadata by matching "extra" field // if at least one entry presents both in disk and metadata entry, // this metadata entry is for our disk var extra = disk.get('extra') || []; result = _.find(disksMetaData, (diskMetaData) => _.isArray(diskMetaData.extra) && _.intersection(diskMetaData.extra, extra).length ); // if matching "extra" fields doesn't work, try to search by disk id if (!result) { result = _.find(disksMetaData, {disk: disk.id}); } return result; }, getVolumesInfo(disk) { var volumes = {}; var unallocatedWidth = 100; disk.get('volumes').each((volume) => { var size = volume.get('size') || 0; var width = this.getVolumeWidth(disk, size); var name = volume.get('name'); unallocatedWidth -= width; volumes[name] = { size: size, width: width, max: volume.getMaxSize(), min: volume.getMinimalSize(this.props.volumes.findWhere({name: name}).get('min_size')), error: volume.validationError }; }); volumes.unallocated = { size: disk.getUnallocatedSpace(), width: unallocatedWidth }; return volumes; }, getVolumeWidth(disk, size) { return disk.get('size') ? utils.floor(size / disk.get('size') * 100, 2) : 0; }, hasErrors() { return this.props.disks.any((disk) => disk.get('volumes').any('validationError') ); }, isSavingPossible() { return !this.state.actionInProgress && this.hasChanges() && !this.hasErrors(); }, render() { var hasChanges = this.hasChanges(); var locked = this.isLocked(); var loadDefaultsDisabled = !!this.state.actionInProgress; var revertChangesDisabled = !!this.state.actionInProgress || !hasChanges; return (
{i18n( 'cluster_page.nodes_tab.configure_disks.' + (locked ? 'read_only_' : '') + 'title', { count: this.props.nodes.length, name: this.props.nodes.length && this.props.nodes.at(0).get('name') } )}
{this.props.disks.length ? this.props.disks.map((disk, index) => { return (); }) :
{i18n('cluster_page.nodes_tab.configure_disks.no_disks', {count: this.props.nodes.length})}
}
{i18n('cluster_page.nodes_tab.back_to_nodes_button')}
{!locked && !!this.props.disks.length &&
}
); } }); var NodeDisk = React.createClass({ getInitialState() { return {collapsed: true}; }, componentDidMount() { $('.disk-details', ReactDOM.findDOMNode(this)) .on('show.bs.collapse', this.setState.bind(this, {collapsed: true}, null)) .on('hide.bs.collapse', this.setState.bind(this, {collapsed: false}, null)); }, updateDisk(name, value) { var size = parseInt(value, 10) || 0; var volumeInfo = this.props.volumesInfo[name]; if (size > volumeInfo.max) { size = volumeInfo.max; } this.props.disk.get('volumes').findWhere({name: name}).set({size: size}) .isValid({minimum: volumeInfo.min}); this.props.disk.trigger('change', this.props.disk); }, toggleDisk(name) { $(ReactDOM.findDOMNode(this.refs[name])).collapse('toggle'); }, render() { var disk = this.props.disk; var volumesInfo = this.props.volumesInfo; var diskMetaData = this.props.diskMetaData; var requiredDiskSize = _.sum(disk.get('volumes').map((volume) => { return volume .getMinimalSize(this.props.volumes.findWhere({name: volume.get('name')}).get('min_size')); })); var diskError = disk.get('size') < requiredDiskSize; var sortOrder = ['name', 'model', 'size']; var ns = 'cluster_page.nodes_tab.configure_disks.'; return (

{disk.get('name')} ({disk.id})

{i18n(ns + 'total_space')} : {utils.showDiskSize(disk.get('size'), 2)}

{this.props.volumes.map((volume, index) => { var volumeName = volume.get('name'); return (
{volume.get('label')}
{utils.showDiskSize(volumesInfo[volumeName].size, 2)}
{!this.props.disabled && volumesInfo[volumeName].min <= 0 && this.state.collapsed &&
×
}
); })}
{i18n(ns + 'unallocated')}
{utils.showDiskSize(volumesInfo.unallocated.size, 2)}
{diskMetaData &&
{i18n(ns + 'disk_information')}
{_.map(utils.sortEntryProperties(diskMetaData, sortOrder), (propertyName) => { return (

{propertyName == 'size' ? utils.showDiskSize(diskMetaData[propertyName]) : diskMetaData[propertyName] }

); })}
}
{i18n(ns + 'volume_groups')}
{this.props.volumes.map((volume, index) => { var volumeName = volume.get('name'); var value = volumesInfo[volumeName].size; var currentMaxSize = volumesInfo[volumeName].max; var currentMinSize = _.max([volumesInfo[volumeName].min, 0]); var validationError = volumesInfo[volumeName].error; var props = { name: volumeName, min: currentMinSize, max: currentMaxSize, disabled: this.props.disabled || currentMaxSize <= currentMinSize }; return (
{i18n('common.size.mb')}
{!!value && value == currentMinSize &&
{i18n(ns + 'minimum_reached')}
} {validationError &&
{validationError}
}
); })} {diskError &&
{i18n(ns + 'not_enough_space')}
}
); } }); export default EditNodeDisksScreen;