mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 15:27:27 +00:00
improvements: address feedback
This commit is contained in:
@@ -10,7 +10,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
t.string("name", 32).notNullable();
|
t.string("name", 32).notNullable();
|
||||||
t.string("description");
|
t.string("description");
|
||||||
t.string("destination").notNullable();
|
t.string("destination").notNullable();
|
||||||
t.boolean("isEnabled").notNullable().defaultTo(true);
|
t.boolean("isAutoSyncEnabled").notNullable().defaultTo(true);
|
||||||
t.integer("version").defaultTo(1).notNullable();
|
t.integer("version").defaultTo(1).notNullable();
|
||||||
t.jsonb("destinationConfig").notNullable();
|
t.jsonb("destinationConfig").notNullable();
|
||||||
t.jsonb("syncOptions").notNullable();
|
t.jsonb("syncOptions").notNullable();
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const SecretSyncsSchema = z.object({
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
description: z.string().nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
destination: z.string(),
|
destination: z.string(),
|
||||||
isEnabled: z.boolean().default(true),
|
isAutoSyncEnabled: z.boolean().default(true),
|
||||||
version: z.number().default(1),
|
version: z.number().default(1),
|
||||||
destinationConfig: z.unknown(),
|
destinationConfig: z.unknown(),
|
||||||
syncOptions: z.unknown(),
|
syncOptions: z.unknown(),
|
||||||
|
|||||||
@@ -1671,7 +1671,7 @@ export const SecretSyncs = {
|
|||||||
connectionId: `The ID of the ${
|
connectionId: `The ID of the ${
|
||||||
APP_CONNECTION_NAME_MAP[SECRET_SYNC_CONNECTION_MAP[destination]]
|
APP_CONNECTION_NAME_MAP[SECRET_SYNC_CONNECTION_MAP[destination]]
|
||||||
} Connection to use for syncing.`,
|
} Connection to use for syncing.`,
|
||||||
isEnabled: `Whether secrets should be synced automatically or not.`,
|
isAutoSyncEnabled: `Whether secrets should be automatically synced when changes occur at the source location or not.`,
|
||||||
syncOptions: "Optional parameters to modify how secrets are synced."
|
syncOptions: "Optional parameters to modify how secrets are synced."
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -1686,7 +1686,7 @@ export const SecretSyncs = {
|
|||||||
environment: `The updated slug of the project environment to sync secrets from.`,
|
environment: `The updated slug of the project environment to sync secrets from.`,
|
||||||
secretPath: `The updated folder path to sync secrets from.`,
|
secretPath: `The updated folder path to sync secrets from.`,
|
||||||
description: `The updated description of the ${destinationName} Sync.`,
|
description: `The updated description of the ${destinationName} Sync.`,
|
||||||
isEnabled: `Whether secrets should be synced automatically or not.`,
|
isAutoSyncEnabled: `Whether secrets should be automatically synced when changes occur at the source location or not.`,
|
||||||
syncOptions: "Optional parameters to modify how secrets are synced."
|
syncOptions: "Optional parameters to modify how secrets are synced."
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const registerSyncSecretsEndpoints = <T extends TSecretSync, I extends TS
|
|||||||
destinationConfig: I["destinationConfig"];
|
destinationConfig: I["destinationConfig"];
|
||||||
syncOptions: I["syncOptions"];
|
syncOptions: I["syncOptions"];
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
isEnabled?: boolean;
|
isAutoSyncEnabled?: boolean;
|
||||||
}>;
|
}>;
|
||||||
updateSchema: z.ZodType<{
|
updateSchema: z.ZodType<{
|
||||||
connectionId?: string;
|
connectionId?: string;
|
||||||
@@ -38,6 +38,7 @@ export const registerSyncSecretsEndpoints = <T extends TSecretSync, I extends TS
|
|||||||
destinationConfig?: I["destinationConfig"];
|
destinationConfig?: I["destinationConfig"];
|
||||||
syncOptions?: I["syncOptions"];
|
syncOptions?: I["syncOptions"];
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
|
isAutoSyncEnabled?: boolean;
|
||||||
}>;
|
}>;
|
||||||
responseSchema: z.ZodTypeAny;
|
responseSchema: z.ZodTypeAny;
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ const AwsParameterStoreSyncDestinationConfigSchema = z.object({
|
|||||||
.trim()
|
.trim()
|
||||||
.min(1, "Parameter Store Path required")
|
.min(1, "Parameter Store Path required")
|
||||||
.max(2048, "Cannot exceed 2048 characters")
|
.max(2048, "Cannot exceed 2048 characters")
|
||||||
.regex(/^\/([/]|(([\w-]+\/)+))?$/)
|
.regex(/^\/([/]|(([\w-]+\/)+))?$/, 'Invalid path - must follow "/example/path/" format')
|
||||||
.describe(SecretSyncs.DESTINATION_CONFIG.AWS_PARAMETER_STORE.PATH)
|
.describe(SecretSyncs.DESTINATION_CONFIG.AWS_PARAMETER_STORE.PATH)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export const SecretSyncFns = {
|
|||||||
return GithubSyncFns.syncSecrets(secretSync, secretMap);
|
return GithubSyncFns.syncSecrets(secretSync, secretMap);
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled sync destination for push secrets: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -88,7 +88,7 @@ export const SecretSyncFns = {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled sync destination for push secrets: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ export const SecretSyncFns = {
|
|||||||
return GithubSyncFns.removeSecrets(secretSync, secretMap);
|
return GithubSyncFns.removeSecrets(secretSync, secretMap);
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled sync destination for removing secrets: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -827,7 +827,7 @@ export const secretSyncQueueFactory = ({
|
|||||||
`Could not find folder at path "${secretPath}" for environment with slug "${environmentSlug}" in project with ID "${projectId}"`
|
`Could not find folder at path "${secretPath}" for environment with slug "${environmentSlug}" in project with ID "${projectId}"`
|
||||||
);
|
);
|
||||||
|
|
||||||
const secretSyncs = await secretSyncDAL.find({ folderId: folder.id, isEnabled: true });
|
const secretSyncs = await secretSyncDAL.find({ folderId: folder.id, isAutoSyncEnabled: true });
|
||||||
|
|
||||||
await Promise.all(secretSyncs.map((secretSync) => queueSecretSyncSyncSecretsById({ syncId: secretSync.id })));
|
await Promise.all(secretSyncs.map((secretSync) => queueSecretSyncSyncSecretsById({ syncId: secretSync.id })));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const GenericCreateSecretSyncFieldsSchema = (destination: SecretSync, syn
|
|||||||
.min(1, "Secret path required")
|
.min(1, "Secret path required")
|
||||||
.transform(removeTrailingSlash)
|
.transform(removeTrailingSlash)
|
||||||
.describe(SecretSyncs.CREATE(destination).secretPath),
|
.describe(SecretSyncs.CREATE(destination).secretPath),
|
||||||
isEnabled: z.boolean().default(true).describe(SecretSyncs.CREATE(destination).isEnabled),
|
isAutoSyncEnabled: z.boolean().default(true).describe(SecretSyncs.CREATE(destination).isAutoSyncEnabled),
|
||||||
syncOptions: SyncOptionsSchema(destination, syncOptionsConfig).describe(SecretSyncs.CREATE(destination).syncOptions)
|
syncOptions: SyncOptionsSchema(destination, syncOptionsConfig).describe(SecretSyncs.CREATE(destination).syncOptions)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ export const GenericUpdateSecretSyncFieldsSchema = (destination: SecretSync, syn
|
|||||||
.transform(removeTrailingSlash)
|
.transform(removeTrailingSlash)
|
||||||
.optional()
|
.optional()
|
||||||
.describe(SecretSyncs.UPDATE(destination).secretPath),
|
.describe(SecretSyncs.UPDATE(destination).secretPath),
|
||||||
isEnabled: z.boolean().optional().describe(SecretSyncs.UPDATE(destination).isEnabled),
|
isAutoSyncEnabled: z.boolean().optional().describe(SecretSyncs.UPDATE(destination).isAutoSyncEnabled),
|
||||||
syncOptions: SyncOptionsSchema(destination, syncOptionsConfig)
|
syncOptions: SyncOptionsSchema(destination, syncOptionsConfig)
|
||||||
.optional()
|
.optional()
|
||||||
.describe(SecretSyncs.UPDATE(destination).syncOptions)
|
.describe(SecretSyncs.UPDATE(destination).syncOptions)
|
||||||
|
|||||||
@@ -218,14 +218,14 @@ export const secretSyncServiceFactory = ({
|
|||||||
const sync = await secretSyncDAL.create({
|
const sync = await secretSyncDAL.create({
|
||||||
folderId: folder.id,
|
folderId: folder.id,
|
||||||
...params,
|
...params,
|
||||||
...(params.isEnabled && { syncStatus: SecretSyncStatus.Pending }),
|
...(params.isAutoSyncEnabled && { syncStatus: SecretSyncStatus.Pending }),
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
return sync;
|
return sync;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (secretSync.isEnabled) await secretSyncQueue.queueSecretSyncSyncSecretsById({ syncId: secretSync.id });
|
if (secretSync.isAutoSyncEnabled) await secretSyncQueue.queueSecretSyncSyncSecretsById({ syncId: secretSync.id });
|
||||||
|
|
||||||
return secretSync as TSecretSync;
|
return secretSync as TSecretSync;
|
||||||
};
|
};
|
||||||
@@ -317,18 +317,19 @@ export const secretSyncServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const isEnabled = params.isEnabled ?? secretSync.isEnabled;
|
const isAutoSyncEnabled = params.isAutoSyncEnabled ?? secretSync.isAutoSyncEnabled;
|
||||||
|
|
||||||
const updatedSync = await secretSyncDAL.updateById(syncId, {
|
const updatedSync = await secretSyncDAL.updateById(syncId, {
|
||||||
...params,
|
...params,
|
||||||
...(isEnabled && folderId && { syncStatus: SecretSyncStatus.Pending }),
|
...(isAutoSyncEnabled && folderId && { syncStatus: SecretSyncStatus.Pending }),
|
||||||
folderId
|
folderId
|
||||||
});
|
});
|
||||||
|
|
||||||
return updatedSync;
|
return updatedSync;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (updatedSecretSync.isEnabled) await secretSyncQueue.queueSecretSyncSyncSecretsById({ syncId: secretSync.id });
|
if (updatedSecretSync.isAutoSyncEnabled)
|
||||||
|
await secretSyncQueue.queueSecretSyncSyncSecretsById({ syncId: secretSync.id });
|
||||||
|
|
||||||
return updatedSecretSync as TSecretSync;
|
return updatedSecretSync as TSecretSync;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export type TCreateSecretSyncDTO = Pick<TSecretSync, "syncOptions" | "destinatio
|
|||||||
projectId: string;
|
projectId: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
isEnabled?: boolean;
|
isAutoSyncEnabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateSecretSyncDTO = Partial<Omit<TCreateSecretSyncDTO, "projectId">> & {
|
export type TUpdateSecretSyncDTO = Partial<Omit<TCreateSecretSyncDTO, "projectId">> & {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ description: "Learn how to configure an AWS Parameter Store Sync for Infisical."
|
|||||||
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
|
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
|
||||||
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint prior to syncing, prioritizing values present in Infisical if secrets conflict.
|
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint prior to syncing, prioritizing values present in Infisical if secrets conflict.
|
||||||
- **Import Secrets (Prioritize Parameter Store)**: Imports secrets from the destination endpoint prior to syncing, prioritizing values present in Parameter Store if secrets conflict.
|
- **Import Secrets (Prioritize Parameter Store)**: Imports secrets from the destination endpoint prior to syncing, prioritizing values present in Parameter Store if secrets conflict.
|
||||||
- **Enabled**: If enabled, secrets will automatically be synced from the source. Disable to prevent syncing until enabled.
|
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
|
||||||
|
|
||||||
6. Configure the **Details** of your Parameter Store Sync, then click **Next**.
|
6. Configure the **Details** of your Parameter Store Sync, then click **Next**.
|
||||||

|

|
||||||
|
|||||||
@@ -62,7 +62,7 @@ description: "Learn how to configure a GitHub Sync for Infisical."
|
|||||||
<Note>
|
<Note>
|
||||||
GitHub does not support importing secrets.
|
GitHub does not support importing secrets.
|
||||||
</Note>
|
</Note>
|
||||||
- **Enabled**: If enabled, secrets will automatically be synced from the source. Disable to prevent syncing until enabled.
|
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
|
||||||
|
|
||||||
6. Configure the **Details** of your GitHub Sync, then click **Next**.
|
6. Configure the **Details** of your GitHub Sync, then click **Next**.
|
||||||

|

|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop
|
|||||||
resolver: zodResolver(SecretSyncFormSchema),
|
resolver: zodResolver(SecretSyncFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
destination,
|
destination,
|
||||||
isEnabled: true,
|
isAutoSyncEnabled: true,
|
||||||
syncOptions: {
|
syncOptions: {
|
||||||
initialSyncBehavior: syncOption?.canImportSecrets
|
initialSyncBehavior: syncOption?.canImportSecrets
|
||||||
? undefined
|
? undefined
|
||||||
@@ -161,26 +161,26 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop
|
|||||||
<SecretSyncOptionsFields />
|
<SecretSyncOptionsFields />
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="isEnabled"
|
name="isAutoSyncEnabled"
|
||||||
render={({ field: { value, onChange }, fieldState: { error } }) => {
|
render={({ field: { value, onChange }, fieldState: { error } }) => {
|
||||||
return (
|
return (
|
||||||
<FormControl
|
<FormControl
|
||||||
helperText={
|
helperText={
|
||||||
value
|
value
|
||||||
? "Secrets will automatically be synced when changes occur in the source location"
|
? "Secrets will automatically be synced when changes occur in the source location."
|
||||||
: "Secrets will not be synced until enabled"
|
: "Secrets will not automatically be synced when changes occur in the source location. You can still trigger syncs manually."
|
||||||
}
|
}
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
>
|
>
|
||||||
<Switch
|
<Switch
|
||||||
className="bg-red/50 shadow-inner data-[state=checked]:bg-green/50"
|
className="bg-mineshaft-400/50 shadow-inner data-[state=checked]:bg-green/50"
|
||||||
id="secret-sync-enabled"
|
id="auto-sync-enabled"
|
||||||
thumbClassName="bg-mineshaft-800"
|
thumbClassName="bg-mineshaft-800"
|
||||||
onCheckedChange={onChange}
|
onCheckedChange={onChange}
|
||||||
isChecked={value}
|
isChecked={value}
|
||||||
>
|
>
|
||||||
<p className="w-14">{value ? "Enabled" : "Disabled"}</p>
|
<p className="w-[8.4rem]">Auto-Sync {value ? "Enabled" : "Disabled"}</p>
|
||||||
</Switch>
|
</Switch>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
@@ -204,9 +204,11 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop
|
|||||||
containerClassName="-mt-5"
|
containerClassName="-mt-5"
|
||||||
onCheckedChange={(isChecked) => setConfirmOverwrite(Boolean(isChecked))}
|
onCheckedChange={(isChecked) => setConfirmOverwrite(Boolean(isChecked))}
|
||||||
>
|
>
|
||||||
<p className={`mt-5 text-wrap ${confirmOverwrite ? "text-mineshaft-200" : "text-red"}`}>
|
<p
|
||||||
|
className={`mt-5 text-wrap text-xs ${confirmOverwrite ? "text-mineshaft-200" : "text-red"}`}
|
||||||
|
>
|
||||||
I understand all secrets present in the configured {destinationName} destination will
|
I understand all secrets present in the configured {destinationName} destination will
|
||||||
be removed that are not present within Infisical.
|
be removed if they are not present within Infisical.
|
||||||
</p>
|
</p>
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
)}
|
)}
|
||||||
@@ -222,7 +224,6 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop
|
|||||||
>
|
>
|
||||||
{isFinalStep ? "Create Sync" : "Next"}
|
{isFinalStep ? "Create Sync" : "Next"}
|
||||||
</Button>
|
</Button>
|
||||||
{}
|
|
||||||
{selectedTabIndex > 0 && (
|
{selectedTabIndex > 0 && (
|
||||||
<Button onClick={handlePrev} colorSchema="secondary">
|
<Button onClick={handlePrev} colorSchema="secondary">
|
||||||
Back
|
Back
|
||||||
|
|||||||
+4
-4
@@ -26,7 +26,7 @@ export const SecretSyncReviewFields = () => {
|
|||||||
initialSyncBehavior
|
initialSyncBehavior
|
||||||
},
|
},
|
||||||
destination,
|
destination,
|
||||||
isEnabled
|
isAutoSyncEnabled
|
||||||
} = watch();
|
} = watch();
|
||||||
|
|
||||||
const destinationName = SECRET_SYNC_MAP[destination].name;
|
const destinationName = SECRET_SYNC_MAP[destination].name;
|
||||||
@@ -67,9 +67,9 @@ export const SecretSyncReviewFields = () => {
|
|||||||
<span className="text-sm text-mineshaft-300">Options</span>
|
<span className="text-sm text-mineshaft-300">Options</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-x-8 gap-y-2">
|
<div className="flex flex-wrap gap-x-8 gap-y-2">
|
||||||
<SecretSyncLabel label="Sync Enabled">
|
<SecretSyncLabel label="Auto-Sync">
|
||||||
<Badge variant={isEnabled ? "success" : "danger"}>
|
<Badge variant={isAutoSyncEnabled ? "success" : "danger"}>
|
||||||
{isEnabled ? "Enabled" : "Disabled"}
|
{isAutoSyncEnabled ? "Enabled" : "Disabled"}
|
||||||
</Badge>
|
</Badge>
|
||||||
</SecretSyncLabel>
|
</SecretSyncLabel>
|
||||||
<SecretSyncLabel label="Initial Sync Behavior">
|
<SecretSyncLabel label="Initial Sync Behavior">
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ export const AwsParameterStoreSyncDestinationSchema = z.object({
|
|||||||
.trim()
|
.trim()
|
||||||
.min(1, "Parameter Store Path required")
|
.min(1, "Parameter Store Path required")
|
||||||
.max(2048, "Cannot exceed 2048 characters")
|
.max(2048, "Cannot exceed 2048 characters")
|
||||||
.regex(/^\/([/]|(([\w-]+\/)+))?$/),
|
.regex(/^\/([/]|(([\w-]+\/)+))?$/, 'Invalid path - must follow "/example/path/" format'),
|
||||||
region: z.string().min(1, "Region required")
|
region: z.string().min(1, "Region required")
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const BaseSecretSyncSchema = z.object({
|
|||||||
// .transform((str) => str.toUpperCase())
|
// .transform((str) => str.toUpperCase())
|
||||||
// .optional()
|
// .optional()
|
||||||
}),
|
}),
|
||||||
isEnabled: z.boolean()
|
isAutoSyncEnabled: z.boolean()
|
||||||
});
|
});
|
||||||
|
|
||||||
const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
|
const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export type TCreateSecretSyncDTO = DiscriminativePick<
|
|||||||
| "connectionId"
|
| "connectionId"
|
||||||
| "syncOptions"
|
| "syncOptions"
|
||||||
| "destination"
|
| "destination"
|
||||||
| "isEnabled"
|
| "isAutoSyncEnabled"
|
||||||
> & { environment: string; secretPath: string; projectId: string };
|
> & { environment: string; secretPath: string; projectId: string };
|
||||||
|
|
||||||
export type TUpdateSecretSyncDTO = Partial<
|
export type TUpdateSecretSyncDTO = Partial<
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export type TRootSecretSync = {
|
|||||||
connectionId: string;
|
connectionId: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
isEnabled: boolean;
|
isAutoSyncEnabled: boolean;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
syncStatus: SecretSyncStatus | null;
|
syncStatus: SecretSyncStatus | null;
|
||||||
lastSyncJobId: string | null;
|
lastSyncJobId: string | null;
|
||||||
|
|||||||
+55
-55
@@ -76,7 +76,7 @@ export const SecretSyncRow = ({
|
|||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
syncStatus,
|
syncStatus,
|
||||||
isEnabled,
|
isAutoSyncEnabled,
|
||||||
projectId
|
projectId
|
||||||
} = secretSync;
|
} = secretSync;
|
||||||
|
|
||||||
@@ -131,8 +131,7 @@ export const SecretSyncRow = ({
|
|||||||
}
|
}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"group h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700",
|
"group h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700",
|
||||||
syncStatus === SecretSyncStatus.Failed && "bg-red/5 hover:bg-red/10",
|
syncStatus === SecretSyncStatus.Failed && "bg-red/5 hover:bg-red/10"
|
||||||
!isEnabled && "bg-mineshaft-400/15 opacity-50 hover:opacity-100"
|
|
||||||
)}
|
)}
|
||||||
key={`sync-${id}`}
|
key={`sync-${id}`}
|
||||||
>
|
>
|
||||||
@@ -180,58 +179,59 @@ export const SecretSyncRow = ({
|
|||||||
<SecretSyncDestinationCol secretSync={secretSync} />
|
<SecretSyncDestinationCol secretSync={secretSync} />
|
||||||
<Td>
|
<Td>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{isEnabled ? (
|
{syncStatus && (
|
||||||
syncStatus && (
|
<Tooltip
|
||||||
<Tooltip
|
position="left"
|
||||||
position="left"
|
className="max-w-sm"
|
||||||
className="max-w-sm"
|
content={
|
||||||
content={
|
[SecretSyncStatus.Succeeded, SecretSyncStatus.Failed].includes(syncStatus) ? (
|
||||||
[SecretSyncStatus.Succeeded, SecretSyncStatus.Failed].includes(syncStatus) ? (
|
<div className="flex flex-col gap-2 whitespace-normal py-1">
|
||||||
<div className="flex flex-col gap-2 whitespace-normal py-1">
|
{lastSyncedAt && (
|
||||||
{lastSyncedAt && (
|
<div>
|
||||||
<div>
|
<div
|
||||||
<div
|
className={`mb-2 flex self-start ${syncStatus === SecretSyncStatus.Failed ? "text-yellow" : "text-green"}`}
|
||||||
className={`mb-2 flex self-start ${syncStatus === SecretSyncStatus.Failed ? "text-yellow" : "text-green"}`}
|
>
|
||||||
>
|
<FontAwesomeIcon
|
||||||
<FontAwesomeIcon
|
icon={faCalendarCheck}
|
||||||
icon={faCalendarCheck}
|
className="ml-1 pr-1.5 pt-0.5 text-sm"
|
||||||
className="ml-1 pr-1.5 pt-0.5 text-sm"
|
/>
|
||||||
/>
|
<div className="text-xs">Last Synced</div>
|
||||||
<div className="text-xs">Last Synced</div>
|
|
||||||
</div>
|
|
||||||
<div className="rounded bg-mineshaft-600 p-2 text-xs">
|
|
||||||
{format(new Date(lastSyncedAt), "yyyy-MM-dd, hh:mm aaa")}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="rounded bg-mineshaft-600 p-2 text-xs">
|
||||||
{failureMessage && (
|
{format(new Date(lastSyncedAt), "yyyy-MM-dd, hh:mm aaa")}
|
||||||
<div>
|
|
||||||
<div className="mb-2 flex self-start text-red">
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faXmark}
|
|
||||||
className="ml-1 pr-1.5 pt-0.5 text-sm"
|
|
||||||
/>
|
|
||||||
<div className="text-xs">Failure Reason</div>
|
|
||||||
</div>
|
|
||||||
<div className="rounded bg-mineshaft-600 p-2 text-xs">
|
|
||||||
{failureMessage}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
) : undefined
|
{failureMessage && (
|
||||||
}
|
<div>
|
||||||
>
|
<div className="mb-2 flex self-start text-red">
|
||||||
<div>
|
<FontAwesomeIcon icon={faXmark} className="ml-1 pr-1.5 pt-0.5 text-sm" />
|
||||||
<SecretSyncStatusBadge status={syncStatus} />
|
<div className="text-xs">Failure Reason</div>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
<div className="rounded bg-mineshaft-600 p-2 text-xs">{failureMessage}</div>
|
||||||
)
|
</div>
|
||||||
) : (
|
)}
|
||||||
<Badge className="flex w-min items-center gap-1.5 bg-mineshaft-400/50 text-bunker-300">
|
</div>
|
||||||
<FontAwesomeIcon icon={faBan} />
|
) : undefined
|
||||||
<span>Disabled</span>
|
}
|
||||||
</Badge>
|
>
|
||||||
|
<div>
|
||||||
|
<SecretSyncStatusBadge status={syncStatus} />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{!isAutoSyncEnabled && (
|
||||||
|
<Tooltip
|
||||||
|
className="text-xs"
|
||||||
|
content="Auto-Sync is disabled. Changes to the source location will not be automatically synced to the destination."
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
|
||||||
|
<FontAwesomeIcon icon={faBan} />
|
||||||
|
{!syncStatus && "Auto-Sync Disabled"}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<SecretSyncImportStatusBadge mini secretSync={secretSync} />
|
<SecretSyncImportStatusBadge mini secretSync={secretSync} />
|
||||||
<SecretSyncRemoveStatusBadge mini secretSync={secretSync} />
|
<SecretSyncRemoveStatusBadge mini secretSync={secretSync} />
|
||||||
@@ -359,13 +359,13 @@ export const SecretSyncRow = ({
|
|||||||
{(isAllowed: boolean) => (
|
{(isAllowed: boolean) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
isDisabled={!isAllowed}
|
isDisabled={!isAllowed}
|
||||||
icon={<FontAwesomeIcon icon={isEnabled ? faToggleOff : faToggleOn} />}
|
icon={<FontAwesomeIcon icon={isAutoSyncEnabled ? faToggleOff : faToggleOn} />}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onToggleEnable(secretSync);
|
onToggleEnable(secretSync);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isEnabled ? "Disable" : "Enable"} Sync
|
{isAutoSyncEnabled ? "Disable" : "Enable"} Auto-Sync
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
|
|||||||
+13
-30
@@ -2,7 +2,6 @@ import { useMemo, useState } from "react";
|
|||||||
import {
|
import {
|
||||||
faArrowDown,
|
faArrowDown,
|
||||||
faArrowUp,
|
faArrowUp,
|
||||||
faBan,
|
|
||||||
faCheck,
|
faCheck,
|
||||||
faCheckCircle,
|
faCheckCircle,
|
||||||
faFilter,
|
faFilter,
|
||||||
@@ -61,17 +60,10 @@ enum SecretSyncsOrderBy {
|
|||||||
|
|
||||||
type SecretSyncFilters = {
|
type SecretSyncFilters = {
|
||||||
destinations: SecretSync[];
|
destinations: SecretSync[];
|
||||||
status: SecretSyncStatusCol[];
|
status: SecretSyncStatus[];
|
||||||
environmentIds: string[];
|
environmentIds: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
enum SecretSyncStatusCol {
|
|
||||||
Pending = "pending",
|
|
||||||
Success = "success",
|
|
||||||
Failed = "failed",
|
|
||||||
Disabled = "disabled"
|
|
||||||
}
|
|
||||||
|
|
||||||
const getSyncStatusOrderValue = (syncStatus: SecretSyncStatus | null) => {
|
const getSyncStatusOrderValue = (syncStatus: SecretSyncStatus | null) => {
|
||||||
switch (syncStatus) {
|
switch (syncStatus) {
|
||||||
case SecretSyncStatus.Failed:
|
case SecretSyncStatus.Failed:
|
||||||
@@ -91,10 +83,10 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const STATUS_ICON_MAP = {
|
const STATUS_ICON_MAP = {
|
||||||
[SecretSyncStatusCol.Success]: { icon: faCheck, className: "text-green", name: "Synced" },
|
[SecretSyncStatus.Succeeded]: { icon: faCheck, className: "text-green", name: "Synced" },
|
||||||
[SecretSyncStatusCol.Failed]: { icon: faWarning, className: "text-red", name: "Not Synced" },
|
[SecretSyncStatus.Failed]: { icon: faWarning, className: "text-red", name: "Not Synced" },
|
||||||
[SecretSyncStatusCol.Pending]: { icon: faRotate, className: "text-yellow", name: "Syncing" },
|
[SecretSyncStatus.Pending]: { icon: faRotate, className: "text-yellow", name: "Syncing" },
|
||||||
[SecretSyncStatusCol.Disabled]: { icon: faBan, className: "text-mineshaft-400", name: "Disabled" }
|
[SecretSyncStatus.Running]: { icon: faRotate, className: "text-yellow", name: "Syncing" }
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SecretSyncsTable = ({ secretSyncs }: Props) => {
|
export const SecretSyncsTable = ({ secretSyncs }: Props) => {
|
||||||
@@ -133,8 +125,7 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => {
|
|||||||
() =>
|
() =>
|
||||||
secretSyncs
|
secretSyncs
|
||||||
.filter((secretSync) => {
|
.filter((secretSync) => {
|
||||||
const { destination, name, connection, folder, environment, syncStatus, isEnabled } =
|
const { destination, name, connection, folder, environment, syncStatus } = secretSync;
|
||||||
secretSync;
|
|
||||||
|
|
||||||
if (filters.destinations.length && !filters.destinations.includes(destination))
|
if (filters.destinations.length && !filters.destinations.includes(destination))
|
||||||
return false;
|
return false;
|
||||||
@@ -146,12 +137,7 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => {
|
|||||||
)
|
)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const status = isEnabled ? syncStatus : SecretSyncStatusCol.Disabled;
|
if (filters.status.length && (!syncStatus || !filters.status.includes(syncStatus))) {
|
||||||
|
|
||||||
if (
|
|
||||||
filters.status.length &&
|
|
||||||
(!status || !filters.status.includes(status as SecretSyncStatusCol))
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,9 +170,6 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => {
|
|||||||
getSecretSyncDestinationColValues(syncTwo).primaryText.toLowerCase()
|
getSecretSyncDestinationColValues(syncTwo).primaryText.toLowerCase()
|
||||||
);
|
);
|
||||||
case SecretSyncsOrderBy.Status:
|
case SecretSyncsOrderBy.Status:
|
||||||
if (!syncOne.isEnabled && syncTwo.isEnabled) return 1;
|
|
||||||
if (syncOne.isEnabled && !syncTwo.isEnabled) return -1;
|
|
||||||
|
|
||||||
if (!syncOne.syncStatus && syncTwo.syncStatus) return 1;
|
if (!syncOne.syncStatus && syncTwo.syncStatus) return 1;
|
||||||
if (syncOne.syncStatus && !syncTwo.syncStatus) return -1;
|
if (syncOne.syncStatus && !syncTwo.syncStatus) return -1;
|
||||||
if (!syncOne.syncStatus && !syncTwo.syncStatus) return 0;
|
if (!syncOne.syncStatus && !syncTwo.syncStatus) return 0;
|
||||||
@@ -238,22 +221,22 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => {
|
|||||||
const handleToggleEnableSync = async (secretSync: TSecretSync) => {
|
const handleToggleEnableSync = async (secretSync: TSecretSync) => {
|
||||||
const destinationName = SECRET_SYNC_MAP[secretSync.destination].name;
|
const destinationName = SECRET_SYNC_MAP[secretSync.destination].name;
|
||||||
|
|
||||||
const isEnabled = !secretSync.isEnabled;
|
const isAutoSyncEnabled = !secretSync.isAutoSyncEnabled;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateSync.mutateAsync({
|
await updateSync.mutateAsync({
|
||||||
syncId: secretSync.id,
|
syncId: secretSync.id,
|
||||||
destination: secretSync.destination,
|
destination: secretSync.destination,
|
||||||
isEnabled
|
isAutoSyncEnabled
|
||||||
});
|
});
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text: `Successfully ${isEnabled ? "enabled" : "disabled"} ${destinationName} Sync`,
|
text: `Successfully ${isAutoSyncEnabled ? "enabled" : "disabled"} auto-sync for ${destinationName} Sync`,
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
createNotification({
|
createNotification({
|
||||||
text: `Failed to ${isEnabled ? "enable" : "disable"} ${destinationName} Sync`,
|
text: `Failed to ${isAutoSyncEnabled ? "enable" : "disable"} auto-sync for ${destinationName} Sync`,
|
||||||
type: "error"
|
type: "error"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -306,7 +289,7 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => {
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="thin-scrollbar max-h-[70vh] overflow-y-auto" align="end">
|
<DropdownMenuContent className="thin-scrollbar max-h-[70vh] overflow-y-auto" align="end">
|
||||||
<DropdownMenuLabel>Status</DropdownMenuLabel>
|
<DropdownMenuLabel>Status</DropdownMenuLabel>
|
||||||
{Object.values(SecretSyncStatusCol).map((status) => (
|
{Object.values(SecretSyncStatus).map((status) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -442,7 +425,7 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
</Th>
|
</Th>
|
||||||
<Th className="min-w-[10rem]">
|
<Th className="min-w-[10.5rem]">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
Status
|
Status
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|||||||
+31
-6
@@ -1,5 +1,6 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import {
|
import {
|
||||||
|
faBan,
|
||||||
faCheck,
|
faCheck,
|
||||||
faCopy,
|
faCopy,
|
||||||
faDownload,
|
faDownload,
|
||||||
@@ -24,6 +25,7 @@ import {
|
|||||||
SecretSyncRemoveStatusBadge
|
SecretSyncRemoveStatusBadge
|
||||||
} from "@app/components/secret-syncs";
|
} from "@app/components/secret-syncs";
|
||||||
import {
|
import {
|
||||||
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -84,22 +86,22 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => {
|
|||||||
}, [isIdCopied]);
|
}, [isIdCopied]);
|
||||||
|
|
||||||
const handleToggleEnableSync = async () => {
|
const handleToggleEnableSync = async () => {
|
||||||
const isEnabled = !secretSync.isEnabled;
|
const isAutoSyncEnabled = !secretSync.isAutoSyncEnabled;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateSync.mutateAsync({
|
await updateSync.mutateAsync({
|
||||||
syncId: secretSync.id,
|
syncId: secretSync.id,
|
||||||
destination: secretSync.destination,
|
destination: secretSync.destination,
|
||||||
isEnabled
|
isAutoSyncEnabled
|
||||||
});
|
});
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text: `Successfully ${isEnabled ? "enabled" : "disabled"} ${destinationName} Sync`,
|
text: `Successfully ${isAutoSyncEnabled ? "enabled" : "disabled"} auto-sync for ${destinationName} Sync`,
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
createNotification({
|
createNotification({
|
||||||
text: `Failed to ${isEnabled ? "enable" : "disable"} ${destinationName} Sync`,
|
text: `Failed to ${isAutoSyncEnabled ? "enable" : "disable"} auto-sync for ${destinationName} Sync`,
|
||||||
type: "error"
|
type: "error"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -129,6 +131,27 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => {
|
|||||||
<div className="ml-auto mt-4 flex flex-wrap items-center justify-end gap-2">
|
<div className="ml-auto mt-4 flex flex-wrap items-center justify-end gap-2">
|
||||||
<SecretSyncImportStatusBadge secretSync={secretSync} />
|
<SecretSyncImportStatusBadge secretSync={secretSync} />
|
||||||
<SecretSyncRemoveStatusBadge secretSync={secretSync} />
|
<SecretSyncRemoveStatusBadge secretSync={secretSync} />
|
||||||
|
{secretSync.isAutoSyncEnabled ? (
|
||||||
|
<Badge
|
||||||
|
variant="success"
|
||||||
|
className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faRotate} />
|
||||||
|
<span>Auto-Sync Enabled</span>
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<Tooltip
|
||||||
|
className="text-xs"
|
||||||
|
content="Auto-Sync is disabled. Changes to the source location will not be automatically synced to the destination."
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
|
||||||
|
<FontAwesomeIcon icon={faBan} />
|
||||||
|
<span>Auto-Sync Disabled</span>
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionSecretSyncActions.SyncSecrets}
|
I={ProjectPermissionSecretSyncActions.SyncSecrets}
|
||||||
@@ -230,11 +253,13 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => {
|
|||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
isDisabled={!isAllowed}
|
isDisabled={!isAllowed}
|
||||||
icon={
|
icon={
|
||||||
<FontAwesomeIcon icon={secretSync.isEnabled ? faToggleOff : faToggleOn} />
|
<FontAwesomeIcon
|
||||||
|
icon={secretSync.isAutoSyncEnabled ? faToggleOff : faToggleOn}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
onClick={handleToggleEnableSync}
|
onClick={handleToggleEnableSync}
|
||||||
>
|
>
|
||||||
{secretSync.isEnabled ? "Disable" : "Enable"} Sync
|
{secretSync.isAutoSyncEnabled ? "Disable" : "Enable"} Auto-Sync
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan>
|
||||||
|
|||||||
+4
-14
@@ -1,11 +1,11 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { faBan, faEdit } from "@fortawesome/free-solid-svg-icons";
|
import { faEdit } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
|
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import { SecretSyncLabel, SecretSyncStatusBadge } from "@app/components/secret-syncs";
|
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
||||||
import { Badge, IconButton } from "@app/components/v2";
|
import { IconButton } from "@app/components/v2";
|
||||||
import { ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionSub } from "@app/context";
|
||||||
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
|
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
|
||||||
import { SecretSyncStatus, TSecretSync } from "@app/hooks/api/secretSyncs";
|
import { SecretSyncStatus, TSecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
@@ -16,7 +16,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SecretSyncDetailsSection = ({ secretSync, onEditDetails }: Props) => {
|
export const SecretSyncDetailsSection = ({ secretSync, onEditDetails }: Props) => {
|
||||||
const { syncStatus, lastSyncMessage, lastSyncedAt, name, description, isEnabled } = secretSync;
|
const { syncStatus, lastSyncMessage, lastSyncedAt, name, description } = secretSync;
|
||||||
|
|
||||||
const failureMessage = useMemo(() => {
|
const failureMessage = useMemo(() => {
|
||||||
if (syncStatus === SecretSyncStatus.Failed) {
|
if (syncStatus === SecretSyncStatus.Failed) {
|
||||||
@@ -57,16 +57,6 @@ export const SecretSyncDetailsSection = ({ secretSync, onEditDetails }: Props) =
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<SecretSyncLabel label="Name">{name}</SecretSyncLabel>
|
<SecretSyncLabel label="Name">{name}</SecretSyncLabel>
|
||||||
<SecretSyncLabel label="Description">{description}</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 && (
|
{lastSyncedAt && (
|
||||||
<SecretSyncLabel label="Last Synced">
|
<SecretSyncLabel label="Last Synced">
|
||||||
{format(new Date(lastSyncedAt), "yyyy-MM-dd, hh:mm aaa")}
|
{format(new Date(lastSyncedAt), "yyyy-MM-dd, hh:mm aaa")}
|
||||||
|
|||||||
Reference in New Issue
Block a user