/* Copyright (C) 2017 Cloudbase Solutions SRL This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ // @flow import React from 'react' import styled from 'styled-components' import { Collapse } from 'react-collapse' import Arrow from '../../atoms/Arrow' import Palette from '../../styleUtils/Palette' import StyleProps from '../../styleUtils/StyleProps' import type { MainItem } from '../../../types/MainItem' import type { Instance, Nic, Disk } from '../../../types/Instance' import type { Network } from '../../../types/Network' import instanceIcon from './images/instance.svg' import networkIcon from './images/network.svg' import storageIcon from './images/storage.svg' import arrowIcon from './images/arrow.svg' const Wrapper = styled.div` margin-top: 24px; margin-bottom: 48px; ` const ArrowStyled = styled(Arrow)` position: absolute; left: -24px; ` const Header = styled.div` display: flex; ` const HeaderLabel = styled.div` font-size: 10px; color: ${Palette.grayscale[3]}; font-weight: ${StyleProps.fontWeights.medium}; text-transform: uppercase; width: 50%; margin-bottom: 8px; &:last-child { margin-left: 36px; } ` const InstanceInfo = styled.div` background: ${Palette.grayscale[1]}; border-radius: ${StyleProps.borderRadius}; margin-bottom: 32px; &:last-child { margin-bottom: 0; } ` const InstanceName = styled.div` padding: 16px; border-bottom: 1px solid ${Palette.grayscale[5]}; font-size: 16px; ` const InstanceBody = styled.div` font-size: 14px; ` const Row = styled.div` position: relative; padding: 8px 0; border-bottom: 1px solid white; transition: all ${StyleProps.animations.swift}; &:last-child { border-bottom: 0; border-bottom-left-radius: ${StyleProps.borderRadius}; border-bottom-right-radius: ${StyleProps.borderRadius}; } &:hover { background: ${Palette.grayscale[0]}; ${ArrowStyled} { opacity: 1; } } cursor: pointer; ` const RowHeader = styled.div` display: flex; align-items: center; padding: 0 16px; ` const RowHeaderColumn = styled.div` display: flex; align-items: center; ${StyleProps.exactWidth('50%')} &:last-child { margin-left: 19px; } ` const HeaderName = styled.div` overflow: hidden; text-overflow: ellipsis; ${props => StyleProps.exactWidth(`calc(100% - ${props.source ? 120 : 8}px)`)} ` const RowBody = styled.div` display: flex; color: ${Palette.grayscale[5]}; padding: 0 16px; margin-top: 4px; ` const RowBodyColumn = styled.div` width: 50%; &:first-child { margin-left: 32px; } &:last-child { margin-left: 6px; } ` const getHeaderIcon = (icon: 'instance' | 'network' | 'storage'): string => { switch (icon) { case 'instance': return instanceIcon case 'network': return networkIcon default: return storageIcon } } const HeaderIcon = styled.div` width: 16px; height: 16px; background: url('${props => getHeaderIcon(props.icon)}') center no-repeat; margin-right: 16px; ` const ArrowIcon = styled.div` width: 32px; height: 16px; background: url('${arrowIcon}') center no-repeat; margin-left: 16px; ` export const TEST_ID = 'mainDetailsTable' export type Props = { item: ?MainItem, instancesDetails: Instance[], networks?: Network[], } type State = { openedRows: string[], } class MainDetailsTable extends React.Component { state = { openedRows: [], } getTransferResult(instance: Instance): ?Instance { if (this.props.item && this.props.item.transfer_result) { let transferInstanceKey = Object.keys(this.props.item.transfer_result).find(i => i.indexOf(instance.name)) if (transferInstanceKey && this.props.item && this.props.item.transfer_result) { let result = this.props.item.transfer_result[transferInstanceKey] result.instance_name = transferInstanceKey return result } } return null } handleRowClick(id: string) { if (this.state.openedRows.find(i => i === id)) { this.setState({ openedRows: this.state.openedRows.filter(i => i !== id), }) } else { this.setState({ openedRows: [...this.state.openedRows, id], }) } } renderRow( id: string, icon: 'instance' | 'network' | 'storage', sourceName: string, destinationName: string, sourceBody: string[], destinationBody: string[] ) { let isOpened: boolean = Boolean(this.state.openedRows.find(i => i === id)) return ( { this.handleRowClick(id) }}> {sourceName} {destinationName ? : null} {destinationName} {sourceBody.map(l =>
{l}
)}
{destinationBody.map(l =>
{l}
)}
) } renderStorage(instance: Instance) { let storageMapping = this.props.item && this.props.item.storage_mappings let transferResult = this.getTransferResult(instance) let rows = [] instance.devices.disks.forEach(disk => { let sourceName = disk.id let mappedDisk = storageMapping && storageMapping.disk_mappings && storageMapping.disk_mappings.find(m => String(m.disk_id) === String(disk.id)) let destinationName: string = ( this.props.item && this.props.item.storage_mappings && this.props.item.storage_mappings.default ) || 'Default' if (mappedDisk) { destinationName = mappedDisk.destination } let getBody = (d: Disk): string[] => { let body: string[] = [] if (d.size_bytes) { body.push(`Size: ${(d.size_bytes / 1024 / 1024).toFixed(0)} MB`) } if (d.storage_backend_identifier) { body.push(`Backend Identifier: ${d.storage_backend_identifier}`) } if (d.format) { body.push(`Format: ${d.format}`) } if (d.guest_device) { body.push(`Guest Device: ${d.guest_device}`) } return body } let sourceBody = getBody(disk) let destinationBody = [] if (transferResult) { let transferDisk = transferResult.devices.disks.find(d => d.storage_backend_identifier === destinationName) if (transferDisk) { destinationName = transferDisk.name || transferDisk.id destinationBody = getBody(transferDisk) } } else if (this.props.item && this.props.item.status === 'RUNNING' && this.props.item.type === 'migration') { destinationBody = ['Waiting for migration to finish'] } rows.push(this.renderRow( `${instance.instance_name}-${sourceName}-${destinationName}`, 'storage', sourceName, destinationName, sourceBody, destinationBody )) }) return rows } renderNetworks(instance: Instance) { let destinationNetworkMap = null if (this.props.item && this.props.item.network_map) { destinationNetworkMap = this.props.item.network_map } if (destinationNetworkMap == null) { return null } let transferResult = this.getTransferResult(instance) let rows = [] instance.devices.nics.forEach(nic => { if (destinationNetworkMap && destinationNetworkMap[nic.network_name]) { let getBody = (n: Nic): string[] => { let body: string[] = [`Name: ${n.network_name}`] let ipv4 = n.ip_addresses ? n.ip_addresses.find(ip => /(?:\d+?\.){3}\d+/g.exec(ip)) : null let ipv6 = n.ip_addresses ? n.ip_addresses.find(ip => /\w*:\w*/g.exec(ip)) : null if (ipv4) { body.push(`IP Address (IPv4): ${ipv4}`) } if (ipv6) { body.push(`IP Address (IPv6): ${ipv6}`) } return body } let sourceBody = getBody(nic) let destinationBody = [] let destinationNetworkId = String(destinationNetworkMap[nic.network_name]) let destinationNetworkName = destinationNetworkId let destinationNetwork = this.props.networks && this.props.networks.find(n => n.id === destinationNetworkId) if (destinationNetwork) { destinationNetworkName = destinationNetwork.name } if (transferResult) { let destinationNic = transferResult.devices.nics .find(n => n.network_id === destinationNetworkId || n.network_name === destinationNetworkId) if (destinationNic) { destinationNetworkName = destinationNic.network_name destinationBody = getBody(destinationNic) } } else if (this.props.item && this.props.item.status === 'RUNNING' && this.props.item.type === 'migration') { destinationBody = ['Waiting for migration to finish'] } rows.push(this.renderRow( `${instance.instance_name}-${nic.network_name}`, 'network', nic.mac_address, destinationNetworkName, sourceBody, destinationBody )) } }) return rows } renderInstanceDetails(instance: Instance) { let getBody = (i: Instance): string[] => [ `Cores: ${i.num_cpu}`, `Memory: ${i.memory_mb} MB`, `Flavor Name: ${i.flavor_name || 'N/A'}`, `OS Type: ${i.os_type}`, ] let sourceBody: string[] = getBody(instance) let destinationBody: string[] = [] let destinationName: string = '' let transferResult = this.getTransferResult(instance) if (transferResult) { destinationName = transferResult.instance_name destinationBody = getBody(transferResult) } else if (this.props.item && this.props.item.status === 'RUNNING' && this.props.item.type === 'migration') { destinationName = 'Waiting for migration to finish' } return this.renderRow( instance.instance_name, 'instance', instance.instance_name, destinationName, sourceBody, destinationBody ) } render() { if (this.props.instancesDetails.length === 0 || !this.props.item) { return null } return (
Source Destination
{this.props.instancesDetails.map(instance => ( {instance.name} {this.renderInstanceDetails(instance)} {this.renderNetworks(instance)} {this.renderStorage(instance)} ))}
) } } export default MainDetailsTable