mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 17:40:44 +00:00
feature: secret sync base + aws parameter store & github
This commit is contained in:
+254
@@ -0,0 +1,254 @@
|
||||
import { useCallback } from "react";
|
||||
import {
|
||||
faCheck,
|
||||
faCopy,
|
||||
faDownload,
|
||||
faEllipsisV,
|
||||
faEraser,
|
||||
faInfoCircle,
|
||||
faRotate,
|
||||
faToggleOff,
|
||||
faToggleOn,
|
||||
faTrash
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
DeleteSecretSyncModal,
|
||||
SecretSyncImportSecretsModal,
|
||||
SecretSyncImportStatusBadge,
|
||||
SecretSyncRemoveSecretsModal,
|
||||
SecretSyncRemoveStatusBadge
|
||||
} from "@app/components/secret-syncs";
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
IconButton,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { ROUTE_PATHS } from "@app/const/routes";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
|
||||
import { usePopUp, useToggle } from "@app/hooks";
|
||||
import {
|
||||
TSecretSync,
|
||||
useSecretSyncOption,
|
||||
useTriggerSecretSyncSyncSecrets,
|
||||
useUpdateSecretSync
|
||||
} from "@app/hooks/api/secretSyncs";
|
||||
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
||||
|
||||
type Props = {
|
||||
secretSync: TSecretSync;
|
||||
};
|
||||
|
||||
export const SecretSyncActionTriggers = ({ secretSync }: Props) => {
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||
"importSecrets",
|
||||
"removeSecrets",
|
||||
"deleteSync"
|
||||
] as const);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const triggerSyncSecrets = useTriggerSecretSyncSyncSecrets();
|
||||
const updateSync = useUpdateSecretSync();
|
||||
|
||||
const { destination } = secretSync;
|
||||
|
||||
const destinationName = SECRET_SYNC_MAP[destination].name;
|
||||
const { syncOption } = useSecretSyncOption(destination);
|
||||
|
||||
const [isIdCopied, setIsIdCopied] = useToggle(false);
|
||||
|
||||
const handleCopyId = useCallback(() => {
|
||||
setIsIdCopied.on();
|
||||
navigator.clipboard.writeText(secretSync.id);
|
||||
|
||||
createNotification({
|
||||
text: "Secret Sync ID copied to clipboard",
|
||||
type: "info"
|
||||
});
|
||||
|
||||
const timer = setTimeout(() => setIsIdCopied.off(), 2000);
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
return () => clearTimeout(timer);
|
||||
}, [isIdCopied]);
|
||||
|
||||
const handleToggleEnableSync = async () => {
|
||||
const isEnabled = !secretSync.isEnabled;
|
||||
|
||||
try {
|
||||
await updateSync.mutateAsync({
|
||||
syncId: secretSync.id,
|
||||
destination: secretSync.destination,
|
||||
isEnabled
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: `Successfully ${isEnabled ? "enabled" : "disabled"} ${destinationName} Sync`,
|
||||
type: "success"
|
||||
});
|
||||
} catch {
|
||||
createNotification({
|
||||
text: `Failed to ${isEnabled ? "enable" : "disable"} ${destinationName} Sync`,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleTriggerSync = async () => {
|
||||
try {
|
||||
await triggerSyncSecrets.mutateAsync({
|
||||
syncId: secretSync.id,
|
||||
destination: secretSync.destination
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: `Successfully triggered ${destinationName} Sync`,
|
||||
type: "success"
|
||||
});
|
||||
} catch {
|
||||
createNotification({
|
||||
text: `Failed to trigger ${destinationName} Sync`,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="ml-auto mt-4 flex flex-wrap items-center justify-end gap-2">
|
||||
<SecretSyncImportStatusBadge secretSync={secretSync} />
|
||||
<SecretSyncRemoveStatusBadge secretSync={secretSync} />
|
||||
<div>
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faRotate} />}
|
||||
onClick={handleTriggerSync}
|
||||
className="h-9 rounded-r-none bg-mineshaft-500"
|
||||
>
|
||||
Trigger Sync
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<IconButton
|
||||
ariaLabel="add-folder-or-import"
|
||||
variant="outline_bg"
|
||||
className="h-9 w-10 rounded-l-none border-l-2 border-mineshaft border-l-mineshaft-700 bg-mineshaft-500"
|
||||
>
|
||||
<FontAwesomeIcon icon={faEllipsisV} />
|
||||
</IconButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
icon={<FontAwesomeIcon icon={isIdCopied ? faCheck : faCopy} />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleCopyId();
|
||||
}}
|
||||
>
|
||||
Copy Sync ID
|
||||
</DropdownMenuItem>
|
||||
{syncOption?.canImportSecrets && (
|
||||
<DropdownMenuItem
|
||||
icon={<FontAwesomeIcon icon={faDownload} />}
|
||||
onClick={() => handlePopUpOpen("importSecrets")}
|
||||
>
|
||||
<Tooltip
|
||||
position="left"
|
||||
sideOffset={42}
|
||||
content={`Import secrets from this ${destinationName} destination into Infisical.`}
|
||||
>
|
||||
<div className="flex h-full w-full items-center justify-between gap-1">
|
||||
<span>Import Secrets</span>
|
||||
<FontAwesomeIcon className="text-bunker-300" size="sm" icon={faInfoCircle} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
icon={<FontAwesomeIcon icon={faEraser} />}
|
||||
onClick={() => handlePopUpOpen("removeSecrets")}
|
||||
>
|
||||
<Tooltip
|
||||
position="left"
|
||||
sideOffset={42}
|
||||
content={`Remove secrets synced by Infisical from this ${destinationName} destination.`}
|
||||
>
|
||||
<div className="flex h-full w-full items-center justify-between gap-1">
|
||||
<span>Remove Secrets</span>
|
||||
<FontAwesomeIcon className="text-bunker-300" size="sm" icon={faInfoCircle} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</DropdownMenuItem>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
isDisabled={!isAllowed}
|
||||
icon={
|
||||
<FontAwesomeIcon icon={secretSync.isEnabled ? faToggleOff : faToggleOn} />
|
||||
}
|
||||
onClick={handleToggleEnableSync}
|
||||
>
|
||||
{secretSync.isEnabled ? "Disable" : "Enable"} Sync
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
>
|
||||
{(isAllowed: boolean) => (
|
||||
<DropdownMenuItem
|
||||
isDisabled={!isAllowed}
|
||||
icon={<FontAwesomeIcon icon={faTrash} />}
|
||||
onClick={() => handlePopUpOpen("deleteSync")}
|
||||
>
|
||||
Delete Sync
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
<SecretSyncImportSecretsModal
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("importSecrets", isOpen)}
|
||||
isOpen={popUp.importSecrets.isOpen}
|
||||
secretSync={secretSync}
|
||||
/>
|
||||
<SecretSyncRemoveSecretsModal
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("removeSecrets", isOpen)}
|
||||
isOpen={popUp.removeSecrets.isOpen}
|
||||
secretSync={secretSync}
|
||||
/>
|
||||
<DeleteSecretSyncModal
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("deleteSync", isOpen)}
|
||||
isOpen={popUp.deleteSync.isOpen}
|
||||
secretSync={secretSync}
|
||||
onComplete={() =>
|
||||
navigate({
|
||||
to: ROUTE_PATHS.SecretManager.IntegrationsListPage.path,
|
||||
params: {
|
||||
projectId: secretSync.projectId
|
||||
},
|
||||
search: {
|
||||
selectedTab: IntegrationsListPageTabs.SecretSyncs
|
||||
}
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
import { faFingerprint } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
|
||||
import { useSubscription } from "@app/context";
|
||||
import { EventType } from "@app/hooks/api/auditLogs/enums";
|
||||
import { TSecretSync } from "@app/hooks/api/secretSyncs";
|
||||
import { LogsSection } from "@app/pages/organization/AuditLogsPage/components/LogsSection";
|
||||
|
||||
// Add more events if needed
|
||||
const INTEGRATION_EVENTS = [
|
||||
EventType.SECRET_SYNC_SYNC_SECRETS,
|
||||
EventType.SECRET_SYNC_REMOVE_SECRETS,
|
||||
EventType.SECRET_SYNC_IMPORT_SECRETS
|
||||
];
|
||||
|
||||
type Props = {
|
||||
secretSync: TSecretSync;
|
||||
};
|
||||
|
||||
export const SecretSyncAuditLogsSection = ({ secretSync }: Props) => {
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const auditLogsRetentionDays = subscription?.auditLogsRetentionDays ?? 30;
|
||||
|
||||
return (
|
||||
<div className="flex max-h-full w-full flex-col gap-3 rounded-lg border border-mineshaft-600 bg-mineshaft-900 px-4 py-3">
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-2">
|
||||
<h3 className="font-semibold text-mineshaft-100">Sync Logs</h3>
|
||||
{subscription.auditLogs && (
|
||||
<p className="text-xs text-bunker-300">
|
||||
Displaying audit logs from the last {auditLogsRetentionDays} days
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{subscription.auditLogs ? (
|
||||
<LogsSection
|
||||
refetchInterval={4000}
|
||||
remappedHeaders={{
|
||||
Metadata: "Sync Status"
|
||||
}}
|
||||
showFilters={false}
|
||||
presets={{
|
||||
eventMetadata: { syncId: secretSync.id },
|
||||
startDate: new Date(new Date().setDate(new Date().getDate() - auditLogsRetentionDays)),
|
||||
eventType: INTEGRATION_EVENTS
|
||||
}}
|
||||
filterClassName="bg-mineshaft-900 static"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full items-center justify-center rounded-lg bg-mineshaft-800 text-sm text-mineshaft-200">
|
||||
<div className="flex flex-col items-center gap-4 py-20">
|
||||
<FontAwesomeIcon size="2x" icon={faFingerprint} />
|
||||
<p>
|
||||
Please{" "}
|
||||
{subscription && subscription.slug !== null ? (
|
||||
<Link to="/organization/billing" target="_blank" rel="noopener noreferrer">
|
||||
<a
|
||||
className="cursor-pointer underline transition-all hover:text-white"
|
||||
target="_blank"
|
||||
>
|
||||
upgrade your subscription
|
||||
</a>
|
||||
</Link>
|
||||
) : (
|
||||
<a
|
||||
href="https://infisical.com/scheduledemo"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<a
|
||||
className="cursor-pointer underline transition-all hover:text-white"
|
||||
target="_blank"
|
||||
>
|
||||
upgrade your subscription
|
||||
</a>
|
||||
</a>
|
||||
)}{" "}
|
||||
to view sync logs.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
||||
import { Badge } from "@app/components/v2";
|
||||
import { AWS_REGIONS } from "@app/helpers/appConnections";
|
||||
import { TAwsParameterStoreSync } from "@app/hooks/api/secretSyncs/types/aws-parameter-store-sync";
|
||||
|
||||
type Props = {
|
||||
secretSync: TAwsParameterStoreSync;
|
||||
};
|
||||
|
||||
export const AwsParameterStoreSyncDestinationSection = ({ secretSync }: Props) => {
|
||||
const {
|
||||
destinationConfig: { path, region }
|
||||
} = secretSync;
|
||||
|
||||
const awsRegion = AWS_REGIONS.find((r) => r.slug === region);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SecretSyncLabel label="Region">
|
||||
{awsRegion?.name}
|
||||
<Badge className="ml-1" variant="success">
|
||||
{awsRegion?.slug}{" "}
|
||||
</Badge>
|
||||
</SecretSyncLabel>
|
||||
<SecretSyncLabel label="Path">{path}</SecretSyncLabel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
import { ReactNode } from "react";
|
||||
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
||||
import { GitHubSyncSelectedRepositoriesTooltipContent } from "@app/components/secret-syncs/github/GitHubSyncSelectedRepositoriesTooltipContent";
|
||||
import { Tooltip } from "@app/components/v2";
|
||||
import {
|
||||
GitHubSyncScope,
|
||||
GitHubSyncVisibility,
|
||||
TGitHubSync
|
||||
} from "@app/hooks/api/secretSyncs/types/github-sync";
|
||||
|
||||
type Props = {
|
||||
secretSync: TGitHubSync;
|
||||
};
|
||||
|
||||
export const GitHubSyncDestinationSection = ({ secretSync }: Props) => {
|
||||
const { destinationConfig } = secretSync;
|
||||
|
||||
let Components: ReactNode;
|
||||
switch (destinationConfig.scope) {
|
||||
case GitHubSyncScope.Organization:
|
||||
Components = (
|
||||
<>
|
||||
<SecretSyncLabel label="Organization">{destinationConfig.org}</SecretSyncLabel>
|
||||
<SecretSyncLabel label="Visibility" className="capitalize">
|
||||
{destinationConfig.visibility} Repositories
|
||||
</SecretSyncLabel>
|
||||
{destinationConfig.visibility === GitHubSyncVisibility.Selected && (
|
||||
<SecretSyncLabel label="Selected Repositories">
|
||||
{destinationConfig.selectedRepositoryIds?.length ?? 0} Repositories
|
||||
<Tooltip
|
||||
side="bottom"
|
||||
content={<GitHubSyncSelectedRepositoriesTooltipContent secretSync={secretSync} />}
|
||||
>
|
||||
<FontAwesomeIcon size="xs" className="ml-1 text-bunker-300" icon={faInfoCircle} />
|
||||
</Tooltip>
|
||||
</SecretSyncLabel>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
break;
|
||||
case GitHubSyncScope.Repository:
|
||||
Components = (
|
||||
<SecretSyncLabel label="Repository">
|
||||
{destinationConfig.owner}/{destinationConfig.repo}
|
||||
</SecretSyncLabel>
|
||||
);
|
||||
break;
|
||||
case GitHubSyncScope.RepositoryEnvironment:
|
||||
Components = (
|
||||
<>
|
||||
<SecretSyncLabel label="Repository">
|
||||
{destinationConfig.owner}/{destinationConfig.repo}
|
||||
</SecretSyncLabel>
|
||||
<SecretSyncLabel label="Environment">{destinationConfig.env}</SecretSyncLabel>
|
||||
</>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Uhandled GitHub Sync Destination Section Scope ${secretSync.destinationConfig.scope}`
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SecretSyncLabel className="capitalize" label="Scope">
|
||||
{destinationConfig.scope.replace("-", " ")}
|
||||
</SecretSyncLabel>
|
||||
{Components}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
import { ReactNode } from "react";
|
||||
import { faEdit } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
||||
import { IconButton } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
|
||||
import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs";
|
||||
import { AwsParameterStoreSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsParameterStoreSyncDestinationSection";
|
||||
import { GitHubSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/GitHubSyncDestinationSection";
|
||||
|
||||
type Props = {
|
||||
secretSync: TSecretSync;
|
||||
onEditDestination: VoidFunction;
|
||||
};
|
||||
|
||||
export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }: Props) => {
|
||||
const { destination, connection } = secretSync;
|
||||
|
||||
const app = APP_CONNECTION_MAP[connection.app].name;
|
||||
|
||||
let DestinationComponents: ReactNode;
|
||||
switch (secretSync.destination) {
|
||||
case SecretSync.AWSParameterStore:
|
||||
DestinationComponents = <AwsParameterStoreSyncDestinationSection secretSync={secretSync} />;
|
||||
break;
|
||||
case SecretSync.GitHub:
|
||||
DestinationComponents = <GitHubSyncDestinationSection secretSync={secretSync} />;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Destination Section components: ${destination}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-3 rounded-lg border border-mineshaft-600 bg-mineshaft-900 px-4 py-3">
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-2">
|
||||
<h3 className="font-semibold text-mineshaft-100">Destination Configuration</h3>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
variant="plain"
|
||||
colorSchema="secondary"
|
||||
isDisabled={!isAllowed}
|
||||
ariaLabel="Edit sync destination"
|
||||
onClick={onEditDestination}
|
||||
>
|
||||
<FontAwesomeIcon icon={faEdit} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
<div className="flex w-full flex-wrap gap-8">
|
||||
<SecretSyncLabel label={`${app} Connection`}>{connection.name}</SecretSyncLabel>
|
||||
{DestinationComponents}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from "./SecretSyncDestinatonSection";
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
import { faBan, faEdit } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format } from "date-fns";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { SecretSyncLabel, SecretSyncStatusBadge } from "@app/components/secret-syncs";
|
||||
import { Badge, IconButton } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { SecretSyncStatus, TSecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
type Props = {
|
||||
secretSync: TSecretSync;
|
||||
onEditDetails: VoidFunction;
|
||||
};
|
||||
|
||||
export const SecretSyncDetailsSection = ({ secretSync, onEditDetails }: Props) => {
|
||||
const { syncStatus, lastSyncMessage, lastSyncedAt, name, description, isEnabled } = secretSync;
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-3 rounded-lg border border-mineshaft-600 bg-mineshaft-900 px-4 py-3">
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-2">
|
||||
<h3 className="font-semibold text-mineshaft-100">Details</h3>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
variant="plain"
|
||||
colorSchema="secondary"
|
||||
isDisabled={!isAllowed}
|
||||
ariaLabel="Edit sync details"
|
||||
onClick={onEditDetails}
|
||||
>
|
||||
<FontAwesomeIcon icon={faEdit} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
<div>
|
||||
<div className="space-y-3">
|
||||
<SecretSyncLabel label="Name">{name}</SecretSyncLabel>
|
||||
<SecretSyncLabel label="Description">{description}</SecretSyncLabel>
|
||||
<SecretSyncLabel label="Status">
|
||||
{isEnabled ? (
|
||||
syncStatus && <SecretSyncStatusBadge status={syncStatus} />
|
||||
) : (
|
||||
<Badge className="flex w-min items-center gap-1.5 bg-mineshaft-400/50 text-bunker-300">
|
||||
<FontAwesomeIcon icon={faBan} />
|
||||
<span>Disabled</span>
|
||||
</Badge>
|
||||
)}
|
||||
</SecretSyncLabel>
|
||||
{lastSyncedAt && (
|
||||
<SecretSyncLabel label="Last Synced">
|
||||
{format(new Date(lastSyncedAt), "yyyy-MM-dd, hh:mm aaa")}
|
||||
</SecretSyncLabel>
|
||||
)}
|
||||
{syncStatus === SecretSyncStatus.Failed && lastSyncMessage && (
|
||||
<SecretSyncLabel labelClassName="text-red" label="Last Sync Error">
|
||||
<p className="rounded bg-mineshaft-600 p-2 text-xs">{lastSyncMessage}</p>
|
||||
</SecretSyncLabel>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import { faEdit } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
||||
import { IconButton } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP } from "@app/helpers/secretSyncs";
|
||||
import { TSecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
type Props = {
|
||||
secretSync: TSecretSync;
|
||||
onEditOptions: VoidFunction;
|
||||
};
|
||||
|
||||
export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) => {
|
||||
const {
|
||||
destination,
|
||||
syncOptions: { appendSuffix, prependPrefix, initialSyncBehavior },
|
||||
lastSyncedAt
|
||||
} = secretSync;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex w-full flex-col gap-3 rounded-lg border border-mineshaft-600 bg-mineshaft-900 px-4 py-3">
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-2">
|
||||
<h3 className="font-semibold text-mineshaft-100">Sync Options</h3>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
variant="plain"
|
||||
colorSchema="secondary"
|
||||
isDisabled={!isAllowed}
|
||||
ariaLabel="Edit sync options"
|
||||
onClick={onEditOptions}
|
||||
>
|
||||
<FontAwesomeIcon icon={faEdit} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
<div>
|
||||
<div className="space-y-3">
|
||||
{!lastSyncedAt && (
|
||||
<SecretSyncLabel label="Initial Sync Behavior">
|
||||
{SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP[initialSyncBehavior](destination).name}
|
||||
</SecretSyncLabel>
|
||||
)}
|
||||
<SecretSyncLabel label="Prefix">{prependPrefix}</SecretSyncLabel>
|
||||
<SecretSyncLabel label="Suffix">{appendSuffix}</SecretSyncLabel>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { faEdit } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
||||
import { IconButton } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { TSecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
type Props = {
|
||||
secretSync: TSecretSync;
|
||||
onEditSource: VoidFunction;
|
||||
};
|
||||
|
||||
export const SecretSyncSourceSection = ({ secretSync, onEditSource }: Props) => {
|
||||
const { folder, environment } = secretSync;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex w-full flex-col gap-3 rounded-lg border border-mineshaft-600 bg-mineshaft-900 px-4 py-3">
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-2">
|
||||
<h3 className="font-semibold text-mineshaft-100">Source</h3>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={ProjectPermissionSub.SecretSyncs}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
variant="plain"
|
||||
colorSchema="secondary"
|
||||
isDisabled={!isAllowed}
|
||||
ariaLabel="Edit sync source"
|
||||
onClick={onEditSource}
|
||||
>
|
||||
<FontAwesomeIcon icon={faEdit} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
<div>
|
||||
<div className="space-y-3">
|
||||
<SecretSyncLabel label="Environment">{environment.name}</SecretSyncLabel>
|
||||
<SecretSyncLabel label="Path">{folder.path}</SecretSyncLabel>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
export * from "./SecretSyncActionTriggers";
|
||||
export * from "./SecretSyncAuditLogsSection";
|
||||
export * from "./SecretSyncDestinationSection";
|
||||
export * from "./SecretSyncDetailsSection";
|
||||
export * from "./SecretSyncOptionsSection";
|
||||
export * from "./SecretSyncSourceSection";
|
||||
Reference in New Issue
Block a user