| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533 |
- /*
- 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 <http://www.gnu.org/licenses/>.
- */
- import React from 'react'
- import styled, { css } from 'styled-components'
- import moment from 'moment'
- import { observer } from 'mobx-react'
- import DetailsNavigation from '../../NavigationModule/DetailsNavigation/DetailsNavigation'
- import Button from '../../../ui/Button/Button'
- import StatusImage from '../../../ui/StatusComponents/StatusImage/StatusImage'
- import DropdownLink from '../../../ui/Dropdowns/DropdownLink/DropdownLink'
- import Table from '../../../ui/Table/Table'
- import AssessedVmListItem from '../AssessedVmListItem/AssessedVmListItem'
- import DropdownFilter from '../../../ui/Dropdowns/DropdownFilter/DropdownFilter'
- import Checkbox from '../../../ui/Checkbox/Checkbox'
- import SmallLoading from '../../../ui/SmallLoading/SmallLoading'
- import Palette from '../../../styleUtils/Palette'
- import StyleProps from '../../../styleUtils/StyleProps'
- import type { Assessment, VmItem, AzureLocation } from '../../../../@types/Assessment'
- import type { Endpoint } from '../../../../@types/Endpoint'
- import type { Instance, Nic } from '../../../../@types/Instance'
- import type { Network, NetworkMap } from '../../../../@types/Network'
- import azureMigrateImage from './images/logo.svg'
- import arrowImage from './images/arrow.svg'
- const Wrapper = styled.div<any>`
- display: flex;
- justify-content: center;
- `
- const Buttons = styled.div<any>`
- margin-top: 46px;
- display: flex;
- flex-direction: column;
- button:first-child {
- margin-bottom: 16px;
- }
- `
- const DetailsBody = styled.div<any>`
- ${StyleProps.exactWidth(StyleProps.contentWidth)}
- margin-bottom: 32px;
- `
- const Columns = styled.div<any>`
- display: flex;
- margin-left: -32px;
- `
- const Column = styled.div<any>`
- width: 50%;
- margin-left: 32px;
- `
- const Row = styled.div<any>`
- margin-bottom: 32px;
- `
- const Field = styled.div<any>`
- display: flex;
- flex-direction: column;
- `
- const Label = styled.div<any>`
- font-size: 10px;
- color: ${Palette.grayscale[3]};
- font-weight: ${StyleProps.fontWeights.medium};
- text-transform: uppercase;
- `
- const Value = styled.div<any>`
- display: ${props => (props.flex ? 'flex' : 'inline-table')};
- margin-top: 3px;
- ${props => (props.capitalize ? 'text-transform: capitalize;' : '')}
- `
- const AzureMigrateLogo = styled.div<any>`
- width: 208px;
- height: 32px;
- background: url('${azureMigrateImage}') center no-repeat;
- `
- const LoadingWrapper = styled.div<any>`
- display: flex;
- flex-direction: column;
- align-items: center;
- margin: 32px 0;
- `
- const LoadingText = styled.div<any>`
- font-size: 18px;
- margin-top: 32px;
- `
- const SmallLoadingWrapper = styled.div<any>`
- display: flex;
- align-items: center;
- justify-content: center;
- `
- const SmallLoadingText = styled.div<any>`
- font-size: 14px;
- margin-left: 16px;
- `
- const TableStyled = styled(Table)<any>`
- margin-top: 62px;
- ${props => (props.addWidthPadding ? css`
- margin-left: -24px;
- &:after {
- margin-left: 24px;
- }
- ` : '')}
- `
- const TableHeaderStyle = css`
- margin-left: 24px;
- `
- const TableBodyStyle = css`
- padding-left: 24px;
- `
- const NetworkItem = styled.div<any>`
- display: flex;
- width: 100%;
- `
- const column = () => css`
- padding-right: 32px;
- width: 100%;
- max-width: 25%;
- `
- const NetworkName = styled.div<any>`
- ${column()}
- `
- const Arrow = styled.div<any>`
- width: 32px;
- height: 16px;
- position: absolute;
- right: 0;
- background: url('${arrowImage}') no-repeat;
- background-position-y: center;
- `
- const ColumnStub = styled.div<any>`
- ${column()}
- position: relative;
- &:last-child {
- padding-right: 0;
- }
- `
- const VmHeaderItem = styled.div<any>`
- display: flex;
- font-size: 14px;
- `
- const VmHeaderItemLabel = styled.div<any>`
- font-size: 10px;
- margin-left: 8px;
- `
- const NavigationItems = [
- {
- label: 'Details',
- value: '',
- },
- ]
- type Props = {
- item: Assessment | null,
- detailsLoading: boolean,
- instancesDetailsLoading: boolean,
- instancesLoading: boolean,
- networksLoading: boolean,
- instancesDetailsProgress: number | null,
- targetEndpoint: Endpoint,
- targetEndpoints: Endpoint[],
- onTargetEndpointChange: (endpoint: Endpoint) => void,
- targetEndpointsLoading: boolean,
- sourceEndpoints: Endpoint[],
- sourceEndpoint: Endpoint | null,
- sourceEndpointsLoading: boolean,
- locations: AzureLocation[],
- selectedLocation: string | null,
- onLocationChange: (locationName: string) => void,
- selectedResourceGroup: string,
- resourceGroups: string[],
- onResourceGroupChange: (resourceGroupName: string) => void,
- targetOptionsLoading: boolean,
- assessedVmsCount: number,
- filteredAssessedVms: VmItem[],
- selectedVms: string[],
- instancesDetails: Instance[],
- instances: Instance[],
- loadingVmSizes: boolean,
- vmSizes: string[],
- onVmSizeChange: (vmId: string, size: string) => void,
- onGetSelectedVmSize: (vm: VmItem) => string | null,
- networks: Network[],
- page: string,
- onSourceEndpointChange: (endpoint: Endpoint) => void,
- onVmSearchValueChange: (value: string) => void,
- vmSearchValue: string,
- onVmSelectedChange: (vm: VmItem, selected: boolean) => void,
- onNetworkChange: (sourceNic: Nic, targetNetwork: Network) => void,
- onRefresh: () => void,
- onMigrateClick: () => void,
- selectedNetworks: NetworkMap[],
- selectAllVmsChecked: boolean,
- onSelectAllVmsChange: (selected: boolean) => void,
- }
- @observer
- class AssessmentDetailsContent extends React.Component<Props> {
- static defaultProps = {
- page: '',
- }
- doesVmMatchSource(vm: VmItem) {
- if (!this.props.sourceEndpoint || !this.props.sourceEndpoint.connection_info) {
- return false
- }
- if (this.props.instances.length > 0
- && !this.props.instances.find(i => i.name === vm.properties.displayName
- || i.instance_name === vm.properties.displayName)) {
- return false
- }
- return this.props.sourceEndpoint.connection_info.host
- === vm.properties.datacenterManagementServerName
- }
- renderBottomControls() {
- return (
- <Buttons>
- <Button
- alert
- hollow
- onClick={() => { }}
- >Migrate
- </Button>
- </Buttons>
- )
- }
- renderMainDetails() {
- if (this.props.page !== '' || !this.props.item || !this.props.item.id) {
- return null
- }
- const status = this.props.item
- ? this.props.item.properties.status === 'Completed' ? 'Ready for Migration' : this.props.item.properties.status : ''
- const locationItem: AzureLocation | undefined = this.props.locations
- .find(l => l.id === this.props.selectedLocation)
- return (
- <Columns>
- <Column>
- <Row>
- <AzureMigrateLogo />
- </Row>
- <Row>
- <Field>
- <Label>Last Update</Label>
- <Value>
- {this.props.item ? moment(this.props.item.properties.updatedTimestamp).format('YYYY-MM-DD HH:mm:ss') : '-'}
- </Value>
- </Field>
- </Row>
- <Row>
- <Field>
- <Label>Migration Project</Label>
- <Value>{this.props.item ? this.props.item.projectName : ''}</Value>
- </Field>
- </Row>
- <Row>
- <Field>
- <Label>VM Group</Label>
- <Value>{this.props.item ? this.props.item.groupName : ''}</Value>
- </Field>
- </Row>
- <Row>
- <Field>
- <Label>Status</Label>
- <Value>{status}</Value>
- </Field>
- </Row>
- </Column>
- <Column>
- <Row>
- <Field>
- <Label>Source Endpoint</Label>
- <Value>
- <DropdownLink
- selectedItem={this.props.sourceEndpoint ? this.props.sourceEndpoint.id : ''}
- items={this.props.sourceEndpoints.map(endpoint => ({
- label: endpoint.name, value: endpoint.id, endpoint,
- }))}
- onChange={item => { this.props.onSourceEndpointChange(item.endpoint) }}
- selectItemLabel="Select Endpoint"
- noItemsLabel={this.props.sourceEndpointsLoading ? 'Loading ....' : 'No matching endpoints'}
- />
- </Value>
- </Field>
- </Row>
- <Row>
- <Field>
- <Label>Target endpoint</Label>
- <Value>
- <DropdownLink
- selectedItem={this.props.targetEndpoint ? this.props.targetEndpoint.id : ''}
- items={this.props.targetEndpoints.map(endpoint => ({
- label: endpoint.name, value: endpoint.id, endpoint,
- }))}
- onChange={item => { this.props.onTargetEndpointChange(item.endpoint) }}
- selectItemLabel="Select Endpoint"
- noItemsLabel={this.props.targetEndpointsLoading ? 'Loading ....' : 'No Azure endpoints'}
- />
- </Value>
- </Field>
- </Row>
- <Row>
- <Field>
- <Label>Resource Group</Label>
- <Value>
- <DropdownLink
- selectedItem={this.props.selectedResourceGroup}
- items={this.props.resourceGroups.map(group => ({ label: group, value: group }))}
- onChange={item => { this.props.onResourceGroupChange(item.value) }}
- noItemsLabel={this.props.targetOptionsLoading ? 'Loading ....' : 'No Resource Groups found'}
- />
- </Value>
- </Field>
- </Row>
- <Row>
- <Field>
- <Label>Location</Label>
- <Value>
- <DropdownLink
- selectedItem={locationItem ? locationItem.id : ''}
- items={this.props.locations.map(location => ({
- label: location.name, value: location.id,
- }))}
- onChange={item => { this.props.onLocationChange(item.value) }}
- noItemsLabel={this.props.targetOptionsLoading ? 'Loading ....' : 'No Locations found'}
- />
- </Value>
- </Field>
- </Row>
- </Column>
- </Columns>
- )
- }
- renderVmsTable() {
- const loading = this.props.instancesLoading
- const items = this.props.filteredAssessedVms.map(vm => (
- <AssessedVmListItem
- item={vm}
- selected={this.props.selectedVms.filter(m => m === vm.properties.displayName).length > 0}
- onSelectedChange={(selectedVm, selected) => {
- this.props.onVmSelectedChange(selectedVm, selected)
- }}
- disabled={!this.doesVmMatchSource(vm)}
- loadingVmSizes={this.props.loadingVmSizes}
- recommendedVmSize={vm.properties.recommendedSize}
- vmSizes={this.props.vmSizes}
- selectedVmSize={this.props.onGetSelectedVmSize(vm)}
- onVmSizeChange={size => { this.props.onVmSizeChange(vm.properties.displayName, size) }}
- />
- ))
- const vmCountLabel = `(${this.props.filteredAssessedVms.length === this.props.assessedVmsCount ? this.props.assessedVmsCount
- : `${this.props.filteredAssessedVms.length} OUT OF ${this.props.assessedVmsCount}`})`
- const vmHeaderItem = (
- <VmHeaderItem>
- {loading ? null : (
- <Checkbox
- checked={this.props.selectAllVmsChecked}
- onChange={checked => { this.props.onSelectAllVmsChange(checked) }}
- />
- )}
- <VmHeaderItemLabel>Virtual Machine {vmCountLabel}</VmHeaderItemLabel>
- <DropdownFilter
- searchPlaceholder="Filter Virtual Machines"
- searchValue={this.props.vmSearchValue}
- onSearchChange={value => { this.props.onVmSearchValueChange(value) }}
- />
- </VmHeaderItem>
- )
- return (
- <TableStyled
- addWidthPadding
- items={loading ? [] : items}
- bodyStyle={TableBodyStyle}
- headerStyle={TableHeaderStyle}
- header={[vmHeaderItem, 'OS', 'Target Disk Type', 'Azure VM Size']}
- useSecondaryStyle
- noItemsComponent={this.renderLoading('Loading instances, please wait ...')}
- />
- )
- }
- renderNetworkTable() {
- const loading = this.props.networksLoading || this.props.instancesDetailsLoading
- if (loading) {
- return (
- <TableStyled
- items={[]}
- header={['Source Network', '', '', 'Target Network']}
- useSecondaryStyle
- noItemsStyle={{ marginLeft: 0 }}
- noItemsComponent={this.renderNetworksLoading()}
- />
- )
- }
- const nics: Nic[] = []
- this.props.instancesDetails.forEach(instance => {
- if (!instance.devices || !instance.devices.nics) {
- return
- }
- instance.devices.nics.forEach(nic => {
- if (nics.find(n => n.network_name === nic.network_name)) {
- return
- }
- nics.push(nic)
- })
- })
- if (nics.length === 0) {
- return null
- }
- const items = nics.map(nic => {
- let selectedNetworkName: string | undefined
- const selectedNetwork = this.props.selectedNetworks
- && this.props.selectedNetworks.find(n => n.sourceNic.network_name === nic.network_name)
- if (selectedNetwork) {
- selectedNetworkName = selectedNetwork.targetNetwork?.name
- }
- return (
- <NetworkItem key={nic.network_name}>
- <NetworkName width="25%">{nic.network_name}</NetworkName>
- <ColumnStub width="25%"><Arrow /></ColumnStub>
- <ColumnStub width="25%" />
- <ColumnStub width="25%">
- <DropdownLink
- width="208px"
- noItemsLabel="No Networks found"
- selectItemLabel="Select Network"
- selectedItem={selectedNetworkName}
- onChange={item => { this.props.onNetworkChange(nic, item.network) }}
- items={this.props.networks.map(network => ({ value: network.name || '', label: network.name || '', network }))}
- />
- </ColumnStub>
- </NetworkItem>
- )
- })
- return (
- <TableStyled
- items={loading ? [] : items}
- header={['Source Network', '', '', 'Target Network']}
- useSecondaryStyle
- noItemsStyle={{ marginLeft: 0 }}
- noItemsComponent={this.renderNetworksLoading()}
- />
- )
- }
- renderNetworksLoading() {
- let loadingProgress = -1
- if (this.props.instancesDetailsLoading) {
- if (this.props.instancesDetailsProgress != null) {
- loadingProgress = Math.round(this.props.instancesDetailsProgress * 100)
- }
- }
- return (
- <SmallLoadingWrapper>
- <SmallLoading loadingProgress={loadingProgress} />
- <SmallLoadingText>Loading networks, please wait ...</SmallLoadingText>
- </SmallLoadingWrapper>
- )
- }
- renderButtons() {
- return (
- <Buttons>
- <Button secondary onClick={this.props.onRefresh}>Refresh</Button>
- <Button
- disabled={this.props.selectedVms.length === 0 || this.props.selectedNetworks.length === 0}
- onClick={() => { this.props.onMigrateClick() }}
- >Migrate / Replicate
- </Button>
- </Buttons>
- )
- }
- renderLoading(message: string) {
- return (
- <LoadingWrapper>
- <StatusImage loading />
- <LoadingText>{message}</LoadingText>
- </LoadingWrapper>
- )
- }
- render() {
- return (
- <Wrapper>
- <DetailsNavigation
- items={NavigationItems}
- selectedValue={this.props.page}
- itemId={this.props.item ? this.props.item.id : ''}
- customHref={() => '#'}
- />
- <DetailsBody>
- {this.props.detailsLoading ? null : this.renderMainDetails()}
- {this.props.detailsLoading ? this.renderLoading('Loading assessment...') : null}
- {this.props.detailsLoading ? null : this.renderVmsTable()}
- {this.props.detailsLoading || this.props.instancesLoading
- ? null : this.renderNetworkTable()}
- {this.props.detailsLoading ? null : this.renderButtons()}
- </DetailsBody>
- </Wrapper>
- )
- }
- }
- export default AssessmentDetailsContent
|