From c5f368c25301c28f0791d89b981875fd2e109228 Mon Sep 17 00:00:00 2001 From: Nashwan Azhari Date: Thu, 11 Apr 2024 18:33:59 +0300 Subject: [PATCH 01/41] Add naive copy of Migration components as Deployments. Signed-off-by: Nashwan Azhari --- src/@types/MainItem.ts | 18 +- .../DeploymentDetailsContent.spec.tsx | 74 +++ .../DeploymentDetailsContent.tsx | 142 +++++ .../DeploymentDetailsContent/package.json | 6 + .../DeploymentDetailsContent/story.tsx | 95 +++ .../ReplicaDeploymentOptions.spec.tsx | 154 +++++ .../ReplicaDeploymentOptions.tsx | 343 ++++++++++ .../images/replica-deployment.svg | 25 + .../ReplicaDeploymentOptions/package.json | 6 + .../replicaDeploymentFields.ts | 33 + .../ReplicaDeploymentOptions/story.tsx | 25 + .../DeploymentDetailsPage.tsx | 600 ++++++++++++++++++ .../images/deployment.svg | 15 + .../smart/DeploymentDetailsPage/package.json | 6 + .../smart/DeploymentsPage/DeploymentsPage.tsx | 377 +++++++++++ .../images/deployment-large.svg | 16 + .../DeploymentsPage/images/deployment.svg | 14 + .../smart/DeploymentsPage/package.json | 6 + src/sources/DeploymentSource.ts | 369 +++++++++++ src/stores/DeploymentStore.ts | 198 ++++++ tests/mocks/TransferMock.ts | 28 + 21 files changed, 2548 insertions(+), 2 deletions(-) create mode 100644 src/components/modules/TransferModule/DeploymentDetailsContent/DeploymentDetailsContent.spec.tsx create mode 100644 src/components/modules/TransferModule/DeploymentDetailsContent/DeploymentDetailsContent.tsx create mode 100644 src/components/modules/TransferModule/DeploymentDetailsContent/package.json create mode 100644 src/components/modules/TransferModule/DeploymentDetailsContent/story.tsx create mode 100644 src/components/modules/TransferModule/ReplicaDeploymentOptions/ReplicaDeploymentOptions.spec.tsx create mode 100644 src/components/modules/TransferModule/ReplicaDeploymentOptions/ReplicaDeploymentOptions.tsx create mode 100644 src/components/modules/TransferModule/ReplicaDeploymentOptions/images/replica-deployment.svg create mode 100644 src/components/modules/TransferModule/ReplicaDeploymentOptions/package.json create mode 100644 src/components/modules/TransferModule/ReplicaDeploymentOptions/replicaDeploymentFields.ts create mode 100644 src/components/modules/TransferModule/ReplicaDeploymentOptions/story.tsx create mode 100644 src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx create mode 100644 src/components/smart/DeploymentDetailsPage/images/deployment.svg create mode 100644 src/components/smart/DeploymentDetailsPage/package.json create mode 100644 src/components/smart/DeploymentsPage/DeploymentsPage.tsx create mode 100644 src/components/smart/DeploymentsPage/images/deployment-large.svg create mode 100644 src/components/smart/DeploymentsPage/images/deployment.svg create mode 100644 src/components/smart/DeploymentsPage/package.json create mode 100644 src/sources/DeploymentSource.ts create mode 100644 src/stores/DeploymentStore.ts diff --git a/src/@types/MainItem.ts b/src/@types/MainItem.ts index 92d13a34..3bd69651 100644 --- a/src/@types/MainItem.ts +++ b/src/@types/MainItem.ts @@ -116,7 +116,17 @@ export type MigrationItemOptions = MigrationItem & { shutdown_instances: boolean; }; -export type TransferItem = ReplicaItem | MigrationItem; +export type DeploymentItem = BaseItem & { + type: "deployment"; + replica_id?: string; +}; + +export type DeploymentItemOptions = DeploymentItem & { + skip_os_morphing: boolean; + shutdown_instances: boolean; +}; + +export type TransferItem = ReplicaItem | MigrationItem | DeploymentItem; export type ReplicaItemDetails = ReplicaItem & { executions: Execution[]; @@ -126,7 +136,11 @@ export type MigrationItemDetails = MigrationItem & { tasks: Task[]; }; -export type TransferItemDetails = ReplicaItemDetails | MigrationItemDetails; +export type DeploymentItemDetails = DeploymentItem & { + tasks: Task[]; +}; + +export type TransferItemDetails = ReplicaItemDetails | MigrationItemDetails | DeploymentItemDetails; export const getTransferItemTitle = (item: TransferItem | null) => { if (!item) { diff --git a/src/components/modules/TransferModule/DeploymentDetailsContent/DeploymentDetailsContent.spec.tsx b/src/components/modules/TransferModule/DeploymentDetailsContent/DeploymentDetailsContent.spec.tsx new file mode 100644 index 00000000..ef501fa3 --- /dev/null +++ b/src/components/modules/TransferModule/DeploymentDetailsContent/DeploymentDetailsContent.spec.tsx @@ -0,0 +1,74 @@ +/* +Copyright (C) 2024 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 React from "react"; + +import { render } from "@testing-library/react"; + +import DeploymentDetailsContent from "."; +import { DEPLOYMENT_ITEM_DETAILS_MOCK } from "@tests/mocks/TransferMock"; +import { MINION_POOL_MOCK } from "@tests/mocks/MinionPoolMock"; +import { STORAGE_BACKEND_MOCK } from "@tests/mocks/StoragesMock"; +import { INSTANCE_MOCK } from "@tests/mocks/InstancesMock"; +import { NETWORK_MOCK } from "@tests/mocks/NetworksMock"; +import { + OPENSTACK_ENDPOINT_MOCK, + VMWARE_ENDPOINT_MOCK, +} from "@tests/mocks/EndpointsMock"; + +jest.mock("@src/components/modules/EndpointModule/EndpointLogos", () => ({ + __esModule: true, + default: (props: any) =>
{props.endpoint}
, +})); +jest.mock("react-router-dom", () => ({ Link: "a" })); + +describe("DeploymentDetailsContent", () => { + let defaultProps: DeploymentDetailsContent["props"]; + + beforeEach(() => { + defaultProps = { + item: DEPLOYMENT_ITEM_DETAILS_MOCK, + itemId: DEPLOYMENT_ITEM_DETAILS_MOCK.id, + minionPools: [MINION_POOL_MOCK], + detailsLoading: false, + storageBackends: [STORAGE_BACKEND_MOCK], + instancesDetails: [INSTANCE_MOCK], + instancesDetailsLoading: false, + networks: [NETWORK_MOCK], + sourceSchema: [], + sourceSchemaLoading: false, + destinationSchema: [], + destinationSchemaLoading: false, + endpoints: [OPENSTACK_ENDPOINT_MOCK, VMWARE_ENDPOINT_MOCK], + page: "", + onDeleteDeploymentClick: jest.fn(), + }; + }); + + it("renders without crashing", () => { + const { getByText } = render(); + expect(getByText(DEPLOYMENT_ITEM_DETAILS_MOCK.id)).toBeTruthy(); + }); + + it("renders tasks page", () => { + const { getByText } = render( + + ); + expect( + getByText( + DEPLOYMENT_ITEM_DETAILS_MOCK.tasks[0].task_type.replace("_", " ") + ) + ).toBeTruthy(); + }); +}); diff --git a/src/components/modules/TransferModule/DeploymentDetailsContent/DeploymentDetailsContent.tsx b/src/components/modules/TransferModule/DeploymentDetailsContent/DeploymentDetailsContent.tsx new file mode 100644 index 00000000..ed316a0f --- /dev/null +++ b/src/components/modules/TransferModule/DeploymentDetailsContent/DeploymentDetailsContent.tsx @@ -0,0 +1,142 @@ +/* +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 React from "react"; +import styled from "styled-components"; + +import { DeploymentItemDetails } from "@src/@types/MainItem"; +import { MinionPool } from "@src/@types/MinionPool"; +import { Network } from "@src/@types/Network"; +import DetailsNavigation from "@src/components/modules/NavigationModule/DetailsNavigation"; +import MainDetails from "@src/components/modules/TransferModule/MainDetails"; +import Tasks from "@src/components/modules/TransferModule/Tasks"; +import { ThemeProps } from "@src/components/Theme"; +import Button from "@src/components/ui/Button"; + +import type { Instance } from "@src/@types/Instance"; +import type { Endpoint, StorageBackend } from "@src/@types/Endpoint"; +import type { Field } from "@src/@types/Field"; +const Wrapper = styled.div` + display: flex; + justify-content: center; +`; + +const Buttons = styled.div` + margin-top: 24px; + & > button:last-child { + float: right; + } +`; +const DetailsBody = styled.div` + ${ThemeProps.exactWidth(ThemeProps.contentWidth)} +`; + +const NavigationItems = [ + { + label: "Deployment", + value: "", + }, + { + label: "Tasks", + value: "tasks", + }, +]; + +type Props = { + item: DeploymentItemDetails | null; + itemId: string; + minionPools: MinionPool[]; + detailsLoading: boolean; + storageBackends: StorageBackend[]; + instancesDetails: Instance[]; + instancesDetailsLoading: boolean; + networks: Network[]; + sourceSchema: Field[]; + sourceSchemaLoading: boolean; + destinationSchema: Field[]; + destinationSchemaLoading: boolean; + endpoints: Endpoint[]; + page: string; + onDeleteDeploymentClick: () => void; +}; +@observer +class DeploymentDetailsContent extends React.Component { + renderBottomControls() { + return ( + + + + ); + } + + renderMainDetails() { + if (this.props.page !== "") { + return null; + } + + return ( + + ); + } + + renderTasks() { + if (this.props.page !== "tasks" || !this.props.item?.tasks) { + return null; + } + + return ( + + ); + } + + render() { + return ( + + + + {this.renderMainDetails()} + {this.renderTasks()} + + + ); + } +} + +export default DeploymentDetailsContent; diff --git a/src/components/modules/TransferModule/DeploymentDetailsContent/package.json b/src/components/modules/TransferModule/DeploymentDetailsContent/package.json new file mode 100644 index 00000000..fc28403a --- /dev/null +++ b/src/components/modules/TransferModule/DeploymentDetailsContent/package.json @@ -0,0 +1,6 @@ +{ + "name": "DeploymentDetailsContent", + "version": "0.0.0", + "private": true, + "main": "./DeploymentDetailsContent.tsx" +} diff --git a/src/components/modules/TransferModule/DeploymentDetailsContent/story.tsx b/src/components/modules/TransferModule/DeploymentDetailsContent/story.tsx new file mode 100644 index 00000000..286605f1 --- /dev/null +++ b/src/components/modules/TransferModule/DeploymentDetailsContent/story.tsx @@ -0,0 +1,95 @@ +/* +Copyright (C) 2024 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 . +*/ + +/* eslint-disable react/jsx-props-no-spreading */ + +import React from "react"; +import { storiesOf } from "@storybook/react"; +import DeploymentDetailsContent from "."; + +const tasks: any = [ + { + progress_updates: [ + { message: "the task has a progress of 10%", created_at: new Date() }, + ], + exception_details: "Exception details", + status: "COMPLETED", + created_at: new Date(), + depends_on: ["depends on id"], + id: "task-1", + task_type: "Task name 1", + }, + { + progress_updates: [ + { message: "the task has a progress of 50%", created_at: new Date() }, + { message: "the task is almost done", created_at: new Date() }, + ], + exception_details: "Exception details", + status: "RUNNING", + created_at: new Date(), + depends_on: ["depends on id"], + id: "task-2", + task_type: "Task name 2", + }, +]; +const endpoints: any = [ + { id: "endpoint-1", name: "Endpoint OPS", type: "openstack" }, + { id: "endpoint-2", name: "Endpoint AZURE", type: "azure" }, +]; +const item: any = { + origin_endpoint_id: "endpoint-1", + destination_endpoint_id: "endpoint-2", + id: "item-id", + created_at: new Date(2017, 10, 24, 16, 15), + info: { + instance: { + export_info: { devices: { nics: [{ network_name: "map_1" }] } }, + }, + }, + tasks, + destination_environment: { + description: "A description", + network_map: { + map_1: "Mapping 1", + }, + }, + type: "Deployment", +}; +const props: any = {}; +storiesOf("DeploymentDetailsContent", module) + .add("default", () => ( + + )) + .add("details loading", () => ( + + )) + .add("tasks", () => ( + + )); diff --git a/src/components/modules/TransferModule/ReplicaDeploymentOptions/ReplicaDeploymentOptions.spec.tsx b/src/components/modules/TransferModule/ReplicaDeploymentOptions/ReplicaDeploymentOptions.spec.tsx new file mode 100644 index 00000000..7c306d73 --- /dev/null +++ b/src/components/modules/TransferModule/ReplicaDeploymentOptions/ReplicaDeploymentOptions.spec.tsx @@ -0,0 +1,154 @@ +/* +Copyright (C) 2024 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 React from "react"; + +import WizardScripts from "@src/components/modules/WizardModule/WizardScripts"; +import { fireEvent, render } from "@testing-library/react"; +import { INSTANCE_MOCK } from "@tests/mocks/InstancesMock"; +import { MINION_POOL_MOCK } from "@tests/mocks/MinionPoolMock"; +import { REPLICA_ITEM_DETAILS_MOCK } from "@tests/mocks/TransferMock"; +import TestUtils from "@tests/TestUtils"; + +import ReplicaDeploymentOptions from "./"; + +jest.mock("@src/plugins/default/ContentPlugin", () => jest.fn(() => null)); +jest.mock("@src/components/modules/WizardModule/WizardScripts", () => ({ + __esModule: true, + default: (props: WizardScripts["props"]) => ( +
+
+ {props.uploadedScripts.map(s => s.scriptContent).join(", ")} +
+
{ + props.onScriptDataRemove(props.uploadedScripts[0]); + }} + /> +
+ {props.removedScripts.map(s => s.scriptContent).join(", ")} +
+
{ + props.onCancelScript("windows", null); + props.scrollableRef && + props.scrollableRef(null as any as HTMLElement); + }} + /> +
{ + props.onScriptUpload({ + scriptContent: `script-content-${Math.random()}`, + fileName: `script-name.ps1`, + global: "windows", + }); + }} + /> +
+ ), +})); + +describe("ReplicaDeploymentOptions", () => { + let defaultProps: ReplicaDeploymentOptions["props"]; + + beforeEach(() => { + defaultProps = { + instances: [INSTANCE_MOCK], + transferItem: REPLICA_ITEM_DETAILS_MOCK, + minionPools: [ + MINION_POOL_MOCK, + { ...MINION_POOL_MOCK, id: "pool2", name: "Pool2" }, + ], + loadingInstances: false, + onCancelClick: jest.fn(), + onDeployClick: jest.fn(), + onResizeUpdate: jest.fn(), + }; + }); + + it("renders without crashing", () => { + const { getByText } = render(); + expect(getByText("Deploy")).toBeTruthy(); + }); + + it("executes on Enter", () => { + render(); + fireEvent.keyDown(document.body, { key: "Enter" }); + expect(defaultProps.onDeployClick).toHaveBeenCalled(); + }); + + it("calls onResizeUpdate on selectedBarButton state change", () => { + render(); + fireEvent.click(TestUtils.selectAll("ToggleButtonBar__Item-")[1]); + expect(defaultProps.onResizeUpdate).toHaveBeenCalled(); + }); + + it("handles value change", () => { + render(); + expect(TestUtils.select("Switch__Wrapper")?.textContent).toBe("Yes"); + fireEvent.click(TestUtils.select("Switch__InputWrapper")!); + expect(TestUtils.select("Switch__Wrapper")?.textContent).toBe("No"); + }); + + it("handles script operations", () => { + const { getByTestId } = render( + + ); + fireEvent.click(TestUtils.selectAll("ToggleButtonBar__Item-")[1]); + fireEvent.click(getByTestId("ScriptsUpload")); + expect(getByTestId("ScriptsUploaded").textContent).toContain( + "script-content" + ); + fireEvent.click(getByTestId("ScriptsCancel")); + expect(getByTestId("ScriptsUploaded").textContent).toBe(""); + + fireEvent.click(getByTestId("ScriptsUpload")); + expect(getByTestId("ScriptsUploaded").textContent).toContain( + "script-content" + ); + expect(getByTestId("ScriptsRemoved").textContent).toBe(""); + fireEvent.click(getByTestId("ScriptsRemove")); + expect(getByTestId("ScriptsRemoved").textContent).toContain( + "script-content" + ); + }); + + it("doesn't render minion pool mappings", () => { + const { rerender } = render(); + expect(document.body.textContent).toContain("Minion Pool Mappings"); + + rerender(); + expect(document.body.textContent).not.toContain("Minion Pool Mappings"); + }); + + it("changes minion pool mappings value", () => { + render(); + fireEvent.click(TestUtils.select("DropdownButton__Wrapper-")!); + const dropdownItem = TestUtils.selectAll("Dropdown__ListItem-")[2]; + expect(dropdownItem.textContent).toBe("Pool2"); + fireEvent.click(dropdownItem); + expect(TestUtils.select("DropdownButton__Label-")?.textContent).toBe( + "Pool2" + ); + }); + + it("handles migrate click", () => { + const { getByText } = render(); + fireEvent.click(getByText("Deploy")); + expect(defaultProps.onDeployClick).toHaveBeenCalled(); + }); +}); diff --git a/src/components/modules/TransferModule/ReplicaDeploymentOptions/ReplicaDeploymentOptions.tsx b/src/components/modules/TransferModule/ReplicaDeploymentOptions/ReplicaDeploymentOptions.tsx new file mode 100644 index 00000000..374f5e6c --- /dev/null +++ b/src/components/modules/TransferModule/ReplicaDeploymentOptions/ReplicaDeploymentOptions.tsx @@ -0,0 +1,343 @@ +/* +Copyright (C) 2024 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 React from "react"; +import styled from "styled-components"; + +import { TransferItemDetails } from "@src/@types/MainItem"; +import { MinionPool } from "@src/@types/MinionPool"; +import { INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS } from "@src/components/modules/WizardModule/WizardOptions"; +import WizardScripts from "@src/components/modules/WizardModule/WizardScripts"; +import { ThemeProps } from "@src/components/Theme"; +import Button from "@src/components/ui/Button"; +import FieldInput from "@src/components/ui/FieldInput"; +import LoadingButton from "@src/components/ui/LoadingButton"; +import ToggleButtonBar from "@src/components/ui/ToggleButtonBar"; +import KeyboardManager from "@src/utils/KeyboardManager"; +import LabelDictionary from "@src/utils/LabelDictionary"; + +import replicaDeploymentImage from "./images/replica-deployment.svg"; +import replicaDeploymentFields from "./replicaDeploymentFields"; + +import type { Field } from "@src/@types/Field"; +import type { Instance, InstanceScript } from "@src/@types/Instance"; +const Wrapper = styled.div` + display: flex; + flex-direction: column; + align-items: center; + padding: 0 32px 32px 32px; + min-height: 0; +`; +const Image = styled.div` + ${ThemeProps.exactWidth("288px")} + ${ThemeProps.exactHeight("96px")} + background: url('${replicaDeploymentImage}') center no-repeat; + margin: 80px 0; +`; +const OptionsBody = styled.div` + display: flex; + flex-direction: column; +`; +const ScriptsBody = styled.div` + display: flex; + flex-direction: column; + width: 100%; + overflow: auto; + min-height: 0; + margin-bottom: 32px; +`; +const Form = styled.div` + display: flex; + flex-wrap: wrap; + margin-left: -64px; + justify-content: space-between; + margin: 0 auto; +`; +const Buttons = styled.div` + display: flex; + justify-content: space-between; + width: 100%; +`; +const FieldInputStyled = styled(FieldInput)` + width: 224px; + justify-content: space-between; + margin-bottom: 32px; +`; + +type Props = { + instances: Instance[]; + transferItem: TransferItemDetails | null; + minionPools: MinionPool[]; + loadingInstances: boolean; + defaultSkipOsMorphing?: boolean | null; + deploying?: boolean; + onCancelClick: () => void; + onDeployClick: (opts: { + fields: Field[]; + uploadedUserScripts: InstanceScript[]; + removedUserScripts: InstanceScript[]; + minionPoolMappings: { [instance: string]: string }; + }) => void; + onResizeUpdate?: (scrollableRef: HTMLElement, scrollOffset?: number) => void; +}; +type State = { + fields: Field[]; + selectedBarButton: string; + uploadedScripts: InstanceScript[]; + removedScripts: InstanceScript[]; + minionPoolMappings: { [instance: string]: string }; +}; + +@observer +class ReplicaDeploymentOptions extends React.Component { + state: State = { + fields: [], + selectedBarButton: "options", + uploadedScripts: [], + removedScripts: [], + minionPoolMappings: {}, + }; + + scrollableRef!: HTMLElement; + + UNSAFE_componentWillMount() { + const mappings = + this.props.transferItem?.instance_osmorphing_minion_pool_mappings || {}; + + this.setState({ + fields: replicaDeploymentFields.map(f => + f.name === "skip_os_morphing" + ? { ...f, value: this.props.defaultSkipOsMorphing || null } + : f + ), + minionPoolMappings: { ...mappings }, + }); + } + + componentDidMount() { + KeyboardManager.onEnter( + "deployment-options", + () => { + this.deploy(); + }, + 2 + ); + } + + componentDidUpdate(_: Props, prevState: State) { + if (prevState.selectedBarButton !== this.state.selectedBarButton) { + if (this.props.onResizeUpdate) { + this.props.onResizeUpdate(this.scrollableRef, 0); + } + } + } + + componentWillUnmount() { + KeyboardManager.removeKeyDown("deployment-options"); + } + + deploy() { + this.props.onDeployClick({ + fields: this.state.fields, + uploadedUserScripts: this.state.uploadedScripts, + removedUserScripts: this.state.removedScripts, + minionPoolMappings: this.state.minionPoolMappings, + }); + } + + handleValueChange(field: Field, value: boolean) { + this.setState(prevState => { + const fields = prevState.fields.map(f => { + const newField = { ...f }; + if (f.name === field.name) { + newField.value = value; + } + return newField; + }); + + return { fields }; + }); + } + + handleCancelScript(global: string | null, instanceName: string | null) { + this.setState(prevState => ({ + uploadedScripts: prevState.uploadedScripts.filter(s => + global ? s.global !== global : s.instanceId !== instanceName + ), + })); + } + + handleScriptUpload(script: InstanceScript) { + this.setState(prevState => ({ + uploadedScripts: [...prevState.uploadedScripts, script], + })); + } + + handleScriptRemove(script: InstanceScript) { + this.setState(prevState => ({ + removedScripts: [...prevState.removedScripts, script], + })); + } + + renderField(field: Field) { + return ( + this.handleValueChange(field, value)} + description={LabelDictionary.getDescription(field.name)} + /> + ); + } + + renderMinionPoolMappings() { + const minionPools = this.props.minionPools.filter( + m => m.endpoint_id === this.props.transferItem?.destination_endpoint_id + ); + if (!minionPools.length) { + return null; + } + + const properties: Field[] = this.props.instances.map(instance => ({ + name: instance.instance_name || instance.id, + label: instance.name, + type: "string", + enum: minionPools.map(minionPool => ({ + name: minionPool.name, + id: minionPool.id, + })), + })); + + return ( + + this.state.minionPoolMappings && + this.state.minionPoolMappings[field.name] + } + layout="page" + label="Instance OSMorphing Minion Pool Mappings" + onChange={(value, field) => + this.setState(prevState => { + const minionPoolMappings = { ...prevState.minionPoolMappings }; + minionPoolMappings[field!.name] = value; + return { minionPoolMappings }; + }) + } + properties={properties} + labelRenderer={(propName: string) => + propName.indexOf("/") > -1 + ? propName.split("/")[propName.split("/").length - 1] + : propName + } + /> + ); + } + + renderOptions() { + return ( + <> +
{this.state.fields.map(field => this.renderField(field))}
+ {this.renderMinionPoolMappings()} + + ); + } + + renderScripts() { + return ( + { + this.handleScriptUpload(s); + }} + onScriptDataRemove={s => { + this.handleScriptRemove(s); + }} + onCancelScript={(g, i) => { + this.handleCancelScript(g, i); + }} + uploadedScripts={this.state.uploadedScripts} + removedScripts={this.state.removedScripts} + userScriptData={this.props.transferItem?.user_scripts} + scrollableRef={(r: HTMLElement) => { + this.scrollableRef = r; + }} + layout="modal" + /> + ); + } + + renderBody() { + const Body = + this.state.selectedBarButton === "options" ? OptionsBody : ScriptsBody; + + return ( + + { + this.setState({ selectedBarButton: item.value }); + }} + style={{ marginBottom: "32px" }} + /> + {this.state.selectedBarButton === "options" + ? this.renderOptions() + : this.renderScripts()} + + ); + } + + render() { + return ( + + + {this.renderBody()} + + + {this.props.deploying ? ( + Deploying ... + ) : ( + + )} + + + ); + } +} + +export default ReplicaDeploymentOptions; diff --git a/src/components/modules/TransferModule/ReplicaDeploymentOptions/images/replica-deployment.svg b/src/components/modules/TransferModule/ReplicaDeploymentOptions/images/replica-deployment.svg new file mode 100644 index 00000000..f34c9d29 --- /dev/null +++ b/src/components/modules/TransferModule/ReplicaDeploymentOptions/images/replica-deployment.svg @@ -0,0 +1,25 @@ + + + + + Created with Sketch. + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/modules/TransferModule/ReplicaDeploymentOptions/package.json b/src/components/modules/TransferModule/ReplicaDeploymentOptions/package.json new file mode 100644 index 00000000..5581f77b --- /dev/null +++ b/src/components/modules/TransferModule/ReplicaDeploymentOptions/package.json @@ -0,0 +1,6 @@ +{ + "name": "ReplicaDeploymentOptions", + "version": "0.0.0", + "private": true, + "main": "./ReplicaDeploymentOptions.tsx" +} diff --git a/src/components/modules/TransferModule/ReplicaDeploymentOptions/replicaDeploymentFields.ts b/src/components/modules/TransferModule/ReplicaDeploymentOptions/replicaDeploymentFields.ts new file mode 100644 index 00000000..77aa95f6 --- /dev/null +++ b/src/components/modules/TransferModule/ReplicaDeploymentOptions/replicaDeploymentFields.ts @@ -0,0 +1,33 @@ +/* +Copyright (C) 2024 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 { Field } from "@src/@types/Field"; + +const replicaDeploymentFields: Field[] = [ + { + name: "clone_disks", + type: "boolean", + value: true, + }, + { + name: "force", + type: "boolean", + }, + { + name: "skip_os_morphing", + type: "boolean", + }, +]; + +export default replicaDeploymentFields; diff --git a/src/components/modules/TransferModule/ReplicaDeploymentOptions/story.tsx b/src/components/modules/TransferModule/ReplicaDeploymentOptions/story.tsx new file mode 100644 index 00000000..87e3cebb --- /dev/null +++ b/src/components/modules/TransferModule/ReplicaDeploymentOptions/story.tsx @@ -0,0 +1,25 @@ +/* +Copyright (C) 2024 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 React from "react"; +import { storiesOf } from "@storybook/react"; +import ReplicaDeploymentOptions from "."; + +const props: any = {}; +storiesOf("ReplicaDeploymentOptions", module).add("default", () => ( + // eslint-disable-next-line react/jsx-props-no-spreading +
+ +
+)); diff --git a/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx b/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx new file mode 100644 index 00000000..60bd371b --- /dev/null +++ b/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx @@ -0,0 +1,600 @@ +/* +Copyright (C) 2024 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 React from "react"; +import styled from "styled-components"; + +import { getTransferItemTitle } from "@src/@types/MainItem"; +import DetailsContentHeader from "@src/components/modules/DetailsModule/DetailsContentHeader"; +import DetailsPageHeader from "@src/components/modules/DetailsModule/DetailsPageHeader"; +import DetailsTemplate from "@src/components/modules/TemplateModule/DetailsTemplate"; +import DeploymentDetailsContent from "@src/components/modules/TransferModule/DeploymentDetailsContent"; +import ReplicaDeploymentOptions from "@src/components/modules/TransferModule/ReplicaDeploymentOptions"; +import TransferItemModal from "@src/components/modules/TransferModule/TransferItemModal"; +import { ThemePalette } from "@src/components/Theme"; +import AlertModal from "@src/components/ui/AlertModal"; +import Modal from "@src/components/ui/Modal"; +import { providerTypes } from "@src/constants"; +import endpointStore from "@src/stores/EndpointStore"; +import instanceStore from "@src/stores/InstanceStore"; +import deploymentStore from "@src/stores/DeploymentStore"; +import minionPoolStore from "@src/stores/MinionPoolStore"; +import networkStore from "@src/stores/NetworkStore"; +import notificationStore from "@src/stores/NotificationStore"; +import providerStore from "@src/stores/ProviderStore"; +import userStore from "@src/stores/UserStore"; +import configLoader from "@src/utils/Config"; + +import deploymentImage from "./images/deployment.svg"; + +import type { Field } from "@src/@types/Field"; +import type { InstanceScript } from "@src/@types/Instance"; +const Wrapper = styled.div``; + +type Props = { + match: any; + history: any; +}; +type State = { + showDeleteDeploymentConfirmation: boolean; + showCancelConfirmation: boolean; + showForceCancelConfirmation: boolean; + showEditModal: boolean; + showFromReplicaModal: boolean; + pausePolling: boolean; + initialLoading: boolean; + deploying: boolean; +}; +@observer +class DeploymentDetailsPage extends React.Component { + state: State = { + showDeleteDeploymentConfirmation: false, + showCancelConfirmation: false, + showForceCancelConfirmation: false, + showEditModal: false, + showFromReplicaModal: false, + pausePolling: false, + initialLoading: true, + deploying: false, + }; + + stopPolling: boolean | null = null; + + timeoutRef: any = null; + + componentDidMount() { + document.title = "Deployment Details"; + + this.loadDeploymentAndPollData(); + } + + UNSAFE_componentWillReceiveProps(newProps: any) { + if (newProps.match.params.id === this.props.match.params.id) { + return; + } + this.timeoutRef && clearTimeout(this.timeoutRef); + deploymentStore.cancelDeploymentDetails(); + deploymentStore.clearDetails(); + endpointStore.getEndpoints(); + this.loadDeploymentAndPollData(); + } + + componentWillUnmount() { + deploymentStore.cancelDeploymentDetails(); + deploymentStore.clearDetails(); + this.stopPolling = true; + } + + getStatus() { + return deploymentStore.deploymentDetails?.last_execution_status; + } + + async loadDeploymentAndPollData() { + const loadDeployment = async () => { + await endpointStore.getEndpoints({ showLoading: true }); + this.setState({ initialLoading: false }); + await this.loadDeploymentWithInstances({ + deploymentId: this.props.match.params.id, + cache: true, + onDetailsLoaded: async () => { + const details = deploymentStore.deploymentDetails; + if (!details) { + return; + } + const sourceEndpoint = endpointStore.endpoints.find( + e => e.id === details.origin_endpoint_id + ); + const destinationEndpoint = endpointStore.endpoints.find( + e => e.id === details.destination_endpoint_id + ); + if (!sourceEndpoint || !destinationEndpoint) { + return; + } + const loadOptions = async (optionsType: "source" | "destination") => { + const providerName = + optionsType === "source" + ? sourceEndpoint.type + : destinationEndpoint.type; + // This allows the values to be displayed with their allocated names instead of their IDs + await providerStore.loadOptionsSchema({ + providerName, + optionsType, + useCache: true, + quietError: true, + }); + const getOptionsValuesConfig = { + optionsType, + endpointId: + optionsType === "source" + ? details.origin_endpoint_id + : details.destination_endpoint_id, + providerName, + useCache: true, + quietError: true, + allowMultiple: true, + }; + // For some providers, the API doesn't return the required fields values + // if those required fields are sent in env data, + // so to retrieve those values a request without env data must be made + await providerStore.getOptionsValues(getOptionsValuesConfig); + await providerStore.getOptionsValues({ + ...getOptionsValuesConfig, + envData: + optionsType === "source" + ? details.source_environment + : details.destination_environment, + }); + }; + + await Promise.all([ + loadOptions("source"), + loadOptions("destination"), + ]); + }, + }); + }; + await loadDeployment(); + this.pollData(); + } + + async loadDeploymentWithInstances(options: { + deploymentId: string; + cache: boolean; + onDetailsLoaded?: () => void; + }) { + await deploymentStore.getDeployment(options.deploymentId, { + showLoading: true, + }); + const details = deploymentStore.deploymentDetails; + if (!details) { + return; + } + if (options.onDetailsLoaded) { + options.onDetailsLoaded(); + } + if (details.origin_minion_pool_id || details.destination_minion_pool_id) { + minionPoolStore.loadMinionPools(); + } + + await providerStore.loadProviders(); + + const targetEndpoint = endpointStore.endpoints.find( + e => e.id === details.destination_endpoint_id + ); + const hasStorageMap = targetEndpoint + ? providerStore.providers && providerStore.providers[targetEndpoint.type] + ? !!providerStore.providers[targetEndpoint.type].types.find( + t => t === providerTypes.STORAGE + ) + : false + : false; + if (hasStorageMap) { + endpointStore.loadStorage( + details.destination_endpoint_id, + details.destination_environment + ); + } + + networkStore.loadNetworks( + details.destination_endpoint_id, + details.destination_environment, + { + quietError: true, + cache: options.cache, + } + ); + + instanceStore.loadInstancesDetails({ + endpointId: details.origin_endpoint_id, + instances: details.instances.map(n => ({ id: n })), + cache: options.cache, + quietError: false, + env: details.source_environment, + targetProvider: targetEndpoint?.type, + }); + } + + handleUserItemClick(item: { value: string }) { + switch (item.value) { + case "signout": + userStore.logout(); + break; + default: + } + } + + handleDeleteDeploymentClick() { + this.setState({ showDeleteDeploymentConfirmation: true }); + } + + handleDeleteDeploymentConfirmation() { + this.setState({ showDeleteDeploymentConfirmation: false }); + this.props.history.push("/deployments"); + if (deploymentStore.deploymentDetails) { + deploymentStore.delete(deploymentStore.deploymentDetails.id); + } + } + + handleCloseDeleteDeploymentConfirmation() { + this.setState({ showDeleteDeploymentConfirmation: false }); + } + + handleCancelDeploymentClick(force?: boolean) { + if (force) { + this.setState({ showForceCancelConfirmation: true }); + } else { + this.setState({ showCancelConfirmation: true }); + } + } + + handleRecreateClick() { + if (!deploymentStore.deploymentDetails?.replica_id) { + this.setState({ showEditModal: true, pausePolling: true }); + return; + } + this.setState({ showFromReplicaModal: true, pausePolling: true }); + } + + handleCloseFromReplicaModal() { + this.setState({ showFromReplicaModal: false, pausePolling: false }); + } + + handleCloseCancelConfirmation() { + this.setState({ + showCancelConfirmation: false, + showForceCancelConfirmation: false, + }); + } + + async handleCancelConfirmation(force?: boolean) { + this.setState({ + showCancelConfirmation: false, + showForceCancelConfirmation: false, + }); + if (!deploymentStore.deploymentDetails) { + return; + } + await deploymentStore.cancel(deploymentStore.deploymentDetails.id, force); + if (force) { + notificationStore.alert("Force Canceled", "success"); + } else { + notificationStore.alert("Canceled", "success"); + } + } + + async recreateFromReplica(opts: { + fields: Field[]; + uploadedUserScripts: InstanceScript[]; + removedUserScripts: InstanceScript[]; + minionPoolMappings: { [instance: string]: string }; + }) { + const { + fields, + uploadedUserScripts, + removedUserScripts, + minionPoolMappings, + } = opts; + const replicaId = deploymentStore.deploymentDetails?.replica_id; + if (!replicaId) { + return; + } + + this.setState({ deploying: true }); + try { + const deployment = await this.deploy({ + replicaId, + fields, + uploadedUserScripts, + removedUserScripts, + minionPoolMappings, + }); + this.props.history.push(`/deployments/${deployment.id}/tasks`); + } finally { + this.setState({ deploying: false }); + } + this.handleCloseFromReplicaModal(); + } + + async deploy(opts: { + replicaId: string; + fields: Field[]; + uploadedUserScripts: InstanceScript[]; + removedUserScripts: InstanceScript[]; + minionPoolMappings: { [instance: string]: string }; + }) { + const { + replicaId, + fields, + uploadedUserScripts, + removedUserScripts, + minionPoolMappings, + } = opts; + const deployment = await deploymentStore.deployReplica({ + replicaId, + fields, + uploadedUserScripts, + removedUserScripts, + userScriptData: deploymentStore.deploymentDetails?.user_scripts, + minionPoolMappings, + }); + return deployment; + } + + async pollData() { + if (this.state.pausePolling || this.stopPolling) { + return; + } + await deploymentStore.getDeployment(this.props.match.params.id, { + showLoading: false, + skipLog: true, + }); + this.timeoutRef = setTimeout(() => { + this.pollData(); + }, configLoader.config.requestPollTimeout); + } + + closeEditModal() { + this.setState({ showEditModal: false, pausePolling: false }, () => { + this.pollData(); + }); + } + + handleEditReplicaReload() { + this.loadDeploymentWithInstances({ + deploymentId: this.props.match.params.id, + cache: false, + }); + } + + handleUpdateComplete(redirectTo: string) { + this.props.history.push(redirectTo); + } + + renderEditModal() { + const sourceEndpoint = endpointStore.endpoints.find( + e => + deploymentStore.deploymentDetails && + e.id === deploymentStore.deploymentDetails.origin_endpoint_id + ); + const destinationEndpoint = endpointStore.endpoints.find( + e => + deploymentStore.deploymentDetails && + e.id === deploymentStore.deploymentDetails.destination_endpoint_id + ); + + if ( + !this.state.showEditModal || + !deploymentStore.deploymentDetails || + !destinationEndpoint || + !sourceEndpoint + ) { + return null; + } + + return ( + { + this.closeEditModal(); + }} + onUpdateComplete={url => { + this.handleUpdateComplete(url); + }} + sourceEndpoint={sourceEndpoint} + replica={deploymentStore.deploymentDetails} + destinationEndpoint={destinationEndpoint} + instancesDetails={instanceStore.instancesDetails} + instancesDetailsLoading={instanceStore.loadingInstancesDetails} + networks={networkStore.networks} + networksLoading={networkStore.loading} + onReloadClick={() => { + this.handleEditReplicaReload(); + }} + /> + ); + } + + render() { + const dropdownActions = [ + { + label: "Cancel", + disabled: + this.getStatus() !== "RUNNING" && + this.getStatus() !== "AWAITING_MINION_ALLOCATIONS", + hidden: this.getStatus() === "CANCELLING", + action: () => { + this.handleCancelDeploymentClick(); + }, + }, + { + label: "Force Cancel", + hidden: this.getStatus() !== "CANCELLING", + action: () => { + this.handleCancelDeploymentClick(true); + }, + }, + { + label: "Recreate Deployment", + color: ThemePalette.primary, + action: () => { + this.handleRecreateClick(); + }, + }, + { + label: "Delete Deployment", + color: ThemePalette.alert, + action: () => { + this.handleDeleteDeploymentClick(); + }, + }, + ]; + + return ( + + { + this.handleUserItemClick(item); + }} + /> + } + contentHeaderComponent={ + + } + contentComponent={ + { + this.handleDeleteDeploymentClick(); + }} + /> + } + /> + { + this.handleDeleteDeploymentConfirmation(); + }} + onRequestClose={() => { + this.handleCloseDeleteDeploymentConfirmation(); + }} + /> + { + this.handleCancelConfirmation(); + }} + onRequestClose={() => { + this.handleCloseCancelConfirmation(); + }} + /> + { + this.handleCancelConfirmation(true); + }} + onRequestClose={() => { + this.handleCloseCancelConfirmation(); + }} + /> + {this.state.showFromReplicaModal ? ( + { + this.handleCloseFromReplicaModal(); + }} + > + { + this.handleCloseFromReplicaModal(); + }} + onDeployClick={opts => { + this.recreateFromReplica(opts); + }} + instances={instanceStore.instancesDetails} + loadingInstances={instanceStore.loadingInstancesDetails} + defaultSkipOsMorphing={deploymentStore.getDefaultSkipOsMorphing( + deploymentStore.deploymentDetails + )} + deploying={this.state.deploying} + /> + + ) : null} + {this.renderEditModal()} + + ); + } +} + +export default DeploymentDetailsPage; diff --git a/src/components/smart/DeploymentDetailsPage/images/deployment.svg b/src/components/smart/DeploymentDetailsPage/images/deployment.svg new file mode 100644 index 00000000..7f05dcd4 --- /dev/null +++ b/src/components/smart/DeploymentDetailsPage/images/deployment.svg @@ -0,0 +1,15 @@ + + + + + Created with Sketch. + + + + + + + + + + diff --git a/src/components/smart/DeploymentDetailsPage/package.json b/src/components/smart/DeploymentDetailsPage/package.json new file mode 100644 index 00000000..579c1a02 --- /dev/null +++ b/src/components/smart/DeploymentDetailsPage/package.json @@ -0,0 +1,6 @@ +{ + "name": "DeploymentDetailsPage", + "version": "0.0.0", + "private": true, + "main": "./DeploymentDetailsPage.tsx" +} diff --git a/src/components/smart/DeploymentsPage/DeploymentsPage.tsx b/src/components/smart/DeploymentsPage/DeploymentsPage.tsx new file mode 100644 index 00000000..86a2857e --- /dev/null +++ b/src/components/smart/DeploymentsPage/DeploymentsPage.tsx @@ -0,0 +1,377 @@ +/* +Copyright (C) 2024 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 React from "react"; +import styled from "styled-components"; +import { observer } from "mobx-react"; + +import MainTemplate from "@src/components/modules/TemplateModule/MainTemplate"; +import Navigation from "@src/components/modules/NavigationModule/Navigation"; +import FilterList from "@src/components/ui/Lists/FilterList"; +import PageHeader from "@src/components/smart/PageHeader"; +import AlertModal from "@src/components/ui/AlertModal"; + +import projectStore from "@src/stores/ProjectStore"; +import deploymentStore from "@src/stores/DeploymentStore"; +import endpointStore from "@src/stores/EndpointStore"; +import notificationStore from "@src/stores/NotificationStore"; +import configLoader from "@src/utils/Config"; + +import { ThemePalette } from "@src/components/Theme"; +import replicaDeploymentFields from "@src/components/modules/TransferModule/ReplicaDeploymentOptions/replicaDeploymentFields"; +import { DeploymentItem } from "@src/@types/MainItem"; +import userStore from "@src/stores/UserStore"; +import TransferListItem from "@src/components/modules/TransferModule/TransferListItem"; +import deploymentLargeImage from "./images/deployment-large.svg"; +import deploymentItemImage from "./images/deployment.svg"; + +const Wrapper = styled.div``; + +type State = { + selectedDeployments: DeploymentItem[]; + modalIsOpen: boolean; + showDeleteDeploymentModal: boolean; + showCancelDeploymentModal: boolean; + showRecreateDeploymentsModal: boolean; +}; +@observer +class DeploymentsPage extends React.Component<{ history: any }, State> { + state: State = { + showDeleteDeploymentModal: false, + showCancelDeploymentModal: false, + showRecreateDeploymentsModal: false, + selectedDeployments: [], + modalIsOpen: false, + }; + + pollTimeout = 0; + + stopPolling = false; + + componentDidMount() { + document.title = "Coriolis Deployments"; + + projectStore.getProjects(); + endpointStore.getEndpoints({ showLoading: true }); + userStore.getAllUsers({ + showLoading: userStore.users.length === 0, + quietError: true, + }); + + this.stopPolling = false; + this.pollData(); + } + + componentWillUnmount() { + clearTimeout(this.pollTimeout); + this.stopPolling = true; + } + + getEndpoint(endpointId: string) { + return endpointStore.endpoints.find(endpoint => endpoint.id === endpointId); + } + + getFilterItems() { + return [ + { label: "All", value: "all" }, + { label: "Running", value: "RUNNING" }, + { label: "Error", value: "ERROR" }, + { label: "Completed", value: "COMPLETED" }, + { label: "Canceled", value: "CANCELED" }, + ]; + } + + getStatus(deploymentId: string): string { + const deployment = deploymentStore.deployments.find(m => m.id === deploymentId); + return deployment ? deployment.last_execution_status : ""; + } + + handleProjectChange() { + endpointStore.getEndpoints({ showLoading: true }); + deploymentStore.getDeployments({ showLoading: true }); + } + + handleReloadButtonClick() { + projectStore.getProjects(); + endpointStore.getEndpoints({ showLoading: true }); + deploymentStore.getDeployments({ showLoading: true }); + userStore.getAllUsers({ showLoading: true, quietError: true }); + } + + handleItemClick(item: DeploymentItem) { + if (item.last_execution_status === "RUNNING") { + this.props.history.push(`/deployments/${item.id}/tasks`); + } else { + this.props.history.push(`/deployments/${item.id}`); + } + } + + deleteSelectedDeployments() { + this.state.selectedDeployments.forEach(deployment => { + deploymentStore.delete(deployment.id); + }); + this.setState({ showDeleteDeploymentModal: false }); + } + + cancelSelectedDeployments() { + this.state.selectedDeployments.forEach(deployment => { + const status = this.getStatus(deployment.id); + if (status === "RUNNING" || status === "AWAITING_MINION_ALLOCATIONS") { + deploymentStore.cancel(deployment.id); + } + }); + notificationStore.alert("Canceling deployments"); + this.setState({ showCancelDeploymentModal: false }); + } + + async recreateDeployments() { + notificationStore.alert("Recreating deployments"); + this.setState({ showRecreateDeploymentsModal: false }); + + await Promise.all( + this.state.selectedDeployments.map(async deployment => { + if (deployment.replica_id) { + await deploymentStore.deployReplica({ + replicaId: deployment.replica_id, + fields: replicaDeploymentFields, + uploadedUserScripts: [], + removedUserScripts: [], + userScriptData: deployment.user_scripts, + minionPoolMappings: + deployment.instance_osmorphing_minion_pool_mappings || {}, + }); + } else { + await deploymentStore.recreateFullCopy(deployment as any); + } + }) + ); + + deploymentStore.getDeployments(); + } + + handleEmptyListButtonClick() { + this.props.history.push("/wizard/deployment"); + } + + handleModalOpen() { + this.setState({ modalIsOpen: true }); + } + + handleModalClose() { + this.setState({ modalIsOpen: false }, () => { + this.pollData(); + }); + } + + searchText(item: DeploymentItem, text: string) { + let result = false; + if (item.instances[0].toLowerCase().indexOf(text) > -1) { + return true; + } + if (item.notes && item.notes.toLowerCase().indexOf(text) > -1) { + return true; + } + if (item.destination_environment) { + Object.keys(item.destination_environment).forEach(prop => { + if ( + item.destination_environment[prop]?.toLowerCase && + item.destination_environment[prop].toLowerCase().indexOf(text) > -1 + ) { + result = true; + } + }); + } + return result; + } + + itemFilterFunction( + item: DeploymentItem, + filterStatus?: string | null, + filterText?: string + ) { + if ( + (filterStatus !== "all" && item.last_execution_status !== filterStatus) || + !this.searchText( + item, + (filterText?.toLowerCase && filterText.toLowerCase()) || "" + ) + ) { + return false; + } + + return true; + } + + async pollData() { + if (this.state.modalIsOpen || this.stopPolling) { + return; + } + + await Promise.all([ + deploymentStore.getDeployments({ skipLog: true }), + endpointStore.getEndpoints({ skipLog: true }), + userStore.getAllUsers({ skipLog: true, quietError: true }), + ]); + this.pollTimeout = window.setTimeout(() => { + this.pollData(); + }, configLoader.config.requestPollTimeout); + } + + render() { + let atLeaseOneIsRunning = false; + this.state.selectedDeployments.forEach(deployment => { + const status = this.getStatus(deployment.id); + atLeaseOneIsRunning = + atLeaseOneIsRunning || + status === "RUNNING" || + status === "AWAITING_MINION_ALLOCATIONS"; + }); + const BulkActions = [ + { + label: "Cancel", + disabled: !atLeaseOneIsRunning, + action: () => { + this.setState({ showCancelDeploymentModal: true }); + }, + }, + { + label: "Recreate Deployments", + disabled: atLeaseOneIsRunning, + color: ThemePalette.primary, + action: () => { + this.setState({ showRecreateDeploymentsModal: true }); + }, + }, + { + label: "Delete Deployments", + color: ThemePalette.alert, + action: () => { + this.setState({ showDeleteDeploymentModal: true }); + }, + }, + ]; + + return ( + + } + listComponent={ + { + this.handleItemClick(item); + }} + onReloadButtonClick={() => { + this.handleReloadButtonClick(); + }} + itemFilterFunction={(...args) => this.itemFilterFunction(...args)} + onSelectedItemsChange={selectedDeployments => { + this.setState({ selectedDeployments }); + }} + dropdownActions={BulkActions} + renderItemComponent={options => ( + { + const endpoint = this.getEndpoint(id); + if (endpoint) { + return endpoint.type; + } + if (endpointStore.loading) { + return "Loading..."; + } + return "Not Found"; + }} + getUserName={id => + userStore.users.find(u => u.id === id)?.name + } + userNameLoading={userStore.allUsersLoading} + /> + )} + emptyListImage={deploymentLargeImage} + emptyListMessage="It seems like you don't have any Deployments in this project." + emptyListExtraMessage="A Coriolis Deployment is a full virtual machine deployment between two cloud endpoints." + emptyListButtonLabel="Create a Deployment" + onEmptyListButtonClick={() => { + this.handleEmptyListButtonClick(); + }} + /> + } + headerComponent={ + { + this.handleProjectChange(); + }} + onModalOpen={() => { + this.handleModalOpen(); + }} + onModalClose={() => { + this.handleModalClose(); + }} + /> + } + /> + {this.state.showDeleteDeploymentModal ? ( + { + this.deleteSelectedDeployments(); + }} + onRequestClose={() => { + this.setState({ showDeleteDeploymentModal: false }); + }} + /> + ) : null} + {this.state.showCancelDeploymentModal ? ( + { + this.cancelSelectedDeployments(); + }} + onRequestClose={() => { + this.setState({ showCancelDeploymentModal: false }); + }} + /> + ) : null} + {this.state.showRecreateDeploymentsModal ? ( + { + this.recreateDeployments(); + }} + onRequestClose={() => { + this.setState({ showRecreateDeploymentsModal: false }); + }} + /> + ) : null} + + ); + } +} + +export default DeploymentsPage; diff --git a/src/components/smart/DeploymentsPage/images/deployment-large.svg b/src/components/smart/DeploymentsPage/images/deployment-large.svg new file mode 100644 index 00000000..f7086b60 --- /dev/null +++ b/src/components/smart/DeploymentsPage/images/deployment-large.svg @@ -0,0 +1,16 @@ + + + + + Created with Sketch. + + + + + + + + + + + diff --git a/src/components/smart/DeploymentsPage/images/deployment.svg b/src/components/smart/DeploymentsPage/images/deployment.svg new file mode 100644 index 00000000..beb1ab3c --- /dev/null +++ b/src/components/smart/DeploymentsPage/images/deployment.svg @@ -0,0 +1,14 @@ + + + + + Created with Sketch. + + + + + + + + + diff --git a/src/components/smart/DeploymentsPage/package.json b/src/components/smart/DeploymentsPage/package.json new file mode 100644 index 00000000..00dafe28 --- /dev/null +++ b/src/components/smart/DeploymentsPage/package.json @@ -0,0 +1,6 @@ +{ + "name": "DeploymentsPage", + "version": "0.0.0", + "private": true, + "main": "./DeploymentsPage.tsx" +} diff --git a/src/sources/DeploymentSource.ts b/src/sources/DeploymentSource.ts new file mode 100644 index 00000000..85d752aa --- /dev/null +++ b/src/sources/DeploymentSource.ts @@ -0,0 +1,369 @@ +/* +Copyright (C) 2024 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 { + DeploymentItem, + DeploymentItemDetails, + DeploymentItemOptions, + UserScriptData, +} from "@src/@types/MainItem"; +import { ProgressUpdate, Task } from "@src/@types/Task"; +import { INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS } from "@src/components/modules/WizardModule/WizardOptions"; +import { OptionsSchemaPlugin } from "@src/plugins"; +import DefaultOptionsSchemaPlugin from "@src/plugins/default/OptionsSchemaPlugin"; +import Api from "@src/utils/ApiCaller"; +import configLoader from "@src/utils/Config"; + +import { sortTasks } from "./ReplicaSource"; + +import type { InstanceScript } from "@src/@types/Instance"; +import type { Field } from "@src/@types/Field"; +import type { NetworkMap } from "@src/@types/Network"; +import type { Endpoint, StorageMap } from "@src/@types/Endpoint"; + +class DeploymentSourceUtils { + static sortTaskUpdates(updates: ProgressUpdate[]) { + if (!updates) { + return; + } + updates.sort((a, b) => { + const sortNull = !a && b ? 1 : a && !b ? -1 : !a && !b ? 0 : false; + if (sortNull !== false) { + return sortNull; + } + return a.index - b.index; + }); + } + + static sortDeployments(deployments: any[]) { + deployments.sort( + (a: any, b: any) => + new Date(b.created_at).getTime() - new Date(a.created_at).getTime() + ); + + deployments.forEach((deployment: { tasks: Task[] }) => { + sortTasks(deployment.tasks, DeploymentSourceUtils.sortTaskUpdates); + }); + } +} + +class DeploymentSource { + async getDeployments(skipLog?: boolean): Promise { + const response = await Api.send({ + url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/deployments`, + skipLog, + }); + const deployments = response.data.deployments; + DeploymentSourceUtils.sortDeployments(deployments); + return deployments; + } + + async getDeployment( + deploymentId: string, + skipLog?: boolean + ): Promise { + const response = await Api.send({ + url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/deployments/${deploymentId}`, + skipLog, + cancelId: deploymentId, + }); + const deployment = response.data.deployment; + sortTasks(deployment.tasks, DeploymentSourceUtils.sortTaskUpdates); + return deployment; + } + + async recreateFullCopy( + deployment: DeploymentItemOptions + ): Promise { + const { + // eslint-disable-next-line @typescript-eslint/naming-convention + origin_endpoint_id, + destination_endpoint_id, + destination_environment, + // eslint-disable-next-line @typescript-eslint/naming-convention + network_map, + instances, + storage_mappings, + notes, + destination_minion_pool_id, + // eslint-disable-next-line @typescript-eslint/naming-convention + origin_minion_pool_id, + instance_osmorphing_minion_pool_mappings, + } = deployment; + + const payload: any = { + deployment: { + origin_endpoint_id, + destination_endpoint_id, + destination_environment, + network_map, + instances, + storage_mappings, + notes, + destination_minion_pool_id, + origin_minion_pool_id, + instance_osmorphing_minion_pool_mappings, + }, + }; + + if (deployment.skip_os_morphing != null) { + payload.deployment.skip_os_morphing = deployment.skip_os_morphing; + } + + if (deployment.source_environment) { + payload.deployment.source_environment = deployment.source_environment; + } + + payload.deployment.shutdown_instances = Boolean( + deployment.shutdown_instances + ); + payload.deployment.replication_count = deployment.replication_count || 2; + + const response = await Api.send({ + url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/deployments`, + method: "POST", + data: payload, + }); + return response.data.deployment; + } + + async recreate(opts: { + sourceEndpoint: Endpoint; + destEndpoint: Endpoint; + instanceNames: string[]; + destEnv: { [prop: string]: any } | null; + updatedDestEnv: { [prop: string]: any } | null; + sourceEnv?: { [prop: string]: any } | null; + updatedSourceEnv?: { [prop: string]: any } | null; + storageMappings?: { [prop: string]: any } | null; + updatedStorageMappings: StorageMap[] | null; + defaultStorage?: { value: string | null; busType?: string | null }; + updatedDefaultStorage?: { value: string | null; busType?: string | null }; + networkMappings?: any; + updatedNetworkMappings: NetworkMap[] | null; + defaultSkipOsMorphing: boolean | null; + replicationCount?: number | null; + deployment: DeploymentItemDetails; + uploadedScripts: InstanceScript[]; + removedScripts: InstanceScript[]; + }): Promise { + const getValue = (fieldName: string): string | null => { + const updatedDestEnv = + opts.updatedDestEnv && opts.updatedDestEnv[fieldName]; + return updatedDestEnv != null + ? updatedDestEnv + : opts.destEnv && opts.destEnv[fieldName]; + }; + + const sourceParser = OptionsSchemaPlugin.for(opts.sourceEndpoint.type); + const destParser = OptionsSchemaPlugin.for(opts.destEndpoint.type); + const payload: any = {}; + + payload.deployment = { + origin_endpoint_id: opts.sourceEndpoint.id, + destination_endpoint_id: opts.destEndpoint.id, + shutdown_instances: Boolean( + opts.updatedDestEnv && opts.updatedDestEnv.shutdown_instances + ), + replication_count: + (opts.updatedDestEnv && opts.updatedDestEnv.replication_count) || + opts.replicationCount || + 2, + instances: opts.instanceNames, + notes: opts.updatedDestEnv?.title || opts.deployment.notes || "", + }; + + const skipOsMorphingValue = getValue("skip_os_morphing"); + if (skipOsMorphingValue != null) { + payload.deployment.skip_os_morphing = skipOsMorphingValue; + } else if (opts.defaultSkipOsMorphing != null) { + payload.deployment.skip_os_morphing = opts.defaultSkipOsMorphing; + } + + if ( + opts.networkMappings || + (opts.updatedNetworkMappings && opts.updatedNetworkMappings.length) + ) { + payload.deployment.network_map = { + ...opts.networkMappings, + ...destParser.getNetworkMap(opts.updatedNetworkMappings), + }; + } + + if ( + (opts.storageMappings && Object.keys(opts.storageMappings).length) || + (opts.updatedStorageMappings && opts.updatedStorageMappings.length) + ) { + payload.deployment.storage_mappings = { + ...opts.storageMappings, + ...destParser.getStorageMap( + opts.updatedDefaultStorage || opts.defaultStorage, + opts.updatedStorageMappings + ), + }; + } + const { deployment } = opts; + const sourceEnv: any = { + ...opts.sourceEnv, + }; + const updatedSourceEnv = opts.updatedSourceEnv + ? sourceParser.getDestinationEnv(opts.updatedSourceEnv) + : {}; + const sourceMinionPoolId = + opts?.updatedSourceEnv?.minion_pool_id || deployment.origin_minion_pool_id; + if (sourceMinionPoolId) { + payload.deployment.origin_minion_pool_id = sourceMinionPoolId; + } + payload.deployment.source_environment = { + ...sourceEnv, + ...updatedSourceEnv, + }; + + const destEnv: any = { + ...opts.destEnv, + }; + const updatedDestEnv = opts.updatedDestEnv + ? sourceParser.getDestinationEnv(opts.updatedDestEnv) + : {}; + const destMinionPoolId = + opts?.updatedDestEnv?.minion_pool_id || + deployment.destination_minion_pool_id; + if (destMinionPoolId) { + payload.deployment.destination_minion_pool_id = destMinionPoolId; + } + + const updatedDestEnvMappings = + updatedDestEnv[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] || {}; + const oldMappings = + deployment[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] || {}; + const mergedMappings = { ...oldMappings, ...updatedDestEnvMappings }; + if (Object.keys(mergedMappings).length) { + const newMappings: any = {}; + Object.keys(mergedMappings).forEach(k => { + if (mergedMappings[k] !== null) { + newMappings[k] = mergedMappings[k]; + } + }); + payload.deployment[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] = newMappings; + } + + delete updatedDestEnv[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS]; + + payload.deployment.destination_environment = { + ...destEnv, + ...updatedDestEnv, + }; + + if ( + opts.uploadedScripts?.length || + opts.removedScripts?.length || + deployment.user_scripts + ) { + payload.deployment.user_scripts = + new DefaultOptionsSchemaPlugin().getUserScripts( + opts.uploadedScripts || [], + opts.removedScripts || [], + deployment.user_scripts + ); + } + + const response = await Api.send({ + url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/deployments`, + method: "POST", + data: payload, + }); + return response.data.deployment; + } + + async cancel(deploymentId: string, force?: boolean | null): Promise { + const data: any = { cancel: null }; + if (force) { + data.cancel = { force: true }; + } + await Api.send({ + url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/deployments/${deploymentId}/actions`, + method: "POST", + data, + }); + return deploymentId; + } + + async delete(deploymentId: string): Promise { + await Api.send({ + url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/deployments/${deploymentId}`, + method: "DELETE", + }); + return deploymentId; + } + + async migrateReplica(opts: { + replicaId: string; + options: Field[]; + uploadedUserScripts: InstanceScript[]; + removedUserScripts: InstanceScript[]; + userScriptData: UserScriptData | null | undefined; + minionPoolMappings: { [instance: string]: string }; + }): Promise { + const { + replicaId, + options, + uploadedUserScripts, + removedUserScripts, + userScriptData, + minionPoolMappings, + } = opts; + const payload: any = { + deployment: { + replica_id: replicaId, + }, + }; + options.forEach(o => { + payload.deployment[o.name] = o.value || o.default || false; + }); + + if ( + uploadedUserScripts.length || + removedUserScripts.length || + userScriptData + ) { + payload.deployment.user_scripts = + new DefaultOptionsSchemaPlugin().getUserScripts( + uploadedUserScripts, + removedUserScripts, + userScriptData + ); + } + + if (Object.keys(minionPoolMappings).length) { + const newMappings: any = {}; + Object.keys(minionPoolMappings).forEach(k => { + if (minionPoolMappings[k] !== null) { + newMappings[k] = minionPoolMappings[k]; + } + }); + payload.deployment[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] = newMappings; + } else { + payload.deployment[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] = null; + } + + const response = await Api.send({ + url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/deployments`, + method: "POST", + data: payload, + }); + return response.data.deployment; + } +} + +export default new DeploymentSource(); diff --git a/src/stores/DeploymentStore.ts b/src/stores/DeploymentStore.ts new file mode 100644 index 00000000..982a56fb --- /dev/null +++ b/src/stores/DeploymentStore.ts @@ -0,0 +1,198 @@ +/* +Copyright (C) 2024 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 { observable, action, runInAction } from "mobx"; + +import type { + UpdateData, + DeploymentItem, + DeploymentItemDetails, + DeploymentItemOptions, + UserScriptData, +} from "@src/@types/MainItem"; +import type { Field } from "@src/@types/Field"; +import type { Endpoint } from "@src/@types/Endpoint"; +import type { InstanceScript } from "@src/@types/Instance"; +import DeploymentSource from "@src/sources/DeploymentSource"; +import apiCaller from "@src/utils/ApiCaller"; + +class DeploymentStore { + @observable deployments: DeploymentItem[] = []; + + @observable deploymentDetails: DeploymentItemDetails | null = null; + + @observable loading = true; + + @observable detailsLoading = true; + + deploymentsLoaded = false; + + @action async getDeployments(options?: { + showLoading?: boolean; + skipLog?: boolean; + }) { + if ((options && options.showLoading) || !this.deploymentsLoaded) { + this.loading = true; + } + + try { + const deployments = await DeploymentSource.getDeployments( + options && options.skipLog + ); + runInAction(() => { + this.deployments = deployments; + this.loading = false; + this.deploymentsLoaded = true; + }); + } catch (ex) { + runInAction(() => { + this.loading = false; + }); + throw ex; + } + } + + getDefaultSkipOsMorphing(deployment: DeploymentItemDetails | null) { + const tasks = deployment && deployment.tasks; + if (tasks && !tasks.find(t => t.task_type === "OS_MORPHING")) { + return true; + } + return null; + } + + @action async recreateFullCopy(deployment: DeploymentItemOptions) { + return DeploymentSource.recreateFullCopy(deployment); + } + + @action async recreate(opts: { + deployment: DeploymentItemDetails; + sourceEndpoint: Endpoint; + destEndpoint: Endpoint; + updateData: UpdateData; + defaultStorage: { value: string | null; busType?: string | null }; + updatedDefaultStorage: + | { value: string | null; busType?: string | null } + | undefined; + replicationCount: number | null | undefined; + }): Promise { + const { + deployment, + sourceEndpoint, + destEndpoint, + updateData, + defaultStorage, + updatedDefaultStorage, + replicationCount, + } = opts; + const deploymentResult = await DeploymentSource.recreate({ + sourceEndpoint, + destEndpoint, + deployment, + instanceNames: deployment.instances, + sourceEnv: deployment.source_environment, + updatedSourceEnv: updateData.source, + destEnv: deployment.destination_environment, + updatedDestEnv: updateData.destination, + storageMappings: deployment.storage_mappings, + updatedStorageMappings: updateData.storage, + defaultStorage, + updatedDefaultStorage, + networkMappings: deployment.network_map, + updatedNetworkMappings: updateData.network, + defaultSkipOsMorphing: this.getDefaultSkipOsMorphing(deployment), + replicationCount, + uploadedScripts: updateData.uploadedScripts, + removedScripts: updateData.removedScripts, + }); + return deploymentResult; + } + + @action async getDeployment( + deploymentId: string, + options?: { showLoading?: boolean; skipLog?: boolean } + ) { + if (options && options.showLoading) { + this.detailsLoading = true; + } + + try { + const deployment = await DeploymentSource.getDeployment( + deploymentId, + options && options.skipLog + ); + runInAction(() => { + this.deploymentDetails = deployment; + this.deployments = this.deployments.map(m => + m.id === deployment.id ? deployment : m + ); + }); + } finally { + runInAction(() => { + this.detailsLoading = false; + }); + } + } + + @action async cancel(deploymentId: string, force?: boolean | null) { + await DeploymentSource.cancel(deploymentId, force); + } + + @action async delete(deploymentId: string) { + await DeploymentSource.delete(deploymentId); + runInAction(() => { + this.deployments = this.deployments.filter(r => r.id !== deploymentId); + }); + } + + @action async deployReplica(opts: { + replicaId: string; + fields: Field[]; + uploadedUserScripts: InstanceScript[]; + removedUserScripts: InstanceScript[]; + userScriptData: UserScriptData | null | undefined; + minionPoolMappings: { [instance: string]: string }; + }) { + const { + replicaId, + fields: options, + uploadedUserScripts, + removedUserScripts, + userScriptData, + minionPoolMappings, + } = opts; + const deployment = await DeploymentSource.deployReplica({ + replicaId, + options, + uploadedUserScripts, + removedUserScripts, + userScriptData, + minionPoolMappings, + }); + return deployment; + } + + @action cancelDeploymentDetails() { + if (this.deploymentDetails) { + apiCaller.cancelRequests(this.deploymentDetails.id); + } + this.detailsLoading = false; + } + + @action clearDetails() { + this.detailsLoading = true; + this.deploymentDetails = null; + } +} + +export default new DeploymentStore(); diff --git a/tests/mocks/TransferMock.ts b/tests/mocks/TransferMock.ts index a6c82495..7d25250b 100644 --- a/tests/mocks/TransferMock.ts +++ b/tests/mocks/TransferMock.ts @@ -11,6 +11,7 @@ export const REPLICA_MOCK: ReplicaItem = { id: "replica-id", name: "replica-name", type: "replica", + scenario: "replica", description: "replica-description", notes: "replica-notes", created_at: "2023-11-26T12:00:00Z", @@ -91,7 +92,34 @@ export const MIGRATION_MOCK: MigrationItem = { user_id: "user-id", }; +export const DEPLOYMENT_MOCK: DEPLOYMENT_ITEM = { + id: "deployment-id", + name: "deployment-name", + type: "deployment", + replica_scenario_type: "replica", + description: "deployment-description", + notes: "deployment-notes", + created_at: "2023-11-26T12:00:00Z", + updated_at: "2023-11-26T12:00:00Z", + origin_endpoint_id: "openstack", + destination_endpoint_id: "vmware", + origin_minion_pool_id: "origin-minion-pool-id", + destination_minion_pool_id: "destination-minion-pool-id", + instances: ["instance-id"], + info: {}, + destination_environment: {}, + source_environment: {}, + transfer_result: {}, + last_execution_status: "COMPLETED", + user_id: "user-id", +}; + export const MIGRATION_ITEM_DETAILS_MOCK: MigrationItemDetails = { ...MIGRATION_MOCK, tasks: [{ ...TASK_MOCK, task_type: "migration_task" }], }; + +export const DEPLOYMENT_ITEM_DETAILS_MOCK: DeploymentItemDetails = { + ...DEPLOYMENT_MOCK, + tasks: [{ ...TASK_MOCK, task_type: "deployment_task" }], +}; From 2e64d2ceab938ca642d6a6134247fcbfc8c5a9b5 Mon Sep 17 00:00:00 2001 From: Nashwan Azhari Date: Thu, 11 Apr 2024 18:35:03 +0300 Subject: [PATCH 02/41] Hook 'Deployments' tab into sidebar. Signed-off-by: Nashwan Azhari --- src/components/App.tsx | 5 +++++ .../NavigationModule/Navigation/Navigation.tsx | 4 ++++ .../Navigation/images/deployments-menu.svg | 17 +++++++++++++++++ .../NewItemDropdown/NewItemDropdown.tsx | 6 ++++++ src/constants.ts | 1 + 5 files changed, 33 insertions(+) create mode 100644 src/components/modules/NavigationModule/Navigation/images/deployments-menu.svg diff --git a/src/components/App.tsx b/src/components/App.tsx index 1213648b..d2611062 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -26,6 +26,8 @@ import MessagePage from "@src/components/smart/MessagePage"; import ReplicaDetailsPage from "@src/components/smart/ReplicaDetailsPage"; import MigrationsPage from "@src/components/smart/MigrationsPage"; import MigrationDetailsPage from "@src/components/smart/MigrationDetailsPage"; +import DeploymentsPage from "@src/components/smart/DeploymentsPage"; +import DeploymentDetailsPage from "@src/components/smart/DeploymentDetailsPage"; import MetalHubServersPage from "@src/components/smart/MetalHubServersPage"; import EndpointsPage from "@src/components/smart/EndpointsPage"; import EndpointDetailsPage from "@src/components/smart/EndpointDetailsPage"; @@ -217,6 +219,9 @@ class App extends React.Component, State> { {renderRoute("/migrations", MigrationsPage, true)} {renderRoute("/migrations/:id", MigrationDetailsPage, true)} {renderRoute("/migrations/:id/:page", MigrationDetailsPage)} + {renderRoute("/deployments", DeploymentsPage, true)} + {renderRoute("/deployments/:id", DeploymentDetailsPage, true)} + {renderRoute("/deployments/:id/:page", DeploymentDetailsPage)} {renderRoute("/endpoints", EndpointsPage, true)} {renderRoute("/endpoints/:id", EndpointDetailsPage)} {renderRoute("/minion-pools", MinionPoolsPage, true)} diff --git a/src/components/modules/NavigationModule/Navigation/Navigation.tsx b/src/components/modules/NavigationModule/Navigation/Navigation.tsx index dc85271c..b3880a82 100644 --- a/src/components/modules/NavigationModule/Navigation/Navigation.tsx +++ b/src/components/modules/NavigationModule/Navigation/Navigation.tsx @@ -414,6 +414,10 @@ class Navigation extends React.Component { bullet = "migration"; menuImage = replicaImage; break; + case "deployments": + bullet = "deployment"; + menuImage = replicaImage; + break; case "endpoints": menuImage = endpointImage; break; diff --git a/src/components/modules/NavigationModule/Navigation/images/deployments-menu.svg b/src/components/modules/NavigationModule/Navigation/images/deployments-menu.svg new file mode 100644 index 00000000..c5d049d3 --- /dev/null +++ b/src/components/modules/NavigationModule/Navigation/images/deployments-menu.svg @@ -0,0 +1,17 @@ + + + + + Created with Sketch. + + + + + + + + + + + + diff --git a/src/components/ui/Dropdowns/NewItemDropdown/NewItemDropdown.tsx b/src/components/ui/Dropdowns/NewItemDropdown/NewItemDropdown.tsx index 10467e56..abf29697 100644 --- a/src/components/ui/Dropdowns/NewItemDropdown/NewItemDropdown.tsx +++ b/src/components/ui/Dropdowns/NewItemDropdown/NewItemDropdown.tsx @@ -186,6 +186,12 @@ class NewItemDropdown extends React.Component { description: "Incrementally replicate VMs between two clouds", iconName: "replica", }, + { + title: "Deployment", + href: "/wizard/deployment", + description: "Deploy an already synced Replica or Live Migration", + iconName: "replica", + }, { title: "Endpoint", value: "endpoint", diff --git a/src/constants.ts b/src/constants.ts index 72270093..9d74fa9f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -25,6 +25,7 @@ export const navigationMenu: NavigationMenuType[] = [ { label: "Dashboard", value: "dashboard" }, { label: "Replicas", value: "replicas" }, { label: "Migrations", value: "migrations" }, + { label: "Deployments", value: "deployments" }, { label: "Cloud Endpoints", value: "endpoints" }, { label: "Minion Pools", value: "minion-pools" }, { label: "Bare Metal Servers", value: "bare-metal-servers" }, From 026b6357b52904ed7b3cfe5f1976e095a600a7e8 Mon Sep 17 00:00:00 2001 From: Nashwan Azhari Date: Fri, 12 Apr 2024 13:11:53 +0300 Subject: [PATCH 03/41] Update Replicas list page for Deployments. Signed-off-by: Nashwan Azhari --- .../smart/ReplicasPage/ReplicasPage.tsx | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/components/smart/ReplicasPage/ReplicasPage.tsx b/src/components/smart/ReplicasPage/ReplicasPage.tsx index 36911217..823f3918 100644 --- a/src/components/smart/ReplicasPage/ReplicasPage.tsx +++ b/src/components/smart/ReplicasPage/ReplicasPage.tsx @@ -23,7 +23,7 @@ import PageHeader from "@src/components/smart/PageHeader"; import AlertModal from "@src/components/ui/AlertModal"; import Modal from "@src/components/ui/Modal"; import ReplicaExecutionOptions from "@src/components/modules/TransferModule/ReplicaExecutionOptions"; -import ReplicaMigrationOptions from "@src/components/modules/TransferModule/ReplicaMigrationOptions"; +import ReplicaDeploymentOptions from "@src/components/modules/TransferModule/ReplicaDeploymentOptions"; import DeleteReplicaModal from "@src/components/modules/TransferModule/DeleteReplicaModal"; import type { DropdownAction } from "@src/components/ui/Dropdowns/ActionDropdown"; @@ -32,7 +32,7 @@ import type { InstanceScript } from "@src/@types/Instance"; import projectStore from "@src/stores/ProjectStore"; import replicaStore from "@src/stores/ReplicaStore"; -import migrationStore from "@src/stores/MigrationStore"; +import deploymentStore from "@src/stores/DeploymentStore"; import scheduleStore from "@src/stores/ScheduleStore"; import instanceStore from "@src/stores/InstanceStore"; import endpointStore from "@src/stores/EndpointStore"; @@ -55,7 +55,7 @@ type State = { selectedReplicas: ReplicaItem[]; showCancelExecutionModal: boolean; showExecutionOptionsModal: boolean; - showCreateMigrationsModal: boolean; + showCreateDeploymentsModal: boolean; showDeleteDisksModal: boolean; showDeleteReplicasModal: boolean; }; @@ -65,7 +65,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> { modalIsOpen: false, selectedReplicas: [], showCancelExecutionModal: false, - showCreateMigrationsModal: false, + showCreateDeploymentsModal: false, showExecutionOptionsModal: false, showDeleteDisksModal: false, showDeleteReplicasModal: false, @@ -155,16 +155,16 @@ class ReplicasPage extends React.Component<{ history: any }, State> { this.setState({ showExecutionOptionsModal: false }); } - migrateSelectedReplicas(fields: Field[], uploadedScripts: InstanceScript[]) { - notificationStore.alert("Creating migrations from selected replicas"); - this.migrate(fields, uploadedScripts); - this.setState({ showCreateMigrationsModal: false, modalIsOpen: false }); + deploySelectedReplicas(fields: Field[], uploadedScripts: InstanceScript[]) { + notificationStore.alert("Creating deployments from selected replicas"); + this.deploy(fields, uploadedScripts); + this.setState({ showCreateDeploymentsModal: false, modalIsOpen: false }); } - async migrate(fields: Field[], uploadedScripts: InstanceScript[]) { + async deploy(fields: Field[], uploadedScripts: InstanceScript[]) { await Promise.all( this.state.selectedReplicas.map(replica => - migrationStore.migrateReplica({ + deploymentStore.deployReplica({ replicaId: replica.id, fields, uploadedUserScripts: uploadedScripts.filter( @@ -179,10 +179,10 @@ class ReplicasPage extends React.Component<{ history: any }, State> { ) ); notificationStore.alert( - "Migrations successfully created from replicas.", + "Deployments successfully created from replicas.", "success" ); - this.props.history.push("/migrations"); + this.props.history.push("/deployments"); } handleShowDeleteReplicas() { @@ -257,7 +257,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> { }); } - handleShowCreateMigrationsModal() { + handleShowCreateDeploymentsModal() { instanceStore.loadInstancesDetailsBulk( replicaStore.replicas.map(r => ({ endpointId: r.origin_endpoint_id, @@ -266,7 +266,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> { })) ); - this.setState({ showCreateMigrationsModal: true, modalIsOpen: true }); + this.setState({ showCreateDeploymentsModal: true, modalIsOpen: true }); } async pollData() { @@ -393,10 +393,10 @@ class ReplicasPage extends React.Component<{ history: any }, State> { }, }, { - label: "Create Migrations", + label: "Deploy Replicas", color: ThemePalette.primary, action: () => { - this.handleShowCreateMigrationsModal(); + this.handleShowCreateDeploymentsModal(); }, }, { @@ -535,30 +535,30 @@ class ReplicasPage extends React.Component<{ history: any }, State> { /> ) : null} - {this.state.showCreateMigrationsModal ? ( + {this.state.showCreateDeploymentsModal ? ( { this.setState({ - showCreateMigrationsModal: false, + showCreateDeploymentsModal: false, modalIsOpen: false, }); }} > - { this.setState({ - showCreateMigrationsModal: false, + showCreateDeploymentsModal: false, modalIsOpen: false, }); }} - onMigrateClick={options => { - this.migrateSelectedReplicas( + onDeployClick={options => { + this.deploySelectedReplicas( options.fields, options.uploadedUserScripts ); From e8987355ead259d737e7733c1f722df567291763 Mon Sep 17 00:00:00 2001 From: Nashwan Azhari Date: Fri, 12 Apr 2024 13:19:34 +0300 Subject: [PATCH 04/41] Update Replicas details page for Deployments. Signed-off-by: Nashwan Azhari --- .../ReplicaDetailsContent.spec.tsx | 2 +- .../ReplicaDetailsContent.tsx | 4 +- .../ReplicaDetailsPage/ReplicaDetailsPage.tsx | 54 +++++++++---------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.spec.tsx b/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.spec.tsx index 12728dd4..f2b017c0 100644 --- a/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.spec.tsx +++ b/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.spec.tsx @@ -92,7 +92,7 @@ describe("ReplicaDetailsContent", () => { onCancelExecutionClick: jest.fn(), onDeleteExecutionClick: jest.fn(), onExecuteClick: jest.fn(), - onCreateMigrationClick: jest.fn(), + onCreateDeploymentClick: jest.fn(), onDeleteReplicaClick: jest.fn(), onAddScheduleClick: jest.fn(), onScheduleChange: jest.fn(), diff --git a/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx b/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx index 013e295f..f3e21df9 100644 --- a/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx +++ b/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx @@ -100,7 +100,7 @@ type Props = { ) => void; onDeleteExecutionClick: (execution: Execution | null) => void; onExecuteClick: () => void; - onCreateMigrationClick: () => void; + onCreateDeploymentClick: () => void; onDeleteReplicaClick: () => void; onAddScheduleClick: (schedule: ScheduleType) => void; onScheduleChange: ( @@ -158,7 +158,7 @@ class ReplicaDetailsContent extends React.Component { diff --git a/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx b/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx index 9e22eb6a..c8df9b03 100644 --- a/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx +++ b/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx @@ -23,7 +23,7 @@ import DetailsTemplate from "@src/components/modules/TemplateModule/DetailsTempl import DeleteReplicaModal from "@src/components/modules/TransferModule/DeleteReplicaModal"; import ReplicaDetailsContent from "@src/components/modules/TransferModule/ReplicaDetailsContent"; import ReplicaExecutionOptions from "@src/components/modules/TransferModule/ReplicaExecutionOptions"; -import ReplicaMigrationOptions from "@src/components/modules/TransferModule/ReplicaMigrationOptions"; +import ReplicaDeploymentOptions from "@src/components/modules/TransferModule/ReplicaDeploymentOptions"; import TransferItemModal from "@src/components/modules/TransferModule/TransferItemModal"; import { ThemePalette } from "@src/components/Theme"; import AlertModal from "@src/components/ui/AlertModal"; @@ -31,7 +31,7 @@ import Modal from "@src/components/ui/Modal"; import { providerTypes } from "@src/constants"; import endpointStore from "@src/stores/EndpointStore"; import instanceStore from "@src/stores/InstanceStore"; -import migrationStore from "@src/stores/MigrationStore"; +import deploymentStore from "@src/stores/DeploymentStore"; import minionPoolStore from "@src/stores/MinionPoolStore"; import networkStore from "@src/stores/NetworkStore"; import providerStore from "@src/stores/ProviderStore"; @@ -57,7 +57,7 @@ type Props = { }; type State = { showOptionsModal: boolean; - showMigrationModal: boolean; + showDeploymentModal: boolean; showEditModal: boolean; showDeleteExecutionConfirmation: boolean; showForceCancelConfirmation: boolean; @@ -69,13 +69,13 @@ type State = { isEditableLoading: boolean; pausePolling: boolean; initialLoading: boolean; - migrating: boolean; + deploying: boolean; }; @observer class ReplicaDetailsPage extends React.Component { state: State = { showOptionsModal: false, - showMigrationModal: false, + showDeploymentModal: false, showEditModal: false, showDeleteExecutionConfirmation: false, showDeleteReplicaConfirmation: false, @@ -87,7 +87,7 @@ class ReplicaDetailsPage extends React.Component { isEditableLoading: true, pausePolling: false, initialLoading: true, - migrating: false, + deploying: false, }; stopPolling: boolean | null = null; @@ -395,12 +395,12 @@ class ReplicaDetailsPage extends React.Component { this.setState({ showDeleteReplicaDisksConfirmation: false }); } - handleCloseMigrationModal() { - this.setState({ showMigrationModal: false, pausePolling: false }); + handleCloseDeploymentModal() { + this.setState({ showDeploymentModal: false, pausePolling: false }); } - handleCreateMigrationClick() { - this.setState({ showMigrationModal: true, pausePolling: true }); + handleCreateDeploymentClick() { + this.setState({ showDeploymentModal: true, pausePolling: true }); } handleReplicaEditClick() { @@ -489,7 +489,7 @@ class ReplicaDetailsPage extends React.Component { showCancelConfirmation: false, }); } - async migrate(opts: { + async deploy(opts: { fields: Field[]; uploadedUserScripts: InstanceScript[]; removedUserScripts: InstanceScript[]; @@ -499,7 +499,7 @@ class ReplicaDetailsPage extends React.Component { if (!replica) { return; } - this.setState({ migrating: true }); + this.setState({ deploying: true }); const { fields, uploadedUserScripts, @@ -507,7 +507,7 @@ class ReplicaDetailsPage extends React.Component { minionPoolMappings, } = opts; try { - const migration = await migrationStore.migrateReplica({ + const deployment = await deploymentStore.deployReplica({ replicaId: replica.id, fields, uploadedUserScripts, @@ -515,9 +515,9 @@ class ReplicaDetailsPage extends React.Component { userScriptData: replica.user_scripts, minionPoolMappings, }); - this.props.history.push(`/migrations/${migration.id}/tasks/`); + this.props.history.push(`/deployments/${deployment.id}/tasks/`); } finally { - this.setState({ migrating: false }); + this.setState({ deploying: false }); } } @@ -658,10 +658,10 @@ class ReplicaDetailsPage extends React.Component { }, }, { - label: "Create Migration", + label: "Create Deployment", color: ThemePalette.primary, action: () => { - this.handleCreateMigrationClick(); + this.handleCreateDeploymentClick(); }, }, { @@ -769,8 +769,8 @@ class ReplicaDetailsPage extends React.Component { onExecuteClick={() => { this.handleExecuteClick(); }} - onCreateMigrationClick={() => { - this.handleCreateMigrationClick(); + onCreateDeploymentClick={() => { + this.handleCreateDeploymentClick(); }} onDeleteReplicaClick={() => { this.handleDeleteReplicaClick(); @@ -813,15 +813,15 @@ class ReplicaDetailsPage extends React.Component { }} /> - {this.state.showMigrationModal ? ( + {this.state.showDeploymentModal ? ( { - this.handleCloseMigrationModal(); + this.handleCloseDeploymentModal(); }} > - @@ -831,11 +831,11 @@ class ReplicaDetailsPage extends React.Component { loadingInstances={instanceStore.loadingInstancesDetails} instances={instanceStore.instancesDetails} onCancelClick={() => { - this.handleCloseMigrationModal(); + this.handleCloseDeploymentModal(); }} - migrating={this.state.migrating} - onMigrateClick={opts => { - this.migrate(opts); + deploying={this.state.deploying} + onDeployClick={opts => { + this.deploy(opts); }} /> From 1ba8b1cac804b8dd185b556a0fc4563f62decd7a Mon Sep 17 00:00:00 2001 From: Nashwan Azhari Date: Fri, 12 Apr 2024 15:01:19 +0300 Subject: [PATCH 05/41] Update Replicas details page for Replica scenarios. Signed-off-by: Nashwan Azhari --- .../ReplicaDetailsContent.tsx | 2 +- .../ReplicaDetailsPage/ReplicaDetailsPage.tsx | 23 +++++++++++++++++-- .../images/live_migration.svg | 15 ++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 src/components/smart/ReplicaDetailsPage/images/live_migration.svg diff --git a/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx b/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx index f3e21df9..3686db7f 100644 --- a/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx +++ b/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx @@ -160,7 +160,7 @@ class ReplicaDetailsContent extends React.Component { disabled={this.isEndpointMissing()} onClick={this.props.onCreateDeploymentClick} > - Create Migration + Deploy Replica diff --git a/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx b/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx index c8df9b03..2104cfb7 100644 --- a/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx +++ b/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx @@ -42,6 +42,7 @@ import configLoader from "@src/utils/Config"; import ObjectUtils from "@src/utils/ObjectUtils"; import replicaImage from "./images/replica.svg"; +import liveMigrationImage from "./images/live_migration.svg"; import type { InstanceScript } from "@src/@types/Instance"; import type { Execution } from "@src/@types/Execution"; @@ -205,6 +206,24 @@ class ReplicaDetailsPage extends React.Component { return this.getLastExecution()?.status; } + getReplicaItemType() { + let item_type = "replica"; + let scenario = this.replica?.scenario; + if (scenario && scenario === "live_migration") { + item_type = "migration"; + } + return item_type; + } + + getReplicaImageType() { + let image = replicaImage; + let scenario = this.replica?.scenario; + if (scenario && scenario === "live_migration") { + image = liveMigrationImage; + } + return image; + } + async loadIsEditable(replicaDetails: ReplicaItemDetails) { const targetEndpointId = replicaDetails.destination_endpoint_id; const sourceEndpointId = replicaDetails.origin_endpoint_id; @@ -704,11 +723,11 @@ class ReplicaDetailsPage extends React.Component { } diff --git a/src/components/smart/ReplicaDetailsPage/images/live_migration.svg b/src/components/smart/ReplicaDetailsPage/images/live_migration.svg new file mode 100644 index 00000000..de30f36a --- /dev/null +++ b/src/components/smart/ReplicaDetailsPage/images/live_migration.svg @@ -0,0 +1,15 @@ + + + + + Created with Sketch. + + + + + + + + + + From 350b8c22990d44a44eb547042bfaa7ccd5906f7a Mon Sep 17 00:00:00 2001 From: Nashwan Azhari Date: Fri, 12 Apr 2024 15:55:01 +0300 Subject: [PATCH 06/41] Add appropriate scenario icon to Replicas/Deployments List pages. Signed-off-by: Nashwan Azhari --- src/@types/MainItem.ts | 4 +++- .../TransferListItem/TransferListItem.tsx | 23 +++++++++++++++++-- .../smart/DeploymentsPage/DeploymentsPage.tsx | 22 +++++++++++++++--- ...ment.svg => live-migration-deployment.svg} | 0 .../images/replica-deployment.svg | 14 +++++++++++ .../smart/MigrationsPage/MigrationsPage.tsx | 2 +- .../smart/ReplicasPage/ReplicasPage.tsx | 15 ++++++++++-- .../ReplicasPage/images/live-migration.svg | 14 +++++++++++ 8 files changed, 85 insertions(+), 9 deletions(-) rename src/components/smart/DeploymentsPage/images/{deployment.svg => live-migration-deployment.svg} (100%) create mode 100644 src/components/smart/DeploymentsPage/images/replica-deployment.svg create mode 100644 src/components/smart/ReplicasPage/images/live-migration.svg diff --git a/src/@types/MainItem.ts b/src/@types/MainItem.ts index 3bd69651..3d5c4c35 100644 --- a/src/@types/MainItem.ts +++ b/src/@types/MainItem.ts @@ -94,6 +94,7 @@ type BaseItem = { export type ReplicaItem = BaseItem & { type: "replica"; + scenario?: string; }; export type UserScriptData = { @@ -118,7 +119,8 @@ export type MigrationItemOptions = MigrationItem & { export type DeploymentItem = BaseItem & { type: "deployment"; - replica_id?: string; + replica_id: string; + replica_scenario: string; }; export type DeploymentItemOptions = DeploymentItem & { diff --git a/src/components/modules/TransferModule/TransferListItem/TransferListItem.tsx b/src/components/modules/TransferModule/TransferListItem/TransferListItem.tsx index 4ad6ee16..bb4332f1 100644 --- a/src/components/modules/TransferModule/TransferListItem/TransferListItem.tsx +++ b/src/components/modules/TransferModule/TransferListItem/TransferListItem.tsx @@ -109,7 +109,7 @@ type Props = { item: TransferItem; onClick: () => void; selected: boolean; - image: string; + getListItemImage: (item: TransferItem) => string; showScheduleIcon?: boolean; endpointType: (endpointId: string) => string; getUserName: (userId: string) => string | undefined; @@ -122,6 +122,25 @@ class TransferListItem extends React.Component { return this.props.item.last_execution_status; } + getReplicaScenarioType() { + let scenario = ""; + switch(this.props.item.type) { + case "replica": + scenario = this.props.item.scenario; + break; + case "deployment": + scenario = this.props.item.replica_scenario; + break; + case "migration": + if (this.props.item.replica_id) { + scenario = "replica"; + } + break; + default: + } + return scenario; + } + renderCreationDate() { return ( { onChange={this.props.onSelectedChange} /> - + <TitleLabel>{getTransferItemTitle(this.props.item)}</TitleLabel> <StatusWrapper> diff --git a/src/components/smart/DeploymentsPage/DeploymentsPage.tsx b/src/components/smart/DeploymentsPage/DeploymentsPage.tsx index 86a2857e..25d238b5 100644 --- a/src/components/smart/DeploymentsPage/DeploymentsPage.tsx +++ b/src/components/smart/DeploymentsPage/DeploymentsPage.tsx @@ -34,7 +34,8 @@ import { DeploymentItem } from "@src/@types/MainItem"; import userStore from "@src/stores/UserStore"; import TransferListItem from "@src/components/modules/TransferModule/TransferListItem"; import deploymentLargeImage from "./images/deployment-large.svg"; -import deploymentItemImage from "./images/deployment.svg"; +import replicaDeploymentItemImage from "./images/replica-deployment.svg"; +import liveMigrationDeploymentItemImage from "./images/live-migration-deployment.svg" const Wrapper = styled.div<any>``; @@ -97,6 +98,19 @@ class DeploymentsPage extends React.Component<{ history: any }, State> { return deployment ? deployment.last_execution_status : ""; } + getDeploymentReplicaType(deploymentId: string): string { + const deployment = deploymentStore.deployments.find(m => m.id === deploymentId); + return deployment ? deployment.replica_scenario_type : ""; + } + + getDeploymentItemImage(item: DeploymentItem): string { + let image = replicaDeploymentItemImage; + if (item.replica_scenario_type === "live_migration") { + image = liveMigrationDeploymentItemImage; + } + return image; + } + handleProjectChange() { endpointStore.getEndpoints({ showLoading: true }); deploymentStore.getDeployments({ showLoading: true }); @@ -286,7 +300,9 @@ class DeploymentsPage extends React.Component<{ history: any }, State> { renderItemComponent={options => ( <TransferListItem {...options} - image={deploymentItemImage} + getListItemImage={item => { + return this.getDeploymentItemImage(item); + }} endpointType={id => { const endpoint = this.getEndpoint(id); if (endpoint) { @@ -305,7 +321,7 @@ class DeploymentsPage extends React.Component<{ history: any }, State> { )} emptyListImage={deploymentLargeImage} emptyListMessage="It seems like you don't have any Deployments in this project." - emptyListExtraMessage="A Coriolis Deployment is a full virtual machine deployment between two cloud endpoints." + emptyListExtraMessage="A Coriolis Deployment is a deployment of a Replica between two cloud endpoints." emptyListButtonLabel="Create a Deployment" onEmptyListButtonClick={() => { this.handleEmptyListButtonClick(); diff --git a/src/components/smart/DeploymentsPage/images/deployment.svg b/src/components/smart/DeploymentsPage/images/live-migration-deployment.svg similarity index 100% rename from src/components/smart/DeploymentsPage/images/deployment.svg rename to src/components/smart/DeploymentsPage/images/live-migration-deployment.svg diff --git a/src/components/smart/DeploymentsPage/images/replica-deployment.svg b/src/components/smart/DeploymentsPage/images/replica-deployment.svg new file mode 100644 index 00000000..bba0576f --- /dev/null +++ b/src/components/smart/DeploymentsPage/images/replica-deployment.svg @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch --> + + <desc>Created with Sketch.</desc> + <defs></defs> + <g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="Icon/Replica/ListItem"> + <path d="M8.56957458,6.7868 L1.17775396,19.2118 C0.399760797,20.5218587 0.00617395647,22.238711 0,23.9601474 C0.00616856622,25.7598885 0.397751035,27.4757452 1.17775396,28.7878 L8.56957458,41.2128 C10.1379331,43.8508 13.9249451,45.9998 17.0183762,45.9998 L30.9815989,45.9998 C34.0790566,45.9998 37.8580154,43.8568 39.4314072,41.2128 L46.8222211,28.7878 C47.5923073,27.4918 47.9859069,25.7978 48,24.0958 L48,23.9038 C47.9859069,22.2028 47.593314,20.5088 46.8222211,19.2118 L39.4314072,6.7868 C37.8707893,4.16350357 34.1181723,2.0237712 31.0334058,2 L16.9664762,2 C13.8798077,2.02374356 10.1331854,4.15854855 8.56957458,6.7868 Z" id="Clip-2" fill="#E7E7E7"></path> + <path d="M8.56957458,6.7868 L1.17775396,19.2118 C0.399760797,20.5218587 0.00617395647,22.238711 0,23.9601474 C0.00616856622,25.7598885 0.397751035,27.4757452 1.17775396,28.7878 L8.56957458,41.2128 C10.1379331,43.8508 13.9249451,45.9998 17.0183762,45.9998 L30.9815989,45.9998 C34.0790566,45.9998 37.8580154,43.8568 39.4314072,41.2128 L46.8222211,28.7878 C47.5923073,27.4918 47.9859069,25.7978 48,24.0958 L48,23.9038 C47.9859069,22.2028 47.593314,20.5088 46.8222211,19.2118 L39.4314072,6.7868 C37.8707893,4.16350357 34.1181723,2.0237712 31.0334058,2 L16.9664762,2 C13.8798077,2.02374356 10.1331854,4.15854855 8.56957458,6.7868 Z" id="Clip-2-Copy" fill="#C8CCD7"></path> + <path d="M36.7023973,20.3355088 C38.7699602,21.3593366 40,22.6393201 40,24.0264809 C40,25.4510933 38.7020802,26.7627454 36.5316929,27.7999023 C37.49947,31.6685095 37.1542044,35.1274862 35.2722447,37.1864514 C34.154915,38.409604 32.5587297,39.0299275 30.6537173,38.9978544 C28.6697177,38.9581238 26.4536262,38.2157103 24.2364965,36.9089592 C22.0194849,38.2155847 19.8034331,38.9579812 17.8194656,38.9980691 C15.9186172,39.0396184 14.3189619,38.4098187 13.2009382,37.1866661 C11.3506272,35.1623262 10.9857601,31.7846666 11.8937801,27.9949384 C9.46860093,26.9295813 8,25.5419447 8,24.0264809 C8,22.5472014 9.39869873,21.1898069 11.7201918,20.1349702 C10.7724027,16.2924123 11.12424,12.8603872 12.9957442,10.8128609 C14.0811502,9.62469714 15.61904,9 17.4504891,9 C17.5046206,9 17.5594461,9.00072893 17.6142716,9.00145787 C19.6595682,9.04241602 21.9515193,9.83014736 24.2368973,11.2130837 C26.5222478,9.83028052 28.8140719,9.04262858 30.8589113,9.00167255 C30.9137368,9.00094362 30.9685623,9.00021468 31.0226938,9.00021468 C32.8541429,9.00021468 34.3920327,9.62491182 35.4774387,10.8130756 C37.381406,12.8961182 37.7125289,16.412107 36.7023973,20.3355088 Z M16.3326618,18.6941659 C14.5670076,19.0617718 13.0027664,19.5521958 11.7201918,20.1349702 C12.0482157,21.4648557 12.5319101,22.8439152 13.162153,24.2317725 C13.9618838,22.3774606 15.026008,20.4993191 16.3326618,18.6941659 Z M32.4299399,29.1886495 C33.9984246,28.817974 35.3865083,28.3471485 36.5316929,27.7999023 C36.2411368,26.6384289 35.8322253,25.440032 35.3110108,24.2315137 C34.5558379,25.8944061 33.5902529,27.5699354 32.4299399,29.1886495 Z M36.7023973,20.3355088 C35.47788,19.7291453 33.9595916,19.2126314 32.2290731,18.8169157 C31.74349,18.1391529 31.223771,17.4713337 30.6710671,16.8185548 C28.6892978,14.4774888 26.4662249,12.5621026 24.2368973,11.2130837 C22.0073631,12.562114 19.7839902,14.4775793 17.8021158,16.8187695 C17.282867,17.4320357 16.7927309,18.0585765 16.3326618,18.6941659 C18.610541,18.2199157 21.2236459,17.9500803 23.9996546,17.9500803 C27.0069362,17.9500803 29.8231203,18.2667492 32.2290731,18.8169157 C33.4953092,20.5842914 34.5294247,22.4192823 35.3110108,24.2315137 C35.9096203,22.9133745 36.3760197,21.6031755 36.7023973,20.3355088 Z M24.2364965,36.9089592 C26.5345195,35.5545886 28.8335736,33.5940009 30.8762612,31.1809721 C31.4273236,30.5301319 31.9455968,29.8643406 32.4299399,29.1886495 C29.9804695,29.767526 27.0910293,30.1021526 23.9996546,30.1021526 C21.1429148,30.1021526 18.4586915,29.8163945 16.1347824,29.3161987 C14.9336377,27.6581307 13.9371083,25.9383007 13.162153,24.2317725 C12.6117529,25.5079689 12.1865862,26.772878 11.8937801,27.9949384 C13.0938844,28.5221322 14.5282338,28.9704068 16.1347824,29.3161987 C16.5925274,29.9480728 17.0799887,30.5709772 17.5962278,31.1807575 C19.6392366,33.5937486 21.9384243,35.5545015 24.2364965,36.9089592 Z" id="Combined-Shape" stroke="#F91661" stroke-width="1.5"></path> + </g> + </g> +</svg> diff --git a/src/components/smart/MigrationsPage/MigrationsPage.tsx b/src/components/smart/MigrationsPage/MigrationsPage.tsx index 858972c7..472273e2 100644 --- a/src/components/smart/MigrationsPage/MigrationsPage.tsx +++ b/src/components/smart/MigrationsPage/MigrationsPage.tsx @@ -286,7 +286,7 @@ class MigrationsPage extends React.Component<{ history: any }, State> { renderItemComponent={options => ( <TransferListItem {...options} - image={migrationItemImage} + getListItemImage={item => migrationItemImage} endpointType={id => { const endpoint = this.getEndpoint(id); if (endpoint) { diff --git a/src/components/smart/ReplicasPage/ReplicasPage.tsx b/src/components/smart/ReplicasPage/ReplicasPage.tsx index 823f3918..bf6f32d7 100644 --- a/src/components/smart/ReplicasPage/ReplicasPage.tsx +++ b/src/components/smart/ReplicasPage/ReplicasPage.tsx @@ -45,6 +45,7 @@ import userStore from "@src/stores/UserStore"; import TransferListItem from "@src/components/modules/TransferModule/TransferListItem"; import replicaLargeImage from "./images/replica-large.svg"; import replicaItemImage from "./images/replica.svg"; +import liveMigrationItemImage from "./images/live-migration.svg"; const Wrapper = styled.div<any>``; @@ -118,6 +119,14 @@ class ReplicasPage extends React.Component<{ history: any }, State> { return replica?.last_execution_status || ""; } + getReplicaItemImage(item: ReplicaItem): string { + let image = replicaItemImage; + if (item.scenario === "live_migration") { + image = liveMigrationItemImage; + } + return image; + } + handleProjectChange() { replicaStore.getReplicas(); endpointStore.getEndpoints({ showLoading: true }); @@ -441,7 +450,9 @@ class ReplicasPage extends React.Component<{ history: any }, State> { renderItemComponent={options => ( <TransferListItem {...options} - image={replicaItemImage} + getListItemImage={item => { + return this.getReplicaItemImage(item); + }} showScheduleIcon={this.isReplicaScheduled(options.item.id)} endpointType={id => { const endpoint = this.getEndpoint(id); @@ -461,7 +472,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> { )} emptyListImage={replicaLargeImage} emptyListMessage="It seems like you don't have any Replicas in this project." - emptyListExtraMessage="The Coriolis Replica is obtained by replicating incrementally the virtual machines data from the source cloud endpoint to the target." + emptyListExtraMessage="A Coriolis Replica is obtained by replicating incrementally the virtual machines data from the source cloud endpoint to the target." emptyListButtonLabel="Create a Replica" onEmptyListButtonClick={() => { this.handleEmptyListButtonClick(); diff --git a/src/components/smart/ReplicasPage/images/live-migration.svg b/src/components/smart/ReplicasPage/images/live-migration.svg new file mode 100644 index 00000000..786ae349 --- /dev/null +++ b/src/components/smart/ReplicasPage/images/live-migration.svg @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch --> + + <desc>Created with Sketch.</desc> + <defs></defs> + <g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="Icon/Migration/ListItem"> + <path d="M8.56957458,6.7868 L1.17775396,19.2118 C0.399760797,20.5218587 0.00617395647,22.238711 0,23.9601474 C0.00616856622,25.7598885 0.397751035,27.4757452 1.17775396,28.7878 L8.56957458,41.2128 C10.1379331,43.8508 13.9249451,45.9998 17.0183762,45.9998 L30.9815989,45.9998 C34.0790566,45.9998 37.8580154,43.8568 39.4314072,41.2128 L46.8222211,28.7878 C47.5923073,27.4918 47.9859069,25.7978 48,24.0958 L48,23.9038 C47.9859069,22.2028 47.593314,20.5088 46.8222211,19.2118 L39.4314072,6.7868 C37.8707893,4.16350357 34.1181723,2.0237712 31.0334058,2 L16.9664762,2 C13.8798077,2.02374356 10.1331854,4.15854855 8.56957458,6.7868 Z" id="Clip-2" fill="#E7E7E7"></path> + <path d="M8.56957458,6.7868 L1.17775396,19.2118 C0.399760797,20.5218587 0.00617395647,22.238711 0,23.9601474 C0.00616856622,25.7598885 0.397751035,27.4757452 1.17775396,28.7878 L8.56957458,41.2128 C10.1379331,43.8508 13.9249451,45.9998 17.0183762,45.9998 L30.9815989,45.9998 C34.0790566,45.9998 37.8580154,43.8568 39.4314072,41.2128 L46.8222211,28.7878 C47.5923073,27.4918 47.9859069,25.7978 48,24.0958 L48,23.9038 C47.9859069,22.2028 47.593314,20.5088 46.8222211,19.2118 L39.4314072,6.7868 C37.8707893,4.16350357 34.1181723,2.0237712 31.0334058,2 L16.9664762,2 C13.8798077,2.02374356 10.1331854,4.15854855 8.56957458,6.7868 Z" id="Clip-2-Copy" fill="#C8CCD7"></path> + <path d="M36.7023973,20.3355088 C38.7699602,21.3593366 40,22.6393201 40,24.0264809 C40,25.4510933 38.7020802,26.7627454 36.5316929,27.7999023 C37.49947,31.6685095 37.1542044,35.1274862 35.2722447,37.1864514 C34.154915,38.409604 32.5587297,39.0299275 30.6537173,38.9978544 C28.6697177,38.9581238 26.4536262,38.2157103 24.2364965,36.9089592 C22.0194849,38.2155847 19.8034331,38.9579812 17.8194656,38.9980691 C15.9186172,39.0396184 14.3189619,38.4098187 13.2009382,37.1866661 C11.3506272,35.1623262 10.9857601,31.7846666 11.8937801,27.9949384 C9.46860093,26.9295813 8,25.5419447 8,24.0264809 C8,22.5472014 9.39869873,21.1898069 11.7201918,20.1349702 C10.7724027,16.2924123 11.12424,12.8603872 12.9957442,10.8128609 C14.0811502,9.62469714 15.61904,9 17.4504891,9 C17.5046206,9 17.5594461,9.00072893 17.6142716,9.00145787 C19.6595682,9.04241602 21.9515193,9.83014736 24.2368973,11.2130837 C26.5222478,9.83028052 28.8140719,9.04262858 30.8589113,9.00167255 C30.9137368,9.00094362 30.9685623,9.00021468 31.0226938,9.00021468 C32.8541429,9.00021468 34.3920327,9.62491182 35.4774387,10.8130756 C37.381406,12.8961182 37.7125289,16.412107 36.7023973,20.3355088 Z M16.3326618,18.6941659 C14.5670076,19.0617718 13.0027664,19.5521958 11.7201918,20.1349702 C12.0482157,21.4648557 12.5319101,22.8439152 13.162153,24.2317725 C13.9618838,22.3774606 15.026008,20.4993191 16.3326618,18.6941659 Z M32.4299399,29.1886495 C33.9984246,28.817974 35.3865083,28.3471485 36.5316929,27.7999023 C36.2411368,26.6384289 35.8322253,25.440032 35.3110108,24.2315137 C34.5558379,25.8944061 33.5902529,27.5699354 32.4299399,29.1886495 Z M36.7023973,20.3355088 C35.47788,19.7291453 33.9595916,19.2126314 32.2290731,18.8169157 C31.74349,18.1391529 31.223771,17.4713337 30.6710671,16.8185548 C28.6892978,14.4774888 26.4662249,12.5621026 24.2368973,11.2130837 C22.0073631,12.562114 19.7839902,14.4775793 17.8021158,16.8187695 C17.282867,17.4320357 16.7927309,18.0585765 16.3326618,18.6941659 C18.610541,18.2199157 21.2236459,17.9500803 23.9996546,17.9500803 C27.0069362,17.9500803 29.8231203,18.2667492 32.2290731,18.8169157 C33.4953092,20.5842914 34.5294247,22.4192823 35.3110108,24.2315137 C35.9096203,22.9133745 36.3760197,21.6031755 36.7023973,20.3355088 Z M24.2364965,36.9089592 C26.5345195,35.5545886 28.8335736,33.5940009 30.8762612,31.1809721 C31.4273236,30.5301319 31.9455968,29.8643406 32.4299399,29.1886495 C29.9804695,29.767526 27.0910293,30.1021526 23.9996546,30.1021526 C21.1429148,30.1021526 18.4586915,29.8163945 16.1347824,29.3161987 C14.9336377,27.6581307 13.9371083,25.9383007 13.162153,24.2317725 C12.6117529,25.5079689 12.1865862,26.772878 11.8937801,27.9949384 C13.0938844,28.5221322 14.5282338,28.9704068 16.1347824,29.3161987 C16.5925274,29.9480728 17.0799887,30.5709772 17.5962278,31.1807575 C19.6392366,33.5937486 21.9384243,35.5545015 24.2364965,36.9089592 Z" id="Combined-Shape" stroke="#0044CA" stroke-width="1.5" stroke-linecap="round"></path> + </g> + </g> +</svg> From 48d1fc834d7f0e2cda361612249259983926a2d1 Mon Sep 17 00:00:00 2001 From: Nashwan Azhari <nazhari@cloudbasesolutions.com> Date: Fri, 12 Apr 2024 17:12:55 +0300 Subject: [PATCH 07/41] Make Replica/Deployment Details content header pill scenario-appropriate. Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com> --- .../DeploymentDetailsPage.tsx | 12 +++++++++++- .../ReplicaDetailsPage/ReplicaDetailsPage.tsx | 18 ++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx b/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx index 60bd371b..f1e62229 100644 --- a/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx +++ b/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx @@ -101,6 +101,15 @@ class DeploymentDetailsPage extends React.Component<Props, State> { return deploymentStore.deploymentDetails?.last_execution_status; } + getReplicaTypePillShouldRed(): bool { + let should_red = true; + let scenario = this.deployment?.replica_scenario_type; + if (scenario && scenario === "live_migration") { + should_red = false; + } + return should_red; + } + async loadDeploymentAndPollData() { const loadDeployment = async () => { await endpointStore.getEndpoints({ showLoading: true }); @@ -484,7 +493,8 @@ class DeploymentDetailsPage extends React.Component<Props, State> { backLink="/deployments" typeImage={deploymentImage} dropdownActions={dropdownActions} - primaryInfoPill + alertInfoPill={this.getReplicaTypePillShouldRed()} + primaryInfoPill={!this.getReplicaTypePillShouldRed()} /> } contentComponent={ diff --git a/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx b/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx index 2104cfb7..cb38dbee 100644 --- a/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx +++ b/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx @@ -206,7 +206,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> { return this.getLastExecution()?.status; } - getReplicaItemType() { + getReplicaItemType(): string { let item_type = "replica"; let scenario = this.replica?.scenario; if (scenario && scenario === "live_migration") { @@ -215,7 +215,16 @@ class ReplicaDetailsPage extends React.Component<Props, State> { return item_type; } - getReplicaImageType() { + getReplicaTypePillShouldRed(): bool { + let should_red = true; + let scenario = this.replica?.scenario; + if (scenario && scenario === "live_migration") { + should_red = false; + } + return should_red; + } + + getReplicaScenarioTypeImage(): string { let image = replicaImage; let scenario = this.replica?.scenario; if (scenario && scenario === "live_migration") { @@ -727,8 +736,9 @@ class ReplicaDetailsPage extends React.Component<Props, State> { itemDescription={replica?.description} dropdownActions={dropdownActions} backLink="/replicas" - typeImage={this.getReplicaImageType()} - alertInfoPill + typeImage={this.getReplicaScenarioTypeImage()} + alertInfoPill={this.getReplicaTypePillShouldRed()} + primaryInfoPill={!this.getReplicaTypePillShouldRed()} /> } contentComponent={ From bab869d46f3e8337dd30d525ed6617f16f49663b Mon Sep 17 00:00:00 2001 From: Nashwan Azhari <nazhari@cloudbasesolutions.com> Date: Fri, 12 Apr 2024 17:16:49 +0300 Subject: [PATCH 08/41] Make DeploymentDetails content header pill text scenario-appropriate. Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com> --- .../DeploymentDetailsPage/DeploymentDetailsPage.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx b/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx index f1e62229..0bb73b73 100644 --- a/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx +++ b/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx @@ -101,6 +101,15 @@ class DeploymentDetailsPage extends React.Component<Props, State> { return deploymentStore.deploymentDetails?.last_execution_status; } + getDeploymentReplicaScenarioItemType() { + let item_type = "replica"; + let scenario = this.deployment?.replica_scenario_type; + if (scenario && scenario === "live_migration") { + item_type = "migration"; + } + return item_type; + } + getReplicaTypePillShouldRed(): bool { let should_red = true; let scenario = this.deployment?.replica_scenario_type; @@ -488,7 +497,7 @@ class DeploymentDetailsPage extends React.Component<Props, State> { deploymentStore.deploymentDetails?.last_execution_status } itemTitle={getTransferItemTitle(deploymentStore.deploymentDetails)} - itemType="deployment" + itemType={this.getDeploymentReplicaScenarioItemType()} itemDescription={deploymentStore.deploymentDetails?.description} backLink="/deployments" typeImage={deploymentImage} From d5a6d435c4d3820d39e8660090f559710c671b00 Mon Sep 17 00:00:00 2001 From: Nashwan Azhari <nazhari@cloudbasesolutions.com> Date: Fri, 12 Apr 2024 17:41:37 +0300 Subject: [PATCH 09/41] Make DeploymentDetails header icon replica-scenario-appropriate. Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com> --- .../DeploymentDetailsPage.tsx | 16 +++++++++++++--- ...loyment.svg => live-migration-deployment.svg} | 4 ++-- .../images/replica-deployment.svg | 15 +++++++++++++++ 3 files changed, 30 insertions(+), 5 deletions(-) rename src/components/smart/DeploymentDetailsPage/images/{deployment.svg => live-migration-deployment.svg} (95%) create mode 100644 src/components/smart/DeploymentDetailsPage/images/replica-deployment.svg diff --git a/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx b/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx index 0bb73b73..22478d4a 100644 --- a/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx +++ b/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx @@ -37,7 +37,8 @@ import providerStore from "@src/stores/ProviderStore"; import userStore from "@src/stores/UserStore"; import configLoader from "@src/utils/Config"; -import deploymentImage from "./images/deployment.svg"; +import replicaDeploymentImage from "./images/replica-deployment.svg"; +import liveMigrationDeploymentImage from "./images/live-migration-deployment.svg" import type { Field } from "@src/@types/Field"; import type { InstanceScript } from "@src/@types/Instance"; @@ -101,7 +102,7 @@ class DeploymentDetailsPage extends React.Component<Props, State> { return deploymentStore.deploymentDetails?.last_execution_status; } - getDeploymentReplicaScenarioItemType() { + getDeploymentReplicaScenarioItemType(): string { let item_type = "replica"; let scenario = this.deployment?.replica_scenario_type; if (scenario && scenario === "live_migration") { @@ -119,6 +120,15 @@ class DeploymentDetailsPage extends React.Component<Props, State> { return should_red; } + getDeploymentScenarioTypeImage(): string { + let image = replicaDeploymentImage; + let scenario = this.deployment?.replica_scenario_type; + if (scenario && scenario === "live_migration") { + image = liveMigrationDeploymentImage; + } + return image; + } + async loadDeploymentAndPollData() { const loadDeployment = async () => { await endpointStore.getEndpoints({ showLoading: true }); @@ -500,7 +510,7 @@ class DeploymentDetailsPage extends React.Component<Props, State> { itemType={this.getDeploymentReplicaScenarioItemType()} itemDescription={deploymentStore.deploymentDetails?.description} backLink="/deployments" - typeImage={deploymentImage} + typeImage={this.getDeploymentScenarioTypeImage()} dropdownActions={dropdownActions} alertInfoPill={this.getReplicaTypePillShouldRed()} primaryInfoPill={!this.getReplicaTypePillShouldRed()} diff --git a/src/components/smart/DeploymentDetailsPage/images/deployment.svg b/src/components/smart/DeploymentDetailsPage/images/live-migration-deployment.svg similarity index 95% rename from src/components/smart/DeploymentDetailsPage/images/deployment.svg rename to src/components/smart/DeploymentDetailsPage/images/live-migration-deployment.svg index 7f05dcd4..de30f36a 100644 --- a/src/components/smart/DeploymentDetailsPage/images/deployment.svg +++ b/src/components/smart/DeploymentDetailsPage/images/live-migration-deployment.svg @@ -5,9 +5,9 @@ <desc>Created with Sketch.</desc> <defs></defs> <g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> - <g id="Icon/Deployment/PageItem"> + <g id="Icon/Migration/PageItem"> <path d="M22.6219682,2 C18.5064103,2.03237774 13.5109139,4.94348867 11.4260994,8.52748422 L1.57033861,25.470743 C0.533014396,27.2571949 0.00823194196,29.5983677 0,31.9457916 L1.40746491e-17,32.0552922 C0.00822475496,34.3999953 0.530334713,36.7398104 1.57033861,38.5289842 L11.4260994,55.4722431 C13.5172441,59.0695321 18.5665935,62 22.6911683,62 L41.3087985,62 C45.4387421,62 50.4773539,59.077714 52.5752096,55.4722431 L62.4296282,38.5289842 C63.4564098,36.7617035 63.9812092,34.451693 64,32.1307733 L64,31.8689539 C63.9812092,29.549398 63.457752,27.2393875 62.4296282,25.470743 L52.5752096,8.52748422 C50.4943857,4.95024555 45.4908964,2.03241542 41.3778743,2 L22.6219682,2 L22.6219682,2 Z" id="Clip-2" fill="#FFFFFF"></path> - <g id="Icon/Deployment/48" stroke="#979797" transform="translate(8.000000, 8.000000)" stroke-width="1.5" stroke-linecap="round"> + <g id="Icon/Migration/48" stroke="#979797" transform="translate(8.000000, 8.000000)" stroke-width="1.5" stroke-linecap="round"> <path d="M42.259696,19.1140118 C45.2318178,20.4791155 47,22.1857601 47,24.0353079 C47,25.934791 45.1342402,27.6836605 42.0143086,29.0665364 C43.4054881,34.2246793 42.9091688,38.8366483 40.2038517,41.5819352 C38.5976903,43.2128053 36.303174,44.0399034 33.5647186,43.9971392 C30.7127191,43.944165 27.5270877,42.9542804 24.3399638,41.2119456 C21.1530096,42.9541129 17.9674351,43.9439749 15.1154818,43.9974255 C12.3830122,44.0528245 10.0835078,43.2130916 8.47634872,41.5822214 C5.81652658,38.8831017 5.29203012,34.3795555 6.59730895,29.3265845 C3.11111383,27.9061084 1,26.0559263 1,24.0353079 C1,22.0629353 3.01062942,20.2530759 6.34777566,18.8466269 C4.98532884,13.7232164 5.49109498,9.14718292 8.18138231,6.41714784 C9.74165342,4.83292951 11.95237,4 14.5850781,4 C14.6628921,4 14.7417038,4.00097191 14.8205154,4.00194383 C17.7606292,4.05655469 21.055309,5.10686315 24.3405399,6.95077829 C27.6257312,5.1070407 30.9202283,4.05683811 33.859685,4.00223007 C33.9384967,4.00125816 34.0173083,4.00028625 34.0951223,4.00028625 C36.7278304,4.00028625 38.938547,4.83321576 40.4988181,6.41743409 C43.2357711,9.19482422 43.7117603,13.8828094 42.259696,19.1140118 Z M12.9782013,16.9255545 C10.4400735,17.4156957 8.19147668,18.0695944 6.34777566,18.8466269 C6.81931007,20.6198076 7.51462075,22.4585536 8.420595,24.30903 C9.57020798,21.8366141 11.0998865,19.3324255 12.9782013,16.9255545 Z M36.1180386,30.9181994 C38.3727354,30.4239653 40.3681057,29.796198 42.0143086,29.0665364 C41.5966342,27.5179052 41.0088238,25.9200427 40.2595781,24.3086849 C39.1740169,26.5258748 37.7859886,28.7599138 36.1180386,30.9181994 Z M42.259696,19.1140118 C40.4994525,18.3055271 38.3169129,17.6168419 35.8292926,17.089221 C35.1312669,16.1855372 34.3841709,15.2951116 33.589659,14.4247398 C30.7408656,11.3033183 27.5451983,8.74947008 24.3405399,6.95077829 C21.1355845,8.74948537 17.939486,11.303439 15.0905414,14.425026 C14.3441214,15.2427142 13.6395507,16.078102 12.9782013,16.9255545 C16.2526527,16.2932209 20.008991,15.9334404 23.9995035,15.9334404 C28.3224709,15.9334404 32.3707354,16.3556657 35.8292926,17.089221 C37.649507,19.4457218 39.136048,21.8923764 40.2595781,24.3086849 C41.1200793,22.5511659 41.7905283,20.804234 42.259696,19.1140118 Z M24.3399638,41.2119456 C27.6433717,39.4061182 30.9482621,36.7920013 33.8846254,33.5746295 C34.6767777,32.7068426 35.4217954,31.8191207 36.1180386,30.9181994 C32.5969249,31.6900346 28.4433546,32.1362035 23.9995035,32.1362035 C19.89294,32.1362035 16.034369,31.7551926 12.6937498,31.0882649 C10.9671041,28.8775076 9.53459323,26.5844009 8.420595,24.30903 C7.62939485,26.0106251 7.01821764,27.6971707 6.59730895,29.3265845 C8.32245878,30.0295096 10.3843361,30.6272091 12.6937498,31.0882649 C13.3517582,31.9307637 14.0524838,32.761303 14.7945774,33.5743433 C17.7314026,36.7916648 21.0364849,39.406002 24.3399638,41.2119456 Z" id="Combined-Shape" stroke="#0044CA"></path> </g> </g> diff --git a/src/components/smart/DeploymentDetailsPage/images/replica-deployment.svg b/src/components/smart/DeploymentDetailsPage/images/replica-deployment.svg new file mode 100644 index 00000000..b3261a3f --- /dev/null +++ b/src/components/smart/DeploymentDetailsPage/images/replica-deployment.svg @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="64px" height="64px" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch --> + + <desc>Created with Sketch.</desc> + <defs></defs> + <g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="Icon/Replica/PageItem"> + <path d="M22.6219682,2 C18.5064103,2.03237774 13.5109139,4.94348867 11.4260994,8.52748422 L1.57033861,25.470743 C0.533014396,27.2571949 0.00823194196,29.5983677 0,31.9457916 L1.40746491e-17,32.0552922 C0.00822475496,34.3999953 0.530334713,36.7398104 1.57033861,38.5289842 L11.4260994,55.4722431 C13.5172441,59.0695321 18.5665935,62 22.6911683,62 L41.3087985,62 C45.4387421,62 50.4773539,59.077714 52.5752096,55.4722431 L62.4296282,38.5289842 C63.4564098,36.7617035 63.9812092,34.451693 64,32.1307733 L64,31.8689539 C63.9812092,29.549398 63.457752,27.2393875 62.4296282,25.470743 L52.5752096,8.52748422 C50.4943857,4.95024555 45.4908964,2.03241542 41.3778743,2 L22.6219682,2 L22.6219682,2 Z" id="Clip-2" fill="#FFFFFF"></path> + <g id="Icon/Replica/48" stroke="#979797" transform="translate(8.000000, 8.000000)" stroke-width="1.5"> + <path d="M42.259696,19.1140118 C45.2318178,20.4791155 47,22.1857601 47,24.0353079 C47,25.934791 45.1342402,27.6836605 42.0143086,29.0665364 C43.4054881,34.2246793 42.9091688,38.8366483 40.2038517,41.5819352 C38.5976903,43.2128053 36.303174,44.0399034 33.5647186,43.9971392 C30.7127191,43.944165 27.5270877,42.9542804 24.3399638,41.2119456 C21.1530096,42.9541129 17.9674351,43.9439749 15.1154818,43.9974255 C12.3830122,44.0528245 10.0835078,43.2130916 8.47634872,41.5822214 C5.81652658,38.8831017 5.29203012,34.3795555 6.59730895,29.3265845 C3.11111383,27.9061084 1,26.0559263 1,24.0353079 C1,22.0629353 3.01062942,20.2530759 6.34777566,18.8466269 C4.98532884,13.7232164 5.49109498,9.14718292 8.18138231,6.41714784 C9.74165342,4.83292951 11.95237,4 14.5850781,4 C14.6628921,4 14.7417038,4.00097191 14.8205154,4.00194383 C17.7606292,4.05655469 21.055309,5.10686315 24.3405399,6.95077829 C27.6257312,5.1070407 30.9202283,4.05683811 33.859685,4.00223007 C33.9384967,4.00125816 34.0173083,4.00028625 34.0951223,4.00028625 C36.7278304,4.00028625 38.938547,4.83321576 40.4988181,6.41743409 C43.2357711,9.19482422 43.7117603,13.8828094 42.259696,19.1140118 Z M12.9782013,16.9255545 C10.4400735,17.4156957 8.19147668,18.0695944 6.34777566,18.8466269 C6.81931007,20.6198076 7.51462075,22.4585536 8.420595,24.30903 C9.57020798,21.8366141 11.0998865,19.3324255 12.9782013,16.9255545 Z M36.1180386,30.9181994 C38.3727354,30.4239653 40.3681057,29.796198 42.0143086,29.0665364 C41.5966342,27.5179052 41.0088238,25.9200427 40.2595781,24.3086849 C39.1740169,26.5258748 37.7859886,28.7599138 36.1180386,30.9181994 Z M42.259696,19.1140118 C40.4994525,18.3055271 38.3169129,17.6168419 35.8292926,17.089221 C35.1312669,16.1855372 34.3841709,15.2951116 33.589659,14.4247398 C30.7408656,11.3033183 27.5451983,8.74947008 24.3405399,6.95077829 C21.1355845,8.74948537 17.939486,11.303439 15.0905414,14.425026 C14.3441214,15.2427142 13.6395507,16.078102 12.9782013,16.9255545 C16.2526527,16.2932209 20.008991,15.9334404 23.9995035,15.9334404 C28.3224709,15.9334404 32.3707354,16.3556657 35.8292926,17.089221 C37.649507,19.4457218 39.136048,21.8923764 40.2595781,24.3086849 C41.1200793,22.5511659 41.7905283,20.804234 42.259696,19.1140118 Z M24.3399638,41.2119456 C27.6433717,39.4061182 30.9482621,36.7920013 33.8846254,33.5746295 C34.6767777,32.7068426 35.4217954,31.8191207 36.1180386,30.9181994 C32.5969249,31.6900346 28.4433546,32.1362035 23.9995035,32.1362035 C19.89294,32.1362035 16.034369,31.7551926 12.6937498,31.0882649 C10.9671041,28.8775076 9.53459323,26.5844009 8.420595,24.30903 C7.62939485,26.0106251 7.01821764,27.6971707 6.59730895,29.3265845 C8.32245878,30.0295096 10.3843361,30.6272091 12.6937498,31.0882649 C13.3517582,31.9307637 14.0524838,32.761303 14.7945774,33.5743433 C17.7314026,36.7916648 21.0364849,39.406002 24.3399638,41.2119456 Z" id="Combined-Shape" stroke="#F91661"></path> + </g> + </g> + </g> +</svg> From 24710e545e30460e7dcc6e206bc222925ea355c5 Mon Sep 17 00:00:00 2001 From: Nashwan Azhari <nazhari@cloudbasesolutions.com> Date: Fri, 12 Apr 2024 17:49:30 +0300 Subject: [PATCH 10/41] testing Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com> --- .../smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx b/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx index 22478d4a..b3b7e6ef 100644 --- a/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx +++ b/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx @@ -104,6 +104,7 @@ class DeploymentDetailsPage extends React.Component<Props, State> { getDeploymentReplicaScenarioItemType(): string { let item_type = "replica"; + console.log(this.deployment); let scenario = this.deployment?.replica_scenario_type; if (scenario && scenario === "live_migration") { item_type = "migration"; @@ -113,6 +114,7 @@ class DeploymentDetailsPage extends React.Component<Props, State> { getReplicaTypePillShouldRed(): bool { let should_red = true; + console.log(this.deployment); let scenario = this.deployment?.replica_scenario_type; if (scenario && scenario === "live_migration") { should_red = false; @@ -122,6 +124,7 @@ class DeploymentDetailsPage extends React.Component<Props, State> { getDeploymentScenarioTypeImage(): string { let image = replicaDeploymentImage; + console.log(this.deployment); let scenario = this.deployment?.replica_scenario_type; if (scenario && scenario === "live_migration") { image = liveMigrationDeploymentImage; From 0354753c5c5cc036215284036fc23e6df714ef55 Mon Sep 17 00:00:00 2001 From: Nashwan Azhari <nazhari@cloudbasesolutions.com> Date: Thu, 18 Apr 2024 15:44:35 +0300 Subject: [PATCH 11/41] Fix DeploymentDetails page. Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com> --- .../DeploymentDetailsPage.tsx | 43 +++++++++++-------- src/sources/DeploymentSource.ts | 2 +- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx b/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx index b3b7e6ef..45ef99e2 100644 --- a/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx +++ b/src/components/smart/DeploymentDetailsPage/DeploymentDetailsPage.tsx @@ -16,7 +16,7 @@ import { observer } from "mobx-react"; import React from "react"; import styled from "styled-components"; -import { getTransferItemTitle } from "@src/@types/MainItem"; +import { getTransferItemTitle, DeploymentItemDetails } from "@src/@types/MainItem"; import DetailsContentHeader from "@src/components/modules/DetailsModule/DetailsContentHeader"; import DetailsPageHeader from "@src/components/modules/DetailsModule/DetailsPageHeader"; import DetailsTemplate from "@src/components/modules/TemplateModule/DetailsTemplate"; @@ -102,30 +102,27 @@ class DeploymentDetailsPage extends React.Component<Props, State> { return deploymentStore.deploymentDetails?.last_execution_status; } - getDeploymentReplicaScenarioItemType(): string { + getDeploymentReplicaScenarioItemType(details: DeploymentItemDetails | null): string { let item_type = "replica"; - console.log(this.deployment); - let scenario = this.deployment?.replica_scenario_type; + let scenario = details?.replica_scenario_type; if (scenario && scenario === "live_migration") { item_type = "migration"; } return item_type; } - getReplicaTypePillShouldRed(): bool { + getReplicaTypePillShouldRed(details: DeploymentItemDetails | null): bool { let should_red = true; - console.log(this.deployment); - let scenario = this.deployment?.replica_scenario_type; + let scenario = details?.replica_scenario_type; if (scenario && scenario === "live_migration") { should_red = false; } return should_red; } - getDeploymentScenarioTypeImage(): string { + getDeploymentScenarioTypeImage(details: DeploymentItemDetails | null): string { let image = replicaDeploymentImage; - console.log(this.deployment); - let scenario = this.deployment?.replica_scenario_type; + let scenario = details?.replica_scenario_type; if (scenario && scenario === "live_migration") { image = liveMigrationDeploymentImage; } @@ -510,13 +507,25 @@ class DeploymentDetailsPage extends React.Component<Props, State> { deploymentStore.deploymentDetails?.last_execution_status } itemTitle={getTransferItemTitle(deploymentStore.deploymentDetails)} - itemType={this.getDeploymentReplicaScenarioItemType()} + itemType={ + this.getDeploymentReplicaScenarioItemType( + deploymentStore.deploymentDetails) + } itemDescription={deploymentStore.deploymentDetails?.description} backLink="/deployments" - typeImage={this.getDeploymentScenarioTypeImage()} + typeImage={ + this.getDeploymentScenarioTypeImage( + deploymentStore.deploymentDetails) + } dropdownActions={dropdownActions} - alertInfoPill={this.getReplicaTypePillShouldRed()} - primaryInfoPill={!this.getReplicaTypePillShouldRed()} + alertInfoPill={ + this.getReplicaTypePillShouldRed( + deploymentStore.deploymentDetails) + } + primaryInfoPill={ + !this.getReplicaTypePillShouldRed( + deploymentStore.deploymentDetails) + } /> } contentComponent={ @@ -561,7 +570,7 @@ class DeploymentDetailsPage extends React.Component<Props, State> { <AlertModal isOpen={this.state.showDeleteDeploymentConfirmation} title="Delete Deployment?" - message="Are you sure you want to delete this deployment?" + message="Are you sure you want to delete this Deployment?" extraMessage="Deleting a Coriolis Deployment is permanent!" onConfirmation={() => { this.handleDeleteDeploymentConfirmation(); @@ -573,7 +582,7 @@ class DeploymentDetailsPage extends React.Component<Props, State> { <AlertModal isOpen={this.state.showCancelConfirmation} title="Cancel Deployment?" - message="Are you sure you want to cancel the deployment?" + message="Are you sure you want to cancel the Deployment?" extraMessage=" " onConfirmation={() => { this.handleCancelConfirmation(); @@ -600,7 +609,7 @@ Note that this may lead to scheduled cleanup tasks being forcibly skipped, and t {this.state.showFromReplicaModal ? ( <Modal isOpen - title="Recreate Deployment from Replica" + title="Recreate Deployment" onRequestClose={() => { this.handleCloseFromReplicaModal(); }} diff --git a/src/sources/DeploymentSource.ts b/src/sources/DeploymentSource.ts index 85d752aa..fd2fffb1 100644 --- a/src/sources/DeploymentSource.ts +++ b/src/sources/DeploymentSource.ts @@ -307,7 +307,7 @@ class DeploymentSource { return deploymentId; } - async migrateReplica(opts: { + async deployReplica(opts: { replicaId: string; options: Field[]; uploadedUserScripts: InstanceScript[]; From 0bb010eb572aa46e7a8e95db8ee16da4ffe815ba Mon Sep 17 00:00:00 2001 From: Nashwan Azhari <nazhari@cloudbasesolutions.com> Date: Fri, 19 Apr 2024 16:41:35 +0300 Subject: [PATCH 12/41] Debug RHEV Migration Recreation options issues. Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com> --- src/plugins/default/OptionsSchemaPlugin.ts | 3 +++ src/plugins/rhev/OptionsSchemaPlugin.ts | 13 ++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/plugins/default/OptionsSchemaPlugin.ts b/src/plugins/default/OptionsSchemaPlugin.ts index e8b80ca6..60a83fc5 100644 --- a/src/plugins/default/OptionsSchemaPlugin.ts +++ b/src/plugins/default/OptionsSchemaPlugin.ts @@ -175,6 +175,9 @@ export const defaultGetMigrationImageMap = ( oldOptions: any, migrationImageMapFieldName: string ) => { + + console.log("defaultGetMigrationImageMap called with old env: ") + console.log(oldOptions) const env: any = {}; const usableOptions = options; if (!usableOptions) { diff --git a/src/plugins/rhev/OptionsSchemaPlugin.ts b/src/plugins/rhev/OptionsSchemaPlugin.ts index faa4cf69..fd979cba 100644 --- a/src/plugins/rhev/OptionsSchemaPlugin.ts +++ b/src/plugins/rhev/OptionsSchemaPlugin.ts @@ -87,13 +87,16 @@ export default class OptionsSchemaParser extends OptionsSchemaPluginBase { options: { [prop: string]: any } | null, oldOptions?: any ) { + let migration_image_map_opt = defaultGetMigrationImageMap( + options, + oldOptions, + this.migrationImageMapFieldName + ); + console.log("RHEV Migration image map computation: "); + console.log(migration_image_map_opt) const env = { ...defaultGetDestinationEnv(options, oldOptions), - ...defaultGetMigrationImageMap( - options, - oldOptions, - this.migrationImageMapFieldName - ), + ...migration_image_map_opt, }; return env; } From 41dddf3105373be8b1a42825d4ffae35d01ff91f Mon Sep 17 00:00:00 2001 From: Nashwan Azhari <nazhari@cloudbasesolutions.com> Date: Wed, 29 May 2024 20:29:18 +0300 Subject: [PATCH 13/41] MainMultiFilterList skeleton. Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com> --- .../MainMultiFilterList.spec.tsx | 140 +++++++++++++ .../MainMultiFilterList.tsx | 185 ++++++++++++++++++ .../ui/Lists/MainMultiFilterList/package.json | 6 + .../ui/Lists/MainMultiFilterList/story.tsx | 70 +++++++ 4 files changed, 401 insertions(+) create mode 100644 src/components/ui/Lists/MainMultiFilterList/MainMultiFilterList.spec.tsx create mode 100644 src/components/ui/Lists/MainMultiFilterList/MainMultiFilterList.tsx create mode 100644 src/components/ui/Lists/MainMultiFilterList/package.json create mode 100644 src/components/ui/Lists/MainMultiFilterList/story.tsx diff --git a/src/components/ui/Lists/MainMultiFilterList/MainMultiFilterList.spec.tsx b/src/components/ui/Lists/MainMultiFilterList/MainMultiFilterList.spec.tsx new file mode 100644 index 00000000..7c3a6cf0 --- /dev/null +++ b/src/components/ui/Lists/MainMultiFilterList/MainMultiFilterList.spec.tsx @@ -0,0 +1,140 @@ +/* +Copyright (C) 2021 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, { useState } from "react"; +import { render } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import MainMultiFilterList from "@src/components/ui/Lists/MainMultiFilterList"; +import { DropdownAction } from "@src/components/ui/Dropdowns/ActionDropdown"; +import TestUtils from "@tests/TestUtils"; +import { ThemePalette } from "@src/components/Theme"; + +const FILTER_ITEMS = [ + { label: "All", value: "all" }, + { label: "Items 1", value: "item-1" }, + { label: "Items 2", value: "item-2" }, + { label: "Items 3", value: "item-3" }, +]; + +const ACTIONS: DropdownAction[] = [ + { + label: "Action 1", + title: "Action 1 Description", + action: jest.fn(), + }, + { + label: "Action 2", + disabled: true, + action: jest.fn(), + }, +]; + +const MainMultiFilterListWrapper = (props?: { + onFilterItemClick?: (item: any) => void; + onReloadButtonClick?: () => void; + onSearchChange?: (value: string) => void; + onSelectAllChange?: (checked: boolean) => void; +}) => { + const [selectAllSelected, setSelectAllSelected] = useState(false); + return ( + <MainMultiFilterList + onFilterItemClick={props?.onFilterItemClick || (() => {})} + onReloadButtonClick={props?.onReloadButtonClick || (() => {})} + onSearchChange={props?.onSearchChange || (() => {})} + onSelectAllChange={checked => { + setSelectAllSelected(checked); + if (props?.onSelectAllChange) { + props.onSelectAllChange(checked); + } + }} + selectedValue="item-2" + selectionInfo={{ total: 3, selected: 1, label: "test item" }} + selectAllSelected={selectAllSelected} + items={FILTER_ITEMS} + dropdownActions={ACTIONS} + searchValue="test" + /> + ); +}; + +describe("MainMultiFilterList", () => { + it("renders all basic elements", () => { + render(<MainMultiFilterListWrapper />); + const items = TestUtils.selectAll("MainMultiFilterList__FilterItem-"); + expect(items).toHaveLength(FILTER_ITEMS.length); + expect(items[2].textContent).toBe(FILTER_ITEMS[2].label); + expect( + TestUtils.select("SearchInput__Wrapper")?.querySelector("input")?.value + ).toBe("test"); + expect(TestUtils.select("MainMultiFilterList__SelectionText")?.textContent).toBe( + "1 of 3\u00a0test item(s) selected" + ); + }); + + it("renders actions", () => { + render(<MainMultiFilterListWrapper />); + TestUtils.select("DropdownButton__Wrapper")?.click(); + const actions = TestUtils.selectAll("ActionDropdown__ListItem-"); + expect(actions).toHaveLength(ACTIONS.length); + expect(actions[0].textContent).toBe(ACTIONS[0].label); + expect(actions[0].hasAttribute("disabled")).toBeFalsy(); + expect(actions[1].hasAttribute("disabled")).toBeTruthy(); + actions[0].click(); + actions[1].click(); + expect(ACTIONS[0].action).toHaveBeenCalled(); + expect(ACTIONS[1].action).not.toHaveBeenCalled(); + }); + + it("fires filter item click", () => { + const onFilterItemClick = jest.fn(); + render(<MainMultiFilterListWrapper onFilterItemClick={onFilterItemClick} />); + TestUtils.selectAll("MainMultiFilterList__FilterItem-")[1].click(); + expect(onFilterItemClick).toHaveBeenCalledWith(FILTER_ITEMS[1]); + }); + + it("has select all change", () => { + const onSelectAllChange = jest.fn(); + render(<MainMultiFilterListWrapper onSelectAllChange={onSelectAllChange} />); + + const checkbox = TestUtils.select("Checkbox__Wrapper")!; + const style = () => window.getComputedStyle(checkbox); + expect(TestUtils.rgbToHex(style().backgroundColor)).toBe("white"); + checkbox.click(); + expect(TestUtils.rgbToHex(style().backgroundColor)).toBe( + ThemePalette.primary + ); + + expect(onSelectAllChange).toHaveBeenCalledWith(true); + checkbox.click(); + expect(onSelectAllChange).toHaveBeenCalledWith(false); + expect(TestUtils.rgbToHex(style().backgroundColor)).toBe("white"); + }); + + it("fires reload button click", () => { + const onReloadButtonClick = jest.fn(); + render(<MainMultiFilterListWrapper onReloadButtonClick={onReloadButtonClick} />); + TestUtils.select("ReloadButton__Wrapper")!.click(); + expect(onReloadButtonClick).toHaveBeenCalled(); + }); + + it("fires search change", () => { + const onSearchChange = jest.fn(); + render(<MainMultiFilterListWrapper onSearchChange={onSearchChange} />); + userEvent.type( + TestUtils.select("SearchInput__Wrapper")?.querySelector("input")!, + "test2" + ); + expect(onSearchChange).toHaveBeenCalledWith("test2"); + }); +}); diff --git a/src/components/ui/Lists/MainMultiFilterList/MainMultiFilterList.tsx b/src/components/ui/Lists/MainMultiFilterList/MainMultiFilterList.tsx new file mode 100644 index 00000000..ef047dd6 --- /dev/null +++ b/src/components/ui/Lists/MainMultiFilterList/MainMultiFilterList.tsx @@ -0,0 +1,185 @@ +/* +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 * as React from "react"; +import { observer } from "mobx-react"; +import styled from "styled-components"; + +import Checkbox from "@src/components/ui/Checkbox"; +import SearchInput from "@src/components/ui/SearchInput"; +import ActionDropdown from "@src/components/ui/Dropdowns/ActionDropdown"; +import ReloadButton from "@src/components/ui/ReloadButton"; + +import { ThemePalette, ThemeProps } from "@src/components/Theme"; + +import type { DropdownAction } from "@src/components/ui/Dropdowns/ActionDropdown"; + +const Wrapper = styled.div<any>` + display: flex; + align-items: center; + padding-top: 8px; + flex-wrap: wrap; + flex-shrink: 0; +`; +const Main = styled.div<any>` + display: flex; + margin-right: 16px; + flex-grow: 1; + margin-bottom: 32px; + height: 32px; + align-items: center; +`; +const FilterGroup = styled.div<any>` + display: flex; + margin: 0 16px 0 ${props => (props.noMargin ? "0" : "32px")}; + border-right: 1px solid ${ThemePalette.grayscale[4]}; +`; +const FilterItem = styled.div<any>` + margin-right: 32px; + color: ${props => + props.selected ? ThemePalette.primary : ThemePalette.grayscale[4]}; + ${props => (props.selected ? "text-decoration: underline;" : "")} + cursor: pointer; + white-space: nowrap; + + &:last-child { + margin-right: 16px; + } +`; +const Selection = styled.div<any>` + display: flex; + align-items: center; + transition: all ${ThemeProps.animations.swift}; + margin-bottom: 32px; + animation: show-animation 0.4s; + + @keyframes show-animation { + from { + opacity: 0; + } + to { + opacity: 1; + } + } +`; +const SelectionText = styled.div<any>` + margin-right: 16px; + color: ${ThemePalette.grayscale[4]}; + white-space: nowrap; +`; + +type DictItem = { value: string; label: string }; +type Props = { + onFilterItemClick: (item: DictItem) => void; + onReloadButtonClick: () => void; + onSearchChange: (value: string) => void; + onSelectAllChange: (checked: boolean) => void; + selectedValue: string; + selectionInfo: { total: number; selected: number; label: string }; + selectAllSelected: boolean | null; + items: DictItem[]; + customFilterComponent?: React.ReactNode; + searchValue?: string; + dropdownActions: DropdownAction[] | null; + largeDropdownActionItems?: boolean; +}; +@observer +class MainMultiFilterList extends React.Component<Props> { + renderFilterGroup() { + const renderCustomComponent = () => { + if (this.props.customFilterComponent) { + return this.props.customFilterComponent; + } + return null; + }; + + return ( + <FilterGroup + noMargin={ + !this.props.dropdownActions || this.props.dropdownActions.length === 0 + } + > + {renderCustomComponent()} + {this.props.items.map(item => ( + <FilterItem + onClick={() => this.props.onFilterItemClick(item)} + key={item.value} + selected={this.props.selectedValue === item.value} + > + {item.label} + </FilterItem> + ))} + </FilterGroup> + ); + } + + renderSelectionInfo() { + if (!this.props.selectionInfo.selected) { + return null; + } + + return ( + <Selection> + <SelectionText> + {this.props.selectionInfo.selected} of{" "} + {this.props.selectionInfo.total}  + {this.props.selectionInfo.label}(s) selected + </SelectionText> + {this.props.dropdownActions && this.props.dropdownActions.length ? ( + <ActionDropdown + actions={this.props.dropdownActions} + largeItems={this.props.largeDropdownActionItems} + style={{ marginLeft: "8px" }} + /> + ) : null} + </Selection> + ); + } + + render() { + const renderCheckbox = () => { + if (this.props.dropdownActions && this.props.dropdownActions.length > 0) { + return ( + <Checkbox + onChange={checked => { + this.props.onSelectAllChange(checked); + }} + checked={!!this.props.selectAllSelected} + /> + ); + } + return null; + }; + + return ( + <Wrapper> + <Main> + {renderCheckbox()} + {this.renderFilterGroup()} + <ReloadButton + style={{ marginRight: "16px" }} + onClick={this.props.onReloadButtonClick} + /> + <SearchInput + onChange={this.props.onSearchChange} + value={this.props.searchValue} + /> + </Main> + {this.renderSelectionInfo()} + </Wrapper> + ); + } +} + +export default MainMultiFilterList; diff --git a/src/components/ui/Lists/MainMultiFilterList/package.json b/src/components/ui/Lists/MainMultiFilterList/package.json new file mode 100644 index 00000000..76a60a34 --- /dev/null +++ b/src/components/ui/Lists/MainMultiFilterList/package.json @@ -0,0 +1,6 @@ +{ + "name": "MainMultiFilterList", + "version": "0.0.0", + "private": true, + "main": "./MainMultiFilterList.tsx" +} diff --git a/src/components/ui/Lists/MainMultiFilterList/story.tsx b/src/components/ui/Lists/MainMultiFilterList/story.tsx new file mode 100644 index 00000000..281b41b6 --- /dev/null +++ b/src/components/ui/Lists/MainMultiFilterList/story.tsx @@ -0,0 +1,70 @@ +/* +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 { storiesOf } from "@storybook/react"; +import MainMultiFilterList from "."; + +const items = [ + { label: "Item 1", value: "item-1" }, + { label: "Item 2", value: "item-2" }, + { label: "Item 3", value: "item-3" }, +]; + +const actions = [ + { label: "Action 1", value: "action-1" }, + { label: "Action 2", value: "action-2" }, +]; + +class Wrapper extends React.Component<any> { + state = { selectedValue: "item-1", selectAllSelected: false }; + + handleChange(selectedValue: string) { + this.setState({ selectedValue }); + } + + handleSelectAllChange(selectAllSelected: boolean) { + this.setState({ selectAllSelected }); + } + + render() { + return ( + <MainMultiFilterList + onReloadButtonClick={() => {}} + onSearchChange={() => {}} + selectionInfo={{ total: 0, selected: 0, label: "" }} + items={[]} + dropdownActions={[]} + // eslint-disable-next-line react/jsx-props-no-spreading + {...this.props} + selectedValue={this.state.selectedValue} + selectAllSelected={this.state.selectAllSelected} + onFilterItemClick={item => { + this.handleChange(item.value); + }} + onSelectAllChange={checked => { + this.handleSelectAllChange(checked); + }} + /> + ); + } +} + +storiesOf("MainMultiFilterList", module).add("default", () => ( + <Wrapper + items={items} + actions={actions} + selectionInfo={{ selected: 2, total: 7, label: "items" }} + /> +)); From b59d3f5f592e67fcaac4263689ffac4a421bbd7f Mon Sep 17 00:00:00 2001 From: Nashwan Azhari <nazhari@cloudbasesolutions.com> Date: Thu, 30 May 2024 14:33:33 +0300 Subject: [PATCH 14/41] Update licensing panel nomenclature. Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com> --- cypress/e2e/dashboard/dashboard.cy.ts | 2 +- .../DashboardLicence/DashboardLicence.spec.tsx | 6 +++--- .../DashboardModule/DashboardLicence/DashboardLicence.tsx | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cypress/e2e/dashboard/dashboard.cy.ts b/cypress/e2e/dashboard/dashboard.cy.ts index 9cc1c678..403e7efb 100644 --- a/cypress/e2e/dashboard/dashboard.cy.ts +++ b/cypress/e2e/dashboard/dashboard.cy.ts @@ -58,7 +58,7 @@ describe("Dashboard", () => { cy.get("*[class^='DashboardLicence__ChartHeaderCurrent']").should( "contain.text", - `${applianceStatus.appliance_licence_status.current_performed_replicas} Used Replica ${applianceStatus.appliance_licence_status.current_performed_migrations} Used Migrations` + `${applianceStatus.appliance_licence_status.current_performed_replicas} Fulfilled Replica ${applianceStatus.appliance_licence_status.current_performed_migrations} Fulfilled Migrations` ); }); diff --git a/src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.spec.tsx b/src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.spec.tsx index d48aa2c3..23a1311e 100644 --- a/src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.spec.tsx +++ b/src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.spec.tsx @@ -68,14 +68,14 @@ describe("DashboardLicence", () => { expect( TestUtils.selectAll("DashboardLicence__ChartHeaderCurrent-")[0] .textContent - ).toBe("5 Used Replicas "); + ).toBe("5 Fulfilled Replicas "); expect( TestUtils.selectAll("DashboardLicence__ChartHeaderTotal-")[0].textContent ).toBe("Total 10"); expect( TestUtils.selectAll("DashboardLicence__ChartHeaderCurrent-")[1] .textContent - ).toBe("3 Used Migrations "); + ).toBe("3 Fulfilled Migrations "); expect( TestUtils.selectAll("DashboardLicence__ChartHeaderTotal-")[1].textContent ).toBe("Total 5"); @@ -176,6 +176,6 @@ describe("DashboardLicence", () => { expect( TestUtils.selectAll("DashboardLicence__ChartHeaderCurrent-")[0] .textContent - ).toBe("1 Used Replica "); + ).toBe("1 Fuldilled Replica "); }); }); diff --git a/src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.tsx b/src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.tsx index 8be0c700..284f873d 100644 --- a/src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.tsx +++ b/src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.tsx @@ -201,8 +201,8 @@ class DashboardLicence extends React.Component<Props> { color: ThemePalette.alert, current: info.currentPerformedReplicas, total: info.currentAvailableReplicas, - label: "Used Replica", - info: `The number of replicas consumed over the number of replicas available in + label: "Fulfilled Replica", + info: `The number of replicas fulfilled over the number of replicas available in all currently active licences (including non-activated floating licences)`, }, ], @@ -211,8 +211,8 @@ class DashboardLicence extends React.Component<Props> { color: ThemePalette.primary, current: info.currentPerformedMigrations, total: info.currentAvailableMigrations, - label: "Used Migration", - info: `The number of migrations consumed over the number of migrations available in + label: "Fulfilled Migration", + info: `The number of migrations fulfilled over the number of migrations available in all currently active licences (including non-activated floating licences)`, }, ], From 593f690b09ed9c1ab19e1690a3101784e04ee4a1 Mon Sep 17 00:00:00 2001 From: Nashwan Azhari <nazhari@cloudbasesolutions.com> Date: Thu, 30 May 2024 12:24:07 +0300 Subject: [PATCH 15/41] Completely disable migrations view/routes. Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com> --- src/components/App.tsx | 5 ---- .../Navigation/Navigation.tsx | 4 --- .../NewItemDropdown/NewItemDropdown.tsx | 28 +++++++++---------- src/constants.ts | 2 +- 4 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index d2611062..1977580e 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -24,8 +24,6 @@ import LoginPage from "@src/components/smart/LoginPage"; import ReplicasPage from "@src/components/smart/ReplicasPage"; import MessagePage from "@src/components/smart/MessagePage"; import ReplicaDetailsPage from "@src/components/smart/ReplicaDetailsPage"; -import MigrationsPage from "@src/components/smart/MigrationsPage"; -import MigrationDetailsPage from "@src/components/smart/MigrationDetailsPage"; import DeploymentsPage from "@src/components/smart/DeploymentsPage"; import DeploymentDetailsPage from "@src/components/smart/DeploymentDetailsPage"; import MetalHubServersPage from "@src/components/smart/MetalHubServersPage"; @@ -216,9 +214,6 @@ class App extends React.Component<Record<string, unknown>, State> { {renderRoute("/replicas", ReplicasPage, true)} {renderRoute("/replicas/:id", ReplicaDetailsPage, true)} {renderRoute("/replicas/:id/:page", ReplicaDetailsPage)} - {renderRoute("/migrations", MigrationsPage, true)} - {renderRoute("/migrations/:id", MigrationDetailsPage, true)} - {renderRoute("/migrations/:id/:page", MigrationDetailsPage)} {renderRoute("/deployments", DeploymentsPage, true)} {renderRoute("/deployments/:id", DeploymentDetailsPage, true)} {renderRoute("/deployments/:id/:page", DeploymentDetailsPage)} diff --git a/src/components/modules/NavigationModule/Navigation/Navigation.tsx b/src/components/modules/NavigationModule/Navigation/Navigation.tsx index b3880a82..88f2b6c4 100644 --- a/src/components/modules/NavigationModule/Navigation/Navigation.tsx +++ b/src/components/modules/NavigationModule/Navigation/Navigation.tsx @@ -410,10 +410,6 @@ class Navigation extends React.Component<Props> { bullet = "replica"; menuImage = replicaImage; break; - case "migrations": - bullet = "migration"; - menuImage = replicaImage; - break; case "deployments": bullet = "deployment"; menuImage = replicaImage; diff --git a/src/components/ui/Dropdowns/NewItemDropdown/NewItemDropdown.tsx b/src/components/ui/Dropdowns/NewItemDropdown/NewItemDropdown.tsx index abf29697..fddf638e 100644 --- a/src/components/ui/Dropdowns/NewItemDropdown/NewItemDropdown.tsx +++ b/src/components/ui/Dropdowns/NewItemDropdown/NewItemDropdown.tsx @@ -174,22 +174,22 @@ class NewItemDropdown extends React.Component<Props, State> { ? configLoader.config.disabledPages : []; const items: ItemType[] = [ + // { + // title: "Migration", + // href: "/wizard/migration", + // description: "Migrate VMs between two clouds", + // iconName: "migration", + // }, + // { + // title: "Deployment", + // href: "/wizard/deployment", + // description: "Deploy an already synced Replica or Live Migration", + // iconName: "replica", + // }, { - title: "Migration", - href: "/wizard/migration", - description: "Migrate VMs between two clouds", - iconName: "migration", - }, - { - title: "Replica", + title: "Transfer", href: "/wizard/replica", - description: "Incrementally replicate VMs between two clouds", - iconName: "replica", - }, - { - title: "Deployment", - href: "/wizard/deployment", - description: "Deploy an already synced Replica or Live Migration", + description: "Incrementally transfer VMs between two clouds", iconName: "replica", }, { diff --git a/src/constants.ts b/src/constants.ts index 9d74fa9f..3c16bfc3 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -24,7 +24,7 @@ export type NavigationMenuType = { export const navigationMenu: NavigationMenuType[] = [ { label: "Dashboard", value: "dashboard" }, { label: "Replicas", value: "replicas" }, - { label: "Migrations", value: "migrations" }, + // { label: "Migrations", value: "migrations" }, { label: "Deployments", value: "deployments" }, { label: "Cloud Endpoints", value: "endpoints" }, { label: "Minion Pools", value: "minion-pools" }, From ed58c04cbfb740ebbcb389196d9117aaf1612966 Mon Sep 17 00:00:00 2001 From: Nashwan Azhari <nazhari@cloudbasesolutions.com> Date: Thu, 30 May 2024 17:19:27 +0300 Subject: [PATCH 16/41] Patch API source for scenario field passed form Wizard. Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com> --- .../smart/WizardPage/WizardPage.tsx | 9 +--- src/constants.ts | 2 +- src/sources/WizardSource.ts | 42 ++++++++++--------- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/components/smart/WizardPage/WizardPage.tsx b/src/components/smart/WizardPage/WizardPage.tsx index 6f045c66..bc6fd8f0 100644 --- a/src/components/smart/WizardPage/WizardPage.tsx +++ b/src/components/smart/WizardPage/WizardPage.tsx @@ -208,16 +208,11 @@ class WizardPage extends React.Component<Props, State> { } if (items.length === 1) { - let location = `/${this.state.type}s/${items[0].id}/`; - if (this.state.type === "replica") { - location += "executions"; - } else { - location += "tasks"; - } + let location = `/replicas/${items[0].id}/executions`; await schedulePromise; this.props.history.push(location); } else { - this.props.history.push(`/${this.state.type}s`); + this.props.history.push(`/replicas`); } } diff --git a/src/constants.ts b/src/constants.ts index 3c16bfc3..8613a1d1 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -120,7 +120,7 @@ export const wizardPages: WizardPage[] = [ id: "schedule", title: "Schedule", breadcrumb: "Schedule", - excludeFrom: "migration", + // excludeFrom: "migration", }, { id: "summary", title: "Summary", breadcrumb: "Summary" }, ]; diff --git a/src/sources/WizardSource.ts b/src/sources/WizardSource.ts index c0b54008..824011ed 100644 --- a/src/sources/WizardSource.ts +++ b/src/sources/WizardSource.ts @@ -43,8 +43,9 @@ class WizardSource { const destParser = data.target ? OptionsSchemaPlugin.for(data.target.type) : new DefaultOptionsSchemaParser(); - const payload: any = {}; - payload[type] = { + + let payload: any = {}; + payload = { origin_endpoint_id: data.source ? data.source.id : "null", destination_endpoint_id: data.target ? data.target.id : "null", network_map: destParser.getNetworkMap(data.networks), @@ -56,20 +57,20 @@ class WizardSource { }; if (data.destOptions && data.destOptions.skip_os_morphing != null) { - payload[type].skip_os_morphing = data.destOptions.skip_os_morphing; + payload.skip_os_morphing = data.destOptions.skip_os_morphing; } if (data.sourceOptions) { const sourceEnv = sourceParser.getDestinationEnv(data.sourceOptions); if (data.sourceOptions.minion_pool_id) { - payload[type].origin_minion_pool_id = data.sourceOptions.minion_pool_id; + payload.origin_minion_pool_id = data.sourceOptions.minion_pool_id; } - payload[type].source_environment = sourceEnv; + payload.source_environment = sourceEnv; } const destEnv = destParser.getDestinationEnv(data.destOptions); if (data.destOptions?.minion_pool_id) { - payload[type].destination_minion_pool_id = + payload.destination_minion_pool_id = data.destOptions.minion_pool_id; } @@ -78,12 +79,12 @@ class WizardSource { Object.keys(poolMappings).forEach(instanceId => { if ( poolMappings[instanceId] && - payload[type].instances.find((i: string) => i === instanceId) + payload.instances.find((i: string) => i === instanceId) ) { - if (!payload[type][INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS]) { - payload[type][INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] = {}; + if (!payload[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS]) { + payload[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS] = {}; } - payload[type][INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS][instanceId] = + payload[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS][instanceId] = poolMappings[instanceId]; } }); @@ -91,31 +92,32 @@ class WizardSource { delete destEnv[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS]; - payload[type].destination_environment = destEnv; + payload.destination_environment = destEnv; - payload[type].shutdown_instances = Boolean( + payload.shutdown_instances = Boolean( data.destOptions && data.destOptions.shutdown_instances ); if (uploadedUserScripts.length) { - payload[type].user_scripts = destParser.getUserScripts( + payload.user_scripts = destParser.getUserScripts( uploadedUserScripts, [], {} ); } - if (type === "migration") { - payload[type].replication_count = - data.destOptions?.replication_count || 2; - } + const scenario = type == "replica" + ? "replica" + : "live_migration"; + payload.scenario = scenario; + const payload_body_key = "replica"; const response = await Api.send({ - url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/${type}s`, + url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas`, method: "POST", - data: payload, + data: { [payload_body_key]: payload }, }); - return response.data[type]; + return response.data[payload_body_key]; } async createMultiple(opts: { From 2c3daf27bfbb0057ba3f74af71f1e6e75aa348ad Mon Sep 17 00:00:00 2001 From: Nashwan Azhari <nazhari@cloudbasesolutions.com> Date: Thu, 30 May 2024 18:22:22 +0300 Subject: [PATCH 17/41] Make wizard display Execute Now options for Live Migrations too. Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com> --- .../WizardOptions/WizardOptions.tsx | 37 +++++++++--------- .../WizardSummary/WizardSummary.tsx | 38 +++++++++---------- .../smart/WizardPage/WizardPage.tsx | 16 ++++---- 3 files changed, 45 insertions(+), 46 deletions(-) diff --git a/src/components/modules/WizardModule/WizardOptions/WizardOptions.tsx b/src/components/modules/WizardModule/WizardOptions/WizardOptions.tsx index 56069435..2acbfcf2 100644 --- a/src/components/modules/WizardModule/WizardOptions/WizardOptions.tsx +++ b/src/components/modules/WizardModule/WizardOptions/WizardOptions.tsx @@ -331,7 +331,7 @@ class WizardOptions extends React.Component<Props> { fieldsSchema.push(titleFieldSchema); } - if (this.props.wizardType === "replica") { + if (this.props.wizardType === "replica" || this.props.wizardType === "migration") { fieldsSchema.push({ name: "execute_now", type: "boolean", @@ -350,24 +350,25 @@ class WizardOptions extends React.Component<Props> { ? "Enable 'Execute Now' to set 'Execute Now Options'" : `Set the options for ${this.props.wizardType} execution`, }); - } else if ( - this.props.wizardType === "migration" || - this.props.wizardType === "migration-destination-options-edit" - ) { - const shutdownInstanceField = migrationFields.find( - f => f.name === "shutdown_instances" - )!; - shutdownInstanceField.disabled = this.props.executeNowOptionsDisabled; - shutdownInstanceField.description = this.props.executeNowOptionsDisabled - ? "The 'Shutdown Instances' option is disabled for the source provider" - : shutdownInstanceField.description; - fieldsSchema = [ - ...fieldsSchema, - ...migrationFields.map(f => - f.name === "shutdown_instances" ? shutdownInstanceField : f - ), - ]; } + // } else if ( + // this.props.wizardType === "migration" || + // this.props.wizardType === "migration-destination-options-edit" + // ) { + // const shutdownInstanceField = migrationFields.find( + // f => f.name === "shutdown_instances" + // )!; + // shutdownInstanceField.disabled = this.props.executeNowOptionsDisabled; + // shutdownInstanceField.description = this.props.executeNowOptionsDisabled + // ? "The 'Shutdown Instances' option is disabled for the source provider" + // : shutdownInstanceField.description; + // fieldsSchema = [ + // ...fieldsSchema, + // ...migrationFields.map(f => + // f.name === "shutdown_instances" ? shutdownInstanceField : f + // ), + // ]; + // } return fieldsSchema; } diff --git a/src/components/modules/WizardModule/WizardSummary/WizardSummary.tsx b/src/components/modules/WizardModule/WizardSummary/WizardSummary.tsx index 7582d34f..93fbdda3 100644 --- a/src/components/modules/WizardModule/WizardSummary/WizardSummary.tsx +++ b/src/components/modules/WizardModule/WizardSummary/WizardSummary.tsx @@ -448,24 +448,24 @@ class WizardSummary extends React.Component<Props> { </Option> ); - const migrationOptions = [ - <Option key="shutdown"> - <OptionLabel>Shutdown Instances</OptionLabel> - <OptionValue> - {this.getDefaultBooleanOption("shutdown_instances", false) - ? "Yes" - : "No"} - </OptionValue> - </Option>, - <Option key="count"> - <OptionLabel>Replication Count</OptionLabel> - <OptionValue> - {(this.props.data.destOptions && - this.props.data.destOptions.replication_count) || - 2} - </OptionValue> - </Option>, - ]; + // const migrationOptions = [ + // <Option key="shutdown"> + // <OptionLabel>Shutdown Instances</OptionLabel> + // <OptionValue> + // {this.getDefaultBooleanOption("shutdown_instances", false) + // ? "Yes" + // : "No"} + // </OptionValue> + // </Option>, + // <Option key="count"> + // <OptionLabel>Replication Count</OptionLabel> + // <OptionValue> + // {(this.props.data.destOptions && + // this.props.data.destOptions.replication_count) || + // 2} + // </OptionValue> + // </Option>, + // ]; const renderDefaultStorageOption = () => ( <Option> @@ -487,7 +487,7 @@ class WizardSummary extends React.Component<Props> { <SectionTitle>{type} Target Options</SectionTitle> <OptionsList> {this.props.wizardType === "replica" ? executeNowOption : null} - {this.props.wizardType === "migration" ? migrationOptions : null} + {this.props.wizardType === "migration" ? executeNowOption: null} {this.props.data.selectedInstances && this.props.data.selectedInstances.length > 1 ? separateVmOption diff --git a/src/components/smart/WizardPage/WizardPage.tsx b/src/components/smart/WizardPage/WizardPage.tsx index bc6fd8f0..163ccf06 100644 --- a/src/components/smart/WizardPage/WizardPage.tsx +++ b/src/components/smart/WizardPage/WizardPage.tsx @@ -197,15 +197,13 @@ class WizardPage extends React.Component<Props, State> { ); let schedulePromise = Promise.resolve(); - if (this.state.type === "replica") { - items.forEach(replica => { - if (replica.type !== "replica") { - return; - } - this.executeCreatedReplica(replica); - schedulePromise = this.scheduleReplica(replica); - }); - } + items.forEach(replica => { + if (replica.type !== "replica") { + return; + } + this.executeCreatedReplica(replica); + schedulePromise = this.scheduleReplica(replica); + }); if (items.length === 1) { let location = `/replicas/${items[0].id}/executions`; From 518367acb8f0f93f9cb0f120168b8cd342c21ead Mon Sep 17 00:00:00 2001 From: Nashwan Azhari <nazhari@cloudbasesolutions.com> Date: Mon, 3 Jun 2024 14:24:05 +0300 Subject: [PATCH 18/41] Update Dashboard widgets with Deployments. Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com> --- .../DashboardContent/DashboardContent.tsx | 34 ++++++++++++------- .../DashboardExecutions.tsx | 12 +++---- .../DashboardTopEndpoints.tsx | 23 ++++++------- .../smart/DashboardPage/DashboardPage.tsx | 8 ++--- 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/components/modules/DashboardModule/DashboardContent/DashboardContent.tsx b/src/components/modules/DashboardModule/DashboardContent/DashboardContent.tsx index 72f60743..9c25a646 100644 --- a/src/components/modules/DashboardModule/DashboardContent/DashboardContent.tsx +++ b/src/components/modules/DashboardModule/DashboardContent/DashboardContent.tsx @@ -30,7 +30,7 @@ import type { Project } from "@src/@types/Project"; import type { User } from "@src/@types/User"; import type { Licence, LicenceServerStatus } from "@src/@types/Licence"; import type { NotificationItemData } from "@src/@types/NotificationItem"; -import { ReplicaItem, MigrationItem } from "@src/@types/MainItem"; +import { ReplicaItem, DeploymentItem } from "@src/@types/MainItem"; const MIDDLE_WIDTHS = ["264px", "264px", "264px"]; @@ -53,11 +53,11 @@ const MiddleMobileLayout = styled.div<any>` type Props = { replicas: ReplicaItem[]; - migrations: MigrationItem[]; + deployments: DeploymentItem[]; endpoints: Endpoint[]; projects: Project[]; replicasLoading: boolean; - migrationsLoading: boolean; + deploymentsLoading: boolean; endpointsLoading: boolean; usersLoading: boolean; projectsLoading: boolean; @@ -127,11 +127,10 @@ class DashboardContent extends React.Component<Props, State> { <DashboardTopEndpoints key="top-endpoints" replicas={this.props.replicas} - migrations={this.props.migrations} endpoints={this.props.endpoints} loading={ this.props.replicasLoading || - this.props.migrationsLoading || + this.props.deploymentsLoading || this.props.endpointsLoading } style={{ @@ -175,21 +174,30 @@ class DashboardContent extends React.Component<Props, State> { ); } + getReplicas() { + return this.props.replicas.filter( + (r: ReplicaItem) => r.scenario === "replica"); + } + + getLiveMigrations() { + return this.props.replicas.filter( + (r: ReplicaItem) => r.scenario === "live_migration"); + } + render() { let infoCountData = [ { label: "Replicas", - value: this.props.replicas.length, + value: this.getReplicas().length, color: ThemePalette.alert, link: "/replicas", loading: this.props.replicasLoading, }, { label: "Migrations", - value: this.props.migrations.length, - color: ThemePalette.primary, - link: "/migrations", - loading: this.props.migrationsLoading, + value: this.getLiveMigrations().length, + link: "/replicas", + loading: this.props.replicasLoading, }, { label: "Endpoints", @@ -224,9 +232,9 @@ class DashboardContent extends React.Component<Props, State> { <DashboardInfoCount data={infoCountData} /> {this.renderMiddleModules()} <DashboardExecutions - replicas={this.props.replicas} - migrations={this.props.migrations} - loading={this.props.replicasLoading || this.props.migrationsLoading} + replicas={this.getReplicas()} + migrations={this.getLiveMigrations()} + loading={this.props.replicasLoading || this.props.deploymentsLoading} /> </Wrapper> ); diff --git a/src/components/modules/DashboardModule/DashboardExecutions/DashboardExecutions.tsx b/src/components/modules/DashboardModule/DashboardExecutions/DashboardExecutions.tsx index 353bd500..6df9a835 100644 --- a/src/components/modules/DashboardModule/DashboardExecutions/DashboardExecutions.tsx +++ b/src/components/modules/DashboardModule/DashboardExecutions/DashboardExecutions.tsx @@ -130,13 +130,13 @@ const EmptyBackgroundImage = styled.div<any>` type Props = { // eslint-disable-next-line react/no-unused-prop-types replicas: ReplicaItem[]; - migrations: MigrationItem[]; + migrations: ReplicaItem[]; loading: boolean; }; type GroupedData = { label: string; values: number[]; - data?: string; + data?: string }; type TooltipData = { title: string; @@ -169,7 +169,7 @@ class DashboardExecutions extends React.Component<Props, State> { } groupCreations(props: Props) { - let creations: TransferItem[] = [...props.replicas, ...props.migrations]; + let creations: ReplicaItem[] = [...props.replicas, ...props.migrations]; const periodUnit: any = this.state.selectedPeriod.split("-")[1]; const periodValue: any = Number(this.state.selectedPeriod.split("-")[0]); @@ -187,7 +187,7 @@ class DashboardExecutions extends React.Component<Props, State> { this.groupByPeriod(creations, periodUnit); } - groupByPeriod(transferItems: TransferItem[], periodUnit: string) { + groupByPeriod(transferItems: ReplicaItem[], periodUnit: string) { const groupedData: GroupedData[] = []; const periods: { [period: string]: { replicas: number; migrations: number }; @@ -202,9 +202,9 @@ class DashboardExecutions extends React.Component<Props, State> { if (!periods[period]) { periods[period] = { replicas: 0, migrations: 0 }; } - if (item.type === "replica") { + if (item.scenario === "replica") { periods[period].replicas += 1; - } else if (item.type === "migration") { + } else if (item.scenario === "live_migration") { periods[period].migrations += 1; } }); diff --git a/src/components/modules/DashboardModule/DashboardTopEndpoints/DashboardTopEndpoints.tsx b/src/components/modules/DashboardModule/DashboardTopEndpoints/DashboardTopEndpoints.tsx index 2d1d20b3..d2d6f44c 100644 --- a/src/components/modules/DashboardModule/DashboardTopEndpoints/DashboardTopEndpoints.tsx +++ b/src/components/modules/DashboardModule/DashboardTopEndpoints/DashboardTopEndpoints.tsx @@ -26,7 +26,7 @@ import { ThemePalette, ThemeProps } from "@src/components/Theme"; import type { Endpoint } from "@src/@types/Endpoint"; -import { ReplicaItem, MigrationItem, TransferItem } from "@src/@types/MainItem"; +import { ReplicaItem } from "@src/@types/MainItem"; import endpointImage from "./images/endpoint.svg"; const Wrapper = styled.div<any>` @@ -131,15 +131,13 @@ const Message = styled.div<any>` type GroupedEndpoint = { endpoint: Endpoint; replicasCount: number; - migrationsCount: number; + deploymentsCount: number; value: number; }; type Props = { // eslint-disable-next-line react/no-unused-prop-types replicas: ReplicaItem[]; // eslint-disable-next-line react/no-unused-prop-types - migrations: MigrationItem[]; - // eslint-disable-next-line react/no-unused-prop-types endpoints: Endpoint[]; style: React.CSSProperties; loading: boolean; @@ -178,21 +176,22 @@ class DashboardTopEndpoints extends React.Component<Props, State> { calculateGroupedEndpoints(props: Props) { const groupedEndpoints: GroupedEndpoint[] = []; - const count = (mainItems: TransferItem[], endpointId: string) => + const count = (mainItems: ReplicaItem[], endpointId: string, scenario: string) => mainItems.filter( r => - r.destination_endpoint_id === endpointId || - r.origin_endpoint_id === endpointId + r.scenario === scenario && ( + r.destination_endpoint_id === endpointId || + r.origin_endpoint_id === endpointId) ).length; props.endpoints.forEach(endpoint => { - const replicasCount = count(props.replicas, endpoint.id); - const migrationsCount = count(props.migrations, endpoint.id); + const replicasCount = count(props.replicas, endpoint.id, "replica"); + const deploymentsCount = count(props.replicas, endpoint.id, "live_migration"); groupedEndpoints.push({ endpoint, replicasCount, - migrationsCount, - value: replicasCount + migrationsCount, + deploymentsCount, + value: replicasCount + deploymentsCount, }); }); this.setState({ groupedEndpoints }); @@ -263,7 +262,7 @@ class DashboardTopEndpoints extends React.Component<Props, State> { <TooltipRows> <TooltipRow>{groupedEndpoint.replicasCount} Replicas</TooltipRow> <TooltipRow> - {groupedEndpoint.migrationsCount} Migrations + {groupedEndpoint.deploymentsCount} Deployments </TooltipRow> <TooltipRow>{groupedEndpoint.value} Total</TooltipRow> </TooltipRows> diff --git a/src/components/smart/DashboardPage/DashboardPage.tsx b/src/components/smart/DashboardPage/DashboardPage.tsx index f6ddf567..4947e779 100644 --- a/src/components/smart/DashboardPage/DashboardPage.tsx +++ b/src/components/smart/DashboardPage/DashboardPage.tsx @@ -22,7 +22,7 @@ import MainTemplate from "@src/components/modules/TemplateModule/MainTemplate"; import PageHeader from "@src/components/smart/PageHeader"; import endpointStore from "@src/stores/EndpointStore"; import licenceStore from "@src/stores/LicenceStore"; -import migrationStore from "@src/stores/MigrationStore"; +import deploymentStore from "@src/stores/DeploymentStore"; import notificationStore from "@src/stores/NotificationStore"; import projectStore from "@src/stores/ProjectStore"; import replicaStore from "@src/stores/ReplicaStore"; @@ -100,7 +100,7 @@ class ProjectsPage extends React.Component<{ history: any }, State> { await Promise.all([ replicaStore.getReplicas({ skipLog: true, showLoading }), - migrationStore.getMigrations({ skipLog: true, showLoading }), + deploymentStore.getDeployments({ skipLog: true, showLoading }), endpointStore.getEndpoints({ skipLog: true, showLoading }), projectStore.getProjects({ skipLog: true, showLoading }), ]); @@ -129,7 +129,7 @@ class ProjectsPage extends React.Component<{ history: any }, State> { listComponent={ <DashboardContent replicas={replicaStore.replicas} - migrations={migrationStore.migrations} + deployments={deploymentStore.deployments} endpoints={endpointStore.endpoints} users={userStore.users} projects={projectStore.projects} @@ -141,7 +141,7 @@ class ProjectsPage extends React.Component<{ history: any }, State> { notificationItems={notificationStore.notificationItems} notificationItemsLoading={notificationStore.loading} endpointsLoading={endpointStore.loading} - migrationsLoading={migrationStore.loading} + deploymentsLoading={deploymentStore.loading} projectsLoading={projectStore.projects.length === 0} usersLoading={userStore.users.length === 0} licenceLoading={licenceStore.loadingLicenceInfo} From 95d70e57b3d8d21478fb517692697ef278bceaa7 Mon Sep 17 00:00:00 2001 From: Nashwan Azhari <nazhari@cloudbasesolutions.com> Date: Tue, 4 Jun 2024 13:27:33 +0300 Subject: [PATCH 19/41] Update Enpoint Details transfer list. Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com> --- .../EndpointDetailsContent.tsx | 15 ++++------- .../EndpointDetailsPage.tsx | 26 +++++++++---------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/components/modules/EndpointModule/EndpointDetailsContent/EndpointDetailsContent.tsx b/src/components/modules/EndpointModule/EndpointDetailsContent/EndpointDetailsContent.tsx index cb9508b2..683b4807 100644 --- a/src/components/modules/EndpointModule/EndpointDetailsContent/EndpointDetailsContent.tsx +++ b/src/components/modules/EndpointModule/EndpointDetailsContent/EndpointDetailsContent.tsx @@ -20,9 +20,7 @@ import styled from "styled-components"; import { Field as FieldType } from "@src/@types/Field"; import { getTransferItemTitle, - MigrationItem, ReplicaItem, - TransferItem, } from "@src/@types/MainItem"; import { Region } from "@src/@types/Region"; import EndpointLogos from "@src/components/modules/EndpointModule/EndpointLogos"; @@ -102,7 +100,7 @@ type Props = { regions: Region[]; connectionInfo: Endpoint["connection_info"] | null; loading: boolean; - usage: { migrations: MigrationItem[]; replicas: ReplicaItem[] }; + replicas: ReplicaItem[], connectionInfoSchema: FieldType[]; onDeleteClick: () => void; onValidateClick: () => void; @@ -227,12 +225,12 @@ class EndpointDetailsContent extends React.Component<Props> { ); } - renderUsage(items: TransferItem[]) { + renderUsage(items: ReplicaItem[]) { return ( <TransferItems> {items.map(item => ( <TransferItemWrapper key={item.id}> - <LinkStyled to={`/${item.type}s/${item.id}`}> + <LinkStyled to={`/replicas/${item.id}`}> {getTransferItemTitle(item)} </LinkStyled> </TransferItemWrapper> @@ -250,9 +248,6 @@ class EndpointDetailsContent extends React.Component<Props> { created_at: createdAt, id, } = this.props.item || {}; - const usage: TransferItem[] = this.props.usage.replicas.concat( - this.props.usage.migrations as any[] - ); return ( <Wrapper> @@ -293,8 +288,8 @@ class EndpointDetailsContent extends React.Component<Props> { )} </Field> <Field> - <Label>Used in replicas/migrations ({usage.length})</Label> - {usage.length > 0 ? this.renderUsage(usage) : <Value>-</Value>} + <Label>Used in transfers ({this.props.replicas.length})</Label> + {this.props.replicas.length > 0 ? this.renderUsage(this.props.replicas) : <Value>-</Value>} </Field> {!this.props.connectionInfo ? this.renderConnectionInfoLoading() diff --git a/src/components/smart/EndpointDetailsPage/EndpointDetailsPage.tsx b/src/components/smart/EndpointDetailsPage/EndpointDetailsPage.tsx index ed076a86..1aee6ea2 100644 --- a/src/components/smart/EndpointDetailsPage/EndpointDetailsPage.tsx +++ b/src/components/smart/EndpointDetailsPage/EndpointDetailsPage.tsx @@ -27,7 +27,7 @@ import EndpointModal from "@src/components/modules/EndpointModule/EndpointModal" import EndpointDuplicateOptions from "@src/components/modules/EndpointModule/EndpointDuplicateOptions"; import endpointStore from "@src/stores/EndpointStore"; -import migrationStore from "@src/stores/MigrationStore"; +import deploymentStore from "@src/stores/DeploymentStore"; import replicaStore from "@src/stores/ReplicaStore"; import userStore from "@src/stores/UserStore"; import projectStore from "@src/stores/ProjectStore"; @@ -37,7 +37,7 @@ import type { Endpoint as EndpointType } from "@src/@types/Endpoint"; import { ThemePalette } from "@src/components/Theme"; import regionStore from "@src/stores/RegionStore"; -import { MigrationItem, ReplicaItem } from "@src/@types/MainItem"; +import { DeploymentItem, ReplicaItem } from "@src/@types/MainItem"; import providerStore from "@src/stores/ProviderStore"; import endpointImage from "./images/endpoint.svg"; @@ -53,7 +53,7 @@ type State = { showEndpointModal: boolean; showEndpointInUseModal: boolean; showEndpointInUseLoadingModal: boolean; - endpointUsage: { replicas: ReplicaItem[]; migrations: MigrationItem[] }; + endpointUsage: { replicas: ReplicaItem[]; deployments: DeploymentItem[] }; showDuplicateModal: boolean; duplicating: boolean; }; @@ -67,7 +67,7 @@ class EndpointDetailsPage extends React.Component<Props, State> { showEndpointInUseLoadingModal: false, showDuplicateModal: false, duplicating: false, - endpointUsage: { replicas: [], migrations: [] }, + endpointUsage: { replicas: [], deployments: [] }, }; componentDidMount() { @@ -87,20 +87,20 @@ class EndpointDetailsPage extends React.Component<Props, State> { ); } - getEndpointUsage(): { migrations: MigrationItem[]; replicas: ReplicaItem[] } { + getEndpointUsage(): { deployments: DeploymentItem[]; replicas: ReplicaItem[] } { const endpointId = this.props.match.params.id; const replicas = replicaStore.replicas.filter( r => r.origin_endpoint_id === endpointId || r.destination_endpoint_id === endpointId ); - const migrations = migrationStore.migrations.filter( + const deployments = deploymentStore.deployments.filter( r => r.origin_endpoint_id === endpointId || r.destination_endpoint_id === endpointId ); - return { migrations, replicas }; + return { deployments, replicas }; } handleUserItemClick(item: { value: string }) { @@ -117,12 +117,12 @@ class EndpointDetailsPage extends React.Component<Props, State> { await Promise.all([ replicaStore.getReplicas(), - migrationStore.getMigrations(), + deploymentStore.getDeployments(), ]); const endpointUsage = this.getEndpointUsage(); if ( - endpointUsage.migrations.length === 0 && + endpointUsage.deployments.length === 0 && endpointUsage.replicas.length === 0 ) { this.setState({ @@ -220,7 +220,7 @@ class EndpointDetailsPage extends React.Component<Props, State> { await Promise.all([ replicaStore.getReplicas(), - migrationStore.getMigrations(), + deploymentStore.getDeployments(), regionStore.getRegions(), ]); this.setState({ endpointUsage: this.getEndpointUsage() }); @@ -306,7 +306,7 @@ class EndpointDetailsPage extends React.Component<Props, State> { <EndpointDetailsContent item={endpoint} regions={regionStore.regions} - usage={this.state.endpointUsage} + replicas={this.state.endpointUsage.replicas} loading={ endpointStore.connectionInfoLoading || endpointStore.loading || @@ -339,8 +339,8 @@ class EndpointDetailsPage extends React.Component<Props, State> { type="error" isOpen={this.state.showEndpointInUseModal} title="Endpoint is in use" - message="The endpoint can't be deleted because it is in use by replicas or migrations." - extraMessage="You must first delete the replica or migration which uses this endpoint." + message="The endpoint can't be deleted because it is in use by replicas or deployments." + extraMessage="You must first delete the replica or deployment which uses this endpoint." onRequestClose={() => { this.handleCloseEndpointInUseModal(); }} From 2c3481dd89dfdbe66b7a2c897e37d5aca0ea01ad Mon Sep 17 00:00:00 2001 From: Nashwan Azhari <nazhari@cloudbasesolutions.com> Date: Tue, 4 Jun 2024 13:28:10 +0300 Subject: [PATCH 20/41] Update Minion Pool Details page transfer list. Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com> --- .../MinionPoolDetailsContent.tsx | 8 +++--- .../MinionPoolMachines.tsx | 6 ++--- .../MinionPoolMainDetails.tsx | 16 +++++------ .../MinionPoolDetailsPage.tsx | 27 ++++++++++--------- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolDetailsContent.tsx b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolDetailsContent.tsx index c1deea95..9655f9ac 100644 --- a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolDetailsContent.tsx +++ b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolDetailsContent.tsx @@ -20,7 +20,7 @@ import Button from "@src/components/ui/Button"; import DetailsNavigation from "@src/components/modules/NavigationModule/DetailsNavigation"; import type { Endpoint } from "@src/@types/Endpoint"; import type { Field } from "@src/@types/Field"; -import { ReplicaItem, MigrationItem } from "@src/@types/MainItem"; +import { ReplicaItem, DeploymentItem } from "@src/@types/MainItem"; import { MinionPoolDetails } from "@src/@types/MinionPool"; import StatusImage from "@src/components/ui/StatusComponents/StatusImage"; import { ThemeProps } from "@src/components/Theme"; @@ -76,7 +76,7 @@ type Props = { item?: MinionPoolDetails | null; itemId: string; replicas: ReplicaItem[]; - migrations: MigrationItem[]; + deployments: DeploymentItem[]; endpoints: Endpoint[]; schema: Field[]; schemaLoading: boolean; @@ -145,7 +145,7 @@ class MinionPoolDetailsContent extends React.Component<Props> { <MinionPoolMachines item={this.props.item} replicas={this.props.replicas} - migrations={this.props.migrations} + deployments={this.props.deployments} /> ); } @@ -167,7 +167,7 @@ class MinionPoolDetailsContent extends React.Component<Props> { <MinionPoolMainDetails item={this.props.item} replicas={this.props.replicas} - migrations={this.props.migrations} + deployments={this.props.deployments} schema={this.props.schema} schemaLoading={this.props.schemaLoading} endpoints={this.props.endpoints} diff --git a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMachines.tsx b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMachines.tsx index adff097e..a846d3b5 100644 --- a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMachines.tsx +++ b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMachines.tsx @@ -17,7 +17,7 @@ import { Collapse } from "react-collapse"; import { Link } from "react-router-dom"; import styled, { createGlobalStyle, css } from "styled-components"; -import { MigrationItem, ReplicaItem, TransferItem } from "@src/@types/MainItem"; +import { DeploymentItem, ReplicaItem, TransferItem } from "@src/@types/MainItem"; import { MinionMachine, MinionPool } from "@src/@types/MinionPool"; import { ThemePalette, ThemeProps } from "@src/components/Theme"; import Arrow from "@src/components/ui/Arrow"; @@ -145,7 +145,7 @@ type FilterType = "all" | "allocated" | "not-allocated"; type Props = { item?: MinionPool | null; replicas: ReplicaItem[]; - migrations: MigrationItem[]; + deployments: DeploymentItem[]; }; type State = { filterStatus: FilterType; @@ -282,7 +282,7 @@ class MinionPoolMachines extends React.Component<Props, State> { transferItems.find(i => i.id === machine.allocated_action); const allocatedAction = machine.allocated_action ? findTransferItem(this.props.replicas) || - findTransferItem(this.props.migrations) + findTransferItem(this.props.deployments) : null; return ( <MachineWrapper key={machine.id}> diff --git a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.tsx b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.tsx index 83658201..c1441cc1 100644 --- a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.tsx +++ b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.tsx @@ -18,7 +18,7 @@ import { Link } from "react-router-dom"; import styled, { css } from "styled-components"; import fieldHelper from "@src/@types/Field"; -import { MigrationItem, ReplicaItem, TransferItem } from "@src/@types/MainItem"; +import { DeploymentItem, ReplicaItem, TransferItem } from "@src/@types/MainItem"; import { MinionPool } from "@src/@types/MinionPool"; import EndpointLogos from "@src/components/modules/EndpointModule/EndpointLogos"; import { ThemePalette, ThemeProps } from "@src/components/Theme"; @@ -102,7 +102,7 @@ const PropertyValue = styled.div<any>` type Props = { item?: MinionPool | null; replicas: ReplicaItem[]; - migrations: MigrationItem[]; + deployments: DeploymentItem[]; schema: FieldType[]; schemaLoading: boolean; endpoints: Endpoint[]; @@ -178,8 +178,8 @@ class MinionPoolMainDetails extends React.Component<Props> { let properties: any[] = []; const plugin = endpoint && OptionsSchemaPlugin.for(endpoint.type); - const migrationImageMapFieldName = - plugin && plugin.migrationImageMapFieldName; + const deploymentImageMapFieldName = + plugin && plugin.deploymentImageMapFieldName; let dictionaryKey = ""; if (endpoint) { dictionaryKey = `${endpoint.type}-minion-pool`; @@ -202,8 +202,8 @@ class MinionPoolMainDetails extends React.Component<Props> { } let fieldName = pn; if ( - migrationImageMapFieldName && - fieldName === migrationImageMapFieldName + deploymentImageMapFieldName && + fieldName === deploymentImageMapFieldName ) { fieldName = p; } @@ -264,7 +264,7 @@ class MinionPoolMainDetails extends React.Component<Props> { }; const usage: TransferItem[] = this.props.replicas.concat( - this.props.migrations as any[] + this.props.deployments as any[] ); return ( @@ -329,7 +329,7 @@ class MinionPoolMainDetails extends React.Component<Props> { ) : null} <Row> <Field> - <Label>Used in Replicas/Migrations ({usage.length})</Label> + <Label>Used in Transfers ({usage.length})</Label> {usage.length > 0 ? this.renderUsage(usage) : <Value>-</Value>} </Field> </Row> diff --git a/src/components/smart/MinionPoolDetailsPage/MinionPoolDetailsPage.tsx b/src/components/smart/MinionPoolDetailsPage/MinionPoolDetailsPage.tsx index 62ccf0cb..e66767e8 100644 --- a/src/components/smart/MinionPoolDetailsPage/MinionPoolDetailsPage.tsx +++ b/src/components/smart/MinionPoolDetailsPage/MinionPoolDetailsPage.tsx @@ -35,10 +35,11 @@ import minionPoolStore from "@src/stores/MinionPoolStore"; import MinionPoolModal from "@src/components/modules/MinionModule/MinionPoolModal"; import MinionPoolDetailsContent from "@src/components/modules/MinionModule/MinionPoolDetailsContent"; import replicaStore from "@src/stores/ReplicaStore"; -import migrationStore from "@src/stores/MigrationStore"; +import deploymentStore from "@src/stores/DeploymentStore"; import MinionPoolConfirmationModal from "@src/components/modules/MinionModule/MinionPoolConfirmationModal"; import providerStore from "@src/stores/ProviderStore"; import { Field } from "@src/@types/Field"; +import { TransferItem } from "@src/@types/MainItem"; import minionPoolImage from "./images/minion-pool.svg"; const Wrapper = styled.div<any>``; @@ -135,7 +136,7 @@ class MinionPoolDetailsPage extends React.Component<Props, State> { showLoading: true, }), replicaStore.getReplicas(), - migrationStore.getMigrations(), + deploymentStore.getDeployments(), ]); const minionPool = this.minionPool; if (!minionPool) { @@ -204,7 +205,7 @@ class MinionPoolDetailsPage extends React.Component<Props, State> { skipLog: true, }), replicaStore.getReplicas(), - migrationStore.getMigrations(), + deploymentStore.getDeployments(), ]); setTimeout(() => { @@ -353,6 +354,14 @@ class MinionPoolDetailsPage extends React.Component<Props, State> { }, ]; + const checkPoolUsed = (i: TransferItem): Boolean|undefined => { + return i.origin_minion_pool_id === this.minionPool?.id + || i.destination_minion_pool_id === this.minionPool?.id + || (i.instance_osmorphing_minion_pool_mappings + && Object.values(i.instance_osmorphing_minion_pool_mappings).includes( + this.minionPool?.id)); + } + return ( <Wrapper> <DetailsTemplate @@ -379,16 +388,8 @@ class MinionPoolDetailsPage extends React.Component<Props, State> { <MinionPoolDetailsContent item={this.minionPool} itemId={this.minionPoolId} - replicas={replicaStore.replicas.filter( - r => - r.origin_minion_pool_id === this.minionPool?.id || - r.destination_minion_pool_id === this.minionPool?.id - )} - migrations={migrationStore.migrations.filter( - r => - r.origin_minion_pool_id === this.minionPool?.id || - r.destination_minion_pool_id === this.minionPool?.id - )} + replicas={replicaStore.replicas.filter(checkPoolUsed)} + deployments={deploymentStore.deployments.filter(checkPoolUsed)} endpoints={endpointStore.endpoints} schema={minionPoolStore.minionPoolCombinedSchema} schemaLoading={ From e29f0bb817dd68b5436371923514942c0552980e Mon Sep 17 00:00:00 2001 From: Nashwan Azhari <nazhari@cloudbasesolutions.com> Date: Tue, 4 Jun 2024 15:18:10 +0300 Subject: [PATCH 21/41] Fix dashboard transfer creation button text. Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com> --- .../DashboardModule/DashboardActivity/DashboardActivity.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx b/src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx index 4572a1c2..1797c47d 100644 --- a/src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx +++ b/src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx @@ -149,7 +149,7 @@ class DashboardActivity extends React.Component<Props> { in this project. </Message> <Button hollow primary transparent onClick={this.props.onNewClick}> - New Replica / Migration + New Transfer </Button> </NoItems> ); From 1bbeda1654d679276143d9435abbe3c7eb94603a Mon Sep 17 00:00:00 2001 From: Nashwan Azhari <nazhari@cloudbasesolutions.com> Date: Mon, 3 Jun 2024 14:24:05 +0300 Subject: [PATCH 22/41] Update Dashboard widgets with Deployments. Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com> --- .../DashboardModule/DashboardLicence/DashboardLicence.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.spec.tsx b/src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.spec.tsx index 23a1311e..5e4cdedf 100644 --- a/src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.spec.tsx +++ b/src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.spec.tsx @@ -176,6 +176,6 @@ describe("DashboardLicence", () => { expect( TestUtils.selectAll("DashboardLicence__ChartHeaderCurrent-")[0] .textContent - ).toBe("1 Fuldilled Replica "); + ).toBe("1 Fulfilled Replica "); }); }); From 77668fb020a961b9626f074bab8e2ea83b2e12ae Mon Sep 17 00:00:00 2001 From: Nashwan Azhari <nazhari@cloudbasesolutions.com> Date: Tue, 4 Jun 2024 15:23:03 +0300 Subject: [PATCH 23/41] Update Transfer Wizard operation descriptions. Signed-off-by: Nashwan Azhari <nazhari@cloudbasesolutions.com> --- .../WizardModule/WizardType/WizardType.tsx | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/components/modules/WizardModule/WizardType/WizardType.tsx b/src/components/modules/WizardModule/WizardType/WizardType.tsx index fadd36a1..4e2b69cb 100644 --- a/src/components/modules/WizardModule/WizardType/WizardType.tsx +++ b/src/components/modules/WizardModule/WizardType/WizardType.tsx @@ -76,8 +76,14 @@ class WizardType extends React.Component<Props> { <Column alignRight width="50%"> <Title>Coriolis Migration - A Coriolis Migration is a full instance migration between two - cloud endpoints. + Coriolis Migrations allow for the incremental copying of + a virtual machine's data from the source environment to + disks in the target environment with zero downtime. +

+ Migrations can be synced (executed) any number of times, + but can be deployed in the target environment only once. +

+ Migrations are licenced one-time per each transferred VM.
@@ -90,12 +96,15 @@ class WizardType extends React.Component { Coriolis Replica - The Coriolis Replica is obtained by copying (replicating) - incrementally the virtual machines data from the source - environment to the target, without interfering with any running - workload. A migration replica can then be finalized by - automatically applying the required changes to adapt it to the - target environment (migration phase). + Coriolis Replicas allow for the incremental copying of + a virtual machine's data from the source environment to + disks in the target environment with zero downtime. +

+ Replicas can be synced (executed) any number of times, + and can be deployed in the target environment any time a + disaster recovery procedure is desired. +

+ Replicas are licenced monthly per each replicated VM.
From 591caa0bf9a75315de66199e3d81b3b926a20c72 Mon Sep 17 00:00:00 2001 From: Nashwan Azhari Date: Tue, 4 Jun 2024 15:36:41 +0300 Subject: [PATCH 24/41] Update all '/replicas' paths to '/transfers'. Signed-off-by: Nashwan Azhari --- src/components/App.tsx | 6 +-- .../DashboardActivity/DashboardActivity.tsx | 2 +- .../DashboardContent/DashboardContent.tsx | 4 +- .../EndpointDetailsContent.tsx | 2 +- .../DeleteReplicaModal/DeleteReplicaModal.tsx | 4 +- .../MainDetails/MainDetails.tsx | 2 +- .../ReplicaDetailsContent.tsx | 2 +- .../TransferItemModal/TransferItemModal.tsx | 2 +- .../ReplicaDetailsPage/ReplicaDetailsPage.tsx | 22 +++++----- .../smart/ReplicasPage/ReplicasPage.tsx | 40 +++++++++---------- .../smart/WizardPage/WizardPage.tsx | 4 +- src/constants.ts | 2 +- 12 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 1977580e..f71d7761 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -211,9 +211,9 @@ class App extends React.Component, State> { } {renderRoute("/dashboard", DashboardPage)} - {renderRoute("/replicas", ReplicasPage, true)} - {renderRoute("/replicas/:id", ReplicaDetailsPage, true)} - {renderRoute("/replicas/:id/:page", ReplicaDetailsPage)} + {renderRoute("/transfers", ReplicasPage, true)} + {renderRoute("/transfers/:id", ReplicaDetailsPage, true)} + {renderRoute("/transfers/:id/:page", ReplicaDetailsPage)} {renderRoute("/deployments", DeploymentsPage, true)} {renderRoute("/deployments/:id", DeploymentDetailsPage, true)} {renderRoute("/deployments/:id/:page", DeploymentDetailsPage)} diff --git a/src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx b/src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx index 1797c47d..c396b3de 100644 --- a/src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx +++ b/src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx @@ -116,7 +116,7 @@ class DashboardActivity extends React.Component { return ( { label: "Replicas", value: this.getReplicas().length, color: ThemePalette.alert, - link: "/replicas", + link: "/transfers", loading: this.props.replicasLoading, }, { label: "Migrations", value: this.getLiveMigrations().length, - link: "/replicas", + link: "/transfers", loading: this.props.replicasLoading, }, { diff --git a/src/components/modules/EndpointModule/EndpointDetailsContent/EndpointDetailsContent.tsx b/src/components/modules/EndpointModule/EndpointDetailsContent/EndpointDetailsContent.tsx index 683b4807..5bacdbdd 100644 --- a/src/components/modules/EndpointModule/EndpointDetailsContent/EndpointDetailsContent.tsx +++ b/src/components/modules/EndpointModule/EndpointDetailsContent/EndpointDetailsContent.tsx @@ -230,7 +230,7 @@ class EndpointDetailsContent extends React.Component { {items.map(item => ( - + {getTransferItemTitle(item)} diff --git a/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.tsx b/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.tsx index fa085c0b..f028aee2 100644 --- a/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.tsx +++ b/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.tsx @@ -159,8 +159,8 @@ class DeleteReplicaModal extends React.Component { render() { const title = this.props.isMultiReplicaSelection - ? "Delete Selected Replicas?" - : "Delete Replica?"; + ? "Delete Selected Transfers?" + : "Delete Transfer?"; return ( {this.props.loading ? this.renderLoading() : this.renderContent()} diff --git a/src/components/modules/TransferModule/MainDetails/MainDetails.tsx b/src/components/modules/TransferModule/MainDetails/MainDetails.tsx index 3d476639..3350996e 100644 --- a/src/components/modules/TransferModule/MainDetails/MainDetails.tsx +++ b/src/components/modules/TransferModule/MainDetails/MainDetails.tsx @@ -428,7 +428,7 @@ class MainDetails extends React.Component { - + {this.props.item.replica_id} diff --git a/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx b/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx index 3686db7f..8644fc96 100644 --- a/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx +++ b/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx @@ -256,7 +256,7 @@ class ReplicaDetailsContent extends React.Component { items={NavigationItems} selectedValue={this.props.page} itemId={this.props.itemId} - itemType="replica" + itemType="transfer" /> {this.renderMainDetails()} diff --git a/src/components/modules/TransferModule/TransferItemModal/TransferItemModal.tsx b/src/components/modules/TransferModule/TransferItemModal/TransferItemModal.tsx index 7923e53b..6cfca4de 100644 --- a/src/components/modules/TransferModule/TransferItemModal/TransferItemModal.tsx +++ b/src/components/modules/TransferModule/TransferItemModal/TransferItemModal.tsx @@ -718,7 +718,7 @@ class TransferItemModal extends React.Component { }); this.props.onRequestClose(); this.props.onUpdateComplete( - `/replicas/${this.props.replica.id}/executions` + `/transfers/${this.props.replica.id}/executions` ); } catch (err) { this.setState({ updating: false }); diff --git a/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx b/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx index cb38dbee..91f45d74 100644 --- a/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx +++ b/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx @@ -94,7 +94,7 @@ class ReplicaDetailsPage extends React.Component { stopPolling: boolean | null = null; componentDidMount() { - document.title = "Replica Details"; + document.title = "Transfer Details"; const loadReplica = async () => { await endpointStore.getEndpoints({ showLoading: true }); @@ -186,7 +186,7 @@ class ReplicaDetailsPage extends React.Component { get replicaId() { if (!this.props.match?.params?.id) { - throw new Error("Invalid replica id"); + throw new Error("Invalid transfer id"); } return this.props.match.params.id; } @@ -398,7 +398,7 @@ class ReplicaDetailsPage extends React.Component { if (!replica) { return; } - this.props.history.push("/replicas"); + this.props.history.push("/transfers"); replicaStore.delete(replica.id); } @@ -416,7 +416,7 @@ class ReplicaDetailsPage extends React.Component { return; } replicaStore.deleteDisks(replica.id); - this.props.history.push(`/replicas/${replica.id}/executions`); + this.props.history.push(`/transfers/${replica.id}/executions`); } handleCloseDeleteReplicaDisksConfirmation() { @@ -556,7 +556,7 @@ class ReplicaDetailsPage extends React.Component { } replicaStore.execute(replica.id, fields); this.handleCloseOptionsModal(); - this.props.history.push(`/replicas/${replica.id}/executions`); + this.props.history.push(`/transfers/${replica.id}/executions`); } async pollData() { @@ -659,7 +659,7 @@ class ReplicaDetailsPage extends React.Component { const editTitle = providerStore.providersLoading ? "Loading providers data" : !this.state.isEditable - ? "One of the platform plugins doesn't support editing replica option." + ? "One of the platform plugins doesn't support editing transfer option." : null; const dropdownActions: DropdownAction[] = [ { @@ -735,7 +735,7 @@ class ReplicaDetailsPage extends React.Component { itemType={this.getReplicaItemType()} itemDescription={replica?.description} dropdownActions={dropdownActions} - backLink="/replicas" + backLink="/transfers" typeImage={this.getReplicaScenarioTypeImage()} alertInfoPill={this.getReplicaTypePillShouldRed()} primaryInfoPill={!this.getReplicaTypePillShouldRed()} @@ -845,7 +845,7 @@ class ReplicaDetailsPage extends React.Component { {this.state.showDeploymentModal ? ( { this.handleCloseDeploymentModal(); }} @@ -895,9 +895,9 @@ class ReplicaDetailsPage extends React.Component { ) : null} { this.handleDeleteReplicaDisksConfirmation(); }} diff --git a/src/components/smart/ReplicasPage/ReplicasPage.tsx b/src/components/smart/ReplicasPage/ReplicasPage.tsx index bf6f32d7..d6b404cf 100644 --- a/src/components/smart/ReplicasPage/ReplicasPage.tsx +++ b/src/components/smart/ReplicasPage/ReplicasPage.tsx @@ -83,7 +83,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> { paginatedReplicaIds: string[] = []; componentDidMount() { - document.title = "Coriolis Replicas"; + document.title = "Coriolis Transfers"; projectStore.getProjects(); endpointStore.getEndpoints({ showLoading: true }); @@ -141,9 +141,9 @@ class ReplicasPage extends React.Component<{ history: any }, State> { handleItemClick(item: ReplicaItem) { if (item.last_execution_status === "RUNNING") { - this.props.history.push(`/replicas/${item.id}/executions`); + this.props.history.push(`/transfers/${item.id}/executions`); } else { - this.props.history.push(`/replicas/${item.id}`); + this.props.history.push(`/transfers/${item.id}`); } } @@ -160,12 +160,12 @@ class ReplicasPage extends React.Component<{ history: any }, State> { replicaStore.execute(replica.id, fields); } }); - notificationStore.alert("Executing selected replicas"); + notificationStore.alert("Executing selected transfers"); this.setState({ showExecutionOptionsModal: false }); } deploySelectedReplicas(fields: Field[], uploadedScripts: InstanceScript[]) { - notificationStore.alert("Creating deployments from selected replicas"); + notificationStore.alert("Creating deployments from selected transfers"); this.deploy(fields, uploadedScripts); this.setState({ showCreateDeploymentsModal: false, modalIsOpen: false }); } @@ -188,7 +188,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> { ) ); notificationStore.alert( - "Deployments successfully created from replicas.", + "Deployments successfully created from transfers.", "success" ); this.props.history.push("/deployments"); @@ -207,7 +207,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> { showDeleteDisksModal: false, showDeleteReplicasModal: false, }); - notificationStore.alert("Deleting selected replicas' disks"); + notificationStore.alert("Deleting selected transfers' disks"); } cancelExecutions() { @@ -402,7 +402,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> { }, }, { - label: "Deploy Replicas", + label: "Deploy Transfers", color: ThemePalette.primary, action: () => { this.handleShowCreateDeploymentsModal(); @@ -415,7 +415,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> { }, }, { - label: "Delete Replicas", + label: "Delete Transfers", color: ThemePalette.alert, action: () => { this.handleShowDeleteReplicas(); @@ -426,7 +426,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> { return ( } + navigationComponent={} listComponent={ { /> )} emptyListImage={replicaLargeImage} - emptyListMessage="It seems like you don't have any Replicas in this project." - emptyListExtraMessage="A Coriolis Replica is obtained by replicating incrementally the virtual machines data from the source cloud endpoint to the target." - emptyListButtonLabel="Create a Replica" + emptyListMessage="It seems like you don't have any Transfers in this project." + emptyListExtraMessage="A Coriolis Transfer is performed by replicating incrementally the virtual machines data from the source cloud endpoint to the target." + emptyListButtonLabel="Create a Transfer" onEmptyListButtonClick={() => { this.handleEmptyListButtonClick(); }} @@ -481,7 +481,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> { } headerComponent={ { this.handleProjectChange(); }} @@ -514,7 +514,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> { { this.cancelExecutions(); @@ -527,7 +527,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> { {this.state.showExecutionOptionsModal ? ( { this.setState({ showExecutionOptionsModal: false }); }} @@ -549,7 +549,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> { {this.state.showCreateDeploymentsModal ? ( { this.setState({ showCreateDeploymentsModal: false, @@ -580,9 +580,9 @@ class ReplicasPage extends React.Component<{ history: any }, State> { {this.state.showDeleteDisksModal ? ( { this.deleteReplicasDisks(this.state.selectedReplicas); }} diff --git a/src/components/smart/WizardPage/WizardPage.tsx b/src/components/smart/WizardPage/WizardPage.tsx index 163ccf06..0660c7c7 100644 --- a/src/components/smart/WizardPage/WizardPage.tsx +++ b/src/components/smart/WizardPage/WizardPage.tsx @@ -206,11 +206,11 @@ class WizardPage extends React.Component { }); if (items.length === 1) { - let location = `/replicas/${items[0].id}/executions`; + let location = `/transfers/${items[0].id}/executions`; await schedulePromise; this.props.history.push(location); } else { - this.props.history.push(`/replicas`); + this.props.history.push(`/transfers`); } } diff --git a/src/constants.ts b/src/constants.ts index 8613a1d1..af2ee04c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -23,7 +23,7 @@ export type NavigationMenuType = { }; export const navigationMenu: NavigationMenuType[] = [ { label: "Dashboard", value: "dashboard" }, - { label: "Replicas", value: "replicas" }, + { label: "Transfers", value: "transfers" }, // { label: "Migrations", value: "migrations" }, { label: "Deployments", value: "deployments" }, { label: "Cloud Endpoints", value: "endpoints" }, From b199e619a0bbab82bb6b517874678792c365ec17 Mon Sep 17 00:00:00 2001 From: Nashwan Azhari Date: Tue, 4 Jun 2024 16:36:16 +0300 Subject: [PATCH 25/41] Update nomeclature in Schedules page from Replicas to Transfers. Signed-off-by: Nashwan Azhari --- .../modules/TransferModule/Schedule/Schedule.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/modules/TransferModule/Schedule/Schedule.tsx b/src/components/modules/TransferModule/Schedule/Schedule.tsx index c95edd99..5897ffdb 100644 --- a/src/components/modules/TransferModule/Schedule/Schedule.tsx +++ b/src/components/modules/TransferModule/Schedule/Schedule.tsx @@ -318,13 +318,13 @@ class Schedule extends React.Component { {this.props.secondaryEmpty - ? "Schedule this Replica" - : "This Replica has no Schedules."} + ? "Schedule this Transfer" + : "This Transfer has no Schedules."} {this.props.secondaryEmpty - ? "You can schedule this replica so that it executes automatically." - : "Add a new schedule so that the Replica executes automatically."} + ? "You can schedule this Transfer so that it executes automatically." + : "Add a new schedule so that the Transfer executes automatically."} {this.props.adding ? ( Adding ... From 85b211512d5945ed6da5d18e7b8695fd7c8b978f Mon Sep 17 00:00:00 2001 From: Nashwan Azhari Date: Tue, 4 Jun 2024 16:48:38 +0300 Subject: [PATCH 26/41] Update notifications dropdown for new nomenclature. Signed-off-by: Nashwan Azhari --- .../NotificationDropdown/NotificationDropdown.tsx | 9 +++++++-- src/sources/NotificationSource.ts | 15 +++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/components/ui/Dropdowns/NotificationDropdown/NotificationDropdown.tsx b/src/components/ui/Dropdowns/NotificationDropdown/NotificationDropdown.tsx index 4833a605..8ec530bd 100644 --- a/src/components/ui/Dropdowns/NotificationDropdown/NotificationDropdown.tsx +++ b/src/components/ui/Dropdowns/NotificationDropdown/NotificationDropdown.tsx @@ -274,6 +274,11 @@ class NotificationDropdown extends React.Component { const list = ( {this.props.items.map(item => { + const typeUrl = + item.type === "migration" + ? "deployments" + : "transfers"; + const executionsPath = item.status === "RUNNING" ? item.type === "replica" @@ -295,13 +300,13 @@ class NotificationDropdown extends React.Component { onClick={() => { this.handleItemClick(); }} - to={`/${item.type}s/${item.id}${executionsPath}`} + to={`/${typeUrl}/${item.id}${executionsPath}`} > - {item.type === "replica" ? "RE" : "MI"} + {item.type === "replica" ? "RE" : "DE"} {item.name} diff --git a/src/sources/NotificationSource.ts b/src/sources/NotificationSource.ts index 13c65f7c..b201fb95 100644 --- a/src/sources/NotificationSource.ts +++ b/src/sources/NotificationSource.ts @@ -20,9 +20,9 @@ import type { } from "@src/@types/NotificationItem"; import { TransferItem, - MigrationItem, ReplicaItem, getTransferItemTitle, + DeploymentItem, } from "@src/@types/MainItem"; class NotificationStorage { @@ -79,7 +79,10 @@ class NotificationStorage { class DataUtils { static getItemDescription(item: TransferItem) { - return `New ${item.type} ${item.id.substr( + let item_type = item.type === "replica" + ? "replica" + : "deployment"; + return `New ${item_type} ${item.id.substr( 0, 7 )}... status: ${item.last_execution_status @@ -90,9 +93,9 @@ class DataUtils { class NotificationSource { async loadData(): Promise { - const [migrationsResponse, replicasResponse] = await Promise.all([ + const [deploymentsResponse, replicasResponse] = await Promise.all([ Api.send({ - url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations`, + url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/deployments`, skipLog: true, quietError: true, }), @@ -103,9 +106,9 @@ class NotificationSource { }), ]); - const migrations: MigrationItem[] = migrationsResponse.data.migrations; + const deployments: DeploymentItem[] = deploymentsResponse.data.deployments; const replicas: ReplicaItem[] = replicasResponse.data.replicas; - const apiData = [...migrations, ...replicas]; + const apiData = [...deployments, ...replicas]; apiData.sort( (a, b) => new Date(b.updated_at || b.created_at).getTime() - From b1b591e0bf3be30510cfa4fa1eb4dbe709add224 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Mon, 19 Aug 2024 17:36:01 +0300 Subject: [PATCH 27/41] Update EndpointsPage with Live Migration scenario actions Replaces old Migrations entries with "live_migration" scenario replicas --- .../smart/EndpointsPage/EndpointsPage.tsx | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/smart/EndpointsPage/EndpointsPage.tsx b/src/components/smart/EndpointsPage/EndpointsPage.tsx index 26776b65..8a9003eb 100644 --- a/src/components/smart/EndpointsPage/EndpointsPage.tsx +++ b/src/components/smart/EndpointsPage/EndpointsPage.tsx @@ -30,7 +30,7 @@ import type { Endpoint as EndpointType } from "@src/@types/Endpoint"; import projectStore from "@src/stores/ProjectStore"; import userStore from "@src/stores/UserStore"; import endpointStore from "@src/stores/EndpointStore"; -import migrationStore from "@src/stores/MigrationStore"; +import deploymentStore from "@src/stores/DeploymentStore"; import replicaStore from "@src/stores/ReplicaStore"; import providerStore from "@src/stores/ProviderStore"; import EndpointDuplicateOptions from "@src/components/modules/EndpointModule/EndpointDuplicateOptions"; @@ -108,13 +108,15 @@ class EndpointsPage extends React.Component<{ history: any }, State> { getEndpointUsage(endpointId: string) { const replicasCount = replicaStore.replicas.filter( r => - r.origin_endpoint_id === endpointId || - r.destination_endpoint_id === endpointId + (r.origin_endpoint_id === endpointId || + r.destination_endpoint_id === endpointId) && + r.scenario === "replica" ).length; - const migrationsCount = migrationStore.migrations.filter( + const migrationsCount = replicaStore.replicas.filter( r => - r.origin_endpoint_id === endpointId || - r.destination_endpoint_id === endpointId + (r.origin_endpoint_id === endpointId || + r.destination_endpoint_id === endpointId) && + r.scenario === "live_migration" ).length; return { migrationsCount, replicasCount }; @@ -122,14 +124,14 @@ class EndpointsPage extends React.Component<{ history: any }, State> { handleProjectChange() { endpointStore.getEndpoints({ showLoading: true }); - migrationStore.getMigrations(); + deploymentStore.getDeployments(); replicaStore.getReplicas(); } handleReloadButtonClick() { projectStore.getProjects(); endpointStore.getEndpoints({ showLoading: true }); - migrationStore.getMigrations(); + deploymentStore.getDeployments(); replicaStore.getReplicas(); } @@ -254,7 +256,7 @@ class EndpointsPage extends React.Component<{ history: any }, State> { await Promise.all([ endpointStore.getEndpoints({ showLoading, skipLog: true }), - migrationStore.getMigrations({ skipLog: true }), + deploymentStore.getDeployments({ skipLog: true }), replicaStore.getReplicas({ skipLog: true }), ]); this.pollTimeout = window.setTimeout(() => { From 92431048c6b7ab5a2494b7a7daa2f9490fa77040 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Tue, 20 Aug 2024 11:11:56 +0000 Subject: [PATCH 28/41] Fix Dashboard Recent Activity entries Fixes deployment entry routing and description --- src/@types/MainItem.ts | 4 ++-- .../DashboardActivity/DashboardActivity.tsx | 10 +++++++--- .../NotificationDropdown/NotificationDropdown.tsx | 2 +- src/sources/NotificationSource.ts | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/@types/MainItem.ts b/src/@types/MainItem.ts index 3d5c4c35..030718d3 100644 --- a/src/@types/MainItem.ts +++ b/src/@types/MainItem.ts @@ -128,7 +128,7 @@ export type DeploymentItemOptions = DeploymentItem & { shutdown_instances: boolean; }; -export type TransferItem = ReplicaItem | MigrationItem | DeploymentItem; +export type TransferItem = ReplicaItem | DeploymentItem; export type ReplicaItemDetails = ReplicaItem & { executions: Execution[]; @@ -142,7 +142,7 @@ export type DeploymentItemDetails = DeploymentItem & { tasks: Task[]; }; -export type TransferItemDetails = ReplicaItemDetails | MigrationItemDetails | DeploymentItemDetails; +export type TransferItemDetails = ReplicaItemDetails | DeploymentItemDetails; export const getTransferItemTitle = (item: TransferItem | null) => { if (!item) { diff --git a/src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx b/src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx index c396b3de..155aa78f 100644 --- a/src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx +++ b/src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx @@ -104,11 +104,15 @@ class DashboardActivity extends React.Component { {this.props.notificationItems .filter((_, i) => i < (this.props.large ? 10 : 5)) .map((item, i) => { + const actionHref = + item.type === "replica" + ? "transfers" : "deployments" + const executionsHref = item.status === "RUNNING" ? item.type === "replica" ? "/executions" - : item.type === "migration" + : item.type === "deployment" ? "/tasks" : "" : ""; @@ -116,7 +120,7 @@ class DashboardActivity extends React.Component { return ( { - {item.type === "replica" ? "RE" : "MI"} + {item.type === "replica" ? "TR" : "DE"} {item.name} diff --git a/src/components/ui/Dropdowns/NotificationDropdown/NotificationDropdown.tsx b/src/components/ui/Dropdowns/NotificationDropdown/NotificationDropdown.tsx index 8ec530bd..cbc6f6ed 100644 --- a/src/components/ui/Dropdowns/NotificationDropdown/NotificationDropdown.tsx +++ b/src/components/ui/Dropdowns/NotificationDropdown/NotificationDropdown.tsx @@ -306,7 +306,7 @@ class NotificationDropdown extends React.Component { - {item.type === "replica" ? "RE" : "DE"} + {item.type === "replica" ? "TR" : "DE"} {item.name} diff --git a/src/sources/NotificationSource.ts b/src/sources/NotificationSource.ts index b201fb95..f7ce783f 100644 --- a/src/sources/NotificationSource.ts +++ b/src/sources/NotificationSource.ts @@ -80,7 +80,7 @@ class NotificationStorage { class DataUtils { static getItemDescription(item: TransferItem) { let item_type = item.type === "replica" - ? "replica" + ? "transfer" : "deployment"; return `New ${item_type} ${item.id.substr( 0, From 899e54e22a33122742da4f4e9f20d3bf6474e33e Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Wed, 21 Aug 2024 13:02:05 +0000 Subject: [PATCH 29/41] Fix some Transfer labels Renames "Replica" text into "Transfer" on buttons and labels, on Transfer pages. --- .../modules/TransferModule/Executions/Executions.tsx | 4 ++-- .../ReplicaDetailsContent/ReplicaDetailsContent.tsx | 8 ++++---- .../smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/modules/TransferModule/Executions/Executions.tsx b/src/components/modules/TransferModule/Executions/Executions.tsx index 9583a64a..231f7902 100644 --- a/src/components/modules/TransferModule/Executions/Executions.tsx +++ b/src/components/modules/TransferModule/Executions/Executions.tsx @@ -412,10 +412,10 @@ class Executions extends React.Component { - It looks like there are no executions in this replica. + It looks like there are no executions in this tranfer. - This replica has not been executed yet. + This tranfer has not been executed yet. diff --git a/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx b/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx index 8644fc96..fbf5ad3b 100644 --- a/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx +++ b/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx @@ -59,7 +59,7 @@ const DetailsBody = styled.div` const NavigationItems = [ { - label: "Replica", + label: "Transfer", value: "", }, { @@ -153,19 +153,19 @@ class ReplicaDetailsContent extends React.Component { disabled={this.getStatus() === "RUNNING"} onClick={this.props.onExecuteClick} > - Execute Replica + Execute
diff --git a/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx b/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx index 91f45d74..2f621f7e 100644 --- a/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx +++ b/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx @@ -708,7 +708,7 @@ class ReplicaDetailsPage extends React.Component { }, }, { - label: "Delete Replica", + label: "Delete", color: ThemePalette.alert, action: () => { this.handleDeleteReplicaClick(); From 4b084688c77e084469878039aed9a0cf6316a05c Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Wed, 21 Aug 2024 13:13:24 +0000 Subject: [PATCH 30/41] Fix empty Deployments list page Fixes empty deployments list page description and removes "Create Deployment" button, since it does not make sense to create a deployment using the creation wizard, these need to be created from a pre-existing Transfer. --- src/components/smart/DeploymentsPage/DeploymentsPage.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/smart/DeploymentsPage/DeploymentsPage.tsx b/src/components/smart/DeploymentsPage/DeploymentsPage.tsx index 25d238b5..e88fc237 100644 --- a/src/components/smart/DeploymentsPage/DeploymentsPage.tsx +++ b/src/components/smart/DeploymentsPage/DeploymentsPage.tsx @@ -321,11 +321,7 @@ class DeploymentsPage extends React.Component<{ history: any }, State> { )} emptyListImage={deploymentLargeImage} emptyListMessage="It seems like you don't have any Deployments in this project." - emptyListExtraMessage="A Coriolis Deployment is a deployment of a Replica between two cloud endpoints." - emptyListButtonLabel="Create a Deployment" - onEmptyListButtonClick={() => { - this.handleEmptyListButtonClick(); - }} + emptyListExtraMessage="A Coriolis Deployment is a deployment of a Transfer between two cloud endpoints." /> } headerComponent={ From f9e99283df526ad1cc422dbde222d0675af002d0 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Wed, 21 Aug 2024 13:27:52 +0000 Subject: [PATCH 31/41] Update Replica Delete modal Renames Replica nomenclature with Transfer in all transfer deletion modal messages and labels --- .../DeleteReplicaModal/DeleteReplicaModal.tsx | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.tsx b/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.tsx index f028aee2..73034e26 100644 --- a/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.tsx +++ b/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.tsx @@ -85,29 +85,29 @@ class DeleteReplicaModal extends React.Component { if (this.props.isMultiReplicaSelection) { return ( - Some of the selected Replicas have been executed at least once and + Some of the selected Transfer have been executed at least once and thus may have disks created on the destination platform. If those - Replicas are to be deleted now, the disks on the destination will - persist. If this is not desired, please use the "Delete Replica + Transfers are to be deleted now, the disks on the destination will + persist. If this is not desired, please use the "Delete Transfer Disks" option to delete those disks before deleting the - Replicas themselves. + Transfers themselves. ); } return ( - This Replica has been executed at least once and thus may have disks - created on the destination platform. If the Replica is to be deleted + This Transfer has been executed at least once and thus may have disks + created on the destination platform. If the Transfer is to be deleted now, the disks on the destination will persist. If this is not - desired, please use the "Delete Replica Disks" option to - delete the disks before deleting the Replica itself. + desired, please use the "Delete Transfer Disks" option to + delete the disks before deleting the Transfer itself. ); } return ( - Deleting a Coriolis Replica is permanent! + Deleting a Coriolis Transfer is permanent! ); } @@ -116,7 +116,7 @@ class DeleteReplicaModal extends React.Component { - Validating Replicas Details + Validating Transfer Details Please wait ... @@ -125,8 +125,8 @@ class DeleteReplicaModal extends React.Component { renderContent() { const message = this.props.isMultiReplicaSelection - ? "Are you sure you want to delete the selected replicas?" - : "Are you sure you want to delete this replica?"; + ? "Are you sure you want to delete the selected transfers?" + : "Are you sure you want to delete this transfer?"; return ( @@ -145,11 +145,11 @@ class DeleteReplicaModal extends React.Component { style={{ marginBottom: "16px" }} alert > - Delete Replica Disks + Delete Transfer Disks ) : null} From fdd3356cc0d0c66e3ac7c9c263f4f3a68f45c5f3 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Wed, 9 Oct 2024 15:41:18 +0300 Subject: [PATCH 32/41] Fix Transfers sidebar mini icon not being displayed --- .../modules/NavigationModule/Navigation/Navigation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/modules/NavigationModule/Navigation/Navigation.tsx b/src/components/modules/NavigationModule/Navigation/Navigation.tsx index 88f2b6c4..6f3862cf 100644 --- a/src/components/modules/NavigationModule/Navigation/Navigation.tsx +++ b/src/components/modules/NavigationModule/Navigation/Navigation.tsx @@ -406,7 +406,7 @@ class Navigation extends React.Component { menuImage = dashboardImage; style = { width: "19px", height: "19px" }; break; - case "replicas": + case "transfers": bullet = "replica"; menuImage = replicaImage; break; From ea40652558352fb50e901fba67b81d14b885a178 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Wed, 16 Oct 2024 17:40:11 +0300 Subject: [PATCH 33/41] Fix deployment's transfer link Fixes the label `Created from Replica`, uses `Transfer` nomenclature. --- .../modules/TransferModule/MainDetails/MainDetails.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/modules/TransferModule/MainDetails/MainDetails.tsx b/src/components/modules/TransferModule/MainDetails/MainDetails.tsx index 3350996e..b1e5e6be 100644 --- a/src/components/modules/TransferModule/MainDetails/MainDetails.tsx +++ b/src/components/modules/TransferModule/MainDetails/MainDetails.tsx @@ -427,7 +427,7 @@ class MainDetails extends React.Component { this.props.item.replica_id ? ( - + {this.props.item.replica_id} From 91e11fb33bf25e0d06b35cca26059a151bbec01b Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Wed, 16 Oct 2024 18:48:26 +0300 Subject: [PATCH 34/41] Fix diagnostics deployments download URL Fixes the API path to deployments diagnostics info by routing it to `/deployments` instead of `/migrations` --- src/stores/LogStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/LogStore.ts b/src/stores/LogStore.ts index 9e7cfbb7..a89ee0bc 100644 --- a/src/stores/LogStore.ts +++ b/src/stores/LogStore.ts @@ -50,7 +50,7 @@ const downloadDiagnosticsIntoZip = async (zipRef: JSZip): Promise => { const [diagnosticsResp, replicasResp, migrationsResp] = await Promise.all([ apiCaller.send({ url: `${baseUrl}/diagnostics` }), apiCaller.send({ url: `${baseUrl}/replicas?show_deleted=true` }), - apiCaller.send({ url: `${baseUrl}/migrations?show_deleted=true` }), + apiCaller.send({ url: `${baseUrl}/deployments?show_deleted=true` }), ]); zipRef.file("diagnostics.json", JSON.stringify(diagnosticsResp.data)); From 1ed8bcd475bab42cb02ad573654fc6235afe8753 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Wed, 16 Oct 2024 19:28:01 +0300 Subject: [PATCH 35/41] Fix minion pool usage href --- .../MinionPoolMachines.tsx | 4 ++-- .../MinionPoolMainDetails.tsx | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMachines.tsx b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMachines.tsx index a846d3b5..b80e60fb 100644 --- a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMachines.tsx +++ b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMachines.tsx @@ -330,10 +330,10 @@ class MinionPoolMachines extends React.Component { {allocatedAction ? ( <> - {allocatedAction.type === "replica" ? "RE" : "MI"} + {allocatedAction.type === "replica" ? "TR" : "DE"} {allocatedAction.instances[0]} diff --git a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.tsx b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.tsx index c1441cc1..76d2aa8f 100644 --- a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.tsx +++ b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.tsx @@ -236,13 +236,17 @@ class MinionPoolMainDetails extends React.Component { } renderUsage(items: TransferItem[]) { - return items.map(item => ( -
- + return items.map(item => { + const actionHref = + item.type === "replica" + ? "transfers" : "deployments" + + return (
+ {item.instances[0]} -
- )); +
); + }); } renderTable() { From cbd19b05daa783c4a117a5642ece7e92589c68de Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Tue, 22 Oct 2024 16:34:40 +0300 Subject: [PATCH 36/41] Revert to referring to reserved licences as "Used" This patch will display reserved and consumed licences as `Used`, instead of wrongfully displaying them as `Fulfilled`. --- .../DashboardModule/DashboardLicence/DashboardLicence.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.tsx b/src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.tsx index 284f873d..af1e6230 100644 --- a/src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.tsx +++ b/src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.tsx @@ -201,7 +201,7 @@ class DashboardLicence extends React.Component { color: ThemePalette.alert, current: info.currentPerformedReplicas, total: info.currentAvailableReplicas, - label: "Fulfilled Replica", + label: "Used Replica", info: `The number of replicas fulfilled over the number of replicas available in all currently active licences (including non-activated floating licences)`, }, @@ -211,7 +211,7 @@ class DashboardLicence extends React.Component { color: ThemePalette.primary, current: info.currentPerformedMigrations, total: info.currentAvailableMigrations, - label: "Fulfilled Migration", + label: "Used Migration", info: `The number of migrations fulfilled over the number of migrations available in all currently active licences (including non-activated floating licences)`, }, From b9bbf88e47a4cee13b388de838f8359d0f55aa69 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Tue, 22 Oct 2024 16:40:40 +0300 Subject: [PATCH 37/41] Remove `Replica` nomenclature from Transfer Edit window --- .../TransferItemModal/TransferItemModal.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/components/modules/TransferModule/TransferItemModal/TransferItemModal.tsx b/src/components/modules/TransferModule/TransferItemModal/TransferItemModal.tsx index 6cfca4de..ea5e51fc 100644 --- a/src/components/modules/TransferModule/TransferItemModal/TransferItemModal.tsx +++ b/src/components/modules/TransferModule/TransferItemModal/TransferItemModal.tsx @@ -1098,9 +1098,7 @@ class TransferItemModal extends React.Component { return ( this.scrollableRef} @@ -1117,11 +1115,7 @@ class TransferItemModal extends React.Component { onReloadClick={() => { this.handleReload(); }} - reloadLabel={ - this.props.type === "replica" - ? "Reload All Replica Options" - : "Reload All Migration Options" - } + reloadLabel="Reload All Options" /> ); From f4415c05445f6e3231aef97e9804bdb87edc9d1e Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Thu, 24 Oct 2024 20:27:14 +0300 Subject: [PATCH 38/41] Fix execution erorr handling Prior to this fix, upon clicking on `Execute`, the executions page would get loaded before waiting for a response. If an error would get returned from the request, it would get redirected to executions page with a loading circle, without them actually loading. This patch makes sure to wait for the response before redirecting to the executions page. If an error occurs, it gets displayed in a pop-up, but the modal never closes. --- .../ReplicaExecutionOptions.tsx | 21 ++++++++++++------- .../ReplicaDetailsPage/ReplicaDetailsPage.tsx | 17 +++++++++++---- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/components/modules/TransferModule/ReplicaExecutionOptions/ReplicaExecutionOptions.tsx b/src/components/modules/TransferModule/ReplicaExecutionOptions/ReplicaExecutionOptions.tsx index d59f5a89..1372b5f8 100644 --- a/src/components/modules/TransferModule/ReplicaExecutionOptions/ReplicaExecutionOptions.tsx +++ b/src/components/modules/TransferModule/ReplicaExecutionOptions/ReplicaExecutionOptions.tsx @@ -25,6 +25,7 @@ import { executionOptions } from "@src/constants"; import type { Field } from "@src/@types/Field"; import executionImage from "./images/execution.svg"; +import LoadingButton from "@src/components/ui/LoadingButton/LoadingButton"; const Wrapper = styled.div` display: flex; @@ -56,6 +57,7 @@ type Props = { disableExecutionOptions: boolean; onChange?: (fieldName: string, fieldValue: string) => void; executionLabel: string; + executing?: boolean; onCancelClick: () => void; onExecuteClick: (options: Field[]) => void; }; @@ -66,6 +68,7 @@ type State = { class ReplicaExecutionOptions extends React.Component { static defaultProps = { executionLabel: "Execute", + executing: false, }; state: State = { @@ -137,13 +140,17 @@ class ReplicaExecutionOptions extends React.Component { - + {this.props.executing ? ( + Executing ... + ) : ( + + )}
); diff --git a/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx b/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx index 2f621f7e..11e85a32 100644 --- a/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx +++ b/src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx @@ -71,6 +71,7 @@ type State = { pausePolling: boolean; initialLoading: boolean; deploying: boolean; + executing: boolean; }; @observer class ReplicaDetailsPage extends React.Component { @@ -89,6 +90,7 @@ class ReplicaDetailsPage extends React.Component { pausePolling: false, initialLoading: true, deploying: false, + executing: false, }; stopPolling: boolean | null = null; @@ -549,14 +551,20 @@ class ReplicaDetailsPage extends React.Component { } } - executeReplica(fields: Field[]) { + async executeReplica(fields: Field[]) { const replica = this.replica; if (!replica) { return; } - replicaStore.execute(replica.id, fields); - this.handleCloseOptionsModal(); - this.props.history.push(`/transfers/${replica.id}/executions`); + this.setState({ executing: true }); + try { + await replicaStore.execute(replica.id, fields); + + this.handleCloseOptionsModal(); + this.props.history.push(`/transfers/${replica.id}/executions`); + } finally { + this.setState({ executing: false }); + } } async pollData() { @@ -840,6 +848,7 @@ class ReplicaDetailsPage extends React.Component { onExecuteClick={fields => { this.executeReplica(fields); }} + executing={this.state.executing} /> {this.state.showDeploymentModal ? ( From 3d94d5cac7f01420a2a073b8a93b7ec85537ee08 Mon Sep 17 00:00:00 2001 From: Daniel Vincze Date: Fri, 22 Nov 2024 17:10:09 +0200 Subject: [PATCH 39/41] Fully Refactor transfer/deployment nomenclature --- config.ts | 2 +- src/@types/Execution.ts | 8 +- src/@types/MainItem.ts | 34 +- src/@types/Schedule.ts | 2 +- src/components/App.tsx | 10 +- .../DashboardActivity/DashboardActivity.tsx | 20 +- .../images/{replica.svg => transfer.svg} | 0 .../DashboardContent.spec.tsx | 6 +- .../DashboardContent/DashboardContent.tsx | 28 +- .../DashboardExecutions.spec.tsx | 4 +- .../DashboardExecutions.tsx | 10 +- .../DashboardTopEndpoints.spec.tsx | 14 +- .../DashboardTopEndpoints.tsx | 20 +- .../EndpointDetailsContent.tsx | 10 +- .../MinionPoolDetailsContent.spec.tsx | 2 +- .../MinionPoolDetailsContent.tsx | 8 +- .../MinionPoolMachines.spec.tsx | 2 +- .../MinionPoolMachines.tsx | 18 +- .../MinionPoolMainDetails.spec.tsx | 2 +- .../MinionPoolMainDetails.tsx | 10 +- .../Navigation/Navigation.tsx | 10 +- .../{replica-menu.svg => transfer-menu.svg} | 0 .../DeleteReplicaModal/package.json | 6 - .../DeleteReplicaModal.spec.tsx | 14 +- .../DeleteTransferModal.tsx} | 18 +- .../DeleteTransferModal/package.json | 6 + .../DeploymentFields.ts} | 4 +- .../DeploymentOptions.tsx} | 16 +- .../ReplicaDeploymentOptions.spec.tsx | 2 +- .../images/deployment.svg} | 0 .../DeploymentOptions/package.json | 6 + .../story.tsx | 0 .../MainDetails/MainDetails.tsx | 12 +- .../MigrationDetailsContent.spec.tsx | 74 --- .../MigrationDetailsContent.tsx | 142 ----- .../MigrationDetailsContent/package.json | 6 - .../MigrationDetailsContent/story.tsx | 95 --- .../ReplicaDeploymentOptions/package.json | 6 - .../ReplicaDetailsContent/package.json | 6 - .../ReplicaExecutionOptions/package.json | 6 - .../ReplicaMigrationOptions.spec.tsx | 154 ----- .../ReplicaMigrationOptions.tsx | 343 ---------- .../images/replica-migration.svg | 25 - .../ReplicaMigrationOptions/package.json | 6 - .../replicaMigrationFields.ts | 33 - .../ReplicaMigrationOptions/story.tsx | 25 - .../TransferModule/Schedule/Schedule.tsx | 4 +- .../ReplicaDetailsContent.spec.tsx | 2 +- .../TransferDetailsContent.tsx} | 12 +- .../TransferDetailsContent/package.json | 6 + .../story.tsx | 0 .../TransferDetailsTable.tsx | 10 +- .../ReplicaExecutionOptions.spec.tsx | 2 +- .../TransferExecutionOptions.tsx} | 4 +- .../images/execution.svg | 0 .../TransferExecutionOptions/package.json | 6 + .../story.tsx | 0 .../TransferItemModal/TransferItemModal.tsx | 160 ++--- .../TransferListItem/TransferListItem.tsx | 19 +- .../WizardOptions/WizardOptions.tsx | 25 +- .../WizardPageContent/WizardPageContent.tsx | 7 +- .../WizardSummary/WizardSummary.tsx | 21 - .../AssessmentDetailsPage.tsx | 2 +- .../smart/DashboardPage/DashboardPage.tsx | 10 +- .../DeploymentDetailsPage.tsx | 64 +- .../smart/DeploymentsPage/DeploymentsPage.tsx | 18 +- .../EndpointDetailsPage.tsx | 26 +- .../smart/EndpointsPage/EndpointsPage.tsx | 12 +- .../MetalHubServerDetailsPage.tsx | 2 +- .../MigrationDetailsPage.tsx | 600 ------------------ .../MigrationDetailsPage/images/migration.svg | 15 - .../smart/MigrationDetailsPage/package.json | 6 - .../smart/MigrationsPage/MigrationsPage.tsx | 377 ----------- .../MigrationsPage/images/migration-large.svg | 16 - .../smart/MigrationsPage/images/migration.svg | 14 - .../smart/MigrationsPage/package.json | 6 - .../MinionPoolDetailsPage.tsx | 12 +- .../smart/ReplicaDetailsPage/package.json | 6 - .../smart/ReplicasPage/package.json | 6 - .../TransferDetailsPage.tsx} | 330 +++++----- .../images/live_migration.svg | 0 .../images/replica.svg | 0 .../smart/TransferDetailsPage/package.json | 6 + .../TransfersPage.tsx} | 194 +++--- .../images/live-migration.svg | 0 .../images/replica-large.svg | 0 .../images/replica.svg | 0 .../smart/TransfersPage/package.json | 6 + .../smart/WizardPage/WizardPage.tsx | 22 +- .../NewItemDropdown/NewItemDropdown.tsx | 12 - .../NotificationDropdown.tsx | 14 +- src/constants.ts | 26 +- src/plugins/default/OptionsSchemaPlugin.ts | 3 +- src/plugins/openstack/ContentPlugin.tsx | 2 +- src/sources/DeploymentSource.ts | 22 +- src/sources/MigrationSource.ts | 2 +- src/sources/NotificationSource.ts | 14 +- src/sources/ProviderSource.ts | 4 +- src/sources/ScheduleSource.ts | 26 +- .../{ReplicaSource.ts => TransferSource.ts} | 124 ++-- src/sources/WizardSource.ts | 10 +- src/stores/DeploymentStore.ts | 16 +- src/stores/LogStore.ts | 8 +- src/stores/ProviderStore.ts | 2 - src/stores/ReplicaStore.ts | 321 ---------- src/stores/ScheduleStore.ts | 30 +- src/stores/TransferStore.ts | 321 ++++++++++ src/stores/WizardStore.ts | 8 +- src/utils/LabelDictionary.ts | 10 +- tests/mocks/TransferMock.ts | 8 +- ui-mod-sample.json | 4 +- 111 files changed, 1082 insertions(+), 3158 deletions(-) rename src/components/modules/DashboardModule/DashboardActivity/images/{replica.svg => transfer.svg} (100%) rename src/components/modules/NavigationModule/Navigation/images/{replica-menu.svg => transfer-menu.svg} (100%) delete mode 100644 src/components/modules/TransferModule/DeleteReplicaModal/package.json rename src/components/modules/TransferModule/{DeleteReplicaModal => DeleteTransferModal}/DeleteReplicaModal.spec.tsx (78%) rename src/components/modules/TransferModule/{DeleteReplicaModal/DeleteReplicaModal.tsx => DeleteTransferModal/DeleteTransferModal.tsx} (90%) create mode 100644 src/components/modules/TransferModule/DeleteTransferModal/package.json rename src/components/modules/TransferModule/{ReplicaDeploymentOptions/replicaDeploymentFields.ts => DeploymentOptions/DeploymentFields.ts} (91%) rename src/components/modules/TransferModule/{ReplicaDeploymentOptions/ReplicaDeploymentOptions.tsx => DeploymentOptions/DeploymentOptions.tsx} (95%) rename src/components/modules/TransferModule/{ReplicaDeploymentOptions => DeploymentOptions}/ReplicaDeploymentOptions.spec.tsx (99%) rename src/components/modules/TransferModule/{ReplicaDeploymentOptions/images/replica-deployment.svg => DeploymentOptions/images/deployment.svg} (100%) create mode 100644 src/components/modules/TransferModule/DeploymentOptions/package.json rename src/components/modules/TransferModule/{ReplicaDeploymentOptions => DeploymentOptions}/story.tsx (100%) delete mode 100644 src/components/modules/TransferModule/MigrationDetailsContent/MigrationDetailsContent.spec.tsx delete mode 100644 src/components/modules/TransferModule/MigrationDetailsContent/MigrationDetailsContent.tsx delete mode 100644 src/components/modules/TransferModule/MigrationDetailsContent/package.json delete mode 100644 src/components/modules/TransferModule/MigrationDetailsContent/story.tsx delete mode 100644 src/components/modules/TransferModule/ReplicaDeploymentOptions/package.json delete mode 100644 src/components/modules/TransferModule/ReplicaDetailsContent/package.json delete mode 100644 src/components/modules/TransferModule/ReplicaExecutionOptions/package.json delete mode 100644 src/components/modules/TransferModule/ReplicaMigrationOptions/ReplicaMigrationOptions.spec.tsx delete mode 100644 src/components/modules/TransferModule/ReplicaMigrationOptions/ReplicaMigrationOptions.tsx delete mode 100644 src/components/modules/TransferModule/ReplicaMigrationOptions/images/replica-migration.svg delete mode 100644 src/components/modules/TransferModule/ReplicaMigrationOptions/package.json delete mode 100644 src/components/modules/TransferModule/ReplicaMigrationOptions/replicaMigrationFields.ts delete mode 100644 src/components/modules/TransferModule/ReplicaMigrationOptions/story.tsx rename src/components/modules/TransferModule/{ReplicaDetailsContent => TransferDetailsContent}/ReplicaDetailsContent.spec.tsx (99%) rename src/components/modules/TransferModule/{ReplicaDetailsContent/ReplicaDetailsContent.tsx => TransferDetailsContent/TransferDetailsContent.tsx} (96%) create mode 100644 src/components/modules/TransferModule/TransferDetailsContent/package.json rename src/components/modules/TransferModule/{ReplicaDetailsContent => TransferDetailsContent}/story.tsx (100%) rename src/components/modules/TransferModule/{ReplicaExecutionOptions => TransferExecutionOptions}/ReplicaExecutionOptions.spec.tsx (98%) rename src/components/modules/TransferModule/{ReplicaExecutionOptions/ReplicaExecutionOptions.tsx => TransferExecutionOptions/TransferExecutionOptions.tsx} (97%) rename src/components/modules/TransferModule/{ReplicaExecutionOptions => TransferExecutionOptions}/images/execution.svg (100%) create mode 100644 src/components/modules/TransferModule/TransferExecutionOptions/package.json rename src/components/modules/TransferModule/{ReplicaExecutionOptions => TransferExecutionOptions}/story.tsx (100%) delete mode 100644 src/components/smart/MigrationDetailsPage/MigrationDetailsPage.tsx delete mode 100644 src/components/smart/MigrationDetailsPage/images/migration.svg delete mode 100644 src/components/smart/MigrationDetailsPage/package.json delete mode 100644 src/components/smart/MigrationsPage/MigrationsPage.tsx delete mode 100644 src/components/smart/MigrationsPage/images/migration-large.svg delete mode 100644 src/components/smart/MigrationsPage/images/migration.svg delete mode 100644 src/components/smart/MigrationsPage/package.json delete mode 100644 src/components/smart/ReplicaDetailsPage/package.json delete mode 100644 src/components/smart/ReplicasPage/package.json rename src/components/smart/{ReplicaDetailsPage/ReplicaDetailsPage.tsx => TransferDetailsPage/TransferDetailsPage.tsx} (73%) rename src/components/smart/{ReplicaDetailsPage => TransferDetailsPage}/images/live_migration.svg (100%) rename src/components/smart/{ReplicaDetailsPage => TransferDetailsPage}/images/replica.svg (100%) create mode 100644 src/components/smart/TransferDetailsPage/package.json rename src/components/smart/{ReplicasPage/ReplicasPage.tsx => TransfersPage/TransfersPage.tsx} (73%) rename src/components/smart/{ReplicasPage => TransfersPage}/images/live-migration.svg (100%) rename src/components/smart/{ReplicasPage => TransfersPage}/images/replica-large.svg (100%) rename src/components/smart/{ReplicasPage => TransfersPage}/images/replica.svg (100%) create mode 100644 src/components/smart/TransfersPage/package.json rename src/sources/{ReplicaSource.ts => TransferSource.ts} (73%) delete mode 100644 src/stores/ReplicaStore.ts create mode 100644 src/stores/TransferStore.ts diff --git a/config.ts b/config.ts index 7ebf8a53..f8096148 100644 --- a/config.ts +++ b/config.ts @@ -176,7 +176,7 @@ const conf: Config = { ], // The number of items per page applicable to main lists: - // replicas, migrations, endpoints, users etc. + // transfers, deployments, endpoints, users etc. mainListItemsPerPage: 20, maxMinionPoolEventsPerPage: 50, diff --git a/src/@types/Execution.ts b/src/@types/Execution.ts index 47283734..be6e4de8 100644 --- a/src/@types/Execution.ts +++ b/src/@types/Execution.ts @@ -22,10 +22,10 @@ export type Execution = { updated_at: string; deleted_at?: string; type: - | "replica_execution" - | "replica_disks_delete" - | "replica_deploy" - | "replica_update"; + | "transfer_execution" + | "transfer_disks_delete" + | "transfer_deploy" + | "transfer_update"; }; export type ExecutionTasks = Execution & { diff --git a/src/@types/MainItem.ts b/src/@types/MainItem.ts index 030718d3..bd7c442f 100644 --- a/src/@types/MainItem.ts +++ b/src/@types/MainItem.ts @@ -83,7 +83,7 @@ type BaseItem = { destination_environment: { [prop: string]: any }; source_environment: { [prop: string]: any }; transfer_result: { [prop: string]: Instance } | null; - replication_count?: number; + // replication_count?: number; storage_mappings?: StorageMapping | null; network_map?: TransferNetworkMap; last_execution_status: string; @@ -92,9 +92,9 @@ type BaseItem = { user_scripts?: UserScriptData; }; -export type ReplicaItem = BaseItem & { - type: "replica"; - scenario?: string; +export type TransferItem = BaseItem & { + type: "transfer"; + scenario: string; }; export type UserScriptData = { @@ -107,20 +107,10 @@ export type UserScriptData = { }; }; -export type MigrationItem = BaseItem & { - type: "migration"; - replica_id?: string; -}; - -export type MigrationItemOptions = MigrationItem & { - skip_os_morphing: boolean; - shutdown_instances: boolean; -}; - export type DeploymentItem = BaseItem & { type: "deployment"; - replica_id: string; - replica_scenario: string; + transfer_id: string; + transfer_scenario: string; }; export type DeploymentItemOptions = DeploymentItem & { @@ -128,23 +118,19 @@ export type DeploymentItemOptions = DeploymentItem & { shutdown_instances: boolean; }; -export type TransferItem = ReplicaItem | DeploymentItem; +export type ActionItem = TransferItem | DeploymentItem; -export type ReplicaItemDetails = ReplicaItem & { +export type TransferItemDetails = TransferItem & { executions: Execution[]; }; -export type MigrationItemDetails = MigrationItem & { - tasks: Task[]; -}; - export type DeploymentItemDetails = DeploymentItem & { tasks: Task[]; }; -export type TransferItemDetails = ReplicaItemDetails | DeploymentItemDetails; +export type ActionItemDetails = TransferItemDetails | DeploymentItemDetails; -export const getTransferItemTitle = (item: TransferItem | null) => { +export const getTransferItemTitle = (item: ActionItem | null) => { if (!item) { return null; } diff --git a/src/@types/Schedule.ts b/src/@types/Schedule.ts index d2010983..213bdc8f 100644 --- a/src/@types/Schedule.ts +++ b/src/@types/Schedule.ts @@ -31,6 +31,6 @@ export type Schedule = { }; export type ScheduleBulkItem = { - replicaId: string; + transferId: string; schedules: Schedule[]; }; diff --git a/src/components/App.tsx b/src/components/App.tsx index f71d7761..3f60eb92 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -21,9 +21,9 @@ import { observe } from "mobx"; import Fonts from "@src/components/ui/Fonts"; import NotificationsModule from "@src/components/modules/NotificationsModule"; import LoginPage from "@src/components/smart/LoginPage"; -import ReplicasPage from "@src/components/smart/ReplicasPage"; +import TransfersPage from "@src/components/smart/TransfersPage/TransfersPage"; import MessagePage from "@src/components/smart/MessagePage"; -import ReplicaDetailsPage from "@src/components/smart/ReplicaDetailsPage"; +import TransferDetailsPage from "@src/components/smart/TransferDetailsPage/TransferDetailsPage"; import DeploymentsPage from "@src/components/smart/DeploymentsPage"; import DeploymentDetailsPage from "@src/components/smart/DeploymentDetailsPage"; import MetalHubServersPage from "@src/components/smart/MetalHubServersPage"; @@ -211,9 +211,9 @@ class App extends React.Component, State> { } {renderRoute("/dashboard", DashboardPage)} - {renderRoute("/transfers", ReplicasPage, true)} - {renderRoute("/transfers/:id", ReplicaDetailsPage, true)} - {renderRoute("/transfers/:id/:page", ReplicaDetailsPage)} + {renderRoute("/transfers", TransfersPage, true)} + {renderRoute("/transfers/:id", TransferDetailsPage, true)} + {renderRoute("/transfers/:id/:page", TransferDetailsPage)} {renderRoute("/deployments", DeploymentsPage, true)} {renderRoute("/deployments/:id", DeploymentDetailsPage, true)} {renderRoute("/deployments/:id/:page", DeploymentDetailsPage)} diff --git a/src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx b/src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx index 155aa78f..e50b638a 100644 --- a/src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx +++ b/src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx @@ -23,7 +23,7 @@ import Button from "@src/components/ui/Button"; import { InfoColumn, MainItemInfo, - ItemReplicaBadge, + ItemTransferBadge, ItemTitle, ItemDescription, } from "@src/components/ui/Dropdowns/NotificationDropdown"; @@ -31,7 +31,7 @@ import { import type { NotificationItemData } from "@src/@types/NotificationItem"; import { ThemePalette, ThemeProps } from "@src/components/Theme"; -import replicaImage from "./images/replica.svg"; +import transferImage from "./images/transfer.svg"; const Wrapper = styled.div` flex-grow: 1; @@ -80,9 +80,9 @@ const NoItems = styled.div` align-items: center; width: 100%; `; -const ReplicaImage = styled.div` +const TransferImage = styled.div` ${ThemeProps.exactSize("148px")} - background: url('${replicaImage}') center no-repeat; + background: url('${transferImage}') center no-repeat; `; const Message = styled.div` text-align: center; @@ -105,12 +105,12 @@ class DashboardActivity extends React.Component { .filter((_, i) => i < (this.props.large ? 10 : 5)) .map((item, i) => { const actionHref = - item.type === "replica" + item.type === "transfer" ? "transfers" : "deployments" const executionsHref = item.status === "RUNNING" - ? item.type === "replica" + ? item.type === "transfer" ? "/executions" : item.type === "deployment" ? "/tasks" @@ -129,9 +129,9 @@ class DashboardActivity extends React.Component { - - {item.type === "replica" ? "TR" : "DE"} - + + {item.type === "transfer" ? "TR" : "DE"} + {item.name} {item.description} @@ -146,7 +146,7 @@ class DashboardActivity extends React.Component { renderNoItems() { return ( - + There is no recent activity
diff --git a/src/components/modules/DashboardModule/DashboardActivity/images/replica.svg b/src/components/modules/DashboardModule/DashboardActivity/images/transfer.svg similarity index 100% rename from src/components/modules/DashboardModule/DashboardActivity/images/replica.svg rename to src/components/modules/DashboardModule/DashboardActivity/images/transfer.svg diff --git a/src/components/modules/DashboardModule/DashboardContent/DashboardContent.spec.tsx b/src/components/modules/DashboardModule/DashboardContent/DashboardContent.spec.tsx index 5d0cee24..2af5e887 100644 --- a/src/components/modules/DashboardModule/DashboardContent/DashboardContent.spec.tsx +++ b/src/components/modules/DashboardModule/DashboardContent/DashboardContent.spec.tsx @@ -35,11 +35,11 @@ describe("DashboardContent", () => { beforeEach(() => { defaultProps = { - replicas: [], + transfers: [], migrations: [], endpoints: [], projects: [], - replicasLoading: false, + transfersLoading: false, migrationsLoading: false, endpointsLoading: false, usersLoading: false, @@ -52,7 +52,7 @@ describe("DashboardContent", () => { licenceError: null, notificationItems: [], isAdmin: false, - onNewReplicaClick: jest.fn(), + onNewTransferClick: jest.fn(), onNewEndpointClick: jest.fn(), onAddLicenceClick: jest.fn(), }; diff --git a/src/components/modules/DashboardModule/DashboardContent/DashboardContent.tsx b/src/components/modules/DashboardModule/DashboardContent/DashboardContent.tsx index 67607404..506edb33 100644 --- a/src/components/modules/DashboardModule/DashboardContent/DashboardContent.tsx +++ b/src/components/modules/DashboardModule/DashboardContent/DashboardContent.tsx @@ -30,7 +30,7 @@ import type { Project } from "@src/@types/Project"; import type { User } from "@src/@types/User"; import type { Licence, LicenceServerStatus } from "@src/@types/Licence"; import type { NotificationItemData } from "@src/@types/NotificationItem"; -import { ReplicaItem, DeploymentItem } from "@src/@types/MainItem"; +import { TransferItem, DeploymentItem } from "@src/@types/MainItem"; const MIDDLE_WIDTHS = ["264px", "264px", "264px"]; @@ -52,11 +52,11 @@ const MiddleMobileLayout = styled.div` `; type Props = { - replicas: ReplicaItem[]; + transfers: TransferItem[]; deployments: DeploymentItem[]; endpoints: Endpoint[]; projects: Project[]; - replicasLoading: boolean; + transfersLoading: boolean; deploymentsLoading: boolean; endpointsLoading: boolean; usersLoading: boolean; @@ -69,7 +69,7 @@ type Props = { licenceError: string | null; notificationItems: NotificationItemData[]; isAdmin: boolean; - onNewReplicaClick: () => void; + onNewTransferClick: () => void; onNewEndpointClick: () => void; onAddLicenceClick: () => void; }; @@ -122,14 +122,14 @@ class DashboardContent extends React.Component { width: MIDDLE_WIDTHS[0], } } - onNewClick={this.props.onNewReplicaClick} + onNewClick={this.props.onNewTransferClick} />, { } getReplicas() { - return this.props.replicas.filter( - (r: ReplicaItem) => r.scenario === "replica"); + return this.props.transfers.filter( + (r: TransferItem) => r.scenario === "replica"); } getLiveMigrations() { - return this.props.replicas.filter( - (r: ReplicaItem) => r.scenario === "live_migration"); + return this.props.transfers.filter( + (r: TransferItem) => r.scenario === "live_migration"); } render() { @@ -191,13 +191,13 @@ class DashboardContent extends React.Component { value: this.getReplicas().length, color: ThemePalette.alert, link: "/transfers", - loading: this.props.replicasLoading, + loading: this.props.transfersLoading, }, { label: "Migrations", value: this.getLiveMigrations().length, link: "/transfers", - loading: this.props.replicasLoading, + loading: this.props.transfersLoading, }, { label: "Endpoints", @@ -234,7 +234,7 @@ class DashboardContent extends React.Component { ); diff --git a/src/components/modules/DashboardModule/DashboardExecutions/DashboardExecutions.spec.tsx b/src/components/modules/DashboardModule/DashboardExecutions/DashboardExecutions.spec.tsx index ee2d869e..2ea33c1f 100644 --- a/src/components/modules/DashboardModule/DashboardExecutions/DashboardExecutions.spec.tsx +++ b/src/components/modules/DashboardModule/DashboardExecutions/DashboardExecutions.spec.tsx @@ -15,7 +15,7 @@ along with this program. If not, see . import { DateTime } from "luxon"; import React from "react"; -import { MigrationItem, ReplicaItem } from "@src/@types/MainItem"; +import { MigrationItem, TransferItem } from "@src/@types/MainItem"; import { render } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import TestUtils from "@tests/TestUtils"; @@ -23,7 +23,7 @@ import TestUtils from "@tests/TestUtils"; import DashboardExecutions from "./DashboardExecutions"; type BuildType = T extends "replica" - ? ReplicaItem + ? TransferItem : MigrationItem; const buildItem = ( diff --git a/src/components/modules/DashboardModule/DashboardExecutions/DashboardExecutions.tsx b/src/components/modules/DashboardModule/DashboardExecutions/DashboardExecutions.tsx index 6df9a835..b1b864fe 100644 --- a/src/components/modules/DashboardModule/DashboardExecutions/DashboardExecutions.tsx +++ b/src/components/modules/DashboardModule/DashboardExecutions/DashboardExecutions.tsx @@ -17,7 +17,7 @@ import { observer } from "mobx-react"; import * as React from "react"; import styled from "styled-components"; -import { MigrationItem, ReplicaItem, TransferItem } from "@src/@types/MainItem"; +import { TransferItem, ActionItem } from "@src/@types/MainItem"; import DashboardBarChart from "@src/components/modules/DashboardModule/DashboardBarChart"; import { ThemePalette, ThemeProps } from "@src/components/Theme"; import DropdownLink from "@src/components/ui/Dropdowns/DropdownLink"; @@ -129,8 +129,8 @@ const EmptyBackgroundImage = styled.div` type Props = { // eslint-disable-next-line react/no-unused-prop-types - replicas: ReplicaItem[]; - migrations: ReplicaItem[]; + replicas: TransferItem[]; + migrations: TransferItem[]; loading: boolean; }; type GroupedData = { @@ -169,7 +169,7 @@ class DashboardExecutions extends React.Component { } groupCreations(props: Props) { - let creations: ReplicaItem[] = [...props.replicas, ...props.migrations]; + let creations: TransferItem[] = [...props.replicas, ...props.migrations]; const periodUnit: any = this.state.selectedPeriod.split("-")[1]; const periodValue: any = Number(this.state.selectedPeriod.split("-")[0]); @@ -187,7 +187,7 @@ class DashboardExecutions extends React.Component { this.groupByPeriod(creations, periodUnit); } - groupByPeriod(transferItems: ReplicaItem[], periodUnit: string) { + groupByPeriod(transferItems: TransferItem[], periodUnit: string) { const groupedData: GroupedData[] = []; const periods: { [period: string]: { replicas: number; migrations: number }; diff --git a/src/components/modules/DashboardModule/DashboardTopEndpoints/DashboardTopEndpoints.spec.tsx b/src/components/modules/DashboardModule/DashboardTopEndpoints/DashboardTopEndpoints.spec.tsx index d12c8032..33715081 100644 --- a/src/components/modules/DashboardModule/DashboardTopEndpoints/DashboardTopEndpoints.spec.tsx +++ b/src/components/modules/DashboardModule/DashboardTopEndpoints/DashboardTopEndpoints.spec.tsx @@ -15,7 +15,7 @@ along with this program. If not, see . import React from "react"; import { Endpoint } from "@src/@types/Endpoint"; -import { MigrationItem, ReplicaItem } from "@src/@types/MainItem"; +import { MigrationItem, TransferItem } from "@src/@types/MainItem"; import { fireEvent, render } from "@testing-library/react"; import TestUtils from "@tests/TestUtils"; @@ -24,7 +24,7 @@ import DashboardTopEndpoints from "./DashboardTopEndpoints"; jest.mock("react-router-dom", () => ({ Link: "a" })); type BuildType = T extends "replica" - ? ReplicaItem + ? TransferItem : MigrationItem; const buildItem = ( @@ -64,7 +64,7 @@ const buildEndpoint = (id: string): Endpoint => ({ connection_info: {}, }); -const replicas: DashboardTopEndpoints["props"]["replicas"] = [ +const replicas: DashboardTopEndpoints["props"]["transfers"] = [ buildItem("replica", "a", "b"), buildItem("replica", "a", "b"), buildItem("replica", "c", "d"), @@ -86,7 +86,7 @@ const endpoints: DashboardTopEndpoints["props"]["endpoints"] = [ describe("DashboardTopEndpoints", () => { const defaultProps: DashboardTopEndpoints["props"] = { - replicas, + transfers: replicas, migrations, endpoints, style: {}, @@ -98,7 +98,7 @@ describe("DashboardTopEndpoints", () => { render( { render( @@ -125,7 +125,7 @@ describe("DashboardTopEndpoints", () => { diff --git a/src/components/modules/DashboardModule/DashboardTopEndpoints/DashboardTopEndpoints.tsx b/src/components/modules/DashboardModule/DashboardTopEndpoints/DashboardTopEndpoints.tsx index d2d6f44c..bf3d35b6 100644 --- a/src/components/modules/DashboardModule/DashboardTopEndpoints/DashboardTopEndpoints.tsx +++ b/src/components/modules/DashboardModule/DashboardTopEndpoints/DashboardTopEndpoints.tsx @@ -26,7 +26,7 @@ import { ThemePalette, ThemeProps } from "@src/components/Theme"; import type { Endpoint } from "@src/@types/Endpoint"; -import { ReplicaItem } from "@src/@types/MainItem"; +import { TransferItem } from "@src/@types/MainItem"; import endpointImage from "./images/endpoint.svg"; const Wrapper = styled.div` @@ -131,12 +131,12 @@ const Message = styled.div` type GroupedEndpoint = { endpoint: Endpoint; replicasCount: number; - deploymentsCount: number; + migrationsCount: number; value: number; }; type Props = { // eslint-disable-next-line react/no-unused-prop-types - replicas: ReplicaItem[]; + transfers: TransferItem[]; // eslint-disable-next-line react/no-unused-prop-types endpoints: Endpoint[]; style: React.CSSProperties; @@ -176,7 +176,7 @@ class DashboardTopEndpoints extends React.Component { calculateGroupedEndpoints(props: Props) { const groupedEndpoints: GroupedEndpoint[] = []; - const count = (mainItems: ReplicaItem[], endpointId: string, scenario: string) => + const count = (mainItems: TransferItem[], endpointId: string, scenario: string) => mainItems.filter( r => r.scenario === scenario && ( @@ -185,13 +185,13 @@ class DashboardTopEndpoints extends React.Component { ).length; props.endpoints.forEach(endpoint => { - const replicasCount = count(props.replicas, endpoint.id, "replica"); - const deploymentsCount = count(props.replicas, endpoint.id, "live_migration"); + const replicasCount = count(props.transfers, endpoint.id, "replica"); + const migrationsCount = count(props.transfers, endpoint.id, "live_migration"); groupedEndpoints.push({ endpoint, - replicasCount, - deploymentsCount, - value: replicasCount + deploymentsCount, + replicasCount: replicasCount, + migrationsCount: migrationsCount, + value: replicasCount + migrationsCount, }); }); this.setState({ groupedEndpoints }); @@ -262,7 +262,7 @@ class DashboardTopEndpoints extends React.Component { {groupedEndpoint.replicasCount} Replicas - {groupedEndpoint.deploymentsCount} Deployments + {groupedEndpoint.migrationsCount} Migrations {groupedEndpoint.value} Total diff --git a/src/components/modules/EndpointModule/EndpointDetailsContent/EndpointDetailsContent.tsx b/src/components/modules/EndpointModule/EndpointDetailsContent/EndpointDetailsContent.tsx index 5bacdbdd..3b10ee8f 100644 --- a/src/components/modules/EndpointModule/EndpointDetailsContent/EndpointDetailsContent.tsx +++ b/src/components/modules/EndpointModule/EndpointDetailsContent/EndpointDetailsContent.tsx @@ -20,7 +20,7 @@ import styled from "styled-components"; import { Field as FieldType } from "@src/@types/Field"; import { getTransferItemTitle, - ReplicaItem, + TransferItem, } from "@src/@types/MainItem"; import { Region } from "@src/@types/Region"; import EndpointLogos from "@src/components/modules/EndpointModule/EndpointLogos"; @@ -100,7 +100,7 @@ type Props = { regions: Region[]; connectionInfo: Endpoint["connection_info"] | null; loading: boolean; - replicas: ReplicaItem[], + transfers: TransferItem[], connectionInfoSchema: FieldType[]; onDeleteClick: () => void; onValidateClick: () => void; @@ -225,7 +225,7 @@ class EndpointDetailsContent extends React.Component { ); } - renderUsage(items: ReplicaItem[]) { + renderUsage(items: TransferItem[]) { return ( {items.map(item => ( @@ -288,8 +288,8 @@ class EndpointDetailsContent extends React.Component { )} - - {this.props.replicas.length > 0 ? this.renderUsage(this.props.replicas) : -} + + {this.props.transfers.length > 0 ? this.renderUsage(this.props.transfers) : -} {!this.props.connectionInfo ? this.renderConnectionInfoLoading() diff --git a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolDetailsContent.spec.tsx b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolDetailsContent.spec.tsx index 6c325f2b..15adefdd 100644 --- a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolDetailsContent.spec.tsx +++ b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolDetailsContent.spec.tsx @@ -44,7 +44,7 @@ describe("MinionPoolDetailsContent", () => { defaultProps = { item: MINION_POOL_DETAILS_MOCK, itemId: "minion-pool-id", - replicas: [REPLICA_MOCK], + transfers: [REPLICA_MOCK], migrations: [], endpoints: [OPENSTACK_ENDPOINT_MOCK], schema: [ diff --git a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolDetailsContent.tsx b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolDetailsContent.tsx index 9655f9ac..36d9c096 100644 --- a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolDetailsContent.tsx +++ b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolDetailsContent.tsx @@ -20,7 +20,7 @@ import Button from "@src/components/ui/Button"; import DetailsNavigation from "@src/components/modules/NavigationModule/DetailsNavigation"; import type { Endpoint } from "@src/@types/Endpoint"; import type { Field } from "@src/@types/Field"; -import { ReplicaItem, DeploymentItem } from "@src/@types/MainItem"; +import { TransferItem, DeploymentItem } from "@src/@types/MainItem"; import { MinionPoolDetails } from "@src/@types/MinionPool"; import StatusImage from "@src/components/ui/StatusComponents/StatusImage"; import { ThemeProps } from "@src/components/Theme"; @@ -75,7 +75,7 @@ const NavigationItems = [ type Props = { item?: MinionPoolDetails | null; itemId: string; - replicas: ReplicaItem[]; + transfers: TransferItem[]; deployments: DeploymentItem[]; endpoints: Endpoint[]; schema: Field[]; @@ -144,7 +144,7 @@ class MinionPoolDetailsContent extends React.Component { return ( ); @@ -166,7 +166,7 @@ class MinionPoolDetailsContent extends React.Component { return ( { beforeEach(() => { defaultProps = { item: MINION_POOL_MOCK, - replicas: [REPLICA_MOCK], + transfers: [REPLICA_MOCK], migrations: [MIGRATION_MOCK], }; }); diff --git a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMachines.tsx b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMachines.tsx index b80e60fb..54234f0d 100644 --- a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMachines.tsx +++ b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMachines.tsx @@ -17,12 +17,12 @@ import { Collapse } from "react-collapse"; import { Link } from "react-router-dom"; import styled, { createGlobalStyle, css } from "styled-components"; -import { DeploymentItem, ReplicaItem, TransferItem } from "@src/@types/MainItem"; +import { DeploymentItem, TransferItem, ActionItem } from "@src/@types/MainItem"; import { MinionMachine, MinionPool } from "@src/@types/MinionPool"; import { ThemePalette, ThemeProps } from "@src/components/Theme"; import Arrow from "@src/components/ui/Arrow"; import DropdownLink from "@src/components/ui/Dropdowns/DropdownLink"; -import { ItemReplicaBadge } from "@src/components/ui/Dropdowns/NotificationDropdown"; +import { ItemTransferBadge } from "@src/components/ui/Dropdowns/NotificationDropdown"; import StatusPill from "@src/components/ui/StatusComponents/StatusPill"; import DateUtils from "@src/utils/DateUtils"; @@ -144,7 +144,7 @@ const ValueLink = styled(Link)` type FilterType = "all" | "allocated" | "not-allocated"; type Props = { item?: MinionPool | null; - replicas: ReplicaItem[]; + transfers: TransferItem[]; deployments: DeploymentItem[]; }; type State = { @@ -278,10 +278,10 @@ class MinionPoolMachines extends React.Component { return ( {this.filteredMachines.map(machine => { - const findTransferItem = (transferItems: TransferItem[]) => + const findTransferItem = (transferItems: ActionItem[]) => transferItems.find(i => i.id === machine.allocated_action); const allocatedAction = machine.allocated_action - ? findTransferItem(this.props.replicas) || + ? findTransferItem(this.props.transfers) || findTransferItem(this.props.deployments) : null; return ( @@ -329,11 +329,11 @@ class MinionPoolMachines extends React.Component { Allocated Action: {allocatedAction ? ( <> - - {allocatedAction.type === "replica" ? "TR" : "DE"} - + + {allocatedAction.type === "transfer" ? "TR" : "DE"} + {allocatedAction.instances[0]} diff --git a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.spec.tsx b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.spec.tsx index 9b71a09e..260f33f3 100644 --- a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.spec.tsx +++ b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.spec.tsx @@ -35,7 +35,7 @@ describe("MinionPoolMainDetails", () => { beforeEach(() => { defaultProps = { item: MINION_POOL_MOCK, - replicas: [REPLICA_MOCK], + transfers: [REPLICA_MOCK], migrations: [MIGRATION_MOCK], schema: [], schemaLoading: false, diff --git a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.tsx b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.tsx index 76d2aa8f..1d658e7c 100644 --- a/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.tsx +++ b/src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.tsx @@ -18,7 +18,7 @@ import { Link } from "react-router-dom"; import styled, { css } from "styled-components"; import fieldHelper from "@src/@types/Field"; -import { DeploymentItem, ReplicaItem, TransferItem } from "@src/@types/MainItem"; +import { DeploymentItem, TransferItem, ActionItem } from "@src/@types/MainItem"; import { MinionPool } from "@src/@types/MinionPool"; import EndpointLogos from "@src/components/modules/EndpointModule/EndpointLogos"; import { ThemePalette, ThemeProps } from "@src/components/Theme"; @@ -101,7 +101,7 @@ const PropertyValue = styled.div` type Props = { item?: MinionPool | null; - replicas: ReplicaItem[]; + transfers: TransferItem[]; deployments: DeploymentItem[]; schema: FieldType[]; schemaLoading: boolean; @@ -235,10 +235,10 @@ class MinionPoolMainDetails extends React.Component { ); } - renderUsage(items: TransferItem[]) { + renderUsage(items: ActionItem[]) { return items.map(item => { const actionHref = - item.type === "replica" + item.type === "transfer" ? "transfers" : "deployments" return (
@@ -267,7 +267,7 @@ class MinionPoolMainDetails extends React.Component { : []; }; - const usage: TransferItem[] = this.props.replicas.concat( + const usage: ActionItem[] = this.props.transfers.concat( this.props.deployments as any[] ); diff --git a/src/components/modules/NavigationModule/Navigation/Navigation.tsx b/src/components/modules/NavigationModule/Navigation/Navigation.tsx index 6f3862cf..ca23a271 100644 --- a/src/components/modules/NavigationModule/Navigation/Navigation.tsx +++ b/src/components/modules/NavigationModule/Navigation/Navigation.tsx @@ -29,7 +29,7 @@ import cbsImage from "./images/cbsl-logo.svg"; import cbsImageSmall from "./images/cbsl-logo-small.svg"; import tinyLogo from "./images/logo-small.svg"; -import replicaImage from "./images/replica-menu.svg"; +import transferImage from "./images/transfer-menu.svg"; import endpointImage from "./images/endpoint-menu.svg"; import planningImage from "./images/planning-menu.svg"; import projectImage from "./images/project-menu.svg"; @@ -179,7 +179,7 @@ const SmallMenuItemBullet = styled.div` border-radius: 50%; position: absolute; left: -12px; - background: ${props => (props.bullet === "replica" ? "#E62565" : "#0044CA")}; + background: ${props => (props.bullet === "transfer" ? "#E62565" : "#0044CA")}; `; const MenuImage = styled.div` @@ -407,12 +407,12 @@ class Navigation extends React.Component { style = { width: "19px", height: "19px" }; break; case "transfers": - bullet = "replica"; - menuImage = replicaImage; + bullet = "transfer"; + menuImage = transferImage; break; case "deployments": bullet = "deployment"; - menuImage = replicaImage; + menuImage = transferImage; break; case "endpoints": menuImage = endpointImage; diff --git a/src/components/modules/NavigationModule/Navigation/images/replica-menu.svg b/src/components/modules/NavigationModule/Navigation/images/transfer-menu.svg similarity index 100% rename from src/components/modules/NavigationModule/Navigation/images/replica-menu.svg rename to src/components/modules/NavigationModule/Navigation/images/transfer-menu.svg diff --git a/src/components/modules/TransferModule/DeleteReplicaModal/package.json b/src/components/modules/TransferModule/DeleteReplicaModal/package.json deleted file mode 100644 index e9ade791..00000000 --- a/src/components/modules/TransferModule/DeleteReplicaModal/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "DeleteReplicaModal", - "version": "0.0.0", - "private": true, - "main": "./DeleteReplicaModal.tsx" -} diff --git a/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.spec.tsx b/src/components/modules/TransferModule/DeleteTransferModal/DeleteReplicaModal.spec.tsx similarity index 78% rename from src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.spec.tsx rename to src/components/modules/TransferModule/DeleteTransferModal/DeleteReplicaModal.spec.tsx index eb5348de..1c4ef29e 100644 --- a/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.spec.tsx +++ b/src/components/modules/TransferModule/DeleteTransferModal/DeleteReplicaModal.spec.tsx @@ -17,27 +17,27 @@ import React from "react"; import { render } from "@testing-library/react"; import TestUtils from "@tests/TestUtils"; -import DeleteReplicaModal from "./"; +import DeleteTransferModal from "./DeleteTransferModal"; describe("DeleteReplicaModal", () => { - let defaultProps: DeleteReplicaModal["props"]; + let defaultProps: DeleteTransferModal["props"]; beforeEach(() => { defaultProps = { hasDisks: false, - onDeleteReplica: jest.fn(), + onDeleteTransfer: jest.fn(), onDeleteDisks: jest.fn(), onRequestClose: jest.fn(), }; }); it("renders without crashing", () => { - const { getByText } = render(); + const { getByText } = render(); expect(getByText("Delete Replica")).toBeTruthy(); }); it("renders with disks", () => { - render(); + render(); expect( TestUtils.select("DeleteReplicaModal__ExtraMessage")?.textContent ).toContain("has been executed at least once"); @@ -45,7 +45,7 @@ describe("DeleteReplicaModal", () => { it("is multiple replica selection with disks", () => { render( - + ); expect( TestUtils.select("DeleteReplicaModal__ExtraMessage")?.textContent @@ -53,7 +53,7 @@ describe("DeleteReplicaModal", () => { }); it("renders loading", () => { - render(); + render(); expect(TestUtils.select("DeleteReplicaModal__Loading")).toBeTruthy(); }); }); diff --git a/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.tsx b/src/components/modules/TransferModule/DeleteTransferModal/DeleteTransferModal.tsx similarity index 90% rename from src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.tsx rename to src/components/modules/TransferModule/DeleteTransferModal/DeleteTransferModal.tsx index 73034e26..e59068e0 100644 --- a/src/components/modules/TransferModule/DeleteReplicaModal/DeleteReplicaModal.tsx +++ b/src/components/modules/TransferModule/DeleteTransferModal/DeleteTransferModal.tsx @@ -71,18 +71,18 @@ const LoadingSubtitle = styled.div` type Props = { hasDisks: boolean; - isMultiReplicaSelection?: boolean; + isMultiTransferSelection?: boolean; loading?: boolean; - onDeleteReplica: () => void; + onDeleteTransfer: () => void; onDeleteDisks: () => void; onRequestClose: () => void; }; @observer -class DeleteReplicaModal extends React.Component { +class DeleteTransferModal extends React.Component { renderExtraMessage() { if (this.props.hasDisks) { - if (this.props.isMultiReplicaSelection) { + if (this.props.isMultiTransferSelection) { return ( Some of the selected Transfer have been executed at least once and @@ -124,7 +124,7 @@ class DeleteReplicaModal extends React.Component { } renderContent() { - const message = this.props.isMultiReplicaSelection + const message = this.props.isMultiTransferSelection ? "Are you sure you want to delete the selected transfers?" : "Are you sure you want to delete this transfer?"; @@ -148,8 +148,8 @@ class DeleteReplicaModal extends React.Component { Delete Transfer Disks ) : null} - @@ -158,7 +158,7 @@ class DeleteReplicaModal extends React.Component { } render() { - const title = this.props.isMultiReplicaSelection + const title = this.props.isMultiTransferSelection ? "Delete Selected Transfers?" : "Delete Transfer?"; return ( @@ -169,4 +169,4 @@ class DeleteReplicaModal extends React.Component { } } -export default DeleteReplicaModal; +export default DeleteTransferModal; diff --git a/src/components/modules/TransferModule/DeleteTransferModal/package.json b/src/components/modules/TransferModule/DeleteTransferModal/package.json new file mode 100644 index 00000000..4a59dc88 --- /dev/null +++ b/src/components/modules/TransferModule/DeleteTransferModal/package.json @@ -0,0 +1,6 @@ +{ + "name": "DeleteTransferModal", + "version": "0.0.0", + "private": true, + "main": "./DeleteTransferModal.tsx" +} diff --git a/src/components/modules/TransferModule/ReplicaDeploymentOptions/replicaDeploymentFields.ts b/src/components/modules/TransferModule/DeploymentOptions/DeploymentFields.ts similarity index 91% rename from src/components/modules/TransferModule/ReplicaDeploymentOptions/replicaDeploymentFields.ts rename to src/components/modules/TransferModule/DeploymentOptions/DeploymentFields.ts index 77aa95f6..9926418c 100644 --- a/src/components/modules/TransferModule/ReplicaDeploymentOptions/replicaDeploymentFields.ts +++ b/src/components/modules/TransferModule/DeploymentOptions/DeploymentFields.ts @@ -14,7 +14,7 @@ along with this program. If not, see . import { Field } from "@src/@types/Field"; -const replicaDeploymentFields: Field[] = [ +const deploymentFields: Field[] = [ { name: "clone_disks", type: "boolean", @@ -30,4 +30,4 @@ const replicaDeploymentFields: Field[] = [ }, ]; -export default replicaDeploymentFields; +export default deploymentFields; diff --git a/src/components/modules/TransferModule/ReplicaDeploymentOptions/ReplicaDeploymentOptions.tsx b/src/components/modules/TransferModule/DeploymentOptions/DeploymentOptions.tsx similarity index 95% rename from src/components/modules/TransferModule/ReplicaDeploymentOptions/ReplicaDeploymentOptions.tsx rename to src/components/modules/TransferModule/DeploymentOptions/DeploymentOptions.tsx index 374f5e6c..aca05ae0 100644 --- a/src/components/modules/TransferModule/ReplicaDeploymentOptions/ReplicaDeploymentOptions.tsx +++ b/src/components/modules/TransferModule/DeploymentOptions/DeploymentOptions.tsx @@ -16,7 +16,7 @@ import { observer } from "mobx-react"; import React from "react"; import styled from "styled-components"; -import { TransferItemDetails } from "@src/@types/MainItem"; +import { ActionItemDetails } from "@src/@types/MainItem"; import { MinionPool } from "@src/@types/MinionPool"; import { INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS } from "@src/components/modules/WizardModule/WizardOptions"; import WizardScripts from "@src/components/modules/WizardModule/WizardScripts"; @@ -28,8 +28,8 @@ import ToggleButtonBar from "@src/components/ui/ToggleButtonBar"; import KeyboardManager from "@src/utils/KeyboardManager"; import LabelDictionary from "@src/utils/LabelDictionary"; -import replicaDeploymentImage from "./images/replica-deployment.svg"; -import replicaDeploymentFields from "./replicaDeploymentFields"; +import deploymentImage from "./images/deployment.svg"; +import deploymentFields from "./DeploymentFields"; import type { Field } from "@src/@types/Field"; import type { Instance, InstanceScript } from "@src/@types/Instance"; @@ -43,7 +43,7 @@ const Wrapper = styled.div` const Image = styled.div` ${ThemeProps.exactWidth("288px")} ${ThemeProps.exactHeight("96px")} - background: url('${replicaDeploymentImage}') center no-repeat; + background: url('${deploymentImage}') center no-repeat; margin: 80px 0; `; const OptionsBody = styled.div` @@ -78,7 +78,7 @@ const FieldInputStyled = styled(FieldInput)` type Props = { instances: Instance[]; - transferItem: TransferItemDetails | null; + transferItem: ActionItemDetails | null; minionPools: MinionPool[]; loadingInstances: boolean; defaultSkipOsMorphing?: boolean | null; @@ -101,7 +101,7 @@ type State = { }; @observer -class ReplicaDeploymentOptions extends React.Component { +class DeploymentOptions extends React.Component { state: State = { fields: [], selectedBarButton: "options", @@ -117,7 +117,7 @@ class ReplicaDeploymentOptions extends React.Component { this.props.transferItem?.instance_osmorphing_minion_pool_mappings || {}; this.setState({ - fields: replicaDeploymentFields.map(f => + fields: deploymentFields.map(f => f.name === "skip_os_morphing" ? { ...f, value: this.props.defaultSkipOsMorphing || null } : f @@ -340,4 +340,4 @@ class ReplicaDeploymentOptions extends React.Component { } } -export default ReplicaDeploymentOptions; +export default DeploymentOptions; diff --git a/src/components/modules/TransferModule/ReplicaDeploymentOptions/ReplicaDeploymentOptions.spec.tsx b/src/components/modules/TransferModule/DeploymentOptions/ReplicaDeploymentOptions.spec.tsx similarity index 99% rename from src/components/modules/TransferModule/ReplicaDeploymentOptions/ReplicaDeploymentOptions.spec.tsx rename to src/components/modules/TransferModule/DeploymentOptions/ReplicaDeploymentOptions.spec.tsx index 7c306d73..0ef9f3ec 100644 --- a/src/components/modules/TransferModule/ReplicaDeploymentOptions/ReplicaDeploymentOptions.spec.tsx +++ b/src/components/modules/TransferModule/DeploymentOptions/ReplicaDeploymentOptions.spec.tsx @@ -21,7 +21,7 @@ import { MINION_POOL_MOCK } from "@tests/mocks/MinionPoolMock"; import { REPLICA_ITEM_DETAILS_MOCK } from "@tests/mocks/TransferMock"; import TestUtils from "@tests/TestUtils"; -import ReplicaDeploymentOptions from "./"; +import ReplicaDeploymentOptions from "."; jest.mock("@src/plugins/default/ContentPlugin", () => jest.fn(() => null)); jest.mock("@src/components/modules/WizardModule/WizardScripts", () => ({ diff --git a/src/components/modules/TransferModule/ReplicaDeploymentOptions/images/replica-deployment.svg b/src/components/modules/TransferModule/DeploymentOptions/images/deployment.svg similarity index 100% rename from src/components/modules/TransferModule/ReplicaDeploymentOptions/images/replica-deployment.svg rename to src/components/modules/TransferModule/DeploymentOptions/images/deployment.svg diff --git a/src/components/modules/TransferModule/DeploymentOptions/package.json b/src/components/modules/TransferModule/DeploymentOptions/package.json new file mode 100644 index 00000000..054fb709 --- /dev/null +++ b/src/components/modules/TransferModule/DeploymentOptions/package.json @@ -0,0 +1,6 @@ +{ + "name": "DeploymentOptions", + "version": "0.0.0", + "private": true, + "main": "./DeploymentOptions.tsx" +} diff --git a/src/components/modules/TransferModule/ReplicaDeploymentOptions/story.tsx b/src/components/modules/TransferModule/DeploymentOptions/story.tsx similarity index 100% rename from src/components/modules/TransferModule/ReplicaDeploymentOptions/story.tsx rename to src/components/modules/TransferModule/DeploymentOptions/story.tsx diff --git a/src/components/modules/TransferModule/MainDetails/MainDetails.tsx b/src/components/modules/TransferModule/MainDetails/MainDetails.tsx index b1e5e6be..1932266d 100644 --- a/src/components/modules/TransferModule/MainDetails/MainDetails.tsx +++ b/src/components/modules/TransferModule/MainDetails/MainDetails.tsx @@ -18,7 +18,7 @@ import { Link } from "react-router-dom"; import styled, { css } from "styled-components"; import fieldHelper from "@src/@types/Field"; -import { TransferItem } from "@src/@types/MainItem"; +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"; @@ -128,7 +128,7 @@ const PropertyValue = styled.div` `; type Props = { - item?: TransferItem | null; + item?: ActionItem | null; minionPools: MinionPool[]; storageBackends: StorageBackend[]; destinationSchema: FieldType[]; @@ -423,13 +423,13 @@ class MainDetails extends React.Component { ) : null} - {this.props.item?.type === "migration" && - this.props.item.replica_id ? ( + {this.props.item?.type === "deployment" && + this.props.item.transfer_id ? ( - - {this.props.item.replica_id} + + {this.props.item.transfer_id} diff --git a/src/components/modules/TransferModule/MigrationDetailsContent/MigrationDetailsContent.spec.tsx b/src/components/modules/TransferModule/MigrationDetailsContent/MigrationDetailsContent.spec.tsx deleted file mode 100644 index c2e4bc9a..00000000 --- a/src/components/modules/TransferModule/MigrationDetailsContent/MigrationDetailsContent.spec.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/* -Copyright (C) 2023 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 React from "react"; - -import { render } from "@testing-library/react"; - -import MigrationDetailsContent from "."; -import { MIGRATION_ITEM_DETAILS_MOCK } from "@tests/mocks/TransferMock"; -import { MINION_POOL_MOCK } from "@tests/mocks/MinionPoolMock"; -import { STORAGE_BACKEND_MOCK } from "@tests/mocks/StoragesMock"; -import { INSTANCE_MOCK } from "@tests/mocks/InstancesMock"; -import { NETWORK_MOCK } from "@tests/mocks/NetworksMock"; -import { - OPENSTACK_ENDPOINT_MOCK, - VMWARE_ENDPOINT_MOCK, -} from "@tests/mocks/EndpointsMock"; - -jest.mock("@src/components/modules/EndpointModule/EndpointLogos", () => ({ - __esModule: true, - default: (props: any) =>
{props.endpoint}
, -})); -jest.mock("react-router-dom", () => ({ Link: "a" })); - -describe("MigrationDetailsContent", () => { - let defaultProps: MigrationDetailsContent["props"]; - - beforeEach(() => { - defaultProps = { - item: MIGRATION_ITEM_DETAILS_MOCK, - itemId: MIGRATION_ITEM_DETAILS_MOCK.id, - minionPools: [MINION_POOL_MOCK], - detailsLoading: false, - storageBackends: [STORAGE_BACKEND_MOCK], - instancesDetails: [INSTANCE_MOCK], - instancesDetailsLoading: false, - networks: [NETWORK_MOCK], - sourceSchema: [], - sourceSchemaLoading: false, - destinationSchema: [], - destinationSchemaLoading: false, - endpoints: [OPENSTACK_ENDPOINT_MOCK, VMWARE_ENDPOINT_MOCK], - page: "", - onDeleteMigrationClick: jest.fn(), - }; - }); - - it("renders without crashing", () => { - const { getByText } = render(); - expect(getByText(MIGRATION_ITEM_DETAILS_MOCK.id)).toBeTruthy(); - }); - - it("renders tasks page", () => { - const { getByText } = render( - - ); - expect( - getByText( - MIGRATION_ITEM_DETAILS_MOCK.tasks[0].task_type.replace("_", " ") - ) - ).toBeTruthy(); - }); -}); diff --git a/src/components/modules/TransferModule/MigrationDetailsContent/MigrationDetailsContent.tsx b/src/components/modules/TransferModule/MigrationDetailsContent/MigrationDetailsContent.tsx deleted file mode 100644 index 27cb7711..00000000 --- a/src/components/modules/TransferModule/MigrationDetailsContent/MigrationDetailsContent.tsx +++ /dev/null @@ -1,142 +0,0 @@ -/* -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 React from "react"; -import styled from "styled-components"; - -import { MigrationItemDetails } from "@src/@types/MainItem"; -import { MinionPool } from "@src/@types/MinionPool"; -import { Network } from "@src/@types/Network"; -import DetailsNavigation from "@src/components/modules/NavigationModule/DetailsNavigation"; -import MainDetails from "@src/components/modules/TransferModule/MainDetails"; -import Tasks from "@src/components/modules/TransferModule/Tasks"; -import { ThemeProps } from "@src/components/Theme"; -import Button from "@src/components/ui/Button"; - -import type { Instance } from "@src/@types/Instance"; -import type { Endpoint, StorageBackend } from "@src/@types/Endpoint"; -import type { Field } from "@src/@types/Field"; -const Wrapper = styled.div` - display: flex; - justify-content: center; -`; - -const Buttons = styled.div` - margin-top: 24px; - & > button:last-child { - float: right; - } -`; -const DetailsBody = styled.div` - ${ThemeProps.exactWidth(ThemeProps.contentWidth)} -`; - -const NavigationItems = [ - { - label: "Migration", - value: "", - }, - { - label: "Tasks", - value: "tasks", - }, -]; - -type Props = { - item: MigrationItemDetails | null; - itemId: string; - minionPools: MinionPool[]; - detailsLoading: boolean; - storageBackends: StorageBackend[]; - instancesDetails: Instance[]; - instancesDetailsLoading: boolean; - networks: Network[]; - sourceSchema: Field[]; - sourceSchemaLoading: boolean; - destinationSchema: Field[]; - destinationSchemaLoading: boolean; - endpoints: Endpoint[]; - page: string; - onDeleteMigrationClick: () => void; -}; -@observer -class MigrationDetailsContent extends React.Component { - renderBottomControls() { - return ( - - - - ); - } - - renderMainDetails() { - if (this.props.page !== "") { - return null; - } - - return ( - - ); - } - - renderTasks() { - if (this.props.page !== "tasks" || !this.props.item?.tasks) { - return null; - } - - return ( - - ); - } - - render() { - return ( - - - - {this.renderMainDetails()} - {this.renderTasks()} - - - ); - } -} - -export default MigrationDetailsContent; diff --git a/src/components/modules/TransferModule/MigrationDetailsContent/package.json b/src/components/modules/TransferModule/MigrationDetailsContent/package.json deleted file mode 100644 index 3f60d51d..00000000 --- a/src/components/modules/TransferModule/MigrationDetailsContent/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "MigrationDetailsContent", - "version": "0.0.0", - "private": true, - "main": "./MigrationDetailsContent.tsx" -} diff --git a/src/components/modules/TransferModule/MigrationDetailsContent/story.tsx b/src/components/modules/TransferModule/MigrationDetailsContent/story.tsx deleted file mode 100644 index 99a6e7f6..00000000 --- a/src/components/modules/TransferModule/MigrationDetailsContent/story.tsx +++ /dev/null @@ -1,95 +0,0 @@ -/* -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 . -*/ - -/* eslint-disable react/jsx-props-no-spreading */ - -import React from "react"; -import { storiesOf } from "@storybook/react"; -import MigrationDetailsContent from "."; - -const tasks: any = [ - { - progress_updates: [ - { message: "the task has a progress of 10%", created_at: new Date() }, - ], - exception_details: "Exception details", - status: "COMPLETED", - created_at: new Date(), - depends_on: ["depends on id"], - id: "task-1", - task_type: "Task name 1", - }, - { - progress_updates: [ - { message: "the task has a progress of 50%", created_at: new Date() }, - { message: "the task is almost done", created_at: new Date() }, - ], - exception_details: "Exception details", - status: "RUNNING", - created_at: new Date(), - depends_on: ["depends on id"], - id: "task-2", - task_type: "Task name 2", - }, -]; -const endpoints: any = [ - { id: "endpoint-1", name: "Endpoint OPS", type: "openstack" }, - { id: "endpoint-2", name: "Endpoint AZURE", type: "azure" }, -]; -const item: any = { - origin_endpoint_id: "endpoint-1", - destination_endpoint_id: "endpoint-2", - id: "item-id", - created_at: new Date(2017, 10, 24, 16, 15), - info: { - instance: { - export_info: { devices: { nics: [{ network_name: "map_1" }] } }, - }, - }, - tasks, - destination_environment: { - description: "A description", - network_map: { - map_1: "Mapping 1", - }, - }, - type: "Migration", -}; -const props: any = {}; -storiesOf("MigrationDetailsContent", module) - .add("default", () => ( - - )) - .add("details loading", () => ( - - )) - .add("tasks", () => ( - - )); diff --git a/src/components/modules/TransferModule/ReplicaDeploymentOptions/package.json b/src/components/modules/TransferModule/ReplicaDeploymentOptions/package.json deleted file mode 100644 index 5581f77b..00000000 --- a/src/components/modules/TransferModule/ReplicaDeploymentOptions/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "ReplicaDeploymentOptions", - "version": "0.0.0", - "private": true, - "main": "./ReplicaDeploymentOptions.tsx" -} diff --git a/src/components/modules/TransferModule/ReplicaDetailsContent/package.json b/src/components/modules/TransferModule/ReplicaDetailsContent/package.json deleted file mode 100644 index 340a812a..00000000 --- a/src/components/modules/TransferModule/ReplicaDetailsContent/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "ReplicaDetailsContent", - "version": "0.0.0", - "private": true, - "main": "./ReplicaDetailsContent.tsx" -} diff --git a/src/components/modules/TransferModule/ReplicaExecutionOptions/package.json b/src/components/modules/TransferModule/ReplicaExecutionOptions/package.json deleted file mode 100644 index 94805486..00000000 --- a/src/components/modules/TransferModule/ReplicaExecutionOptions/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "ReplicaExecutionOptions", - "version": "0.0.0", - "private": true, - "main": "./ReplicaExecutionOptions.tsx" -} diff --git a/src/components/modules/TransferModule/ReplicaMigrationOptions/ReplicaMigrationOptions.spec.tsx b/src/components/modules/TransferModule/ReplicaMigrationOptions/ReplicaMigrationOptions.spec.tsx deleted file mode 100644 index f78f5728..00000000 --- a/src/components/modules/TransferModule/ReplicaMigrationOptions/ReplicaMigrationOptions.spec.tsx +++ /dev/null @@ -1,154 +0,0 @@ -/* -Copyright (C) 2023 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 React from "react"; - -import WizardScripts from "@src/components/modules/WizardModule/WizardScripts"; -import { fireEvent, render } from "@testing-library/react"; -import { INSTANCE_MOCK } from "@tests/mocks/InstancesMock"; -import { MINION_POOL_MOCK } from "@tests/mocks/MinionPoolMock"; -import { REPLICA_ITEM_DETAILS_MOCK } from "@tests/mocks/TransferMock"; -import TestUtils from "@tests/TestUtils"; - -import ReplicaMigrationOptions from "./"; - -jest.mock("@src/plugins/default/ContentPlugin", () => jest.fn(() => null)); -jest.mock("@src/components/modules/WizardModule/WizardScripts", () => ({ - __esModule: true, - default: (props: WizardScripts["props"]) => ( -
-
- {props.uploadedScripts.map(s => s.scriptContent).join(", ")} -
-
{ - props.onScriptDataRemove(props.uploadedScripts[0]); - }} - /> -
- {props.removedScripts.map(s => s.scriptContent).join(", ")} -
-
{ - props.onCancelScript("windows", null); - props.scrollableRef && - props.scrollableRef(null as any as HTMLElement); - }} - /> -
{ - props.onScriptUpload({ - scriptContent: `script-content-${Math.random()}`, - fileName: `script-name.ps1`, - global: "windows", - }); - }} - /> -
- ), -})); - -describe("ReplicaMigrationOptions", () => { - let defaultProps: ReplicaMigrationOptions["props"]; - - beforeEach(() => { - defaultProps = { - instances: [INSTANCE_MOCK], - transferItem: REPLICA_ITEM_DETAILS_MOCK, - minionPools: [ - MINION_POOL_MOCK, - { ...MINION_POOL_MOCK, id: "pool2", name: "Pool2" }, - ], - loadingInstances: false, - onCancelClick: jest.fn(), - onMigrateClick: jest.fn(), - onResizeUpdate: jest.fn(), - }; - }); - - it("renders without crashing", () => { - const { getByText } = render(); - expect(getByText("Migrate")).toBeTruthy(); - }); - - it("executes on Enter", () => { - render(); - fireEvent.keyDown(document.body, { key: "Enter" }); - expect(defaultProps.onMigrateClick).toHaveBeenCalled(); - }); - - it("calls onResizeUpdate on selectedBarButton state change", () => { - render(); - fireEvent.click(TestUtils.selectAll("ToggleButtonBar__Item-")[1]); - expect(defaultProps.onResizeUpdate).toHaveBeenCalled(); - }); - - it("handles value change", () => { - render(); - expect(TestUtils.select("Switch__Wrapper")?.textContent).toBe("Yes"); - fireEvent.click(TestUtils.select("Switch__InputWrapper")!); - expect(TestUtils.select("Switch__Wrapper")?.textContent).toBe("No"); - }); - - it("handles script operations", () => { - const { getByTestId } = render( - - ); - fireEvent.click(TestUtils.selectAll("ToggleButtonBar__Item-")[1]); - fireEvent.click(getByTestId("ScriptsUpload")); - expect(getByTestId("ScriptsUploaded").textContent).toContain( - "script-content" - ); - fireEvent.click(getByTestId("ScriptsCancel")); - expect(getByTestId("ScriptsUploaded").textContent).toBe(""); - - fireEvent.click(getByTestId("ScriptsUpload")); - expect(getByTestId("ScriptsUploaded").textContent).toContain( - "script-content" - ); - expect(getByTestId("ScriptsRemoved").textContent).toBe(""); - fireEvent.click(getByTestId("ScriptsRemove")); - expect(getByTestId("ScriptsRemoved").textContent).toContain( - "script-content" - ); - }); - - it("doesn't render minion pool mappings", () => { - const { rerender } = render(); - expect(document.body.textContent).toContain("Minion Pool Mappings"); - - rerender(); - expect(document.body.textContent).not.toContain("Minion Pool Mappings"); - }); - - it("changes minion pool mappings value", () => { - render(); - fireEvent.click(TestUtils.select("DropdownButton__Wrapper-")!); - const dropdownItem = TestUtils.selectAll("Dropdown__ListItem-")[2]; - expect(dropdownItem.textContent).toBe("Pool2"); - fireEvent.click(dropdownItem); - expect(TestUtils.select("DropdownButton__Label-")?.textContent).toBe( - "Pool2" - ); - }); - - it("handles migrate click", () => { - const { getByText } = render(); - fireEvent.click(getByText("Migrate")); - expect(defaultProps.onMigrateClick).toHaveBeenCalled(); - }); -}); diff --git a/src/components/modules/TransferModule/ReplicaMigrationOptions/ReplicaMigrationOptions.tsx b/src/components/modules/TransferModule/ReplicaMigrationOptions/ReplicaMigrationOptions.tsx deleted file mode 100644 index 0dce1170..00000000 --- a/src/components/modules/TransferModule/ReplicaMigrationOptions/ReplicaMigrationOptions.tsx +++ /dev/null @@ -1,343 +0,0 @@ -/* -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 React from "react"; -import styled from "styled-components"; - -import { TransferItemDetails } from "@src/@types/MainItem"; -import { MinionPool } from "@src/@types/MinionPool"; -import { INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS } from "@src/components/modules/WizardModule/WizardOptions"; -import WizardScripts from "@src/components/modules/WizardModule/WizardScripts"; -import { ThemeProps } from "@src/components/Theme"; -import Button from "@src/components/ui/Button"; -import FieldInput from "@src/components/ui/FieldInput"; -import LoadingButton from "@src/components/ui/LoadingButton"; -import ToggleButtonBar from "@src/components/ui/ToggleButtonBar"; -import KeyboardManager from "@src/utils/KeyboardManager"; -import LabelDictionary from "@src/utils/LabelDictionary"; - -import replicaMigrationImage from "./images/replica-migration.svg"; -import replicaMigrationFields from "./replicaMigrationFields"; - -import type { Field } from "@src/@types/Field"; -import type { Instance, InstanceScript } from "@src/@types/Instance"; -const Wrapper = styled.div` - display: flex; - flex-direction: column; - align-items: center; - padding: 0 32px 32px 32px; - min-height: 0; -`; -const Image = styled.div` - ${ThemeProps.exactWidth("288px")} - ${ThemeProps.exactHeight("96px")} - background: url('${replicaMigrationImage}') center no-repeat; - margin: 80px 0; -`; -const OptionsBody = styled.div` - display: flex; - flex-direction: column; -`; -const ScriptsBody = styled.div` - display: flex; - flex-direction: column; - width: 100%; - overflow: auto; - min-height: 0; - margin-bottom: 32px; -`; -const Form = styled.div` - display: flex; - flex-wrap: wrap; - margin-left: -64px; - justify-content: space-between; - margin: 0 auto; -`; -const Buttons = styled.div` - display: flex; - justify-content: space-between; - width: 100%; -`; -const FieldInputStyled = styled(FieldInput)` - width: 224px; - justify-content: space-between; - margin-bottom: 32px; -`; - -type Props = { - instances: Instance[]; - transferItem: TransferItemDetails | null; - minionPools: MinionPool[]; - loadingInstances: boolean; - defaultSkipOsMorphing?: boolean | null; - migrating?: boolean; - onCancelClick: () => void; - onMigrateClick: (opts: { - fields: Field[]; - uploadedUserScripts: InstanceScript[]; - removedUserScripts: InstanceScript[]; - minionPoolMappings: { [instance: string]: string }; - }) => void; - onResizeUpdate?: (scrollableRef: HTMLElement, scrollOffset?: number) => void; -}; -type State = { - fields: Field[]; - selectedBarButton: string; - uploadedScripts: InstanceScript[]; - removedScripts: InstanceScript[]; - minionPoolMappings: { [instance: string]: string }; -}; - -@observer -class ReplicaMigrationOptions extends React.Component { - state: State = { - fields: [], - selectedBarButton: "options", - uploadedScripts: [], - removedScripts: [], - minionPoolMappings: {}, - }; - - scrollableRef!: HTMLElement; - - UNSAFE_componentWillMount() { - const mappings = - this.props.transferItem?.instance_osmorphing_minion_pool_mappings || {}; - - this.setState({ - fields: replicaMigrationFields.map(f => - f.name === "skip_os_morphing" - ? { ...f, value: this.props.defaultSkipOsMorphing || null } - : f - ), - minionPoolMappings: { ...mappings }, - }); - } - - componentDidMount() { - KeyboardManager.onEnter( - "migration-options", - () => { - this.migrate(); - }, - 2 - ); - } - - componentDidUpdate(_: Props, prevState: State) { - if (prevState.selectedBarButton !== this.state.selectedBarButton) { - if (this.props.onResizeUpdate) { - this.props.onResizeUpdate(this.scrollableRef, 0); - } - } - } - - componentWillUnmount() { - KeyboardManager.removeKeyDown("migration-options"); - } - - migrate() { - this.props.onMigrateClick({ - fields: this.state.fields, - uploadedUserScripts: this.state.uploadedScripts, - removedUserScripts: this.state.removedScripts, - minionPoolMappings: this.state.minionPoolMappings, - }); - } - - handleValueChange(field: Field, value: boolean) { - this.setState(prevState => { - const fields = prevState.fields.map(f => { - const newField = { ...f }; - if (f.name === field.name) { - newField.value = value; - } - return newField; - }); - - return { fields }; - }); - } - - handleCancelScript(global: string | null, instanceName: string | null) { - this.setState(prevState => ({ - uploadedScripts: prevState.uploadedScripts.filter(s => - global ? s.global !== global : s.instanceId !== instanceName - ), - })); - } - - handleScriptUpload(script: InstanceScript) { - this.setState(prevState => ({ - uploadedScripts: [...prevState.uploadedScripts, script], - })); - } - - handleScriptRemove(script: InstanceScript) { - this.setState(prevState => ({ - removedScripts: [...prevState.removedScripts, script], - })); - } - - renderField(field: Field) { - return ( - this.handleValueChange(field, value)} - description={LabelDictionary.getDescription(field.name)} - /> - ); - } - - renderMinionPoolMappings() { - const minionPools = this.props.minionPools.filter( - m => m.endpoint_id === this.props.transferItem?.destination_endpoint_id - ); - if (!minionPools.length) { - return null; - } - - const properties: Field[] = this.props.instances.map(instance => ({ - name: instance.instance_name || instance.id, - label: instance.name, - type: "string", - enum: minionPools.map(minionPool => ({ - name: minionPool.name, - id: minionPool.id, - })), - })); - - return ( - - this.state.minionPoolMappings && - this.state.minionPoolMappings[field.name] - } - layout="page" - label="Instance OSMorphing Minion Pool Mappings" - onChange={(value, field) => - this.setState(prevState => { - const minionPoolMappings = { ...prevState.minionPoolMappings }; - minionPoolMappings[field!.name] = value; - return { minionPoolMappings }; - }) - } - properties={properties} - labelRenderer={(propName: string) => - propName.indexOf("/") > -1 - ? propName.split("/")[propName.split("/").length - 1] - : propName - } - /> - ); - } - - renderOptions() { - return ( - <> -
{this.state.fields.map(field => this.renderField(field))}
- {this.renderMinionPoolMappings()} - - ); - } - - renderScripts() { - return ( - { - this.handleScriptUpload(s); - }} - onScriptDataRemove={s => { - this.handleScriptRemove(s); - }} - onCancelScript={(g, i) => { - this.handleCancelScript(g, i); - }} - uploadedScripts={this.state.uploadedScripts} - removedScripts={this.state.removedScripts} - userScriptData={this.props.transferItem?.user_scripts} - scrollableRef={(r: HTMLElement) => { - this.scrollableRef = r; - }} - layout="modal" - /> - ); - } - - renderBody() { - const Body = - this.state.selectedBarButton === "options" ? OptionsBody : ScriptsBody; - - return ( - - { - this.setState({ selectedBarButton: item.value }); - }} - style={{ marginBottom: "32px" }} - /> - {this.state.selectedBarButton === "options" - ? this.renderOptions() - : this.renderScripts()} - - ); - } - - render() { - return ( - - - {this.renderBody()} - - - {this.props.migrating ? ( - Migrating ... - ) : ( - - )} - - - ); - } -} - -export default ReplicaMigrationOptions; diff --git a/src/components/modules/TransferModule/ReplicaMigrationOptions/images/replica-migration.svg b/src/components/modules/TransferModule/ReplicaMigrationOptions/images/replica-migration.svg deleted file mode 100644 index f34c9d29..00000000 --- a/src/components/modules/TransferModule/ReplicaMigrationOptions/images/replica-migration.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - Created with Sketch. - - - - - - - - - - - - - - - - - - - - diff --git a/src/components/modules/TransferModule/ReplicaMigrationOptions/package.json b/src/components/modules/TransferModule/ReplicaMigrationOptions/package.json deleted file mode 100644 index 5f158d0c..00000000 --- a/src/components/modules/TransferModule/ReplicaMigrationOptions/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "ReplicaMigrationOptions", - "version": "0.0.0", - "private": true, - "main": "./ReplicaMigrationOptions.tsx" -} diff --git a/src/components/modules/TransferModule/ReplicaMigrationOptions/replicaMigrationFields.ts b/src/components/modules/TransferModule/ReplicaMigrationOptions/replicaMigrationFields.ts deleted file mode 100644 index 634c840a..00000000 --- a/src/components/modules/TransferModule/ReplicaMigrationOptions/replicaMigrationFields.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* -Copyright (C) 2020 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 { Field } from "@src/@types/Field"; - -const replicaMigrationFields: Field[] = [ - { - name: "clone_disks", - type: "boolean", - value: true, - }, - { - name: "force", - type: "boolean", - }, - { - name: "skip_os_morphing", - type: "boolean", - }, -]; - -export default replicaMigrationFields; diff --git a/src/components/modules/TransferModule/ReplicaMigrationOptions/story.tsx b/src/components/modules/TransferModule/ReplicaMigrationOptions/story.tsx deleted file mode 100644 index 1eea2ca6..00000000 --- a/src/components/modules/TransferModule/ReplicaMigrationOptions/story.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* -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 React from "react"; -import { storiesOf } from "@storybook/react"; -import ReplicaMigrationOptions from "."; - -const props: any = {}; -storiesOf("ReplicaMigrationOptions", module).add("default", () => ( - // eslint-disable-next-line react/jsx-props-no-spreading -
- -
-)); diff --git a/src/components/modules/TransferModule/Schedule/Schedule.tsx b/src/components/modules/TransferModule/Schedule/Schedule.tsx index 5897ffdb..522a6b21 100644 --- a/src/components/modules/TransferModule/Schedule/Schedule.tsx +++ b/src/components/modules/TransferModule/Schedule/Schedule.tsx @@ -17,7 +17,7 @@ import { observer } from "mobx-react"; import React from "react"; import styled from "styled-components"; -import ReplicaExecutionOptions from "@src/components/modules/TransferModule/ReplicaExecutionOptions"; +import TransferExecutionOptions from "@src/components/modules/TransferModule/TransferExecutionOptions"; import ScheduleItem from "@src/components/modules/TransferModule/ScheduleItem"; import { ThemePalette, ThemeProps } from "@src/components/Theme"; import AlertModal from "@src/components/ui/AlertModal"; @@ -407,7 +407,7 @@ class Schedule extends React.Component { this.handleCloseOptionsModal(); }} > - { diff --git a/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.spec.tsx b/src/components/modules/TransferModule/TransferDetailsContent/ReplicaDetailsContent.spec.tsx similarity index 99% rename from src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.spec.tsx rename to src/components/modules/TransferModule/TransferDetailsContent/ReplicaDetailsContent.spec.tsx index f2b017c0..0624df7f 100644 --- a/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.spec.tsx +++ b/src/components/modules/TransferModule/TransferDetailsContent/ReplicaDetailsContent.spec.tsx @@ -31,7 +31,7 @@ import { NETWORK_MOCK } from "@tests/mocks/NetworksMock"; import { STORAGE_BACKEND_MOCK } from "@tests/mocks/StoragesMock"; import { REPLICA_ITEM_DETAILS_MOCK } from "@tests/mocks/TransferMock"; -import ReplicaDetailsContent from "./"; +import ReplicaDetailsContent from "."; const scheduleStoreMock = jest.createMockFromModule( "@src/stores/ScheduleStore" diff --git a/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx b/src/components/modules/TransferModule/TransferDetailsContent/TransferDetailsContent.tsx similarity index 96% rename from src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx rename to src/components/modules/TransferModule/TransferDetailsContent/TransferDetailsContent.tsx index fbf5ad3b..4cf1cf89 100644 --- a/src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx +++ b/src/components/modules/TransferModule/TransferDetailsContent/TransferDetailsContent.tsx @@ -16,7 +16,7 @@ import { observer } from "mobx-react"; import React from "react"; import styled from "styled-components"; -import { ReplicaItemDetails } from "@src/@types/MainItem"; +import { TransferItemDetails } from "@src/@types/MainItem"; import { MinionPool } from "@src/@types/MinionPool"; import DetailsNavigation from "@src/components/modules/NavigationModule/DetailsNavigation"; import Executions from "@src/components/modules/TransferModule/Executions"; @@ -74,7 +74,7 @@ const NavigationItems = [ type TimezoneValue = "utc" | "local"; type Props = { - item?: ReplicaItemDetails | null; + item?: TransferItemDetails | null; itemId: string; endpoints: Endpoint[]; sourceSchema: Field[]; @@ -101,7 +101,7 @@ type Props = { onDeleteExecutionClick: (execution: Execution | null) => void; onExecuteClick: () => void; onCreateDeploymentClick: () => void; - onDeleteReplicaClick: () => void; + onDeleteTransferClick: () => void; onAddScheduleClick: (schedule: ScheduleType) => void; onScheduleChange: ( scheduleId: string | null, @@ -115,7 +115,7 @@ type State = { timezone: TimezoneValue; }; @observer -class ReplicaDetailsContent extends React.Component { +class TransferDetailsContent extends React.Component { state: State = { timezone: "local", }; @@ -164,7 +164,7 @@ class ReplicaDetailsContent extends React.Component { - @@ -268,4 +268,4 @@ class ReplicaDetailsContent extends React.Component { } } -export default ReplicaDetailsContent; +export default TransferDetailsContent; diff --git a/src/components/modules/TransferModule/TransferDetailsContent/package.json b/src/components/modules/TransferModule/TransferDetailsContent/package.json new file mode 100644 index 00000000..17f2b80d --- /dev/null +++ b/src/components/modules/TransferModule/TransferDetailsContent/package.json @@ -0,0 +1,6 @@ +{ + "name": "TransferDetailsContent", + "version": "0.0.0", + "private": true, + "main": "./TransferDetailsContent.tsx" +} diff --git a/src/components/modules/TransferModule/ReplicaDetailsContent/story.tsx b/src/components/modules/TransferModule/TransferDetailsContent/story.tsx similarity index 100% rename from src/components/modules/TransferModule/ReplicaDetailsContent/story.tsx rename to src/components/modules/TransferModule/TransferDetailsContent/story.tsx diff --git a/src/components/modules/TransferModule/TransferDetailsTable/TransferDetailsTable.tsx b/src/components/modules/TransferModule/TransferDetailsTable/TransferDetailsTable.tsx index 9f1ca9f7..ef2c51a8 100644 --- a/src/components/modules/TransferModule/TransferDetailsTable/TransferDetailsTable.tsx +++ b/src/components/modules/TransferModule/TransferDetailsTable/TransferDetailsTable.tsx @@ -20,7 +20,7 @@ import { EndpointUtils, StorageBackend } from "@src/@types/Endpoint"; import { isNetworkMapSecurityGroups, isNetworkMapSourceDest, - TransferItem, + ActionItem, TransferNetworkMap, } from "@src/@types/MainItem"; import { MinionPool } from "@src/@types/MinionPool"; @@ -156,7 +156,7 @@ export const ArrowIcon = styled.div` `; export type Props = { - item?: TransferItem | null; + item?: ActionItem | null; instancesDetails: Instance[]; networks?: Network[]; minionPools: MinionPool[]; @@ -338,7 +338,7 @@ class TransferDetailsTable extends React.Component { destinationBody = destinationBody.concat(getBody(transferDisk)); } } else if ( - this.props.item?.type === "migration" && + this.props.item?.type === "deployment" && (this.props.item.last_execution_status === "RUNNING" || this.props.item.last_execution_status === "AWAITING_MINION_ALLOCATIONS") @@ -446,7 +446,7 @@ class TransferDetailsTable extends React.Component { destinationBody = getBody(destinationNic); } } else if ( - this.props.item?.type === "migration" && + this.props.item?.type === "deployment" && (this.props.item.last_execution_status === "RUNNING" || this.props.item.last_execution_status === "AWAITING_MINION_ALLOCATIONS") @@ -501,7 +501,7 @@ class TransferDetailsTable extends React.Component { destinationName = transferResult.instance_name || transferResult.name; destinationBody = getBody(transferResult); } else if ( - this.props.item?.type === "migration" && + this.props.item?.type === "deployment" && (this.props.item.last_execution_status === "RUNNING" || this.props.item.last_execution_status === "AWAITING_MINION_ALLOCATIONS") ) { diff --git a/src/components/modules/TransferModule/ReplicaExecutionOptions/ReplicaExecutionOptions.spec.tsx b/src/components/modules/TransferModule/TransferExecutionOptions/ReplicaExecutionOptions.spec.tsx similarity index 98% rename from src/components/modules/TransferModule/ReplicaExecutionOptions/ReplicaExecutionOptions.spec.tsx rename to src/components/modules/TransferModule/TransferExecutionOptions/ReplicaExecutionOptions.spec.tsx index 40e11391..729a1b8a 100644 --- a/src/components/modules/TransferModule/ReplicaExecutionOptions/ReplicaExecutionOptions.spec.tsx +++ b/src/components/modules/TransferModule/TransferExecutionOptions/ReplicaExecutionOptions.spec.tsx @@ -17,7 +17,7 @@ import React from "react"; import { fireEvent, render } from "@testing-library/react"; import TestUtils from "@tests/TestUtils"; -import ReplicaExecutionOptions from "./"; +import ReplicaExecutionOptions from "."; jest.mock("@src/plugins/default/ContentPlugin", () => jest.fn(() => null)); diff --git a/src/components/modules/TransferModule/ReplicaExecutionOptions/ReplicaExecutionOptions.tsx b/src/components/modules/TransferModule/TransferExecutionOptions/TransferExecutionOptions.tsx similarity index 97% rename from src/components/modules/TransferModule/ReplicaExecutionOptions/ReplicaExecutionOptions.tsx rename to src/components/modules/TransferModule/TransferExecutionOptions/TransferExecutionOptions.tsx index 1372b5f8..108e69a1 100644 --- a/src/components/modules/TransferModule/ReplicaExecutionOptions/ReplicaExecutionOptions.tsx +++ b/src/components/modules/TransferModule/TransferExecutionOptions/TransferExecutionOptions.tsx @@ -65,7 +65,7 @@ type State = { fields: Field[]; }; @observer -class ReplicaExecutionOptions extends React.Component { +class TransferExecutionOptions extends React.Component { static defaultProps = { executionLabel: "Execute", executing: false, @@ -157,4 +157,4 @@ class ReplicaExecutionOptions extends React.Component { } } -export default ReplicaExecutionOptions; +export default TransferExecutionOptions; diff --git a/src/components/modules/TransferModule/ReplicaExecutionOptions/images/execution.svg b/src/components/modules/TransferModule/TransferExecutionOptions/images/execution.svg similarity index 100% rename from src/components/modules/TransferModule/ReplicaExecutionOptions/images/execution.svg rename to src/components/modules/TransferModule/TransferExecutionOptions/images/execution.svg diff --git a/src/components/modules/TransferModule/TransferExecutionOptions/package.json b/src/components/modules/TransferModule/TransferExecutionOptions/package.json new file mode 100644 index 00000000..d3a07135 --- /dev/null +++ b/src/components/modules/TransferModule/TransferExecutionOptions/package.json @@ -0,0 +1,6 @@ +{ + "name": "TransferExecutionOptions", + "version": "0.0.0", + "private": true, + "main": "./TransferExecutionOptions.tsx" +} diff --git a/src/components/modules/TransferModule/ReplicaExecutionOptions/story.tsx b/src/components/modules/TransferModule/TransferExecutionOptions/story.tsx similarity index 100% rename from src/components/modules/TransferModule/ReplicaExecutionOptions/story.tsx rename to src/components/modules/TransferModule/TransferExecutionOptions/story.tsx diff --git a/src/components/modules/TransferModule/TransferItemModal/TransferItemModal.tsx b/src/components/modules/TransferModule/TransferItemModal/TransferItemModal.tsx index ea5e51fc..ce9c4837 100644 --- a/src/components/modules/TransferModule/TransferItemModal/TransferItemModal.tsx +++ b/src/components/modules/TransferModule/TransferItemModal/TransferItemModal.tsx @@ -19,8 +19,7 @@ import styled from "styled-components"; import providerStore, { getFieldChangeOptions, } from "@src/stores/ProviderStore"; -import replicaStore from "@src/stores/ReplicaStore"; -import migrationStore from "@src/stores/MigrationStore"; +import transferStore from "@src/stores/TransferStore"; import endpointStore from "@src/stores/EndpointStore"; import { OptionsSchemaPlugin } from "@src/plugins"; @@ -39,8 +38,8 @@ import WizardStorage from "@src/components/modules/WizardModule/WizardStorage"; import type { UpdateData, - TransferItemDetails, - MigrationItemDetails, + ActionItemDetails, + DeploymentItemDetails, } from "@src/@types/MainItem"; import { Endpoint, @@ -57,7 +56,7 @@ import { SecurityGroup, } from "@src/@types/Network"; -import { providerTypes, migrationFields } from "@src/constants"; +import { providerTypes } from "@src/constants"; import configLoader from "@src/utils/Config"; import LoadingButton from "@src/components/ui/LoadingButton"; import minionPoolStore from "@src/stores/MinionPoolStore"; @@ -105,11 +104,11 @@ const Buttons = styled.div` type Width = "normal" | "wide"; type Props = { - type?: "replica" | "migration"; + type?: "transfer" | "deployment"; isOpen: boolean; onRequestClose: () => void; onUpdateComplete: (redirectTo: string) => void; - replica: TransferItemDetails; + transfer: ActionItemDetails; destinationEndpoint: Endpoint; sourceEndpoint: Endpoint; instancesDetails: Instance[]; @@ -187,7 +186,7 @@ class TransferItemModal extends React.Component { getStorageMap(storageBackends: StorageBackend[]): StorageMap[] { const storageMap: StorageMap[] = []; - const currentStorage = this.props.replica.storage_mappings; + const currentStorage = this.props.transfer.storage_mappings; const buildStorageMap = ( type: "backend" | "disk", mapping: any @@ -246,7 +245,7 @@ class TransferItemModal extends React.Component { getSelectedNetworks(): NetworkMap[] { const selectedNetworks: NetworkMap[] = []; - const networkMap: any = this.props.replica.network_map; + const networkMap: any = this.props.transfer.network_map; if (networkMap) { Object.keys(networkMap).forEach(sourceNetworkName => { @@ -334,8 +333,8 @@ class TransferItemModal extends React.Component { return defaultStorage; }; - if (this.props.replica.storage_mappings?.default) { - return buildDefaultStorage(this.props.replica.storage_mappings.default); + if (this.props.transfer.storage_mappings?.default) { + return buildDefaultStorage(this.props.transfer.storage_mappings.default); } if (endpointStore.storageConfigDefault) { @@ -354,8 +353,8 @@ class TransferItemModal extends React.Component { const currentData = type === "source" ? this.state.sourceData : this.state.destinationData; - const replicaMinionMappings = - this.props.replica[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS]; + const transferMinionMappings = + this.props.transfer[INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS]; if (parentFieldName) { if ( @@ -366,10 +365,10 @@ class TransferItemModal extends React.Component { } if ( parentFieldName === INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS && - replicaMinionMappings && - replicaMinionMappings[fieldName] !== undefined + transferMinionMappings && + transferMinionMappings[fieldName] !== undefined ) { - return replicaMinionMappings[fieldName]; + return transferMinionMappings[fieldName]; } } @@ -378,8 +377,8 @@ class TransferItemModal extends React.Component { } if (fieldName === "title") { - if (this.props.replica.notes) { - return this.props.replica.notes; + if (this.props.transfer.notes) { + return this.props.transfer.notes; } let title = this.props.instancesDetails?.[0]?.name; if ( @@ -393,22 +392,22 @@ class TransferItemModal extends React.Component { if (fieldName === "minion_pool_id") { return type === "source" - ? this.props.replica.origin_minion_pool_id - : this.props.replica.destination_minion_pool_id; + ? this.props.transfer.origin_minion_pool_id + : this.props.transfer.destination_minion_pool_id; } - const replicaData: any = + const transferData: any = type === "source" - ? this.props.replica.source_environment - : this.props.replica.destination_environment; + ? this.props.transfer.source_environment + : this.props.transfer.destination_environment; if (parentFieldName) { - if (replicaData[parentFieldName]?.[fieldName] !== undefined) { - return replicaData[parentFieldName][fieldName]; + if (transferData[parentFieldName]?.[fieldName] !== undefined) { + return transferData[parentFieldName][fieldName]; } } - if (replicaData[fieldName] !== undefined) { - return replicaData[fieldName]; + if (transferData[fieldName] !== undefined) { + return transferData[fieldName]; } const endpoint = type === "source" @@ -419,16 +418,10 @@ class TransferItemModal extends React.Component { const osMapping = /^(windows|linux)/.exec(fieldName); if (osMapping) { const osData = - replicaData[`${plugin.migrationImageMapFieldName}/${osMapping[0]}`]; + transferData[`${plugin.migrationImageMapFieldName}/${osMapping[0]}`]; return osData; } - const anyData = this.props.replica as any; - if (migrationFields.find(f => f.name === fieldName) && anyData[fieldName]) { - return anyData[fieldName]; - } - if (fieldName === "skip_os_morphing" && this.props.type === "migration") { - return migrationStore.getDefaultSkipOsMorphing(anyData); - } + return defaultValue; } @@ -474,11 +467,9 @@ class TransferItemModal extends React.Component { useCache, }); } catch (err) { - if (optionsType === "destination" || this.props.type === "migration") { + if (optionsType === "destination") { const destinationFailedMessage = - this.props.type === "replica" - ? "An error has occurred during the loading of the Replica's options for editing. There could be connection issues with the destination platform. Please retry the operation." - : "An error has occurred during loading of the source or destination platforms' environment options for editing of the Migration's parameters. You may still recreate the Migration with the same parameters as the original one by clicking \"Create\"."; + "An error has occurred during the loading of the Transfer's options for editing. There could be connection issues with the destination platform. Please retry the operation."; this.setState({ destinationFailedMessage }); } throw err; @@ -505,8 +496,8 @@ class TransferItemModal extends React.Component { : this.props.destinationEndpoint; const env = ObjectUtils.clone( type === "source" - ? this.props.replica.source_environment - : this.props.replica.destination_environment + ? this.props.transfer.source_environment + : this.props.transfer.destination_environment ); const stateEnv = type === "source" ? this.state.sourceData : this.state.destinationData; @@ -555,7 +546,7 @@ class TransferItemModal extends React.Component { isUpdateDisabled() { const isDestFailed = - this.props.type === "replica" && this.state.destinationFailedMessage; + this.props.type === "transfer" && this.state.destinationFailedMessage; return this.state.updateDisabled || isDestFailed; } @@ -593,8 +584,8 @@ class TransferItemModal extends React.Component { validateOptions(type: "source" | "destination") { const env = ObjectUtils.clone( type === "source" - ? this.props.replica.source_environment - : this.props.replica.destination_environment + ? this.props.transfer.source_environment + : this.props.transfer.destination_environment ); const data = @@ -632,13 +623,13 @@ class TransferItemModal extends React.Component { ? { ...this.state.sourceData } : { ...this.state.destinationData }; - const replicaData: any = + const transferData: any = type === "source" - ? this.props.replica.source_environment - : this.props.replica.destination_environment; + ? this.props.transfer.source_environment + : this.props.transfer.destination_environment; if (field.type === "array") { const currentValues: string[] = data[field.name] || []; - const oldValues: string[] = replicaData[field.name] || []; + const oldValues: string[] = transferData[field.name] || []; let values: string[] = currentValues; if (!currentValues.length) { values = [...oldValues]; @@ -658,7 +649,7 @@ class TransferItemModal extends React.Component { // existing fields from Object options from the previous Migration/Replica, // we always re-merge all the values on an object field update. data[parentFieldName] = - data[parentFieldName] || replicaData[parentFieldName] || {}; + data[parentFieldName] || transferData[parentFieldName] || {}; data[parentFieldName][field.name] = value; } else { data[field.name] = value; @@ -706,51 +697,21 @@ class TransferItemModal extends React.Component { uploadedScripts: this.state.uploadedScripts, removedScripts: this.state.removedScripts, }; - if (this.props.type === "replica") { - try { - await replicaStore.update({ - replica: this.props.replica as any, - sourceEndpoint: this.props.sourceEndpoint, - destinationEndpoint: this.props.destinationEndpoint, - updateData, - defaultStorage: this.getDefaultStorage(), - storageConfigDefault: endpointStore.storageConfigDefault, - }); - this.props.onRequestClose(); - this.props.onUpdateComplete( - `/transfers/${this.props.replica.id}/executions` - ); - } catch (err) { - this.setState({ updating: false }); - } - } else { - try { - const defaultStorage = EndpointUtils.getBusTypeStorageId( - endpointStore.storageBackends, - this.props.replica.storage_mappings?.default || null - ); - const replicaDefaultStorage: { - value: string | null; - busType?: string | null; - } = { - value: defaultStorage.id, - busType: defaultStorage.busType, - }; - const migration: MigrationItemDetails = await migrationStore.recreate({ - migration: this.props.replica as any, - sourceEndpoint: this.props.sourceEndpoint, - destEndpoint: this.props.destinationEndpoint, - updateData, - defaultStorage: replicaDefaultStorage, - updatedDefaultStorage: this.state.defaultStorage, - replicationCount: this.props.replica.replication_count, - }); - migrationStore.clearDetails(); - this.props.onRequestClose(); - this.props.onUpdateComplete(`/migrations/${migration.id}/tasks`); - } catch (err) { - this.setState({ updating: false }); - } + try { + await transferStore.update({ + transfer: this.props.transfer as any, + sourceEndpoint: this.props.sourceEndpoint, + destinationEndpoint: this.props.destinationEndpoint, + updateData, + defaultStorage: this.getDefaultStorage(), + storageConfigDefault: endpointStore.storageConfigDefault, + }); + this.props.onRequestClose(); + this.props.onUpdateComplete( + `/transfers/${this.props.transfer.id}/executions` + ); + } catch (err) { + this.setState({ updating: false }); } } @@ -842,7 +803,7 @@ class TransferItemModal extends React.Component { ? providerStore.sourceSchema : providerStore.destinationSchema; const fields = - this.props.type === "replica" ? schema.filter(f => !f.readOnly) : schema; + this.props.type === "transfer" ? schema.filter(f => !f.readOnly) : schema; const extraOptionsConfig = configLoader.config.extraOptionsApiCalls.find( o => { const provider = @@ -870,7 +831,7 @@ class TransferItemModal extends React.Component { return ( this.getFieldValue({ type, @@ -918,7 +879,6 @@ class TransferItemModal extends React.Component { "description", "execute_now", "execute_now_options", - ...migrationFields.map(f => f.name), ]} dictionaryKey={dictionaryKey} executeNowOptionsDisabled={ @@ -989,7 +949,7 @@ class TransferItemModal extends React.Component { }} uploadedScripts={this.state.uploadedScripts} removedScripts={this.state.removedScripts} - userScriptData={this.props.replica?.user_scripts} + userScriptData={this.props.transfer?.user_scripts} scrollableRef={(r: HTMLElement) => { this.scrollableRef = r; }} @@ -1030,7 +990,7 @@ class TransferItemModal extends React.Component { Loading ... ) : this.state.updating ? ( - {this.props.type === "replica" ? "Updating" : "Creating"} ... + {this.props.type === "transfer" ? "Updating" : "Creating"} ... ) : ( )} diff --git a/src/components/modules/TransferModule/TransferListItem/TransferListItem.tsx b/src/components/modules/TransferModule/TransferListItem/TransferListItem.tsx index bb4332f1..c9c35a98 100644 --- a/src/components/modules/TransferModule/TransferListItem/TransferListItem.tsx +++ b/src/components/modules/TransferModule/TransferListItem/TransferListItem.tsx @@ -16,7 +16,7 @@ import { observer } from "mobx-react"; import React from "react"; import styled from "styled-components"; -import { getTransferItemTitle, TransferItem } from "@src/@types/MainItem"; +import { getTransferItemTitle, ActionItem } from "@src/@types/MainItem"; import EndpointLogos from "@src/components/modules/EndpointModule/EndpointLogos"; import { ThemePalette, ThemeProps } from "@src/components/Theme"; import Checkbox from "@src/components/ui/Checkbox"; @@ -106,10 +106,10 @@ const Column = styled.div` `; type Props = { - item: TransferItem; + item: ActionItem; onClick: () => void; selected: boolean; - getListItemImage: (item: TransferItem) => string; + getListItemImage: (item: ActionItem) => string; showScheduleIcon?: boolean; endpointType: (endpointId: string) => string; getUserName: (userId: string) => string | undefined; @@ -122,19 +122,14 @@ class TransferListItem extends React.Component { return this.props.item.last_execution_status; } - getReplicaScenarioType() { + getTransferScenarioType() { let scenario = ""; switch(this.props.item.type) { - case "replica": + case "transfer": scenario = this.props.item.scenario; break; case "deployment": - scenario = this.props.item.replica_scenario; - break; - case "migration": - if (this.props.item.replica_id) { - scenario = "replica"; - } + scenario = this.props.item.transfer_scenario; break; default: } @@ -223,7 +218,7 @@ class TransferListItem extends React.Component { ) : null} {this.props.showScheduleIcon ? ( - + ) : null} diff --git a/src/components/modules/WizardModule/WizardOptions/WizardOptions.tsx b/src/components/modules/WizardModule/WizardOptions/WizardOptions.tsx index 2acbfcf2..8e5752a2 100644 --- a/src/components/modules/WizardModule/WizardOptions/WizardOptions.tsx +++ b/src/components/modules/WizardModule/WizardOptions/WizardOptions.tsx @@ -24,7 +24,7 @@ import { ThemePalette, ThemeProps } from "@src/components/Theme"; import FieldInput from "@src/components/ui/FieldInput"; import StatusImage from "@src/components/ui/StatusComponents/StatusImage"; import ToggleButtonBar from "@src/components/ui/ToggleButtonBar"; -import { executionOptions, migrationFields } from "@src/constants"; +import { executionOptions } from "@src/constants"; import { MinionPoolStoreUtils } from "@src/stores/MinionPoolStore"; import configLoader from "@src/utils/Config"; import LabelDictionary from "@src/utils/LabelDictionary"; @@ -297,10 +297,7 @@ class WizardOptions extends React.Component { }); } - if ( - this.props.wizardType === "migration" || - this.props.wizardType === "migration-destination-options-edit" - ) { + if (this.props.wizardType === "migration-destination-options-edit") { fieldsSchema.push({ name: "skip_os_morphing", type: "boolean", @@ -351,24 +348,6 @@ class WizardOptions extends React.Component { : `Set the options for ${this.props.wizardType} execution`, }); } - // } else if ( - // this.props.wizardType === "migration" || - // this.props.wizardType === "migration-destination-options-edit" - // ) { - // const shutdownInstanceField = migrationFields.find( - // f => f.name === "shutdown_instances" - // )!; - // shutdownInstanceField.disabled = this.props.executeNowOptionsDisabled; - // shutdownInstanceField.description = this.props.executeNowOptionsDisabled - // ? "The 'Shutdown Instances' option is disabled for the source provider" - // : shutdownInstanceField.description; - // fieldsSchema = [ - // ...fieldsSchema, - // ...migrationFields.map(f => - // f.name === "shutdown_instances" ? shutdownInstanceField : f - // ), - // ]; - // } return fieldsSchema; } diff --git a/src/components/modules/WizardModule/WizardPageContent/WizardPageContent.tsx b/src/components/modules/WizardModule/WizardPageContent/WizardPageContent.tsx index 7601ef48..bdad4437 100644 --- a/src/components/modules/WizardModule/WizardPageContent/WizardPageContent.tsx +++ b/src/components/modules/WizardModule/WizardPageContent/WizardPageContent.tsx @@ -35,7 +35,7 @@ import { ThemePalette, ThemeProps } from "@src/components/Theme"; import Button from "@src/components/ui/Button"; import InfoIcon from "@src/components/ui/InfoIcon"; import LoadingButton from "@src/components/ui/LoadingButton"; -import { migrationFields, providerTypes, wizardPages } from "@src/constants"; +import { providerTypes, wizardPages } from "@src/constants"; import endpointStore from "@src/stores/EndpointStore"; import instanceStore from "@src/stores/InstanceStore"; import minionPoolStore from "@src/stores/MinionPoolStore"; @@ -197,8 +197,8 @@ class WizardPageContent extends React.Component { getProvidersType(type: string) { return type === "source" - ? providerTypes.SOURCE_REPLICA - : providerTypes.TARGET_REPLICA; + ? providerTypes.SOURCE_TRANSFER + : providerTypes.TARGET_TRANSFER; } getProviders(direction: string): ProviderTypes[] { @@ -524,7 +524,6 @@ class WizardPageContent extends React.Component { "title", "execute_now", "execute_now_options", - ...migrationFields.map(f => f.name), ]} selectedInstances={this.props.wizardData.selectedInstances} showSeparatePerVm={Boolean( diff --git a/src/components/modules/WizardModule/WizardSummary/WizardSummary.tsx b/src/components/modules/WizardModule/WizardSummary/WizardSummary.tsx index 93fbdda3..41ad90aa 100644 --- a/src/components/modules/WizardModule/WizardSummary/WizardSummary.tsx +++ b/src/components/modules/WizardModule/WizardSummary/WizardSummary.tsx @@ -24,7 +24,6 @@ import { INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS } from "@src/components/module import { getDisks } from "@src/components/modules/WizardModule/WizardStorage"; import { ThemePalette, ThemeProps } from "@src/components/Theme"; import StatusPill from "@src/components/ui/StatusComponents/StatusPill"; -import { migrationFields } from "@src/constants"; import configLoader from "@src/utils/Config"; import DateUtils from "@src/utils/DateUtils"; import LabelDictionary from "@src/utils/LabelDictionary"; @@ -448,25 +447,6 @@ class WizardSummary extends React.Component { ); - // const migrationOptions = [ - // , - // , - // ]; - const renderDefaultStorageOption = () => (