mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-29 22:02:01 +00:00
Initial dev for container copy
This commit is contained in:
19
src/Explorer/ContainerCopy/Actions/CopyJobActions.tsx
Normal file
19
src/Explorer/ContainerCopy/Actions/CopyJobActions.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
||||
import ContainerCopyMessages from "../ContainerCopyMessages";
|
||||
import CreateCopyJobScreensProvider from "../CreateCopyJob/Screens/CreateCopyJobScreensProvider";
|
||||
import { CopyJobContextState } from "../Types";
|
||||
|
||||
export const openCreateCopyJobPanel = () => {
|
||||
const sidePanelState = useSidePanel.getState()
|
||||
sidePanelState.setPanelHasConsole(false);
|
||||
sidePanelState.openSidePanel(
|
||||
ContainerCopyMessages.createCopyJobPanelTitle,
|
||||
<CreateCopyJobScreensProvider />,
|
||||
"600px"
|
||||
);
|
||||
}
|
||||
|
||||
export const submitCreateCopyJob = (state: CopyJobContextState) => {
|
||||
console.log("Submitting create copy job with state:", state);
|
||||
};
|
||||
31
src/Explorer/ContainerCopy/CommandBar/CopyJobCommandBar.tsx
Normal file
31
src/Explorer/ContainerCopy/CommandBar/CopyJobCommandBar.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
||||
import React from "react";
|
||||
import { StyleConstants } from "../../../Common/StyleConstants";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import * as CommandBarUtil from "../../Menus/CommandBar/CommandBarUtil";
|
||||
import { ContainerCopyProps } from "../Types";
|
||||
import { getCommandBarButtons } from "./Utils";
|
||||
|
||||
const backgroundColor = StyleConstants.BaseLight;
|
||||
const rootStyle = {
|
||||
root: {
|
||||
backgroundColor: backgroundColor,
|
||||
},
|
||||
};
|
||||
|
||||
const CopyJobCommandBar: React.FC<ContainerCopyProps> = ({ container }) => {
|
||||
const commandBarItems: CommandButtonComponentProps[] = getCommandBarButtons(container);
|
||||
const controlButtons: ICommandBarItemProps[] = CommandBarUtil.convertButton(commandBarItems, backgroundColor);
|
||||
|
||||
return (
|
||||
<div className="commandBarContainer">
|
||||
<FluentCommandBar
|
||||
ariaLabel="Use left and right arrow keys to navigate between commands"
|
||||
styles={rootStyle}
|
||||
items={controlButtons}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CopyJobCommandBar;
|
||||
56
src/Explorer/ContainerCopy/CommandBar/Utils.ts
Normal file
56
src/Explorer/ContainerCopy/CommandBar/Utils.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import AddIcon from "../../../../images/Add.svg";
|
||||
import FeedbackIcon from "../../../../images/Feedback-Command.svg";
|
||||
import RefreshIcon from "../../../../images/refresh-cosmos.svg";
|
||||
import { configContext, Platform } from "../../../ConfigContext";
|
||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
import Explorer from "../../Explorer";
|
||||
import * as Actions from "../Actions/CopyJobActions";
|
||||
import ContainerCopyMessages from "../ContainerCopyMessages";
|
||||
import { CopyJobCommandBarBtnType } from "../Types";
|
||||
|
||||
function getCopyJobBtns(): CopyJobCommandBarBtnType[] {
|
||||
const buttons: CopyJobCommandBarBtnType[] = [
|
||||
{
|
||||
key: "createCopyJob",
|
||||
iconSrc: AddIcon,
|
||||
label: ContainerCopyMessages.createCopyJobButtonLabel,
|
||||
ariaLabel: ContainerCopyMessages.createCopyJobButtonAriaLabel,
|
||||
onClick: Actions.openCreateCopyJobPanel,
|
||||
},
|
||||
{
|
||||
key: "refresh",
|
||||
iconSrc: RefreshIcon,
|
||||
label: ContainerCopyMessages.refreshButtonLabel,
|
||||
ariaLabel: ContainerCopyMessages.refreshButtonAriaLabel,
|
||||
onClick: () => { },
|
||||
},
|
||||
];
|
||||
if (configContext.platform === Platform.Portal) {
|
||||
buttons.push({
|
||||
key: "feedback",
|
||||
iconSrc: FeedbackIcon,
|
||||
label: ContainerCopyMessages.feedbackButtonLabel,
|
||||
ariaLabel: ContainerCopyMessages.feedbackButtonAriaLabel,
|
||||
onClick: () => { },
|
||||
});
|
||||
}
|
||||
return buttons;
|
||||
}
|
||||
|
||||
function btnMapper(config: CopyJobCommandBarBtnType): CommandButtonComponentProps {
|
||||
return {
|
||||
iconSrc: config.iconSrc,
|
||||
iconAlt: config.label,
|
||||
onCommandClick: config.onClick,
|
||||
commandButtonLabel: undefined as string | undefined,
|
||||
ariaLabel: config.ariaLabel,
|
||||
tooltipText: config.label,
|
||||
hasPopup: false,
|
||||
disabled: config.disabled ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
export function getCommandBarButtons(_container: Explorer): CommandButtonComponentProps[] {
|
||||
return getCopyJobBtns().map(btnMapper);
|
||||
}
|
||||
|
||||
42
src/Explorer/ContainerCopy/ContainerCopyMessages.ts
Normal file
42
src/Explorer/ContainerCopy/ContainerCopyMessages.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
export default {
|
||||
// Copy Job Command Bar
|
||||
feedbackButtonLabel: "Feedback",
|
||||
feedbackButtonAriaLabel: "Provide feedback on copy jobs",
|
||||
refreshButtonLabel: "Refresh",
|
||||
refreshButtonAriaLabel: "Refresh copy jobs",
|
||||
createCopyJobButtonLabel: "Create Copy Job",
|
||||
createCopyJobButtonAriaLabel: "Create a new container copy job",
|
||||
|
||||
// No Copy Jobs Found
|
||||
noCopyJobsTitle: "No copy jobs to show",
|
||||
createCopyJobButtonText: "Create a container copy job",
|
||||
|
||||
// Create Copy Job Panel
|
||||
createCopyJobPanelTitle: "Copy container",
|
||||
|
||||
// Select Account Screen
|
||||
selectAccountDescription: "Please select a source account from which to copy.",
|
||||
subscriptionDropdownLabel: "Subscription",
|
||||
subscriptionDropdownPlaceholder: "Select a subscription",
|
||||
sourceAccountDropdownLabel: "Account",
|
||||
sourceAccountDropdownPlaceholder: "Select an account",
|
||||
migrationTypeCheckboxLabel: "Copy container in offline mode",
|
||||
|
||||
// Select Source and Target Containers Screen
|
||||
selectSourceAndTargetContainersDescription: "Please select a source container and a destination container to copy to.",
|
||||
sourceContainerSubHeading: "Source container",
|
||||
targetContainerSubHeading: "Destination container",
|
||||
databaseDropdownLabel: "Database",
|
||||
databaseDropdownPlaceholder: "Select a database",
|
||||
containerDropdownLabel: "Container",
|
||||
containerDropdownPlaceholder: "Select a container",
|
||||
|
||||
// Preview and Create Screen
|
||||
jobNameLabel: "Job name",
|
||||
sourceSubscriptionLabel: "Source subscription",
|
||||
sourceAccountLabel: "Source account",
|
||||
sourceDatabaseLabel: "Source database",
|
||||
sourceContainerLabel: "Source container",
|
||||
targetDatabaseLabel: "Destination database",
|
||||
targetContainerLabel: "Destination container",
|
||||
}
|
||||
61
src/Explorer/ContainerCopy/Context/CopyJobContext.tsx
Normal file
61
src/Explorer/ContainerCopy/Context/CopyJobContext.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import React from "react";
|
||||
import { userContext } from "UserContext";
|
||||
import { useAADAuth } from "../../../hooks/useAADAuth";
|
||||
import { useConfig } from "../../../hooks/useConfig";
|
||||
import { CopyJobContextProviderType, CopyJobContextState, CopyJobFlowType } from "../Types";
|
||||
|
||||
export const CopyJobContext = React.createContext<CopyJobContextProviderType>(null);
|
||||
export const useCopyJobContext = (): CopyJobContextProviderType => {
|
||||
const context = React.useContext(CopyJobContext);
|
||||
if (!context) {
|
||||
throw new Error("useCopyJobContext must be used within a CopyJobContextProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
interface CopyJobContextProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const getInitialCopyJobState = (): CopyJobContextState => {
|
||||
return {
|
||||
jobName: "",
|
||||
migrationType: "offline",
|
||||
source: {
|
||||
subscription: null,
|
||||
account: null,
|
||||
databaseId: "",
|
||||
containerId: "",
|
||||
},
|
||||
target: {
|
||||
subscriptionId: userContext.subscriptionId || "",
|
||||
account: userContext.databaseAccount || null,
|
||||
databaseId: "",
|
||||
containerId: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const CopyJobContextProvider: React.FC<CopyJobContextProviderProps> = (props) => {
|
||||
const config = useConfig();
|
||||
const { isLoggedIn, armToken } = useAADAuth(config);
|
||||
const [copyJobState, setCopyJobState] = React.useState<CopyJobContextState>(getInitialCopyJobState());
|
||||
const [flow, setFlow] = React.useState<CopyJobFlowType | null>(null);
|
||||
|
||||
if (!isLoggedIn || !armToken) {
|
||||
// Add a shimmer or loader here
|
||||
return null;
|
||||
}
|
||||
|
||||
const resetCopyJobState = () => {
|
||||
setCopyJobState(getInitialCopyJobState());
|
||||
}
|
||||
|
||||
return (
|
||||
<CopyJobContext.Provider value={{ armToken, copyJobState, setCopyJobState, flow, setFlow, resetCopyJobState }}>
|
||||
{props.children}
|
||||
</CopyJobContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export default CopyJobContextProvider;
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Stack } from "@fluentui/react";
|
||||
import React from "react";
|
||||
|
||||
interface FieldRowProps {
|
||||
label?: string;
|
||||
children: React.ReactNode;
|
||||
labelClassName?: string;
|
||||
}
|
||||
|
||||
const FieldRow: React.FC<FieldRowProps> = ({ label = "", children, labelClassName = "" }) => {
|
||||
return (
|
||||
<Stack horizontal horizontalAlign="space-between" className="flex-row">
|
||||
{
|
||||
label && (
|
||||
<Stack.Item align="center" className="flex-fixed-width">
|
||||
<label className={`field-label ${labelClassName}`}>{label}: </label>
|
||||
</Stack.Item>
|
||||
)
|
||||
}
|
||||
<Stack.Item align="center" className="flex-grow-col">
|
||||
{children}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default FieldRow;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { DefaultButton, PrimaryButton, Stack } from "@fluentui/react";
|
||||
import React from "react";
|
||||
|
||||
type NavigationControlsProps = {
|
||||
primaryBtnText: string;
|
||||
onPrimary: () => void;
|
||||
onPrevious: () => void;
|
||||
onCancel: () => void;
|
||||
isPrimaryDisabled: boolean;
|
||||
isPreviousDisabled: boolean;
|
||||
};
|
||||
|
||||
const NavigationControls: React.FC<NavigationControlsProps> = ({
|
||||
primaryBtnText,
|
||||
onPrimary,
|
||||
onPrevious,
|
||||
onCancel,
|
||||
isPrimaryDisabled,
|
||||
isPreviousDisabled,
|
||||
}) => (
|
||||
<Stack horizontal tokens={{ childrenGap: 20 }}>
|
||||
<PrimaryButton text={primaryBtnText} onClick={onPrimary} allowDisabledFocus disabled={isPrimaryDisabled} />
|
||||
<DefaultButton text="Previous" onClick={onPrevious} allowDisabledFocus disabled={isPreviousDisabled} />
|
||||
<DefaultButton text="Cancel" onClick={onCancel} />
|
||||
</Stack>
|
||||
);
|
||||
|
||||
export default React.memo(NavigationControls);
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Stack } from "@fluentui/react";
|
||||
import React from "react";
|
||||
import { useCopyJobContext } from "../../Context/CopyJobContext";
|
||||
import { useCopyJobNavigation } from "../Utils/useCopyJobNavigation";
|
||||
import NavigationControls from "./Components/NavigationControls";
|
||||
|
||||
const CreateCopyJobScreens: React.FC = () => {
|
||||
const { copyJobState } = useCopyJobContext();
|
||||
const {
|
||||
currentScreen,
|
||||
isPrimaryDisabled,
|
||||
isPreviousDisabled,
|
||||
handlePrimary,
|
||||
handlePrevious,
|
||||
handleCancel,
|
||||
primaryBtnText
|
||||
} = useCopyJobNavigation(copyJobState);
|
||||
|
||||
return (
|
||||
<Stack verticalAlign="space-between" className="createCopyJobScreensContainer">
|
||||
<Stack.Item className="createCopyJobScreensContent">{currentScreen?.component}</Stack.Item>
|
||||
<Stack.Item className="createCopyJobScreensFooter">
|
||||
<NavigationControls
|
||||
primaryBtnText={primaryBtnText}
|
||||
onPrimary={handlePrimary}
|
||||
onPrevious={handlePrevious}
|
||||
onCancel={handleCancel}
|
||||
isPrimaryDisabled={isPrimaryDisabled}
|
||||
isPreviousDisabled={isPreviousDisabled}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateCopyJobScreens;
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
import CopyJobContextProvider from "../../Context/CopyJobContext";
|
||||
import CreateCopyJobScreens from "./CreateCopyJobScreens";
|
||||
|
||||
const CreateCopyJobScreensProvider = () => {
|
||||
return (
|
||||
<CopyJobContextProvider>
|
||||
<CreateCopyJobScreens />
|
||||
</CopyJobContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateCopyJobScreensProvider;
|
||||
@@ -0,0 +1,31 @@
|
||||
import { IColumn } from "@fluentui/react";
|
||||
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
||||
|
||||
export const getPreviewCopyJobDetailsListColumns = function (): IColumn[] {
|
||||
return [
|
||||
{
|
||||
key: 'sourcedbname',
|
||||
name: ContainerCopyMessages.sourceDatabaseLabel,
|
||||
fieldName: 'sourceDatabaseName',
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
key: 'sourcecolname',
|
||||
name: ContainerCopyMessages.sourceContainerLabel,
|
||||
fieldName: 'sourceContainerName',
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
key: 'targetdbname',
|
||||
name: ContainerCopyMessages.targetDatabaseLabel,
|
||||
fieldName: 'targetDatabaseName',
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
key: 'targetcolname',
|
||||
name: ContainerCopyMessages.targetContainerLabel,
|
||||
fieldName: 'targetContainerName',
|
||||
minWidth: 100
|
||||
}
|
||||
];
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
import { DetailsList, DetailsListLayoutMode, Stack, Text, TextField } from '@fluentui/react';
|
||||
import FieldRow from 'Explorer/ContainerCopy/CreateCopyJob/Screens/Components/FieldRow';
|
||||
import React from 'react';
|
||||
import ContainerCopyMessages from '../../../ContainerCopyMessages';
|
||||
import { useCopyJobContext } from '../../../Context/CopyJobContext';
|
||||
import { getPreviewCopyJobDetailsListColumns } from './Utils/PreviewCopyJobUtils';
|
||||
|
||||
const PreviewCopyJob: React.FC = () => {
|
||||
const { copyJobState, setCopyJobState } = useCopyJobContext();
|
||||
|
||||
const selectedDatabaseAndContainers = [{
|
||||
sourceDatabaseName: copyJobState.source?.databaseId,
|
||||
sourceContainerName: copyJobState.source?.containerId,
|
||||
targetDatabaseName: copyJobState.target?.databaseId,
|
||||
targetContainerName: copyJobState.target?.containerId,
|
||||
}];
|
||||
const jobName = copyJobState.jobName;
|
||||
|
||||
const onJobNameChange = (_ev?: React.FormEvent, newValue?: string) => {
|
||||
setCopyJobState((prevState) => ({
|
||||
...prevState,
|
||||
jobName: newValue || '',
|
||||
}));
|
||||
};
|
||||
return (
|
||||
<Stack tokens={{ childrenGap: 20 }} className="previewCopyJobContainer">
|
||||
<FieldRow label={ContainerCopyMessages.jobNameLabel}>
|
||||
<TextField
|
||||
value={jobName}
|
||||
onChange={onJobNameChange}
|
||||
/>
|
||||
</FieldRow>
|
||||
<Stack>
|
||||
<Text className='bold'>{ContainerCopyMessages.sourceSubscriptionLabel}</Text>
|
||||
<Text>{copyJobState.source?.subscription?.displayName}</Text>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Text className='bold'>{ContainerCopyMessages.sourceAccountLabel}</Text>
|
||||
<Text>{copyJobState.source?.account?.name}</Text>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<DetailsList
|
||||
items={selectedDatabaseAndContainers}
|
||||
layoutMode={DetailsListLayoutMode.justified}
|
||||
checkboxVisibility={2}
|
||||
columns={getPreviewCopyJobDetailsListColumns()}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default PreviewCopyJob;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Dropdown } from "@fluentui/react";
|
||||
import React from "react";
|
||||
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
||||
import { DropdownOptionType } from "../../../../Types";
|
||||
import FieldRow from "../../Components/FieldRow";
|
||||
|
||||
interface AccountDropdownProps {
|
||||
options: DropdownOptionType[];
|
||||
selectedKey?: string;
|
||||
disabled: boolean;
|
||||
onChange: (_ev?: React.FormEvent, option?: DropdownOptionType) => void;
|
||||
}
|
||||
|
||||
export const AccountDropdown: React.FC<AccountDropdownProps> = React.memo(
|
||||
({ options, selectedKey, disabled, onChange }) => (
|
||||
<FieldRow label={ContainerCopyMessages.sourceAccountDropdownLabel}>
|
||||
<Dropdown
|
||||
placeholder={ContainerCopyMessages.sourceAccountDropdownPlaceholder}
|
||||
ariaLabel={ContainerCopyMessages.sourceAccountDropdownLabel}
|
||||
options={options}
|
||||
disabled={disabled}
|
||||
required
|
||||
selectedKey={selectedKey}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</FieldRow>
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Checkbox, Stack } from "@fluentui/react";
|
||||
import React from "react";
|
||||
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
||||
|
||||
interface MigrationTypeCheckboxProps {
|
||||
checked: boolean;
|
||||
onChange: (_ev?: React.FormEvent, checked?: boolean) => void;
|
||||
}
|
||||
|
||||
export const MigrationTypeCheckbox: React.FC<MigrationTypeCheckboxProps> = React.memo(
|
||||
({ checked, onChange }) => (
|
||||
<Stack horizontal horizontalAlign="space-between" className="migrationTypeRow">
|
||||
<Checkbox
|
||||
label={ContainerCopyMessages.migrationTypeCheckboxLabel}
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Dropdown } from "@fluentui/react";
|
||||
import React from "react";
|
||||
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
||||
import { DropdownOptionType } from "../../../../Types";
|
||||
import FieldRow from "../../Components/FieldRow";
|
||||
|
||||
interface SubscriptionDropdownProps {
|
||||
options: DropdownOptionType[];
|
||||
selectedKey?: string;
|
||||
onChange: (_ev?: React.FormEvent, option?: DropdownOptionType) => void;
|
||||
}
|
||||
|
||||
export const SubscriptionDropdown: React.FC<SubscriptionDropdownProps> = React.memo(
|
||||
({ options, selectedKey, onChange }) => (
|
||||
<FieldRow label={ContainerCopyMessages.subscriptionDropdownLabel}>
|
||||
<Dropdown
|
||||
placeholder={ContainerCopyMessages.subscriptionDropdownPlaceholder}
|
||||
ariaLabel={ContainerCopyMessages.subscriptionDropdownLabel}
|
||||
options={options}
|
||||
required
|
||||
selectedKey={selectedKey}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</FieldRow>
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,77 @@
|
||||
import React from "react";
|
||||
import { DatabaseAccount, Subscription } from "../../../../../../Contracts/DataModels";
|
||||
import { CopyJobContextProviderType, CopyJobContextState, DropdownOptionType } from "../../../../Types";
|
||||
|
||||
export function useDropdownOptions(
|
||||
subscriptions: Subscription[],
|
||||
accounts: DatabaseAccount[]
|
||||
): {
|
||||
subscriptionOptions: DropdownOptionType[],
|
||||
accountOptions: DropdownOptionType[]
|
||||
} {
|
||||
const subscriptionOptions = React.useMemo(
|
||||
() =>
|
||||
subscriptions?.map((sub) => ({
|
||||
key: sub.subscriptionId,
|
||||
text: sub.displayName,
|
||||
data: sub,
|
||||
})) || [],
|
||||
[subscriptions]
|
||||
);
|
||||
|
||||
const accountOptions = React.useMemo(
|
||||
() =>
|
||||
accounts?.map((account) => ({
|
||||
key: account.id,
|
||||
text: account.name,
|
||||
data: account,
|
||||
})) || [],
|
||||
[accounts]
|
||||
);
|
||||
|
||||
return { subscriptionOptions, accountOptions };
|
||||
}
|
||||
|
||||
type setCopyJobStateType = CopyJobContextProviderType["setCopyJobState"];
|
||||
|
||||
export function useEventHandlers(setCopyJobState: setCopyJobStateType) {
|
||||
const handleSelectSourceAccount = React.useCallback(
|
||||
(type: "subscription" | "account", data: Subscription & DatabaseAccount | undefined) => {
|
||||
setCopyJobState((prevState: CopyJobContextState) => {
|
||||
if (type === "subscription") {
|
||||
return {
|
||||
...prevState,
|
||||
source: {
|
||||
...prevState.source,
|
||||
subscription: data || null,
|
||||
account: null, // reset on subscription change
|
||||
},
|
||||
};
|
||||
}
|
||||
if (type === "account") {
|
||||
return {
|
||||
...prevState,
|
||||
source: {
|
||||
...prevState.source,
|
||||
account: data || null,
|
||||
},
|
||||
};
|
||||
}
|
||||
return prevState;
|
||||
});
|
||||
},
|
||||
[setCopyJobState]
|
||||
);
|
||||
|
||||
const handleMigrationTypeChange = React.useCallback(
|
||||
(_ev?: React.FormEvent<HTMLElement>, checked?: boolean) => {
|
||||
setCopyJobState((prevState: CopyJobContextState) => ({
|
||||
...prevState,
|
||||
migrationType: checked ? "offline" : "online",
|
||||
}));
|
||||
},
|
||||
[setCopyJobState]
|
||||
);
|
||||
|
||||
return { handleSelectSourceAccount, handleMigrationTypeChange };
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Stack } from "@fluentui/react";
|
||||
import React from "react";
|
||||
import { DatabaseAccount, Subscription } from "../../../../../Contracts/DataModels";
|
||||
import { useDatabaseAccounts } from "../../../../../hooks/useDatabaseAccounts";
|
||||
import { useSubscriptions } from "../../../../../hooks/useSubscriptions";
|
||||
import ContainerCopyMessages from "../../../ContainerCopyMessages";
|
||||
import { useCopyJobContext } from "../../../Context/CopyJobContext";
|
||||
import { AccountDropdown } from "./Components/AccountDropdown";
|
||||
import { MigrationTypeCheckbox } from "./Components/MigrationTypeCheckbox";
|
||||
import { SubscriptionDropdown } from "./Components/SubscriptionDropdown";
|
||||
import { useDropdownOptions, useEventHandlers } from "./Utils/selectAccountUtils";
|
||||
|
||||
interface SelectAccountProps { }
|
||||
|
||||
const SelectAccount = React.memo(
|
||||
(_props: SelectAccountProps) => {
|
||||
const { armToken, copyJobState, setCopyJobState } = useCopyJobContext();
|
||||
const selectedSubscriptionId = copyJobState?.source?.subscription?.subscriptionId;
|
||||
|
||||
const subscriptions: Subscription[] = useSubscriptions(armToken);
|
||||
const accounts: DatabaseAccount[] = useDatabaseAccounts(selectedSubscriptionId, armToken);
|
||||
|
||||
const { subscriptionOptions, accountOptions } = useDropdownOptions(subscriptions, accounts);
|
||||
const { handleSelectSourceAccount, handleMigrationTypeChange } = useEventHandlers(setCopyJobState);
|
||||
|
||||
const migrationTypeChecked = copyJobState?.migrationType === "offline";
|
||||
|
||||
return (
|
||||
<Stack className="selectAccountContainer" tokens={{ childrenGap: 15 }}>
|
||||
<span>{ContainerCopyMessages.selectAccountDescription}</span>
|
||||
|
||||
<SubscriptionDropdown
|
||||
options={subscriptionOptions}
|
||||
selectedKey={selectedSubscriptionId}
|
||||
onChange={(_ev, option) => handleSelectSourceAccount("subscription", option?.data)}
|
||||
/>
|
||||
|
||||
<AccountDropdown
|
||||
options={accountOptions}
|
||||
selectedKey={copyJobState?.source?.account?.id}
|
||||
disabled={!selectedSubscriptionId}
|
||||
onChange={(_ev, option) => handleSelectSourceAccount("account", option?.data)}
|
||||
/>
|
||||
|
||||
<MigrationTypeCheckbox
|
||||
checked={migrationTypeChecked}
|
||||
onChange={handleMigrationTypeChange}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default SelectAccount;
|
||||
@@ -0,0 +1,36 @@
|
||||
import { CopyJobContextState, DropdownOptionType } from "../../../../Types";
|
||||
|
||||
export function dropDownChangeHandler(
|
||||
setCopyJobState: React.Dispatch<React.SetStateAction<CopyJobContextState>>
|
||||
) {
|
||||
return (type: "sourceDatabase" | "sourceContainer" | "targetDatabase" | "targetContainer") =>
|
||||
(_evnt: any, option: DropdownOptionType) => {
|
||||
const value = option.key;
|
||||
setCopyJobState((prevState) => {
|
||||
switch (type) {
|
||||
case "sourceDatabase":
|
||||
return {
|
||||
...prevState,
|
||||
source: { ...prevState.source, databaseId: value, containerId: undefined }
|
||||
};
|
||||
case "sourceContainer":
|
||||
return {
|
||||
...prevState,
|
||||
source: { ...prevState.source, containerId: value }
|
||||
};
|
||||
case "targetDatabase":
|
||||
return {
|
||||
...prevState,
|
||||
target: { ...prevState.target, databaseId: value, containerId: undefined }
|
||||
};
|
||||
case "targetContainer":
|
||||
return {
|
||||
...prevState,
|
||||
target: { ...prevState.target, containerId: value }
|
||||
};
|
||||
default:
|
||||
return prevState;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Dropdown, Stack } from "@fluentui/react";
|
||||
import React from "react";
|
||||
import ContainerCopyMessages from "../../../../ContainerCopyMessages";
|
||||
import { DatabaseContainerSectionProps } from "../../../../Types";
|
||||
import FieldRow from "../../Components/FieldRow";
|
||||
|
||||
export const DatabaseContainerSection = ({
|
||||
heading,
|
||||
databaseOptions,
|
||||
selectedDatabase,
|
||||
databaseDisabled,
|
||||
databaseOnChange,
|
||||
containerOptions,
|
||||
selectedContainer,
|
||||
containerDisabled,
|
||||
containerOnChange
|
||||
}: DatabaseContainerSectionProps) => (
|
||||
<Stack tokens={{ childrenGap: 15 }} className="databaseContainerSection">
|
||||
<label className="subHeading">{heading}</label>
|
||||
<FieldRow label={ContainerCopyMessages.databaseDropdownLabel}>
|
||||
<Dropdown
|
||||
placeholder={ContainerCopyMessages.databaseDropdownPlaceholder}
|
||||
ariaLabel={ContainerCopyMessages.databaseDropdownLabel}
|
||||
options={databaseOptions}
|
||||
required
|
||||
disabled={!!databaseDisabled}
|
||||
selectedKey={selectedDatabase}
|
||||
onChange={databaseOnChange}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow label={ContainerCopyMessages.containerDropdownLabel}>
|
||||
<Dropdown
|
||||
placeholder={ContainerCopyMessages.containerDropdownPlaceholder}
|
||||
ariaLabel={ContainerCopyMessages.containerDropdownLabel}
|
||||
options={containerOptions}
|
||||
required
|
||||
disabled={!!containerDisabled}
|
||||
selectedKey={selectedContainer}
|
||||
onChange={containerOnChange}
|
||||
/>
|
||||
</FieldRow>
|
||||
</Stack>
|
||||
);
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Stack } from "@fluentui/react";
|
||||
import React from "react";
|
||||
import { useDatabases } from "../../../../../hooks/useDatabases";
|
||||
import { useDataContainers } from "../../../../../hooks/useDataContainers";
|
||||
import ContainerCopyMessages from "../../../ContainerCopyMessages";
|
||||
import { useCopyJobContext } from "../../../Context/CopyJobContext";
|
||||
import { DatabaseContainerSection } from "./components/DatabaseContainerSection";
|
||||
import { dropDownChangeHandler } from "./Events/DropDownChangeHandler";
|
||||
import { useMemoizedSourceAndTargetData } from "./memoizedData";
|
||||
|
||||
interface SelectSourceAndTargetContainersProps { }
|
||||
|
||||
const SelectSourceAndTargetContainers = (_props: SelectSourceAndTargetContainersProps) => {
|
||||
const { armToken, copyJobState, setCopyJobState } = useCopyJobContext();
|
||||
const {
|
||||
source,
|
||||
target,
|
||||
sourceDbParams,
|
||||
sourceContainerParams,
|
||||
targetDbParams,
|
||||
targetContainerParams
|
||||
} = useMemoizedSourceAndTargetData(copyJobState, armToken);
|
||||
|
||||
// Custom hooks
|
||||
const sourceDatabases = useDatabases(...sourceDbParams) || [];
|
||||
const sourceContainers = useDataContainers(...sourceContainerParams) || [];
|
||||
const targetDatabases = useDatabases(...targetDbParams) || [];
|
||||
const targetContainers = useDataContainers(...targetContainerParams) || [];
|
||||
|
||||
// Memoize option objects for dropdowns
|
||||
const sourceDatabaseOptions = React.useMemo(
|
||||
() => sourceDatabases.map((db: any) => ({ key: db.name, text: db.name, data: db })),
|
||||
[sourceDatabases]
|
||||
);
|
||||
const sourceContainerOptions = React.useMemo(
|
||||
() => sourceContainers.map((c: any) => ({ key: c.name, text: c.name, data: c })),
|
||||
[sourceContainers]
|
||||
);
|
||||
const targetDatabaseOptions = React.useMemo(
|
||||
() => targetDatabases.map((db: any) => ({ key: db.name, text: db.name, data: db })),
|
||||
[targetDatabases]
|
||||
);
|
||||
const targetContainerOptions = React.useMemo(
|
||||
() => targetContainers.map((c: any) => ({ key: c.name, text: c.name, data: c })),
|
||||
[targetContainers]
|
||||
);
|
||||
|
||||
const onDropdownChange = React.useCallback(dropDownChangeHandler(setCopyJobState), [setCopyJobState]);
|
||||
|
||||
return (
|
||||
<Stack className="selectSourceAndTargetContainers" tokens={{ childrenGap: 25 }}>
|
||||
<span>{ContainerCopyMessages.selectSourceAndTargetContainersDescription}</span>
|
||||
<DatabaseContainerSection
|
||||
heading={ContainerCopyMessages.sourceContainerSubHeading}
|
||||
databaseOptions={sourceDatabaseOptions}
|
||||
selectedDatabase={source?.databaseId}
|
||||
databaseDisabled={false}
|
||||
databaseOnChange={onDropdownChange("sourceDatabase")}
|
||||
containerOptions={sourceContainerOptions}
|
||||
selectedContainer={source?.containerId}
|
||||
containerDisabled={!source?.databaseId}
|
||||
containerOnChange={onDropdownChange("sourceContainer")}
|
||||
/>
|
||||
<DatabaseContainerSection
|
||||
heading={ContainerCopyMessages.targetContainerSubHeading}
|
||||
databaseOptions={targetDatabaseOptions}
|
||||
selectedDatabase={target?.databaseId}
|
||||
databaseDisabled={false}
|
||||
databaseOnChange={onDropdownChange("targetDatabase")}
|
||||
containerOptions={targetContainerOptions}
|
||||
selectedContainer={target?.containerId}
|
||||
containerDisabled={!target?.databaseId}
|
||||
containerOnChange={onDropdownChange("targetContainer")}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default SelectSourceAndTargetContainers;
|
||||
@@ -0,0 +1,58 @@
|
||||
import React from "react";
|
||||
import { CopyJobContextState, DatabaseParams, DataContainerParams } from "../../../Types";
|
||||
|
||||
export function useMemoizedSourceAndTargetData(copyJobState: CopyJobContextState, armToken: string) {
|
||||
const { source, target } = copyJobState ?? {};
|
||||
const selectedSourceAccount = source?.account;
|
||||
const selectedTargetAccount = target?.account;
|
||||
|
||||
const sourceDbParams = React.useMemo(
|
||||
() =>
|
||||
[
|
||||
armToken,
|
||||
source?.subscription?.subscriptionId,
|
||||
selectedSourceAccount?.resourceGroup,
|
||||
selectedSourceAccount?.name,
|
||||
'SQL',
|
||||
] as DatabaseParams,
|
||||
[armToken, source?.subscription?.subscriptionId, selectedSourceAccount]
|
||||
);
|
||||
|
||||
const sourceContainerParams = React.useMemo(
|
||||
() =>
|
||||
[
|
||||
armToken,
|
||||
source?.subscription?.subscriptionId,
|
||||
selectedSourceAccount?.resourceGroup,
|
||||
selectedSourceAccount?.name,
|
||||
source?.databaseId,
|
||||
'SQL',
|
||||
] as DataContainerParams,
|
||||
[armToken, source?.subscription?.subscriptionId, selectedSourceAccount, source?.databaseId]
|
||||
);
|
||||
|
||||
const targetDbParams = React.useMemo(
|
||||
() => [
|
||||
armToken,
|
||||
target?.subscriptionId,
|
||||
selectedTargetAccount?.resourceGroup,
|
||||
selectedTargetAccount?.name,
|
||||
'SQL',
|
||||
] as DatabaseParams,
|
||||
[armToken, target?.subscriptionId, selectedTargetAccount]
|
||||
);
|
||||
|
||||
const targetContainerParams = React.useMemo(
|
||||
() => [
|
||||
armToken,
|
||||
target?.subscriptionId,
|
||||
selectedTargetAccount?.resourceGroup,
|
||||
selectedTargetAccount?.name,
|
||||
target?.databaseId,
|
||||
'SQL',
|
||||
] as DataContainerParams,
|
||||
[armToken, target?.subscriptionId, selectedTargetAccount, target?.databaseId]
|
||||
);
|
||||
|
||||
return { source, target, sourceDbParams, sourceContainerParams, targetDbParams, targetContainerParams };
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { submitCreateCopyJob } from "Explorer/ContainerCopy/Actions/CopyJobActions";
|
||||
import { useCallback, useMemo, useReducer } from "react";
|
||||
import { useSidePanel } from "../../../../hooks/useSidePanel";
|
||||
import { CopyJobContextState } from "../../Types";
|
||||
import { SCREEN_KEYS, useCreateCopyJobScreensList } from "./useCreateCopyJobScreensList";
|
||||
|
||||
type NavigationState = {
|
||||
screenHistory: string[];
|
||||
};
|
||||
|
||||
type Action =
|
||||
| { type: "NEXT"; nextScreen: string }
|
||||
| { type: "PREVIOUS" }
|
||||
| { type: "RESET" };
|
||||
|
||||
function navigationReducer(state: NavigationState, action: Action): NavigationState {
|
||||
switch (action.type) {
|
||||
case "NEXT":
|
||||
return {
|
||||
screenHistory: [...state.screenHistory, action.nextScreen],
|
||||
};
|
||||
case "PREVIOUS":
|
||||
return {
|
||||
screenHistory: state.screenHistory.length > 1 ? state.screenHistory.slice(0, -1) : state.screenHistory,
|
||||
};
|
||||
case "RESET":
|
||||
return {
|
||||
screenHistory: [SCREEN_KEYS.SelectAccount],
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export function useCopyJobNavigation(copyJobState: CopyJobContextState) {
|
||||
const screens = useCreateCopyJobScreensList();
|
||||
const [state, dispatch] = useReducer(navigationReducer, { screenHistory: [SCREEN_KEYS.SelectAccount] });
|
||||
|
||||
const currentScreenKey = state.screenHistory[state.screenHistory.length - 1];
|
||||
const currentScreen = screens.find((screen) => screen.key === currentScreenKey);
|
||||
|
||||
const isPrimaryDisabled = useMemo(
|
||||
() => !currentScreen?.validations.every((v) => v.validate(copyJobState)),
|
||||
[currentScreen.key, copyJobState]
|
||||
);
|
||||
const primaryBtnText = useMemo(() => {
|
||||
if (currentScreenKey === SCREEN_KEYS.PreviewCopyJob) {
|
||||
return "Copy";
|
||||
}
|
||||
return "Next";
|
||||
}, [currentScreenKey]);
|
||||
|
||||
const isPreviousDisabled = state.screenHistory.length <= 1;
|
||||
|
||||
const handlePrimary = useCallback(() => {
|
||||
if (currentScreenKey === SCREEN_KEYS.SelectAccount) {
|
||||
dispatch({ type: "NEXT", nextScreen: SCREEN_KEYS.SelectSourceAndTargetContainers });
|
||||
}
|
||||
if (currentScreenKey === SCREEN_KEYS.SelectSourceAndTargetContainers) {
|
||||
dispatch({ type: "NEXT", nextScreen: SCREEN_KEYS.PreviewCopyJob });
|
||||
}
|
||||
if (currentScreenKey === SCREEN_KEYS.PreviewCopyJob) {
|
||||
submitCreateCopyJob(copyJobState);
|
||||
}
|
||||
}, [currentScreenKey, copyJobState]);
|
||||
|
||||
const handlePrevious = useCallback(() => {
|
||||
dispatch({ type: "PREVIOUS" });
|
||||
}, []);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
dispatch({ type: "RESET" });
|
||||
useSidePanel.getState().closeSidePanel();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
currentScreen,
|
||||
isPrimaryDisabled,
|
||||
isPreviousDisabled,
|
||||
handlePrimary,
|
||||
handlePrevious,
|
||||
handleCancel,
|
||||
primaryBtnText,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import React from "react";
|
||||
import { CopyJobContextState } from "../../Types";
|
||||
import PreviewCopyJob from "../Screens/PreviewCopyJob";
|
||||
import SelectAccount from "../Screens/SelectAccount";
|
||||
import SelectSourceAndTargetContainers from "../Screens/SelectSourceAndTargetContainers";
|
||||
|
||||
const SCREEN_KEYS = {
|
||||
SelectAccount: "SelectAccount",
|
||||
SelectSourceAndTargetContainers: "SelectSourceAndTargetContainers",
|
||||
PreviewCopyJob: "PreviewCopyJob",
|
||||
};
|
||||
|
||||
type Validation = {
|
||||
validate: (state: CopyJobContextState) => boolean;
|
||||
message: string;
|
||||
};
|
||||
|
||||
type Screen = {
|
||||
key: string;
|
||||
component: React.ReactElement;
|
||||
validations: Validation[];
|
||||
};
|
||||
|
||||
function useCreateCopyJobScreensList() {
|
||||
return React.useMemo<Screen[]>(
|
||||
() => [
|
||||
{
|
||||
key: SCREEN_KEYS.SelectAccount,
|
||||
component: <SelectAccount />,
|
||||
validations: [
|
||||
{
|
||||
validate: (state) => !!state?.source?.subscription && !!state?.source?.account,
|
||||
message: "Please select a subscription and account to proceed",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: SCREEN_KEYS.SelectSourceAndTargetContainers,
|
||||
component: <SelectSourceAndTargetContainers />,
|
||||
validations: [
|
||||
{
|
||||
validate: (state) => (
|
||||
!!state?.source?.databaseId && !!state?.source?.containerId && !!state?.target?.databaseId && !!state?.target?.containerId
|
||||
),
|
||||
message: "Please select source and target containers to proceed",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: SCREEN_KEYS.PreviewCopyJob,
|
||||
component: <PreviewCopyJob />,
|
||||
validations: [],
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
export { SCREEN_KEYS, useCreateCopyJobScreensList };
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ActionButton, Image } from '@fluentui/react';
|
||||
import React, { useCallback } from 'react';
|
||||
import CopyJobIcon from "../../../../../images/ContainerCopy/copy-jobs.svg";
|
||||
import * as Actions from "../../Actions/CopyJobActions";
|
||||
import ContainerCopyMessages from '../../ContainerCopyMessages';
|
||||
|
||||
interface CopyJobsNotFoundProps { }
|
||||
|
||||
const CopyJobsNotFound: React.FC<CopyJobsNotFoundProps> = () => {
|
||||
|
||||
const handleCreateCopyJob = useCallback(Actions.openCreateCopyJobPanel, []);
|
||||
return (
|
||||
<div className='notFoundContainer flexContainer centerContent'>
|
||||
<Image src={CopyJobIcon} alt={ContainerCopyMessages.noCopyJobsTitle} width={100} height={100} />
|
||||
<h4 className='noCopyJobsMessage'>{ContainerCopyMessages.noCopyJobsTitle}</h4>
|
||||
<ActionButton allowDisabledFocus className='createCopyJobButton' onClick={handleCreateCopyJob}>
|
||||
{ContainerCopyMessages.createCopyJobButtonText}
|
||||
</ActionButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CopyJobsNotFound;
|
||||
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import CopyJobsNotFound from '../MonitorCopyJobs/Components/CopyJobs.NotFound';
|
||||
|
||||
interface MonitorCopyJobsProps { }
|
||||
|
||||
const MonitorCopyJobs: React.FC<MonitorCopyJobsProps> = () => {
|
||||
return (
|
||||
<div className='monitorCopyJobs flexContainer'>
|
||||
<CopyJobsNotFound />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MonitorCopyJobs;
|
||||
99
src/Explorer/ContainerCopy/Types/index.ts
Normal file
99
src/Explorer/ContainerCopy/Types/index.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { DatabaseAccount, Subscription } from "Contracts/DataModels";
|
||||
import React from "react";
|
||||
import { ApiType } from "UserContext";
|
||||
import Explorer from "../../Explorer";
|
||||
|
||||
export interface ContainerCopyProps {
|
||||
container: Explorer;
|
||||
}
|
||||
|
||||
export type CopyJobCommandBarBtnType = {
|
||||
key: string;
|
||||
iconSrc: string;
|
||||
label: string;
|
||||
ariaLabel: string;
|
||||
disabled?: boolean;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export type CopyJobTabForwardRefHandle = {
|
||||
validate: (state: CopyJobContextState) => boolean;
|
||||
};
|
||||
|
||||
export type DropdownOptionType = {
|
||||
key: string,
|
||||
text: string,
|
||||
data: any
|
||||
};
|
||||
|
||||
export type FetchDatabasesListParams = {
|
||||
armToken: string;
|
||||
subscriptionId: string;
|
||||
resourceGroupName: string;
|
||||
accountName: string;
|
||||
apiType?: ApiType;
|
||||
};
|
||||
|
||||
export interface FetchDataContainersListParams extends FetchDatabasesListParams {
|
||||
databaseName: string;
|
||||
}
|
||||
|
||||
export type DatabaseParams = [
|
||||
string,
|
||||
string | undefined,
|
||||
string | undefined,
|
||||
string | undefined,
|
||||
ApiType
|
||||
];
|
||||
export type DataContainerParams = [
|
||||
string,
|
||||
string | undefined,
|
||||
string | undefined,
|
||||
string | undefined,
|
||||
string | undefined,
|
||||
ApiType
|
||||
];
|
||||
|
||||
export interface DatabaseContainerSectionProps {
|
||||
heading: string,
|
||||
databaseOptions: DropdownOptionType[],
|
||||
selectedDatabase: string,
|
||||
databaseDisabled?: boolean,
|
||||
databaseOnChange: (ev: any, option: DropdownOptionType) => void,
|
||||
containerOptions: DropdownOptionType[],
|
||||
selectedContainer: string,
|
||||
containerDisabled?: boolean,
|
||||
containerOnChange: (ev: any, option: DropdownOptionType) => void
|
||||
}
|
||||
|
||||
export interface CopyJobContextState {
|
||||
jobName: string;
|
||||
migrationType: "online" | "offline";
|
||||
// source details
|
||||
source: {
|
||||
subscription: Subscription;
|
||||
account: DatabaseAccount;
|
||||
databaseId: string;
|
||||
containerId: string;
|
||||
},
|
||||
// target details
|
||||
target: {
|
||||
subscriptionId: string;
|
||||
account: DatabaseAccount;
|
||||
databaseId: string;
|
||||
containerId: string;
|
||||
},
|
||||
}
|
||||
|
||||
export interface CopyJobFlowType {
|
||||
currentScreen: string;
|
||||
}
|
||||
|
||||
export interface CopyJobContextProviderType {
|
||||
armToken: string;
|
||||
flow: CopyJobFlowType;
|
||||
setFlow: React.Dispatch<React.SetStateAction<CopyJobFlowType>>;
|
||||
copyJobState: CopyJobContextState | null;
|
||||
setCopyJobState: React.Dispatch<React.SetStateAction<CopyJobContextState>>;
|
||||
resetCopyJobState: () => void;
|
||||
}
|
||||
49
src/Explorer/ContainerCopy/containerCopyStyles.less
Normal file
49
src/Explorer/ContainerCopy/containerCopyStyles.less
Normal file
@@ -0,0 +1,49 @@
|
||||
@import "../../../less/Common/Constants.less";
|
||||
|
||||
#containerCopyWrapper {
|
||||
.centerContent {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.notFoundContainer {
|
||||
.noCopyJobsMessage {
|
||||
font-weight: 600;
|
||||
margin: 0 auto;
|
||||
color: @FocusColor;
|
||||
}
|
||||
button.createCopyJobButton {
|
||||
color: @LinkColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
.createCopyJobScreensContainer {
|
||||
height: 100%;
|
||||
padding: 1em 1.5em;
|
||||
.bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
label.field-label {
|
||||
padding: 0;
|
||||
}
|
||||
.flex-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
label.field-label {
|
||||
font-weight: 600;
|
||||
}
|
||||
.flex-fixed-width {
|
||||
flex: 0 0 auto;
|
||||
width: 150px;
|
||||
}
|
||||
.flex-grow-col {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
.databaseContainerSection {
|
||||
label.subHeading {
|
||||
font: inherit;
|
||||
padding: unset;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/Explorer/ContainerCopy/index.tsx
Normal file
19
src/Explorer/ContainerCopy/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React, { Suspense } from 'react';
|
||||
import CopyJobCommandBar from './CommandBar/CopyJobCommandBar';
|
||||
import { ContainerCopyProps } from './Types';
|
||||
import './containerCopyStyles.less';
|
||||
|
||||
const MonitorCopyJobs = React.lazy(() => import('./MonitorCopyJobs/MonitorCopyJobs'));
|
||||
|
||||
const ContainerCopyPanel: React.FC<ContainerCopyProps> = ({ container }) => {
|
||||
return (
|
||||
<div id="containerCopyWrapper" className="flexContainer hideOverflows">
|
||||
<CopyJobCommandBar container={container} />
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<MonitorCopyJobs />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContainerCopyPanel;
|
||||
Reference in New Issue
Block a user