/* 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 { observer } from "mobx-react"; import * as React from "react"; import { Link } from "react-router"; import styled, { css } from "styled-components"; import fieldHelper from "@src/@types/Field"; import { ActionItem } from "@src/@types/MainItem"; import { MinionPool } from "@src/@types/MinionPool"; import EndpointLogos from "@src/components/modules/EndpointModule/EndpointLogos"; import TransferDetailsTable from "@src/components/modules/TransferModule/TransferDetailsTable"; import { ThemePalette, ThemeProps } from "@src/components/Theme"; import CopyValue from "@src/components/ui/CopyValue"; import PasswordValue from "@src/components/ui/PasswordValue"; import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon"; import StatusImage from "@src/components/ui/StatusComponents/StatusImage"; import DateUtils from "@src/utils/DateUtils"; import LabelDictionary from "@src/utils/LabelDictionary"; import arrowImage from "./images/arrow.svg"; import type { Instance } from "@src/@types/Instance"; import type { Endpoint, StorageBackend } from "@src/@types/Endpoint"; import type { Network } from "@src/@types/Network"; import type { Field as FieldType } from "@src/@types/Field"; const Wrapper = styled.div` display: flex; flex-direction: column; padding-bottom: 48px; `; const WarningWrapper = styled.div` display: flex; background: ${ThemePalette.warning}66; padding: 8px; border-radius: 4px; margin-bottom: 24px; align-items: center; `; const WarningText = styled.div` margin-left: 8px; `; const ColumnsLayout = styled.div` display: flex; `; const Column = styled.div` ${props => ThemeProps.exactWidth(props.width)} `; const Arrow = styled.div` width: 34px; height: 24px; background: url("${arrowImage}") center no-repeat; margin-top: 84px; `; const Row = styled.div` margin-bottom: 32px; &:last-child { margin-bottom: 16px; } `; const Field = styled.div` display: flex; flex-direction: column; `; const Label = styled.div` font-size: 10px; color: ${ThemePalette.grayscale[3]}; font-weight: ${ThemeProps.fontWeights.medium}; text-transform: uppercase; display: flex; align-items: center; `; const StatusIconStub = styled.div` ${ThemeProps.exactSize("16px")} `; const Value = styled.div` display: ${props => props.flex ? "flex" : props.block ? "block" : "inline-table"}; margin-top: 3px; ${props => (props.capitalize ? "text-transform: capitalize;" : "")} `; const ValueLink = styled(Link)` display: flex; margin-top: 3px; color: ${ThemePalette.primary}; text-decoration: none; cursor: pointer; `; const Loading = styled.div` display: flex; justify-content: center; align-items: center; height: 200px; `; const PropertiesTable = styled.div``; const PropertyRow = styled.div` display: flex; justify-content: space-between; margin-bottom: 4px; `; const PropertyText = css``; const PropertyName = styled.div` ${PropertyText} overflow: hidden; text-overflow: ellipsis; max-width: 50%; `; const PropertyValue = styled.div` ${PropertyText} color: ${ThemePalette.grayscale[4]}; text-align: right; overflow: hidden; text-overflow: ellipsis; max-width: calc(50% + 16px); margin-right: -16px; `; type Props = { item?: ActionItem | null; minionPools: MinionPool[]; storageBackends: StorageBackend[]; destinationSchema: FieldType[]; destinationSchemaLoading: boolean; sourceSchema: FieldType[]; sourceSchemaLoading: boolean; instancesDetails: Instance[]; instancesDetailsLoading: boolean; endpoints: Endpoint[]; networks?: Network[]; bottomControls: React.ReactNode; loading: boolean; }; type State = { showPassword: string[]; }; @observer class MainDetails extends React.Component { state = { showPassword: [], }; getSourceEndpoint(): Endpoint | undefined { const endpoint = this.props.endpoints.find( e => this.props.item && e.id === this.props.item.origin_endpoint_id, ); return endpoint; } getDestinationEndpoint(): Endpoint | undefined { const endpoint = this.props.endpoints.find( e => this.props.item && e.id === this.props.item.destination_endpoint_id, ); return endpoint; } renderLastExecutionTime() { return this.props.item ? this.renderValue(DateUtils.formatSafeDate(this.props.item.updated_at)) : "-"; } renderValue(value: string) { return ; } renderEndpointLink(type: string): React.ReactNode { const endpointIsMissing = ( Endpoint is missing ); const endpoint = type === "source" ? this.getSourceEndpoint() : this.getDestinationEndpoint(); if (endpoint) { return ( {endpoint.name} ); } return endpointIsMissing; } renderPropertiesTable( propertyNames: string[], type: "source" | "destination", ) { const endpoint = type === "source" ? this.getSourceEndpoint() : this.getDestinationEndpoint(); const getValue = (name: string, value: any) => { if ( value.join && value.length && value[0].destination && value[0].source ) { return value .map( (v: { source: any; destination: any }) => `${v.source}=${v.destination}`, ) .join(", "); } const schema = type === "source" ? this.props.sourceSchema : this.props.destinationSchema; return fieldHelper.getValueAlias({ name, value, fields: schema, targetProvider: endpoint && endpoint.type, }); }; let properties: any[] = []; let dictionaryKey = ""; if (endpoint) { dictionaryKey = `${endpoint.type}-${type}`; } const environment = this.props.item && (type === "source" ? this.props.item.source_environment : this.props.item.destination_environment); propertyNames.forEach(pn => { const value = environment ? environment[pn] : ""; const label = LabelDictionary.get(pn, dictionaryKey); if (value && value.join) { value.forEach((v: any, i: number) => { const useLabel = i === 0 ? label : ""; properties.push({ label: useLabel, value: v }); }); } else if (value && typeof value === "object") { properties = properties.concat( Object.keys(value).map(p => { if (p === "disk_mappings" || p === "backend_mappings") { return null; } if (value[p] == null || value[p] == undefined) { return null; } return { label: `${label} - ${LabelDictionary.get(p)}`, value: getValue(p, value[p]), }; }), ); } else { properties.push({ label, value: getValue(pn, value) }); } }); return ( {properties .filter(Boolean) .filter(p => p.value != null && p.value !== "") .map(prop => ( {prop.label} {prop.label.toLowerCase().indexOf("password") > -1 && !this.state.showPassword.find( f => f === `${prop.label}-${type}`, ) ? ( this.setState(prevState => ({ showPassword: [ ...prevState.showPassword, `${prop.label}-${type}`, ], })) } /> ) : ( )} ))} ); } renderTable() { if (this.props.loading) { return null; } const sourceEndpoint = this.getSourceEndpoint(); const destinationEndpoint = this.getDestinationEndpoint(); const lastUpdated = this.renderLastExecutionTime(); const getPropertyNames = (type: "source" | "destination") => { const env = this.props.item && (type === "source" ? this.props.item.source_environment : this.props.item.destination_environment); return env ? Object.keys(env).filter( k => k !== "network_map" && (k !== "storage_mappings" || (env[k] != null && typeof env[k] === "object" && Object.keys(env[k]).length > 0)), ) : []; }; const sourceMinionPool = this.props.minionPools.find( m => m.id === this.props.item?.origin_minion_pool_id, ); const destMinionPool = this.props.minionPools.find( m => m.id === this.props.item?.destination_minion_pool_id, ); return ( {this.renderEndpointLink("source")} {getPropertyNames("source").length > 0 ? ( {this.renderPropertiesTable( getPropertyNames("source"), "source", )} ) : null} {this.renderValue( this.props.item ? this.props.item.id || "-" : "-", )} {this.props.item && this.props.item.created_at ? ( this.renderValue( DateUtils.getLocalDate(this.props.item.created_at).toFormat( "yyyy-LL-dd HH:mm:ss", ), ) ) : ( - )} {lastUpdated ? ( {lastUpdated} ) : null} {this.props.item?.origin_minion_pool_id ? ( {sourceMinionPool ? ( {sourceMinionPool.name} ) : ( {this.props.item.origin_minion_pool_id} )} ) : null} {this.props.item?.type === "deployment" && this.props.item.transfer_id ? ( {this.props.item.transfer_id} ) : null} {this.renderEndpointLink("target")} {getPropertyNames("destination").length > 0 ? ( {this.renderPropertiesTable( getPropertyNames("destination"), "destination", )} ) : null} {this.props.item?.destination_minion_pool_id ? ( {destMinionPool ? ( {destMinionPool.name} ) : ( {this.props.item.destination_minion_pool_id} )} ) : null} ); } renderBottomControls() { if (this.props.loading) { return null; } return this.props.bottomControls; } renderLoading() { if (!this.props.loading && !this.props.instancesDetailsLoading) { return null; } return ( ); } renderSpecialError() { if (this.props.item?.last_execution_status !== "ERROR_ALLOCATING_MINIONS") { return null; } return ( There was an error allocating minion machines for this{" "} {this.props.item.type}. Please review the log events for the selected minion pool(s) and the logs of the Coriolis Minion Manager component for full details. ); } render() { return ( {this.renderSpecialError()} {this.renderTable()} {this.props.instancesDetailsLoading || this.props.loading ? null : ( )} {this.renderLoading()} {this.renderBottomControls()} ); } } export default MainDetails;