/*
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;