= { root: { display: 'inline-block' } };
+
+const AddReadPermissionToDefaultIdentity: React.FC = () => {
+ const [readPermissionAssigned, onToggle] = useToggle(false);
+
+ return (
+
+
+ {ContainerCopyMessages.readPermissionAssigned.description}
+
+
+ onToggle(null, false)}
+ onPrimary={() => console.log('Primary action taken')}
+ >
+ {ContainerCopyMessages.readPermissionAssigned.popoverDescription}
+
+
+ );
+};
+
+export default AddReadPermissionToDefaultIdentity;
diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/DefaultManagedIdentity.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/DefaultManagedIdentity.tsx
new file mode 100644
index 000000000..50de01ae2
--- /dev/null
+++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/DefaultManagedIdentity.tsx
@@ -0,0 +1,41 @@
+import { Stack, Toggle } from "@fluentui/react";
+import React from "react";
+import ContainerCopyMessages from "../../../ContainerCopyMessages";
+import InfoTooltip from "../Components/InfoTooltip";
+import PopoverMessage from "../Components/PopoverContainer";
+import useToggle from "./hooks/useToggle";
+
+const managedIdentityTooltip = ContainerCopyMessages.defaultManagedIdentity.tooltip;
+
+const DefaultManagedIdentity: React.FC = () => {
+ const [defaultSystemAssigned, onToggle] = useToggle(false);
+
+ return (
+
+
+ {ContainerCopyMessages.defaultManagedIdentity.description}
+
+
+ onToggle(null, false)}
+ onPrimary={() => console.log('Primary action taken')}
+ >
+ {ContainerCopyMessages.defaultManagedIdentity.popoverDescription}
+
+
+ );
+};
+
+export default DefaultManagedIdentity;
diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/OnlineCopyEnabled.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/OnlineCopyEnabled.tsx
new file mode 100644
index 000000000..b8d041db1
--- /dev/null
+++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/OnlineCopyEnabled.tsx
@@ -0,0 +1,30 @@
+import { PrimaryButton, Stack } from "@fluentui/react";
+import React from "react";
+import ContainerCopyMessages from "../../../ContainerCopyMessages";
+import { useCopyJobContext } from "../../../Context/CopyJobContext";
+import { buildResourceLink } from "../../../CopyJobUtils";
+import useWindowOpenMonitor from "./hooks/useWindowOpenMonitor";
+
+const OnlineCopyEnabled: React.FC = () => {
+ const { copyJobState: { source } = {} } = useCopyJobContext();
+ const sourceAccountLink = buildResourceLink(source?.account);
+ const onlineCopyUrl = `${sourceAccountLink}/Features`;
+ const onWindowClosed = () => {
+ console.log('Online copy window closed');
+ };
+ const openWindowAndMonitor = useWindowOpenMonitor(onlineCopyUrl, onWindowClosed);
+
+ return (
+
+
+ {ContainerCopyMessages.onlineCopyEnabled.description}
+
+
+
+ );
+};
+
+export default OnlineCopyEnabled;
diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/PointInTimeRestore.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/PointInTimeRestore.tsx
new file mode 100644
index 000000000..3ef23d0c3
--- /dev/null
+++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/PointInTimeRestore.tsx
@@ -0,0 +1,31 @@
+import { PrimaryButton, Stack } from "@fluentui/react";
+import React from "react";
+import ContainerCopyMessages from "../../../ContainerCopyMessages";
+import { useCopyJobContext } from "../../../Context/CopyJobContext";
+import { buildResourceLink } from "../../../CopyJobUtils";
+import useWindowOpenMonitor from "./hooks/useWindowOpenMonitor";
+
+const PointInTimeRestore: React.FC = () => {
+ const { copyJobState: { source } = {} } = useCopyJobContext();
+ const sourceAccountLink = buildResourceLink(source?.account);
+ const pitrUrl = `${sourceAccountLink}/backupRestore`;
+
+ const onWindowClosed = () => {
+ console.log('Point-in-time restore window closed');
+ };
+ const openWindowAndMonitor = useWindowOpenMonitor(pitrUrl, onWindowClosed);
+
+ return (
+
+
+ {ContainerCopyMessages.pointInTimeRestore.description}
+
+
+
+ );
+};
+
+export default PointInTimeRestore;
diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/usePermissionsSection.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/usePermissionsSection.tsx
new file mode 100644
index 000000000..b60edeaf0
--- /dev/null
+++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/usePermissionsSection.tsx
@@ -0,0 +1,57 @@
+import ContainerCopyMessages from "../../../../ContainerCopyMessages";
+import { CopyJobMigrationType } from "../../../../Enums";
+import { CopyJobContextState } from "../../../../Types";
+import AddManagedIdentity from "../AddManagedIdentity";
+import AddReadPermissionToDefaultIdentity from "../AddReadPermissionToDefaultIdentity";
+import DefaultManagedIdentity from "../DefaultManagedIdentity";
+import OnlineCopyEnabled from "../OnlineCopyEnabled";
+import PointInTimeRestore from "../PointInTimeRestore";
+
+// Define a typed config for permission sections
+export interface PermissionSectionConfig {
+ id: string;
+ title: string;
+ Component: React.FC;
+ shouldShow?: (state: CopyJobContextState) => boolean; // optional conditional rendering
+}
+
+// Base permission sections with dynamic visibility logic
+const PERMISSION_SECTIONS_CONFIG: PermissionSectionConfig[] = [
+ {
+ id: "addManagedIdentity",
+ title: ContainerCopyMessages.addManagedIdentity.title,
+ Component: AddManagedIdentity,
+ },
+ {
+ id: "defaultManagedIdentity",
+ title: ContainerCopyMessages.defaultManagedIdentity.title,
+ Component: DefaultManagedIdentity,
+ },
+ {
+ id: "readPermissionAssigned",
+ title: ContainerCopyMessages.readPermissionAssigned.title,
+ Component: AddReadPermissionToDefaultIdentity,
+ }
+];
+
+const PERMISSION_SECTIONS_FOR_ONLINE_JOBS: PermissionSectionConfig[] = [
+ {
+ id: "pointInTimeRestore",
+ title: ContainerCopyMessages.pointInTimeRestore.title,
+ Component: PointInTimeRestore,
+ },
+ {
+ id: "onlineCopyEnabled",
+ title: ContainerCopyMessages.onlineCopyEnabled.title,
+ Component: OnlineCopyEnabled,
+ }
+];
+
+const usePermissionSections = (state: CopyJobContextState): PermissionSectionConfig[] => {
+ return [
+ ...PERMISSION_SECTIONS_CONFIG,
+ ...(state.migrationType !== CopyJobMigrationType.Offline ? PERMISSION_SECTIONS_FOR_ONLINE_JOBS : []),
+ ];
+};
+
+export default usePermissionSections;
\ No newline at end of file
diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useToggle.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useToggle.tsx
new file mode 100644
index 000000000..3c995eafc
--- /dev/null
+++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useToggle.tsx
@@ -0,0 +1,11 @@
+import { useCallback, useState } from "react";
+
+const useToggle = (initialState = false) => {
+ const [state, setState] = useState(initialState);
+ const onToggle = useCallback((_, checked?: boolean) => {
+ setState(!!checked);
+ }, []);
+ return [state, onToggle] as const;
+};
+
+export default useToggle;
\ No newline at end of file
diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useWindowOpenMonitor.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useWindowOpenMonitor.tsx
new file mode 100644
index 000000000..ce4daab34
--- /dev/null
+++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/hooks/useWindowOpenMonitor.tsx
@@ -0,0 +1,32 @@
+import { useEffect, useRef } from "react";
+
+const useWindowOpenMonitor = (url: string, onClose?: () => void, intervalMs = 500) => {
+ const intervalRef = useRef(null);
+
+ const openWindowAndMonitor = () => {
+ const newWindow = window.open(url, '_blank');
+ intervalRef.current = setInterval(() => {
+ if (newWindow?.closed) {
+ clearInterval(intervalRef.current!);
+ intervalRef.current = null;
+ console.log('New window has been closed!');
+ if (onClose) {
+ onClose();
+ }
+ }
+ }, intervalMs);
+ };
+
+ useEffect(() => {
+ return () => {
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current);
+ intervalRef.current = null;
+ }
+ };
+ }, []);
+
+ return openWindowAndMonitor;
+};
+
+export default useWindowOpenMonitor;
\ No newline at end of file
diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/index.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/index.tsx
new file mode 100644
index 000000000..ee7934795
--- /dev/null
+++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/AssignPermissions/index.tsx
@@ -0,0 +1,40 @@
+import { Image, Stack, Text } from "@fluentui/react";
+import { Accordion, AccordionHeader, AccordionItem, AccordionPanel } from "@fluentui/react-components";
+import React from "react";
+import WarningIcon from "../../../../../../images/warning.svg";
+import ContainerCopyMessages from "../../../ContainerCopyMessages";
+import { useCopyJobContext } from "../../../Context/CopyJobContext";
+import usePermissionSections, { PermissionSectionConfig } from "./hooks/usePermissionsSection";
+
+const PermissionSection: React.FC = ({ id, title, Component }) => (
+
+
+ {title}
+
+
+
+
+
+
+);
+
+const AssignPermissions = () => {
+ const { copyJobState } = useCopyJobContext();
+ const permissionSections = usePermissionSections(copyJobState);
+ return (
+
+
+ {ContainerCopyMessages.assignPermissions.description}
+
+
+ {
+ permissionSections.map(section => (
+
+ ))
+ }
+
+
+ );
+};
+
+export default AssignPermissions;
\ No newline at end of file
diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/InfoTooltip.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/InfoTooltip.tsx
new file mode 100644
index 000000000..1d6f9808f
--- /dev/null
+++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/InfoTooltip.tsx
@@ -0,0 +1,15 @@
+import { Image, ITooltipHostStyles, TooltipHost } from "@fluentui/react";
+import React from "react";
+import InfoIcon from "../../../../../../images/Info.svg";
+
+const InfoTooltip: React.FC<{ content?: string }> = ({ content }) => {
+ if (!content) return null;
+ const hostStyles: Partial = { root: { display: 'inline-block' } };
+ return (
+
+
+
+ );
+};
+
+export default React.memo(InfoTooltip);
\ No newline at end of file
diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/PopoverContainer.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/PopoverContainer.tsx
new file mode 100644
index 000000000..dc8424233
--- /dev/null
+++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/Components/PopoverContainer.tsx
@@ -0,0 +1,41 @@
+import { DefaultButton, PrimaryButton, Stack, Text } from "@fluentui/react";
+import React from "react";
+
+interface PopoverContainerProps {
+ title?: string;
+ children?: React.ReactNode;
+ onPrimary: () => void;
+ onCancel: () => void;
+}
+
+const PopoverContainer: React.FC = React.memo(({ title, children, onPrimary, onCancel }) => {
+ return (
+
+ {title}
+ {children}
+
+
+
+
+
+ );
+});
+
+interface PopoverMessageProps {
+ visible: boolean;
+ title: string;
+ onCancel: () => void;
+ onPrimary: () => void;
+ children: React.ReactNode;
+}
+
+const PopoverMessage: React.FC = ({ visible, title, onCancel, onPrimary, children }) => {
+ if (!visible) return null;
+ return (
+
+ {children}
+
+ );
+};
+
+export default PopoverMessage;
diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Utils/selectAccountUtils.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Utils/selectAccountUtils.tsx
index 2a79285ea..46c778d08 100644
--- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Utils/selectAccountUtils.tsx
+++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/Utils/selectAccountUtils.tsx
@@ -1,5 +1,6 @@
import React from "react";
import { DatabaseAccount, Subscription } from "../../../../../../Contracts/DataModels";
+import { CopyJobMigrationType } from "../../../../Enums";
import { CopyJobContextProviderType, CopyJobContextState, DropdownOptionType } from "../../../../Types";
export function useDropdownOptions(
@@ -67,7 +68,7 @@ export function useEventHandlers(setCopyJobState: setCopyJobStateType) {
(_ev?: React.FormEvent, checked?: boolean) => {
setCopyJobState((prevState: CopyJobContextState) => ({
...prevState,
- migrationType: checked ? "offline" : "online",
+ migrationType: checked ? CopyJobMigrationType.Offline : CopyJobMigrationType.Online,
}));
},
[setCopyJobState]
diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/index.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/index.tsx
index 241a59536..bf070e413 100644
--- a/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/index.tsx
+++ b/src/Explorer/ContainerCopy/CreateCopyJob/Screens/SelectAccount/index.tsx
@@ -5,6 +5,7 @@ import { useDatabaseAccounts } from "../../../../../hooks/useDatabaseAccounts";
import { useSubscriptions } from "../../../../../hooks/useSubscriptions";
import ContainerCopyMessages from "../../../ContainerCopyMessages";
import { useCopyJobContext } from "../../../Context/CopyJobContext";
+import { CopyJobMigrationType } from "../../../Enums";
import { AccountDropdown } from "./Components/AccountDropdown";
import { MigrationTypeCheckbox } from "./Components/MigrationTypeCheckbox";
import { SubscriptionDropdown } from "./Components/SubscriptionDropdown";
@@ -18,12 +19,13 @@ const SelectAccount = React.memo(
const selectedSubscriptionId = copyJobState?.source?.subscription?.subscriptionId;
const subscriptions: Subscription[] = useSubscriptions(armToken);
- const accounts: DatabaseAccount[] = useDatabaseAccounts(selectedSubscriptionId, armToken);
+ const allAccounts: DatabaseAccount[] = useDatabaseAccounts(selectedSubscriptionId, armToken);
+ const sqlApiOnlyAccounts: DatabaseAccount[] = allAccounts?.filter(account => account.type === "SQL" || account.kind === "GlobalDocumentDB");
- const { subscriptionOptions, accountOptions } = useDropdownOptions(subscriptions, accounts);
+ const { subscriptionOptions, accountOptions } = useDropdownOptions(subscriptions, sqlApiOnlyAccounts);
const { handleSelectSourceAccount, handleMigrationTypeChange } = useEventHandlers(setCopyJobState);
- const migrationTypeChecked = copyJobState?.migrationType === "offline";
+ const migrationTypeChecked = copyJobState?.migrationType === CopyJobMigrationType.Offline;
return (
diff --git a/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCreateCopyJobScreensList.tsx b/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCreateCopyJobScreensList.tsx
index 1d6108ce7..9ea950d22 100644
--- a/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCreateCopyJobScreensList.tsx
+++ b/src/Explorer/ContainerCopy/CreateCopyJob/Utils/useCreateCopyJobScreensList.tsx
@@ -1,5 +1,6 @@
import React from "react";
import { CopyJobContextState } from "../../Types";
+import AssignPermissions from "../Screens/AssignPermissions";
import PreviewCopyJob from "../Screens/PreviewCopyJob";
import SelectAccount from "../Screens/SelectAccount";
import SelectSourceAndTargetContainers from "../Screens/SelectSourceAndTargetContainers";
@@ -8,6 +9,7 @@ const SCREEN_KEYS = {
SelectAccount: "SelectAccount",
SelectSourceAndTargetContainers: "SelectSourceAndTargetContainers",
PreviewCopyJob: "PreviewCopyJob",
+ AssignPermissions: "AssignPermissions",
};
type Validation = {
@@ -29,7 +31,7 @@ function useCreateCopyJobScreensList() {
component: ,
validations: [
{
- validate: (state) => !!state?.source?.subscription && !!state?.source?.account,
+ validate: (state: CopyJobContextState) => !!state?.source?.subscription && !!state?.source?.account,
message: "Please select a subscription and account to proceed",
},
],
@@ -39,7 +41,7 @@ function useCreateCopyJobScreensList() {
component: ,
validations: [
{
- validate: (state) => (
+ validate: (state: CopyJobContextState) => (
!!state?.source?.databaseId && !!state?.source?.containerId && !!state?.target?.databaseId && !!state?.target?.containerId
),
message: "Please select source and target containers to proceed",
@@ -49,6 +51,16 @@ function useCreateCopyJobScreensList() {
{
key: SCREEN_KEYS.PreviewCopyJob,
component: ,
+ validations: [
+ {
+ validate: (state: CopyJobContextState) => !!state?.jobName,
+ message: "Please enter a job name to proceed",
+ },
+ ],
+ },
+ {
+ key: SCREEN_KEYS.AssignPermissions,
+ component: ,
validations: [],
},
],
diff --git a/src/Explorer/ContainerCopy/Enums/index.ts b/src/Explorer/ContainerCopy/Enums/index.ts
new file mode 100644
index 000000000..97beac1ec
--- /dev/null
+++ b/src/Explorer/ContainerCopy/Enums/index.ts
@@ -0,0 +1,10 @@
+export enum CopyJobMigrationType {
+ Offline = "offline",
+ Online = "online",
+}
+
+export enum CopyJobMigrationStatus {
+ Pause = "Pause",
+ Resume = "Resume",
+ Cancel = "Cancel",
+}
\ No newline at end of file
diff --git a/src/Explorer/ContainerCopy/Types/index.ts b/src/Explorer/ContainerCopy/Types/index.ts
index 5d66e8d01..d97eece8e 100644
--- a/src/Explorer/ContainerCopy/Types/index.ts
+++ b/src/Explorer/ContainerCopy/Types/index.ts
@@ -2,6 +2,7 @@ import { DatabaseAccount, Subscription } from "Contracts/DataModels";
import React from "react";
import { ApiType } from "UserContext";
import Explorer from "../../Explorer";
+import { CopyJobMigrationType } from "../Enums";
export interface ContainerCopyProps {
container: Explorer;
@@ -68,7 +69,7 @@ export interface DatabaseContainerSectionProps {
export interface CopyJobContextState {
jobName: string;
- migrationType: "online" | "offline";
+ migrationType: CopyJobMigrationType;
// source details
source: {
subscription: Subscription;
diff --git a/src/Explorer/ContainerCopy/containerCopyStyles.less b/src/Explorer/ContainerCopy/containerCopyStyles.less
index 0c3f41b8e..84ba37b53 100644
--- a/src/Explorer/ContainerCopy/containerCopyStyles.less
+++ b/src/Explorer/ContainerCopy/containerCopyStyles.less
@@ -46,4 +46,27 @@
font-weight: 600;
}
}
+
+ .accordionHeader {
+ button {
+ display: flex;
+ align-items: center;
+ .accordionHeaderText {
+ margin-left: 5px;
+ font-weight: 600;
+ }
+ .statusIcon {
+ margin-left: auto;
+ }
+ }
+ }
+
+ .foreground {
+ z-index: 10;
+ background-color: white;
+ padding: 20px;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
+ // transform: translate(0%, -5%);
+ position: absolute;
+ }
}