fuel-ui/static/plugins/vmware/vmware_tab.js

481 lines
14 KiB
JavaScript

/*
* 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 React from 'react';
import $ from 'jquery';
import i18n from 'i18n';
import _ from 'underscore';
import dispatcher from 'dispatcher';
import {Input, Tooltip} from 'views/controls';
import {unsavedChangesMixin} from 'component_mixins';
import VmWareModels from 'plugins/vmware/vmware_models';
var Field = React.createClass({
onChange(name, value) {
var currentValue = this.props.model.get(name);
if (currentValue.current) {
currentValue.current.id = value;
currentValue.current.label = value;
} else {
currentValue = value;
}
this.props.model.set(name, currentValue);
this.setState({model: this.props.model});
_.defer(() => dispatcher.trigger('vcenter_model_update'));
},
render() {
var metadata = this.props.metadata;
var value = this.props.model.get(metadata.name);
var children = null;
var props = _.extend({
onChange: this.onChange,
disabled: this.props.disabled,
tooltipText: this.props.tooltipText,
error: (this.props.model.validationError || {})[metadata.name]
}, _.pick(metadata, 'name', 'type', 'label', 'description'));
switch (metadata.type) {
case 'password':
props.value = value;
props.toggleable = true;
break;
case 'radio':
case 'checkbox':
props.checked = value;
break;
case 'select':
props.value = value.current.id;
children = value.options.map((value) => {
return <option key={value.id} value={value.id}>{value.label}</option>;
});
break;
case 'file':
props.key = value && value.name;
props.defaultValue = value;
break;
default:
props.value = value;
break;
}
return (
<Input {...props}>{children}</Input>
);
}
});
var FieldGroup = React.createClass({
render() {
var restrictions = this.props.model.testRestrictions();
var metadata = _.filter(this.props.model.get('metadata'), VmWareModels.isRegularField);
var fields = metadata.map((meta) => {
if (restrictions.hide[meta.name] && restrictions.hide[meta.name].result) {
return null;
}
return (
<Field
key={meta.name}
model={this.props.model}
metadata={meta}
disabled={this.props.disabled || restrictions.disable[meta.name].result}
tooltipText={restrictions.disable[meta.name].message}
/>
);
});
return (
<div>
{fields}
</div>
);
}
});
var GenericSection = React.createClass({
render() {
if (!this.props.model) return null;
return (
<div className='col-xs-12 forms-box'>
<h3>
{this.props.title}
{this.props.tooltipText &&
<Tooltip text={this.props.tooltipText} placement='right'>
<i className='glyphicon glyphicon-warning-sign tooltip-icon' />
</Tooltip>
}
</h3>
<FieldGroup model={this.props.model} disabled={this.props.disabled}/>
</div>
);
}
});
var NovaCompute = React.createClass({
render() {
if (!this.props.model) return null;
// add nodes of 'compute-vmware' type to targetNode select
var targetNode = this.props.model.get('target_node') || {};
var nodes = this.props.cluster.get('nodes').filter((node) => node.hasRole('compute-vmware'));
targetNode.options = [];
if (targetNode.current.id === 'controllers' || !this.props.isLocked) {
targetNode.options.push({id: 'controllers', label: 'controllers'});
} else {
targetNode.options.push({id: 'invalid', label: 'Select node'});
}
nodes.forEach((node) => {
targetNode.options.push({
id: node.get('hostname'),
label: node.get('name') + ' (' + node.get('mac').substr(9) + ')'
});
});
this.props.model.set('target_node', targetNode);
return (
<div className='nova-compute'>
<h4>
<div className='btn-group'>
<button
className='btn btn-link'
disabled={this.props.disabled}
onClick={() => {
this.props.onAdd(this.props.model);
}}
>
<i className='glyphicon glyphicon-plus-sign' />
</button>
{!this.props.isRemovable &&
<button
className='btn btn-link'
disabled={this.props.disabled}
onClick={() => {
this.props.onRemove(this.props.model);
}}
>
<i className='glyphicon glyphicon-minus-sign' />
</button>
}
</div>
{i18n('vmware.nova_compute')}
</h4>
<FieldGroup model={this.props.model} disabled={this.props.disabled}/>
</div>
);
}
});
var AvailabilityZone = React.createClass({
addNovaCompute(current) {
var collection = this.props.model.get('nova_computes');
var index = collection.indexOf(current);
var newItem = current.clone();
var targetNode = _.cloneDeep(newItem.get('target_node'));
if (this.props.isLocked) {
targetNode.current = {id: 'invalid'};
}
newItem.set('target_node', targetNode);
collection.add(newItem, {at: index + 1});
this.setState({model: this.props.model});
_.defer(() => dispatcher.trigger('vcenter_model_update'));
},
removeNovaCompute(current) {
var collection = this.props.model.get('nova_computes');
collection.remove(current);
this.setState({model: this.props.model});
_.defer(() => dispatcher.trigger('vcenter_model_update'));
},
renderFields() {
var model = this.props.model;
var meta = model.get('metadata');
meta = _.filter(meta, VmWareModels.isRegularField);
return (
<FieldGroup model={model} disabled={this.props.isLocked || this.props.disabled}/>
);
},
renderComputes(actions) {
var novaComputes = this.props.model.get('nova_computes');
var isSingleInstance = novaComputes.length === 1;
var disabled = actions.disable.nova_computes;
var cluster = this.props.cluster;
return (
<div className='col-xs-offset-1'>
<h3>
{i18n('vmware.nova_computes')}
</h3>
{novaComputes.map((compute) => {
return (
<NovaCompute
key={compute.cid}
model={compute}
onAdd={this.addNovaCompute}
onRemove={this.removeNovaCompute}
isRemovable={isSingleInstance}
disabled={disabled.result || this.props.disabled}
isLocked={this.props.isLocked}
cluster={cluster}
/>
);
})}
</div>
);
},
render() {
var restrictActions = this.props.model.testRestrictions();
return (
<div>
{this.renderFields(restrictActions)}
{this.renderComputes(restrictActions)}
</div>
);
}
});
var AvailabilityZones = React.createClass({
render() {
if (!this.props.collection) return null;
return (
<div className='col-xs-12 forms-box'>
<h3>
{i18n('vmware.availability_zones')}
{this.props.tooltipText &&
<Tooltip text={this.props.tooltipText} placement='right'>
<i className='glyphicon glyphicon-warning-sign tooltip-icon' />
</Tooltip>
}
</h3>
{this.props.collection.map((model) => {
return <AvailabilityZone
key={model.cid}
model={model}
disabled={this.props.disabled}
cluster={this.props.cluster}
isLocked={this.props.isLocked}
/>;
})}
</div>
);
}
});
var UnassignedNodesWarning = React.createClass({
render() {
if (!this.props.errors || !this.props.errors.unassigned_nodes) return null;
return (
<div className='alert alert-danger'>
<div>
{i18n('vmware.unassigned_nodes')}
</div>
<ul className='unassigned-node-list'>
{
this.props.errors.unassigned_nodes.map((node) => {
return (
<li key={node.id}
className='unassigned-node'>
<span
className='unassigned-node-name'>{node.get('name')}</span>
&nbsp;
({node.get('mac')})
</li>
);
})
}
</ul>
</div>
);
}
});
var VmWareTab = React.createClass({
mixins: [
unsavedChangesMixin
],
statics: {
breadcrumbsPath() {
return [
[i18n('vmware.title'), null, {active: true}]
];
},
isVisible(cluster) {
return cluster.get('settings').get('common.use_vcenter').value;
},
fetchData(options) {
if (!options.cluster.get('vcenter_defaults')) {
var defaultModel = new VmWareModels.VCenter({id: options.cluster.id});
defaultModel.loadDefaults = true;
options.cluster.set({vcenter_defaults: defaultModel});
}
return $.when(
options.cluster.get('vcenter').fetch({cache: true}),
options.cluster.get('vcenter_defaults').fetch({cache: true})
);
}
},
onModelSync() {
this.actions = this.model.testRestrictions();
if (!this.model.loadDefaults) {
this.json = JSON.stringify(this.model.toJSON());
}
this.model.loadDefaults = false;
this.setState({model: this.model});
},
componentDidMount() {
this.clusterId = this.props.cluster.id;
this.model = this.props.cluster.get('vcenter');
this.model.on('sync', this.onModelSync, this); // eslint-disable-line no-sync
this.defaultModel = this.props.cluster.get('vcenter_defaults');
this.defaultsJson = JSON.stringify(this.defaultModel.toJSON());
this.setState({model: this.model, defaultModel: this.defaultModel});
this.model.setModels({
cluster: this.props.cluster,
settings: this.props.cluster.get('settings'),
networking_parameters: this.props.cluster.get('networkConfiguration')
.get('networking_parameters'),
current_vcenter: this.model.get('availability_zones').at(0),
glance: this.model.get('glance')
});
this.onModelSync(); // eslint-disable-line no-sync
dispatcher.on('vcenter_model_update', () => {
if (this.isMounted()) {
this.forceUpdate();
}
});
},
componentWillUnmount() {
this.model.off('sync', null, this);
dispatcher.off('vcenter_model_update');
},
getInitialState() {
return {model: null};
},
readData() {
return this.model.fetch();
},
onLoadDefaults() {
this.model.loadDefaults = true;
this.model.fetch().done(() => {
this.model.loadDefaults = false;
});
},
applyChanges() {
this.model.beforeSave();
return this.model.save();
},
revertChanges() {
return this.readData();
},
hasChanges() {
return this.detectChanges(this.json, JSON.stringify(this.model.toJSON()));
},
detectChanges(oldJson, currentJson) {
var old, current;
try {
old = JSON.parse(oldJson);
current = JSON.parse(currentJson);
} catch (error) {
return false;
}
var oldData = JSON.stringify(old, (key, data) => {
if (key === 'target_node') {
delete data.options;
}
return data;
});
var currentData = JSON.stringify(current, (key, data) => {
if (key === 'target_node') {
delete data.options;
}
return data;
});
return oldData !== currentData;
},
isSavingPossible() {
return !this.state.model.validationError;
},
render() {
if (!this.state.model || !this.actions) {
return null;
}
var model = this.state.model;
var currentJson = JSON.stringify(this.model.toJSON());
var editable = this.props.cluster.isAvailableForSettingsChanges();
this.actions = this.model.testRestrictions();
var hide = this.actions.hide || {};
var disable = this.actions.disable || {};
model.isValid();
var hasChanges = this.detectChanges(this.json, currentJson);
var hasDefaultsChanges = this.detectChanges(this.defaultsJson, currentJson);
var saveDisabled = !hasChanges || !this.isSavingPossible();
var defaultsDisabled = !hasDefaultsChanges;
return (
<div className='row'>
<div className='title'>{i18n('vmware.title')}</div>
<UnassignedNodesWarning errors={model.validationError}/>
{!hide.availability_zones.result &&
<AvailabilityZones
collection={model.get('availability_zones')}
disabled={disable.availability_zones.result}
tooltipText={disable.availability_zones.message}
isLocked={!editable}
cluster={this.props.cluster}
/>
}
{!hide.glance.result &&
<GenericSection
model={model.get('glance')}
title={i18n('vmware.glance')}
disabled={!editable || disable.glance.result}
tooltipText={disable.glance.message}
/>
}
<div className='col-xs-12 page-buttons content-elements'>
<div className='well clearfix'>
<div className='btn-group pull-right'>
<button
className='btn btn-default btn-load-defaults'
onClick={this.onLoadDefaults}
disabled={!editable || defaultsDisabled}
>
{i18n('vmware.reset_to_defaults')}
</button>
<button
className='btn btn-default btn-revert-changes'
onClick={this.revertChanges}
disabled={!hasChanges}
>
{i18n('vmware.cancel')}
</button>
<button
className='btn btn-success btn-apply-changes'
onClick={this.applyChanges}
disabled={saveDisabled}
>
{i18n('vmware.apply')}
</button>
</div>
</div>
</div>
</div>
);
}
});
export default VmWareTab;