feat: in-platform migration tooling for Vault policies + scaffolding

This commit is contained in:
Sheen Capadngan
2025-10-14 03:43:24 +08:00
parent fc6e364c60
commit c7d9df419e
21 changed files with 1525 additions and 62 deletions
@@ -7,6 +7,7 @@ import { OrgMembershipRole } from "@app/helpers/roles";
import { usePopUp } from "@app/hooks";
import { SelectImportFromPlatformModal } from "./components/SelectImportFromPlatformModal";
import { VaultConnectionSection } from "./components/VaultConnectionSection";
export const ExternalMigrationsTab = () => {
const { hasOrgRole } = useOrgPermission();
@@ -14,45 +15,72 @@ export const ExternalMigrationsTab = () => {
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["selectImportPlatform"] as const);
return (
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="flex justify-between">
<div className="flex items-center gap-2">
<p className="text-xl font-medium text-mineshaft-100">Import from external source</p>
<div className="flex flex-col gap-6">
{/* In-Platform Migration Tooling Section */}
<div className="border-mineshaft-600 bg-mineshaft-900 rounded-lg border p-4">
<div className="mb-4">
<h2 className="text-mineshaft-100 text-xl font-semibold">
In-Platform Migration Tooling
</h2>
<p className="mb-6 mt-1 text-sm text-gray-400">
Configure platform connections to enable migration features throughout Infisical, such
as importing policies and resources directly within the UI.
</p>
</div>
<VaultConnectionSection />
</div>
<div>
<a
target="_blank"
rel="noopener noreferrer"
href="https://infisical.com/docs/documentation/platform/external-migrations/overview"
>
<div className="ml-2 inline-block rounded-md bg-yellow/20 px-1.5 pt-[0.04rem] pb-[0.03rem] text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
Docs
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="text-xxs mb-[0.07rem] ml-1.5"
/>
</div>
</a>
</div>
{/* Bulk Data Import Section */}
<div className="border-mineshaft-600 bg-mineshaft-900 rounded-lg border p-4">
<div className="mb-4">
<h2 className="text-mineshaft-100 text-xl font-semibold">Bulk Data Import</h2>
<p className="mb-6 mt-1 text-sm text-gray-400">
Perform one-time bulk imports of data from external platforms.
</p>
</div>
<Button
onClick={() => {
handlePopUpOpen("selectImportPlatform");
}}
isDisabled={!hasOrgRole(OrgMembershipRole.Admin)}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
>
Import
</Button>
</div>
<p className="mb-4 text-gray-400">Import data from another platform to Infisical.</p>
<div className="flex items-start justify-between">
<div>
<div className="flex items-center gap-2">
<p className="text-mineshaft-100 text-base font-medium">
Import from external source
</p>
<a
target="_blank"
rel="noopener noreferrer"
href="https://infisical.com/docs/documentation/platform/external-migrations/overview"
>
<div className="bg-yellow/20 text-yellow inline-block rounded-md px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
Docs
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="text-xxs mb-[0.07rem] ml-1.5"
/>
</div>
</a>
</div>
<p className="mt-1 text-sm text-gray-400">
Import data from another platform to Infisical.
</p>
</div>
<SelectImportFromPlatformModal
isOpen={popUp.selectImportPlatform.isOpen}
onToggle={(state) => handlePopUpToggle("selectImportPlatform", state)}
/>
<Button
onClick={() => {
handlePopUpOpen("selectImportPlatform");
}}
isDisabled={!hasOrgRole(OrgMembershipRole.Admin)}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
>
Import
</Button>
</div>
<SelectImportFromPlatformModal
isOpen={popUp.selectImportPlatform.isOpen}
onToggle={(state) => handlePopUpToggle("selectImportPlatform", state)}
/>
</div>
</div>
);
};
@@ -0,0 +1,100 @@
import { useMemo } from "react";
import { createNotification } from "@app/components/notifications";
import { FilterableSelect, FormControl } from "@app/components/v2";
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { useListAppConnections } from "@app/hooks/api/appConnections/queries";
import {
useGetExternalMigrationConfig,
useUpdateExternalMigrationConfig
} from "@app/hooks/api/migration";
import { ExternalMigrationProviders } from "@app/hooks/api/migration/types";
export const VaultConnectionSection = () => {
const { data: appConnections = [], isPending: isLoadingConnections } = useListAppConnections();
const vaultConnections = useMemo(
() => appConnections.filter((conn) => conn.app === AppConnection.HCVault),
[appConnections]
);
const { data: currentConfig, isPending: isLoadingConfig } = useGetExternalMigrationConfig(
ExternalMigrationProviders.Vault
);
const { mutateAsync: updateConfig, isPending: isUpdating } = useUpdateExternalMigrationConfig(
ExternalMigrationProviders.Vault
);
const handleConnectionChange = async (
selectedConnection: { id: string; name: string } | null
) => {
try {
await updateConfig({
connectionId: selectedConnection?.id || null
});
createNotification({
type: "success",
text: "Vault connection updated successfully"
});
} catch (error) {
console.error("Failed to update vault connection:", error);
createNotification({
type: "error",
text: "Failed to update vault connection"
});
}
};
const selectedConnection = useMemo(() => {
if (!currentConfig?.connectionId) return null;
return vaultConnections.find((conn) => conn.id === currentConfig.connectionId) || null;
}, [currentConfig?.connectionId, vaultConnections]);
const isLoading = isLoadingConnections || isLoadingConfig;
return (
<div>
<div className="mb-4 flex items-center gap-3">
<img
src="/images/integrations/Vault.png"
alt="HashiCorp Vault logo"
className="bg-bunker-500 h-10 w-10 rounded-md p-2"
/>
<div>
<h3 className="text-mineshaft-100 text-lg font-medium">HashiCorp Vault</h3>
<p className="text-sm text-gray-400">
Enable in-platform migration tooling for policy imports and secret engine migrations
</p>
</div>
</div>
<div className="max-w-md">
<FormControl
label="HashiCorp Vault Connection"
tooltipText="Select an existing App Connection to enable in-platform migration features. Manage connections in the App Connections section."
>
<FilterableSelect
value={selectedConnection}
onChange={(newValue) => {
handleConnectionChange(newValue as { id: string; name: string } | null);
}}
isLoading={isLoading}
isDisabled={isUpdating}
options={vaultConnections}
placeholder="Select connection..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
isClearable
/>
</FormControl>
</div>
<p className="text-mineshaft-400 mt-2 text-xs">
Select an existing App Connection to enable in-platform migration features. Manage
connections in the App Connections section.
</p>
</div>
);
};