/* 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 . */ import { Info } from "luxon"; import { observer } from "mobx-react"; import React from "react"; import styled from "styled-components"; import fieldHelper from "@src/@types/Field"; import { MinionPool } from "@src/@types/MinionPool"; import { ProviderTypes } from "@src/@types/Providers"; import { INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS } from "@src/components/modules/WizardModule/WizardOptions"; import { getDisks } from "@src/components/modules/WizardModule/WizardStorage"; import { ThemePalette, ThemeProps } from "@src/components/Theme"; import StatusPill from "@src/components/ui/StatusComponents/StatusPill"; import configLoader from "@src/utils/Config"; import DateUtils from "@src/utils/DateUtils"; import LabelDictionary from "@src/utils/LabelDictionary"; import networkArrowImage from "./images/network-arrow.svg"; import type { Schedule } from "@src/@types/Schedule"; import type { WizardData } from "@src/@types/WizardData"; import type { StorageMap, StorageBackend } from "@src/@types/Endpoint"; import type { Instance, Disk, InstanceScript } from "@src/@types/Instance"; import type { Field } from "@src/@types/Field"; const Wrapper = styled.div` width: 100%; display: flex; justify-content: space-between; `; const Column = styled.div` ${ThemeProps.exactWidth("calc(50% - 80px)")} `; const Section = styled.div` margin-bottom: 42px; &:last-child { margin-bottom: 0; } `; const SectionTitle = styled.div` font-size: 24px; font-weight: ${ThemeProps.fontWeights.light}; margin-bottom: 16px; `; const Overview = styled.div``; const OverviewLabel = styled.div` font-size: 10px; font-weight: ${ThemeProps.fontWeights.medium}; text-transform: uppercase; color: ${ThemePalette.grayscale[5]}; margin-bottom: 4px; `; const OverviewRow = styled.div` margin-bottom: 32px; &:last-child { margin-bottom: 0; } `; const OverviewRowData = styled.div` display: flex; `; const OverviewRowLabel = styled.div` margin-left: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; `; const Table = styled.div``; const Row = styled.div` display: flex; flex-direction: ${props => props.direction || "column"}; padding: 8px 0; border-top: 1px solid ${ThemePalette.grayscale[1]}; color: ${ThemePalette.grayscale[4]}; &:last-child { border-bottom: 1px solid ${ThemePalette.grayscale[1]}; } `; const ScriptFileName = styled.div` max-width: 124px; text-overflow: ellipsis; overflow: hidden; margin-left: 16px; white-space: nowrap; flex-shrink: 0; `; const InstanceRowTitle = styled.div` margin-bottom: 4px; `; const InstanceRowSubtitle = styled.div` font-size: 10px; color: ${ThemePalette.grayscale[5]}; margin-bottom: 4px; &:last-child { margin-bottom: 0; } `; const SourceNetwork = styled.div` width: 50%; margin-right: 16px; overflow-wrap: break-word; `; const NetworkArrow = styled.div` width: 32px; height: 16px; background: url("${networkArrowImage}") center no-repeat; `; const TargetNetwork = styled.div` width: 50%; text-align: right; margin-left: 20px; display: flex; flex-direction: column; margin-top: -16px; `; const TargetNetworkName = styled.div` width: 100%; text-overflow: ellipsis; overflow: hidden; margin-top: 8px; &:first-child { margin-top: 16px; } `; const OptionsList = styled.div``; const Option = styled.div` display: flex; margin-bottom: 8px; `; const OptionLabel = styled.div` color: ${ThemePalette.grayscale[4]}; ${ThemeProps.exactWidth("50%")} overflow: hidden; text-overflow: ellipsis; white-space: nowrap; `; const OptionValue = styled.div` text-align: right; ${ThemeProps.exactWidth("50%")} text-overflow: ellipsis; overflow: hidden; `; const ObjectTable = styled.div` margin-top: 24px; `; const ObjectTableTitle = styled.div` margin-bottom: 8px; `; type Props = { data: WizardData; wizardType: "replica" | "migration"; schedules: Schedule[]; minionPools: MinionPool[]; defaultStorage: { value: string | null; busType?: string | null } | undefined; storageMap: StorageMap[]; instancesDetails: Instance[]; sourceSchema: Field[]; destinationSchema: Field[]; uploadedUserScripts: InstanceScript[]; }; @observer class WizardSummary extends React.Component { getDefaultBooleanOption(fieldName: string, defaultValue: boolean): boolean { if (!this.props.data.destOptions) { return defaultValue; } if (this.props.data.destOptions[fieldName] != null) { return this.props.data.destOptions[fieldName]; } return defaultValue; } renderScheduleLabel(schedule: Schedule) { const scheduleInfo = schedule.schedule; let monthLabel; if (!scheduleInfo) { return null; } if (scheduleInfo.month == null) { monthLabel = "Every month"; } else { monthLabel = `Every ${ Info.months()[scheduleInfo.month ? scheduleInfo.month - 1 : 0] }`; } let dayOfMonthLabel; if (scheduleInfo.dom == null) { dayOfMonthLabel = "every day"; } else { dayOfMonthLabel = `every ${DateUtils.getOrdinalDay(scheduleInfo.dom)}`; } let dayOfWeekLabel; if (scheduleInfo.dow == null) { dayOfWeekLabel = "every weekday"; } else { dayOfWeekLabel = `every ${Info.weekdays()[scheduleInfo.dow]}`; } const padNumber = (number: number) => (number || 0) < 10 ? `0${number || 0}` : (number || 0).toString(); let timeLabel; if (scheduleInfo.minute == null) { if (scheduleInfo.hour == null) { timeLabel = "every hour, every minute"; } else { timeLabel = `at ${padNumber( scheduleInfo.hour )} o'clock, every minute UTC`; } } else if (scheduleInfo.hour == null) { timeLabel = `every hour, at minute ${padNumber(scheduleInfo.minute)} UTC`; } else { timeLabel = `at ${padNumber(scheduleInfo.hour)}:${padNumber( scheduleInfo.minute )} UTC`; } return `${monthLabel}, ${dayOfMonthLabel}, ${dayOfWeekLabel}, ${timeLabel}`; } renderScheduleSection() { const schedules = this.props.schedules; if ( this.props.wizardType !== "replica" || !schedules || schedules.length === 0 ) { return null; } return (
Schedule {schedules.map(schedule => ( {this.renderScheduleLabel(schedule)} ))}
); } renderSourceOptionsSection() { const data = this.props.data; const type = this.props.wizardType.charAt(0).toUpperCase() + this.props.wizardType.substr(1); const provider = this.props.data && this.props.data.source && this.props.data.source.type; if (!data.sourceOptions) { return null; } return (
{type} Source Options {data.sourceOptions ? Object.keys(data.sourceOptions).map(optionName => { if ( !data.sourceOptions || data.sourceOptions[optionName] == null || data.sourceOptions[optionName] === "" || typeof data.sourceOptions[optionName] === "object" ) { return null; } const optionLabel = optionName .split("/") .map(n => LabelDictionary.get( n, `${data.source ? data.source.type : ""}-source` ) ) .join(" - "); const optionValue = fieldHelper.getValueAlias({ name: optionName, value: data.sourceOptions?.[optionName], fields: this.props.sourceSchema, targetProvider: provider, }); return ( ); }) : null} {this.renderObjectTable( data.sourceOptions, this.props.sourceSchema, provider )}
); } renderObjectTable( options: any, schema: Field[], provider?: ProviderTypes | null ) { if (!options) { return null; } const objectKeys: string[] = Object.keys(options).filter( key => typeof options[key] === "object" && key !== INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS ); return objectKeys.map(key => options[key] != null ? ( {LabelDictionary.get(key)} {Object.keys(options[key]).map(propertyName => { const value = options[key][propertyName]; if (value == null || value === "") { return null; } let optionValue; if ( key.indexOf("password") > -1 || propertyName.indexOf("password") > -1 ) { optionValue = "•••••••••"; } else { optionValue = fieldHelper.getValueAlias({ name: propertyName, value, fields: schema, targetProvider: provider, }); } return ( ); })} ) : null ); } renderMinionPoolMapping() { const allMappings = this.props.data.destOptions?.[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS]; if (!allMappings) { return null; } const mappings: any = {}; Object.keys(allMappings).forEach(map => { if (allMappings[map]) { mappings[map] = allMappings[map]; } }); if (!Object.keys(mappings).length) { return null; } const getMinionPoolName = (id: string) => { const minionPool = this.props.minionPools.find(m => m.id === id); return minionPool?.name || id; }; return ( Instance OSMorphing Minion Pool Mappings {Object.keys(mappings).map(instanceId => { const instanceName = this.props.instancesDetails.find( i => i.instance_name === instanceId || i.id === instanceId )?.name || instanceId; return ( ); })} ); } renderTargetOptionsSection() { const data = this.props.data; const provider = data?.target?.type; const type = this.props.wizardType.charAt(0).toUpperCase() + this.props.wizardType.substr(1); const executeNowOption = ( ); const separateVmOption = ( ); const renderDefaultStorageOption = () => ( ); return (
{type} Target Options {this.props.wizardType === "replica" ? executeNowOption : null} {this.props.wizardType === "migration" ? executeNowOption : null} {this.props.data.selectedInstances && this.props.data.selectedInstances.length > 1 ? separateVmOption : null} {this.props.defaultStorage ? renderDefaultStorageOption() : null} {data.destOptions ? Object.keys(data.destOptions).map(optionName => { if ( optionName === "execute_now" || optionName === "separate_vm" || !data.destOptions || data.destOptions[optionName] == null || data.destOptions[optionName] === "" || typeof data.destOptions[optionName] === "object" ) { return null; } const optionLabel = optionName .split("/") .map(n => LabelDictionary.get( n, `${data.target ? data.target.type : ""}-destination` ) ) .join(" - "); const optionValue = fieldHelper.getValueAlias({ name: optionName, value: data.destOptions?.[optionName], fields: this.props.destinationSchema, targetProvider: provider, }); return ( ); }) : null} {this.renderMinionPoolMapping()} {this.renderObjectTable( data.destOptions, this.props.destinationSchema, provider )}
); } renderStorageSection(type: "backend" | "disk") { const storageMap = this.props.storageMap.filter( mapping => mapping.type === type ); const disks = getDisks(this.props.instancesDetails, type); if (disks.length === 0 || storageMap.length === 0) { return null; } const fieldName = type === "backend" ? "storage_backend_identifier" : "id"; let fullStorageMap: { source: Disk; target: StorageBackend | null; busType?: string | null; }[] = disks .filter(d => d[fieldName]) .map(disk => { const diskMapped = storageMap.find( s => s.source[fieldName] === disk[fieldName] ); if (diskMapped) { return { source: diskMapped.source, target: diskMapped.target, busType: diskMapped.targetBusType, }; } return { source: disk, target: null }; }); fullStorageMap.sort((m1, m2) => String(m1.source[fieldName]).localeCompare(String(m2.source[fieldName])) ); fullStorageMap = fullStorageMap.filter(fsm => fsm.target && fsm.target.id); const title = type === "backend" ? "Storage Backend Mapping" : "Disk Mapping"; if (fullStorageMap.length === 0) { return null; } return (
{title} {fullStorageMap .filter(m => m.target) .map(mapping => ( {mapping.source[fieldName]} {mapping.target ? mapping.target.name : "Default"} {mapping.busType ? ( Bus Type: {mapping.busType} ) : null} ))}
); } renderNetworksSection() { const data = this.props.data; if (data.networks == null) { return null; } return (
Networks {data.networks.map(mapping => ( {mapping.sourceNic.network_name} {mapping.targetNetwork!.name} {mapping.targetSecurityGroups?.length ? ( Security Groups:{" "} {mapping.targetSecurityGroups .map(s => (typeof s === "string" ? s : s.name)) .join(", ")} ) : null} {mapping.targetPortKey ? ( Port Key: {mapping.targetPortKey} ) : null} ))}
); } renderInstancesSection() { const data = this.props.data; return (
Instances {data.selectedInstances ? data.selectedInstances.map(instance => { const flavorName = instance.flavor_name ? `/${instance.flavor_name}` : ""; return ( {instance.name} {instance.instance_name || instance.id} {`${instance.num_cpu}vCPU/${instance.memory_mb}MB${flavorName}`} ); }) : null}
); } renderUserScripts() { if (this.props.uploadedUserScripts.length === 0) { return null; } return (
Uploaded User Scripts {this.props.uploadedUserScripts.map(s => ( {s.global ? s.global === "windows" ? "Global Windows Script" : "Global Linux Script" : s.instanceId} {s.fileName} ))}
); } renderOverviewSection() { const data = this.props.data; const type = this.props.wizardType.charAt(0).toUpperCase() + this.props.wizardType.substr(1); return (
Overview Source {data.source ? data.source.name : ""} Target {data.target && data.target.name} Type Coriolis {type}
); } render() { return ( {this.renderOverviewSection()} {this.renderInstancesSection()} {this.renderNetworksSection()} {this.renderUserScripts()} {this.renderSourceOptionsSection()} {this.renderTargetOptionsSection()} {this.renderStorageSection("backend")} {this.renderStorageSection("disk")} {this.renderScheduleSection()} ); } } export default WizardSummary;