Revert "Add bulk offline node removal"

This reverts commit de101edf03.

Change-Id: I6cfd82366f8b57d86af24b9eeb20473dbd40a8b4
This commit is contained in:
Alexey Shtokolov 2017-01-27 20:29:40 +00:00
parent de101edf03
commit 811dcf6966
7 changed files with 151 additions and 195 deletions

View File

@ -442,27 +442,26 @@ button, .btn:not(.btn-link) {.font-semibold;}
padding: 0;
line-height: 1;
}
.btn, .open > .dropdown-toggle {
&.btn-success {
.button-mixin(@btn-success);
}
&.btn-primary {
.button-mixin(@btn-primary);
}
&.btn-warning {
.button-mixin(@btn-warning);
}
&.btn-danger {
.button-mixin(@btn-danger);
}
&.btn-info {
.button-mixin(@btn-info);
}
&.btn-link {
color: @link-color;
&:hover {
color: @link-color-hover;
}
.btn-success {
.button-mixin(@btn-success);
}
.btn-primary {
.button-mixin(@btn-primary);
}
.btn-warning {
.button-mixin(@btn-warning);
}
.btn-danger {
.button-mixin(@btn-danger);
}
.btn-info {
.button-mixin(@btn-info);
}
.btn-link {
color: @link-color;
&:hover {
color: @link-color-hover;
}
}
@ -1943,6 +1942,7 @@ input[type=range] {
.control-buttons-box {
margin-bottom: @management-panel-indent * 3;
overflow: hidden;
padding-left: 0;
.btn-group {
vertical-align: baseline;
@ -1951,9 +1951,6 @@ input[type=range] {
.glyphicon {
margin-right: @management-panel-indent;
}
&.dropdown-toggle {
padding: 6px 8px;
}
}
&:not(:first-child) button:first-child {
margin-left: @management-panel-indent;

View File

@ -73,13 +73,7 @@ define([
'Removing of offline nodes is available on the page')
.clickByCssSelector('.node.pending_addition > label')
.assertElementNotExists('.control-buttons-box .btn',
'No management buttons for selected online node')
.clickByCssSelector('.node.pending_addition > label')
.clickByCssSelector('.node.offline > label')
.assertElementExists('.control-buttons-box .btn',
'Management buttons for selected offline node')
.assertElementExists('.control-buttons-box .btn-remove-nodes',
'Only removing of offline nodes is available on the page')
'No management buttons for selected node')
.assertElementExists('.node-list-management-buttons .btn-labels:not(:disabled)',
'Nodes can be labelled on the page')
.assertElementsExist('.node.pending_addition .btn-view-logs', 4,

View File

@ -586,7 +586,6 @@
"search_placeholder": "Node name, MAC or IP address",
"edit_roles_button": "Edit Roles",
"add_nodes_button": "Add Nodes",
"remove_offline_nodes": "Remove offline nodes",
"label_value_not_specified" : "Value is not specified",
"label_not_assigned": "Label is not assigned",
"labels": {
@ -1253,9 +1252,9 @@
"cant_discard_instruction_start": "Use the ",
"cant_discard_instruction_end": " tab to adjust the configuration manually."
},
"remove_nodes": {
"default_message": "Fuel will remove the offline node(s) from its database. This operation will not erase MBR or any data on the node(s). If you power on a node, it will boot bootstrap (discovery) system, and a node will appear in Fuel as a new unallocated server.",
"title": "Remove Offline Nodes"
"remove_node": {
"default_message": "Fuel will remove this node from its database. This operation will not erase MBR or any data on this node. If you power on the node, it will boot bootstrap (discovery) system, and the node will appear in Fuel as a new unallocated server.",
"title": "Remove Node"
},
"remove_cluster": {
"title": "Delete Environment",
@ -2339,6 +2338,10 @@
"discard_deletion": "你确定要放弃删除节点吗?",
"cant_discard": "不能放弃变更。"
},
"remove_node": {
"default_message": "Fuel 会从数据库中删除这个节点。这个操作不会删除节点的 MBR 及任何数据。如果你开启这个节点,它会进入(发现)系统,这个节点会重新成为 Fuel 未分配节点。",
"title": "删除节点"
},
"remove_cluster": {
"title": "删除环境",
"incomplete_actions_text": "这个环境有操作未完成,删除环境可能失败,并导致状态不一致。",
@ -3030,6 +3033,10 @@
"alert_text": "本当に変更を破棄してもよろしいですか?",
"cant_discard": "ノードの変更を破棄することができません"
},
"remove_node": {
"default_message": "Fuelはデータベースからこのードを削除します。この操作ではードのMBRやそのほかのデータは削除されません。ードの起動した場合、ブートストラップディスカバリーシステムが起動され、新規または未割り当てのサーバとしてFuelに再表示されます。",
"title": "ノードの削除"
},
"remove_cluster": {
"title": "環境の削除",
"incomplete_actions_text": "完了していないアクションがあります。 環境を削除すると、失敗し、一貫性のない状態につながる可能性があります。",

View File

@ -15,11 +15,13 @@
**/
import _ from 'underscore';
import i18n from 'i18n';
import Backbone from 'backbone';
import React from 'react';
import utils from 'utils';
import models from 'models';
import dispatcher from 'dispatcher';
import {Input, Popover, Tooltip} from 'views/controls';
import {DeleteNodesDialog, RemoveOfflineNodesDialog, ShowNodeInfoDialog} from 'views/dialogs';
import {DeleteNodesDialog, RemoveOfflineNodeDialog, ShowNodeInfoDialog} from 'views/dialogs';
import {renamingMixin} from 'component_mixins';
var Node = React.createClass({
@ -112,9 +114,37 @@ var Node = React.createClass({
},
removeNode(e) {
e.preventDefault();
var {node, viewMode, cluster} = this.props;
if (viewMode === 'compact') this.toggleExtendedNodePanel();
RemoveOfflineNodesDialog.show({nodes: new models.Nodes(node), cluster});
if (this.props.viewMode === 'compact') this.toggleExtendedNodePanel();
RemoveOfflineNodeDialog
.show()
.done(() => {
// sync('delete') is used instead of node.destroy() because we want
// to keep showing the 'Removing' status until the node is truly removed
// Otherwise this node would disappear and might reappear again upon
// cluster nodes refetch with status 'Removing' which would look ugly
// to the end user
return Backbone
.sync('delete', this.props.node)
.then(
(task) => {
dispatcher.trigger('networkConfigurationUpdated updateNodeStats ' +
'updateNotifications labelsConfigurationUpdated');
if (task.status === 'ready') {
// Do not send the 'DELETE' request again, just get rid
// of this node.
this.props.node.trigger('destroy', this.props.node);
return;
}
if (this.props.cluster) {
this.props.cluster.get('tasks').add(new models.Task(task), {parse: true});
}
this.props.node.set('status', 'removing');
},
(response) => {
utils.showErrorDialog({response: response});
}
);
});
},
showNodeDetails(e) {
e.preventDefault();

View File

@ -24,7 +24,7 @@ import utils from 'utils';
import models from 'models';
import dispatcher from 'dispatcher';
import {Input, Popover, Tooltip, MultiSelectControl, ProgressButton} from 'views/controls';
import {DeleteNodesDialog, RemoveOfflineNodesDialog} from 'views/dialogs';
import {DeleteNodesDialog} from 'views/dialogs';
import {backboneMixin, pollingMixin, dispatcherMixin, unsavedChangesMixin} from 'component_mixins';
import Node from 'views/cluster_page_tabs/nodes_tab_screens/node';
import {Sorter, Filter} from 'views/cluster_page_tabs/nodes_tab_screens/sorter_and_filter';
@ -679,12 +679,6 @@ ManagementPanel = React.createClass({
_.pluck(this.props.nodes.where({status: 'ready'}), 'id'), null, true)
);
},
showRemoveNodesDialog() {
var {cluster, nodes, selectNodes} = this.props;
RemoveOfflineNodesDialog
.show({nodes, cluster})
.then(_.partial(selectNodes, _.map(nodes.filter({status: 'removing'}), 'id'), null, false));
},
hasChanges() {
return this.props.hasChanges;
},
@ -863,7 +857,7 @@ ManagementPanel = React.createClass({
},
render() {
var {
cluster, nodes, screenNodes, filteredNodes, mode, locked, showBatchActionButtons,
nodes, screenNodes, filteredNodes, mode, locked, showBatchActionButtons,
viewMode, changeViewMode, showViewModeButtons,
search,
activeSorters, availableSorters, labelSorters, defaultSorting, changeSortingOrder, addSorting,
@ -1023,121 +1017,85 @@ ManagementPanel = React.createClass({
</div>
<div className='control-buttons-box col-xs-7 text-right'>
{showBatchActionButtons && (
cluster ? (
mode !== 'list' ?
<div className='btn-group' role='group'>
<button
className='btn btn-default'
disabled={this.state.actionInProgress}
onClick={() => {
revertChanges();
this.changeScreen();
}}
>
{i18n('common.cancel_button')}
</button>
<ProgressButton
className='btn btn-success btn-apply'
disabled={!this.isSavingPossible()}
onClick={this.applyAndRedirect}
progress={this.state.actionInProgress}
>
{i18n('common.apply_changes_button')}
</ProgressButton>
</div>
:
[
!!nodes.length &&
<div className='btn-group' role='group' key='configuration-buttons'>
<button
className='btn btn-default btn-configure-disks'
onClick={() => this.goToConfigurationScreen('disks', disksConflict)}
>
{disksConflict && <i className='glyphicon glyphicon-danger-sign' />}
{i18n('dialog.show_node.disk_configuration' +
(_.every(nodes.invoke('areDisksConfigurable')) ? '_action' : ''))
}
</button>
<button
className='btn btn-default btn-configure-interfaces'
onClick={
() => this.goToConfigurationScreen('interfaces', interfaceConflict)
}
>
{interfaceConflict && <i className='glyphicon glyphicon-danger-sign' />}
{i18n('dialog.show_node.network_configuration' +
(_.every(nodes.invoke('areInterfacesConfigurable')) ?
'_action' : '')
)
}
</button>
</div>,
!locked && !!nodes.length &&
<div className='btn-group' role='group' key='edit-nodes-button'>
<button
className='btn btn-default btn-edit-roles'
onClick={_.partial(this.changeScreen, 'edit', true)}
>
{i18n(ns + 'edit_roles_button')}
</button>
</div>,
!locked && !!nodes.length && nodes.some({pending_deletion: false}) &&
<div className='btn-group' role='group' key='delete-nodes-button'>
{nodes.every({online: false}) ?
<div className='btn-group'>
<button
className='btn btn-danger btn-delete-nodes'
onClick={this.showDeleteNodesDialog}
>
{i18n('common.delete_button')}
</button>
<button
className='btn btn-danger dropdown-toggle btn-toggle-delete'
data-toggle='dropdown'
>
<span className='caret' />
</button>
<ul className='dropdown-menu'>
<li>
<button
className='btn btn-link btn-remove-nodes'
onClick={this.showRemoveNodesDialog}
>
{i18n(ns + 'remove_offline_nodes')}
</button>
</li>
</ul>
</div>
:
<button
className='btn btn-danger btn-delete-nodes'
onClick={this.showDeleteNodesDialog}
>
{i18n('common.delete_button')}
</button>
}
</div>,
!locked &&
<div className='btn-group' role='group' key='add-nodes-button'>
<button
className='btn btn-success btn-add-nodes'
onClick={_.partial(this.changeScreen, 'add', false)}
disabled={locked}
>
<i className='glyphicon glyphicon-plus-white' />
{i18n(ns + 'add_nodes_button')}
</button>
</div>
]
)
:
!!nodes.length && nodes.every({online: false}) &&
mode !== 'list' ?
<div className='btn-group' role='group'>
<button
className='btn btn-danger btn-remove-nodes'
onClick={this.showRemoveNodesDialog}
className='btn btn-default'
disabled={this.state.actionInProgress}
onClick={() => {
revertChanges();
this.changeScreen();
}}
>
{i18n('common.remove_button')}
{i18n('common.cancel_button')}
</button>
<ProgressButton
className='btn btn-success btn-apply'
disabled={!this.isSavingPossible()}
onClick={this.applyAndRedirect}
progress={this.state.actionInProgress}
>
{i18n('common.apply_changes_button')}
</ProgressButton>
</div>
:
[
<div className='btn-group' role='group' key='configuration-buttons'>
<button
className='btn btn-default btn-configure-disks'
disabled={!nodes.length}
onClick={_.bind(this.goToConfigurationScreen, this, 'disks', disksConflict)}
>
{disksConflict && <i className='glyphicon glyphicon-danger-sign' />}
{i18n('dialog.show_node.disk_configuration' +
(_.all(nodes.invoke('areDisksConfigurable')) ? '_action' : ''))
}
</button>
<button
className='btn btn-default btn-configure-interfaces'
disabled={!nodes.length}
onClick={_.bind(this.goToConfigurationScreen, this, 'interfaces',
interfaceConflict)
}
>
{interfaceConflict && <i className='glyphicon glyphicon-danger-sign' />}
{i18n('dialog.show_node.network_configuration' +
(_.all(nodes.invoke('areInterfacesConfigurable')) ? '_action' : ''))
}
</button>
</div>,
<div className='btn-group' role='group' key='role-management-buttons'>
{!locked && !!nodes.length && nodes.any({pending_deletion: false}) &&
<button
className='btn btn-danger btn-delete-nodes'
onClick={this.showDeleteNodesDialog}
>
<i className='glyphicon glyphicon-trash' />
{i18n('common.delete_button')}
</button>
}
{!locked && !!nodes.length &&
<button
className='btn btn-success btn-edit-roles'
onClick={_.partial(this.changeScreen, 'edit', true)}
>
<i className='glyphicon glyphicon-edit' />
{i18n(ns + 'edit_roles_button')}
</button>
}
</div>,
!locked &&
<div className='btn-group' role='group' key='add-nodes-button'>
<button
className='btn btn-success btn-add-nodes'
onClick={_.partial(this.changeScreen, 'add', false)}
disabled={locked}
>
<i className='glyphicon glyphicon-plus-white' />
{i18n(ns + 'add_nodes_button')}
</button>
</div>
]
)}
</div>
{mode !== 'edit' && !!screenNodes.length && [

View File

@ -2020,45 +2020,14 @@ export var DiscardSettingsChangesDialog = React.createClass({
}
});
export var RemoveOfflineNodesDialog = React.createClass({
export var RemoveOfflineNodeDialog = React.createClass({
mixins: [dialogMixin],
getDefaultProps() {
return {
title: i18n('dialog.remove_nodes.title'),
defaultMessage: i18n('dialog.remove_nodes.default_message')
title: i18n('dialog.remove_node.title'),
defaultMessage: i18n('dialog.remove_node.default_message')
};
},
removeNodes() {
this.setState({actionInProgress: true});
var {cluster, nodes} = this.props;
var offlineNodes = nodes.filter({online: false});
// sync('delete') is used instead of node.destroy() because we want
// to keep showing the 'Removing' status until the nodes is truly removed
// Otherwise this nodes would disappear and might reappear again upon
// cluster nodes refetch with status 'Removing' which would look ugly
// to the end user
Backbone.sync('delete', nodes, {
url: _.result(nodes, 'url') + '/?' + $.param({ids: _.map(offlineNodes, 'id').join(',')})
})
.then(
(task) => {
dispatcher.trigger('networkConfigurationUpdated updateNodeStats ' +
'updateNotifications labelsConfigurationUpdated');
if (task.status === 'ready') {
// Do not send the 'DELETE' request again, just get rid of the nodes.
_.each(offlineNodes, (node) => node.trigger('destroy', node));
} else {
if (cluster) {
cluster.get('tasks').add(new models.Task(task), {parse: true});
}
_.each(offlineNodes, (node) => node.set({status: 'removing'}));
}
this.resolveResult();
this.close();
},
(response) => this.showError(response)
);
},
renderBody() {
return (
<div className='text-danger'>
@ -2075,10 +2044,10 @@ export var RemoveOfflineNodesDialog = React.createClass({
<ProgressButton
key='remove'
className='btn btn-danger btn-delete'
onClick={this.removeNodes}
onClick={this.submitAction}
progress={this.state.actionInProgress}
>
{i18n('common.remove_button')}
{i18n('cluster_page.nodes_tab.node.remove')}
</ProgressButton>
];
}

View File

@ -118,6 +118,7 @@ EquipmentPage = React.createClass({
selectedNodeIds={this.state.selectedNodeIds}
selectNodes={this.selectNodes}
updateUISettings={this.updateUISettings}
showBatchActionButtons={false}
/>
</div>
</div>