Add Node Expanded view
Adds ability to expand each Node in NodesListView to get more information. This change lists only data provided by Node itself Subsequent patch will add fetching Introspection data from ironic-inspector Change-Id: I3d34f43c5555b1884f34010e3b47b8ffb1691218
This commit is contained in:
parent
69cebd336f
commit
880ab278c6
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Nodes in list view can be expanded to grant access to additional information
|
||||
about the node.
|
|
@ -0,0 +1,51 @@
|
|||
import { FormattedDate, FormattedTime } from 'react-intl';
|
||||
import { startCase } from 'lodash';
|
||||
import React, { PropTypes } from 'react';
|
||||
import { Row, Col } from 'react-bootstrap';
|
||||
|
||||
class NodeExtendedInfo extends React.Component {
|
||||
render() {
|
||||
const { node } = this.props;
|
||||
return (
|
||||
<Row>
|
||||
<Col lg={3} md={6}>
|
||||
<dl className="dl-horizontal dl-horizontal-condensed">
|
||||
<dt>UUID:</dt>
|
||||
<dd>{node.uuid}</dd>
|
||||
<dt>Registered:</dt>
|
||||
<dd>
|
||||
<FormattedDate value={node.created_at} />
|
||||
|
||||
<FormattedTime value={node.created_at} />
|
||||
</dd>
|
||||
<dt>Architecture:</dt>
|
||||
<dd>{node.properties.cpu_arch}</dd>
|
||||
</dl>
|
||||
</Col>
|
||||
<Col lg={2} md={4}>
|
||||
<dl>
|
||||
<dt>Mac Addresses:</dt>
|
||||
{node.macs.map(mac => <dd key={mac}>{mac}</dd>)}
|
||||
</dl>
|
||||
</Col>
|
||||
<Col lg={4} md={6}>
|
||||
<dl className="dl-horizontal dl-horizontal-condensed">
|
||||
<dt>Driver:</dt>
|
||||
<dd>{node.driver}</dd>
|
||||
{Object.keys(node.driver_info).map(key => (
|
||||
<span key={key}>
|
||||
<dt>{startCase(key)}:</dt>
|
||||
<dd>{node.driver_info[key]}</dd>
|
||||
</span>
|
||||
))}
|
||||
</dl>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
NodeExtendedInfo.propTypes = {
|
||||
node: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default NodeExtendedInfo;
|
|
@ -0,0 +1,151 @@
|
|||
import ClassNames from 'classnames';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import React, { PropTypes } from 'react';
|
||||
|
||||
import {
|
||||
ListViewAdditionalInfo,
|
||||
ListViewAdditionalInfoItem,
|
||||
ListViewBody,
|
||||
ListViewCheckbox,
|
||||
ListViewDescription,
|
||||
ListViewDescriptionHeading,
|
||||
ListViewDescriptionText,
|
||||
ListViewExpand,
|
||||
ListViewIcon,
|
||||
ListViewItem,
|
||||
ListViewItemContainer,
|
||||
ListViewItemHeader,
|
||||
ListViewLeft,
|
||||
ListViewMainInfo
|
||||
} from '../../ui/ListView';
|
||||
import NodeExtendedInfo from './NodeExtendedInfo';
|
||||
import {
|
||||
NodeMaintenanceState,
|
||||
NodePowerState,
|
||||
NodeProvisionState
|
||||
} from './NodeStates';
|
||||
import { parseNodeCapabilities } from '../../../utils/nodes';
|
||||
|
||||
const messages = defineMessages({
|
||||
profile: {
|
||||
id: 'NodeListItem.Profile',
|
||||
defaultMessage: 'Profile:'
|
||||
},
|
||||
cpuCores: {
|
||||
id: 'NodeListItem.cpuCores',
|
||||
defaultMessage: `CPU {cpuCores, plural,
|
||||
one {Core} other {Cores}}`
|
||||
},
|
||||
ram: {
|
||||
id: 'NodeListItem.mbRam',
|
||||
defaultMessage: 'MB RAM'
|
||||
},
|
||||
disk: {
|
||||
id: 'NodeListItem.gbDisk',
|
||||
defaultMessage: 'GB Disk'
|
||||
}
|
||||
});
|
||||
|
||||
export default class NodeListItem extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
expanded: false
|
||||
};
|
||||
}
|
||||
|
||||
toggleExpanded() {
|
||||
this.setState(prevState => ({ expanded: !prevState.expanded }));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { node, inProgress } = this.props;
|
||||
|
||||
const iconClass = ClassNames({
|
||||
'pficon pficon-server': true,
|
||||
running: inProgress
|
||||
});
|
||||
|
||||
return (
|
||||
<ListViewItem expanded={this.state.expanded} stacked>
|
||||
<ListViewItemHeader>
|
||||
<ListViewExpand
|
||||
expanded={this.state.expanded}
|
||||
toggleExpanded={this.toggleExpanded.bind(this)}
|
||||
/>
|
||||
<ListViewCheckbox
|
||||
disabled={inProgress}
|
||||
name={`values.${node.uuid}`}
|
||||
/>
|
||||
<ListViewMainInfo>
|
||||
<ListViewLeft>
|
||||
<ListViewIcon size="sm" icon={iconClass} />
|
||||
</ListViewLeft>
|
||||
<ListViewBody>
|
||||
<ListViewDescription>
|
||||
<ListViewDescriptionHeading>
|
||||
{node.name || node.uuid}
|
||||
</ListViewDescriptionHeading>
|
||||
<ListViewDescriptionText>
|
||||
<NodePowerState
|
||||
powerState={node.power_state}
|
||||
targetPowerState={node.target_power_state}
|
||||
/>
|
||||
<NodeMaintenanceState
|
||||
maintenance={node.maintenance}
|
||||
reason={node.maintenance_reason}
|
||||
/>
|
||||
{' | '}
|
||||
<NodeProvisionState
|
||||
provisionState={node.provision_state}
|
||||
targetProvisionState={node.target_provision_state}
|
||||
/>
|
||||
</ListViewDescriptionText>
|
||||
</ListViewDescription>
|
||||
<ListViewAdditionalInfo>
|
||||
<ListViewAdditionalInfoItem>
|
||||
<span className="pficon pficon-flavor" />
|
||||
<FormattedMessage {...messages.profile} />
|
||||
|
||||
{parseNodeCapabilities(node.properties.capabilities)
|
||||
.profile || '-'}
|
||||
</ListViewAdditionalInfoItem>
|
||||
<ListViewAdditionalInfoItem>
|
||||
<span className="pficon pficon-cpu" />
|
||||
<strong>{node.properties.cpus || '-'}</strong>
|
||||
|
||||
<FormattedMessage
|
||||
{...messages.cpuCores}
|
||||
values={{ cpuCores: node.properties.cpus }}
|
||||
/>
|
||||
</ListViewAdditionalInfoItem>
|
||||
<ListViewAdditionalInfoItem>
|
||||
<span className="pficon pficon-memory" />
|
||||
<strong>{node.properties.memory_mb || '-'}</strong>
|
||||
|
||||
<FormattedMessage {...messages.ram} />
|
||||
</ListViewAdditionalInfoItem>
|
||||
<ListViewAdditionalInfoItem>
|
||||
<span className="fa fa-database" />
|
||||
<strong>{node.properties.local_gb || '-'}</strong>
|
||||
|
||||
<FormattedMessage {...messages.disk} />
|
||||
</ListViewAdditionalInfoItem>
|
||||
</ListViewAdditionalInfo>
|
||||
</ListViewBody>
|
||||
</ListViewMainInfo>
|
||||
</ListViewItemHeader>
|
||||
<ListViewItemContainer
|
||||
onClose={this.toggleExpanded.bind(this)}
|
||||
expanded={this.state.expanded}
|
||||
>
|
||||
<NodeExtendedInfo node={node} />
|
||||
</ListViewItemContainer>
|
||||
</ListViewItem>
|
||||
);
|
||||
}
|
||||
}
|
||||
NodeListItem.propTypes = {
|
||||
inProgress: PropTypes.bool.isRequired,
|
||||
node: PropTypes.object.isRequired
|
||||
};
|
|
@ -37,10 +37,10 @@ const messages = defineMessages({
|
|||
}
|
||||
});
|
||||
|
||||
export const NodeMaintenanceState = ({ maintenance }) => {
|
||||
export const NodeMaintenanceState = ({ maintenance, reason }) => {
|
||||
if (maintenance) {
|
||||
return (
|
||||
<span>
|
||||
<span title={reason}>
|
||||
{' | '}
|
||||
<span className="pficon pficon-warning-triangle-o" />
|
||||
|
||||
|
@ -51,7 +51,11 @@ export const NodeMaintenanceState = ({ maintenance }) => {
|
|||
return null;
|
||||
};
|
||||
NodeMaintenanceState.propTypes = {
|
||||
maintenance: PropTypes.bool.isRequired
|
||||
maintenance: PropTypes.bool.isRequired,
|
||||
reason: PropTypes.string
|
||||
};
|
||||
NodeMaintenanceState.defaultProps = {
|
||||
reason: ''
|
||||
};
|
||||
|
||||
export const NodeProvisionState = ({
|
||||
|
|
|
@ -1,48 +1,8 @@
|
|||
import ClassNames from 'classnames';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import React, { PropTypes } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
ListView,
|
||||
ListViewAdditionalInfo,
|
||||
ListViewAdditionalInfoItem,
|
||||
ListViewBody,
|
||||
ListViewCheckbox,
|
||||
ListViewDescription,
|
||||
ListViewDescriptionHeading,
|
||||
ListViewDescriptionText,
|
||||
ListViewIcon,
|
||||
ListViewItem,
|
||||
ListViewLeft,
|
||||
ListViewMainInfo
|
||||
} from '../../ui/ListView';
|
||||
import {
|
||||
NodeMaintenanceState,
|
||||
NodePowerState,
|
||||
NodeProvisionState
|
||||
} from './NodeStates';
|
||||
import { parseNodeCapabilities } from '../../../utils/nodes';
|
||||
|
||||
const messages = defineMessages({
|
||||
profile: {
|
||||
id: 'NodeListItem.Profile',
|
||||
defaultMessage: 'Profile:'
|
||||
},
|
||||
cpuCores: {
|
||||
id: 'NodeListItem.cpuCores',
|
||||
defaultMessage: `CPU {cpuCores, plural,
|
||||
one {Core} other {Cores}}`
|
||||
},
|
||||
ram: {
|
||||
id: 'NodeListItem.mbRam',
|
||||
defaultMessage: 'MB RAM'
|
||||
},
|
||||
disk: {
|
||||
id: 'NodeListItem.gbDisk',
|
||||
defaultMessage: 'GB Disk'
|
||||
}
|
||||
});
|
||||
import { ListView } from '../../ui/ListView';
|
||||
import NodeListItem from './NodeListItem';
|
||||
|
||||
export default class NodesListView extends React.Component {
|
||||
render() {
|
||||
|
@ -66,78 +26,3 @@ NodesListView.propTypes = {
|
|||
nodes: ImmutablePropTypes.map.isRequired,
|
||||
nodesInProgress: ImmutablePropTypes.set.isRequired
|
||||
};
|
||||
|
||||
export class NodeListItem extends React.Component {
|
||||
render() {
|
||||
const { node, inProgress } = this.props;
|
||||
|
||||
const iconClass = ClassNames({
|
||||
'pficon pficon-server': true,
|
||||
running: inProgress
|
||||
});
|
||||
|
||||
return (
|
||||
<ListViewItem stacked>
|
||||
<ListViewCheckbox disabled={inProgress} name={`values.${node.uuid}`} />
|
||||
<ListViewMainInfo>
|
||||
<ListViewLeft>
|
||||
<ListViewIcon size="sm" icon={iconClass} />
|
||||
</ListViewLeft>
|
||||
<ListViewBody>
|
||||
<ListViewDescription>
|
||||
<ListViewDescriptionHeading>
|
||||
{node.name || node.uuid}
|
||||
</ListViewDescriptionHeading>
|
||||
<ListViewDescriptionText>
|
||||
<NodePowerState
|
||||
powerState={node.power_state}
|
||||
targetPowerState={node.target_power_state}
|
||||
/>
|
||||
<NodeMaintenanceState maintenance={node.maintenance} />
|
||||
{' | '}
|
||||
<NodeProvisionState
|
||||
provisionState={node.provision_state}
|
||||
targetProvisionState={node.target_provision_state}
|
||||
/>
|
||||
</ListViewDescriptionText>
|
||||
</ListViewDescription>
|
||||
<ListViewAdditionalInfo>
|
||||
<ListViewAdditionalInfoItem>
|
||||
<span className="pficon pficon-flavor" />
|
||||
<FormattedMessage {...messages.profile} />
|
||||
|
||||
{parseNodeCapabilities(node.properties.capabilities).profile ||
|
||||
'-'}
|
||||
</ListViewAdditionalInfoItem>
|
||||
<ListViewAdditionalInfoItem>
|
||||
<span className="pficon pficon-cpu" />
|
||||
<strong>{node.properties.cpus || '-'}</strong>
|
||||
|
||||
<FormattedMessage
|
||||
{...messages.cpuCores}
|
||||
values={{ cpuCores: node.properties.cpus }}
|
||||
/>
|
||||
</ListViewAdditionalInfoItem>
|
||||
<ListViewAdditionalInfoItem>
|
||||
<span className="pficon pficon-memory" />
|
||||
<strong>{node.properties.memory_mb || '-'}</strong>
|
||||
|
||||
<FormattedMessage {...messages.ram} />
|
||||
</ListViewAdditionalInfoItem>
|
||||
<ListViewAdditionalInfoItem>
|
||||
<span className="fa fa-database" />
|
||||
<strong>{node.properties.local_gb || '-'}</strong>
|
||||
|
||||
<FormattedMessage {...messages.disk} />
|
||||
</ListViewAdditionalInfoItem>
|
||||
</ListViewAdditionalInfo>
|
||||
</ListViewBody>
|
||||
</ListViewMainInfo>
|
||||
</ListViewItem>
|
||||
);
|
||||
}
|
||||
}
|
||||
NodeListItem.propTypes = {
|
||||
inProgress: PropTypes.bool.isRequired,
|
||||
node: PropTypes.object.isRequired
|
||||
};
|
||||
|
|
|
@ -157,7 +157,7 @@ class NodesTable extends React.Component {
|
|||
<FormattedMessage {...messages.macAddresses} />
|
||||
</DataTableHeaderCell>
|
||||
}
|
||||
cell={<DataTableDataFieldCell data={filteredData} field="macs" />}
|
||||
cell={<NodesTableMacsCell data={filteredData} field="macs" />}
|
||||
/>
|
||||
<DataTableColumn
|
||||
key="name"
|
||||
|
@ -299,6 +299,20 @@ NodesTableMaintenanceCell.propTypes = {
|
|||
rowIndex: React.PropTypes.number
|
||||
};
|
||||
|
||||
export const NodesTableMacsCell = props => {
|
||||
const value = _.result(props.data[props.rowIndex], props.field).join(', ');
|
||||
return (
|
||||
<DataTableCell {...props}>
|
||||
{value}
|
||||
</DataTableCell>
|
||||
);
|
||||
};
|
||||
NodesTableMacsCell.propTypes = {
|
||||
data: React.PropTypes.array.isRequired,
|
||||
field: React.PropTypes.string.isRequired,
|
||||
rowIndex: React.PropTypes.number
|
||||
};
|
||||
|
||||
export class NodesTableCheckBoxCell extends React.Component {
|
||||
render() {
|
||||
const nodeId = _.result(
|
||||
|
|
|
@ -2,6 +2,50 @@ import ClassNames from 'classnames';
|
|||
import { Field } from 'redux-form';
|
||||
import React, { PropTypes } from 'react';
|
||||
|
||||
/* ListView example usage:
|
||||
|
||||
<ListView>
|
||||
<ListViewItem stacked expanded>
|
||||
|
||||
<ListViewItemHeader> // required only if the ListViewItem is supposed to be expandable
|
||||
<ListViewExpand toggleExpanded={functionToToggle} expanded />
|
||||
<ListViewCheckbox disabled={inProgress} name={`values.${node.uuid}`} />
|
||||
<ListViewMainInfo>
|
||||
<ListViewLeft>
|
||||
<ListViewIcon size="sm" icon={iconClass} />
|
||||
</ListViewLeft>
|
||||
<ListViewBody>
|
||||
<ListViewDescription>
|
||||
<ListViewDescriptionHeading>
|
||||
{name}
|
||||
</ListViewDescriptionHeading>
|
||||
<ListViewDescriptionText>
|
||||
{description}
|
||||
</ListViewDescriptionText>
|
||||
</ListViewDescription>
|
||||
<ListViewAdditionalInfo>
|
||||
<ListViewAdditionalInfoItem>
|
||||
<span className="pficon pficon-flavor" />
|
||||
{Item1}
|
||||
</ListViewAdditionalInfoItem>
|
||||
<ListViewAdditionalInfoItem>
|
||||
<span className="pficon pficon-cpu" />
|
||||
{Item2}
|
||||
</ListViewAdditionalInfoItem>
|
||||
</ListViewAdditionalInfo>
|
||||
</ListViewBody>
|
||||
</ListViewMainInfo>
|
||||
</ListViewItemHeader>
|
||||
|
||||
<ListViewItemContainer onClose={functionWhichClosesMe} expanded> // expandable content
|
||||
<Row>Some content goes here</Row>
|
||||
</ListViewItemContainer>
|
||||
|
||||
</ListViewItem>
|
||||
...
|
||||
</ListView>
|
||||
*/
|
||||
|
||||
export const ListView = ({ children }) => (
|
||||
<div className="list-group list-view-pf list-view-pf-view">
|
||||
{children}
|
||||
|
@ -33,6 +77,39 @@ ListViewItem.defaultProps = {
|
|||
stacked: false
|
||||
};
|
||||
|
||||
export const ListViewItemHeader = ({ children }) => (
|
||||
<div className="list-group-item-header">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
ListViewItemHeader.propTypes = {
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
export const ListViewItemContainer = ({ children, expanded, onClose }) => {
|
||||
const classes = ClassNames({
|
||||
'list-group-item-container container-fluid': true,
|
||||
hidden: !expanded
|
||||
});
|
||||
return (
|
||||
<div className={classes}>
|
||||
{onClose &&
|
||||
<div className="close">
|
||||
<span className="pficon pficon-close" onClick={() => onClose()} />
|
||||
</div>}
|
||||
{expanded && children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
ListViewItemContainer.propTypes = {
|
||||
children: PropTypes.node,
|
||||
expanded: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func
|
||||
};
|
||||
ListViewItemContainer.defaultProps = {
|
||||
expanded: false
|
||||
};
|
||||
|
||||
export const ListViewCheckbox = ({ disabled, name }) => (
|
||||
<div className="list-view-pf-checkbox">
|
||||
<Field name={name} type="checkbox" component="input" disabled={disabled} />
|
||||
|
@ -46,19 +123,20 @@ ListViewCheckbox.defaultProps = {
|
|||
disabled: false
|
||||
};
|
||||
|
||||
export const ListViewExpand = ({ expanded }) => {
|
||||
export const ListViewExpand = ({ expanded, toggleExpanded }) => {
|
||||
const classes = ClassNames({
|
||||
'fa fa-angle-right': true,
|
||||
'fa-angle-down': expanded
|
||||
});
|
||||
return (
|
||||
<a className="list-view-pf-expand">
|
||||
<a className="list-view-pf-expand" onClick={() => toggleExpanded()}>
|
||||
<span className={classes} />
|
||||
</a>
|
||||
);
|
||||
};
|
||||
ListViewExpand.propTypes = {
|
||||
expanded: PropTypes.bool.isRequired
|
||||
expanded: PropTypes.bool.isRequired,
|
||||
toggleExpanded: PropTypes.func.isRequired
|
||||
};
|
||||
ListViewExpand.defaultProps = {
|
||||
expanded: false
|
||||
|
|
|
@ -28,7 +28,8 @@ export const getNodesWithMacs = createSelector(
|
|||
'macs',
|
||||
ports
|
||||
.filter(p => node.get('uuid') === p.node_uuid)
|
||||
.reduce((str, v) => str + v.address, '')
|
||||
.map(port => port.address)
|
||||
.toList()
|
||||
)
|
||||
)
|
||||
);
|
||||
|
|
|
@ -120,6 +120,7 @@
|
|||
@import "ui/ListView";
|
||||
@import "ui/Modals";
|
||||
@import "ui/Tables";
|
||||
@import "ui/Type";
|
||||
@import "ui/Navs";
|
||||
@import "ui/FixedContainer";
|
||||
@import "ui/Sidebar";
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// Typography overrides
|
||||
// --------------------------------------------------
|
||||
.dl-horizontal.dl-horizontal-condensed {
|
||||
@media (min-width: @dl-horizontal-breakpoint) {
|
||||
dt {
|
||||
width: 110px;
|
||||
}
|
||||
dd {
|
||||
margin-left: 120px;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue