| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618 |
- /*
- 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 { observer } from "mobx-react";
- import React from "react";
- import styled, { css } from "styled-components";
- import AssessedVmListItem from "@src/components/modules/AssessmentModule/AssessedVmListItem";
- import DetailsNavigation from "@src/components/modules/NavigationModule/DetailsNavigation";
- import { ThemePalette, ThemeProps } from "@src/components/Theme";
- import Button from "@src/components/ui/Button";
- import Checkbox from "@src/components/ui/Checkbox";
- import DropdownFilter from "@src/components/ui/Dropdowns/DropdownFilter";
- import DropdownLink from "@src/components/ui/Dropdowns/DropdownLink";
- import SmallLoading from "@src/components/ui/SmallLoading";
- import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
- import Table from "@src/components/ui/Table";
- import DateUtils from "@src/utils/DateUtils";
- import arrowImage from "./images/arrow.svg";
- import azureMigrateImage from "./images/logo.svg";
- import type { Assessment, VmItem, AzureLocation } from "@src/@types/Assessment";
- import type { Endpoint } from "@src/@types/Endpoint";
- import type { Instance, Nic } from "@src/@types/Instance";
- import type { Network, NetworkMap } from "@src/@types/Network";
- 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>`
- ${ThemeProps.exactWidth(ThemeProps.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: ${ThemePalette.grayscale[3]};
- font-weight: ${ThemeProps.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
- );
- }
- 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
- ? DateUtils.getLocalDate(
- this.props.item.properties.updatedTimestamp
- ).toFormat("yyyy-LL-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
- key={vm.id}
- 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;
|