mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat: added integration sync status
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasIsSyncedColumn = await knex.schema.hasColumn(TableName.Integration, "isSynced");
|
||||||
|
const hasSyncMessageColumn = await knex.schema.hasColumn(TableName.Integration, "syncMessage");
|
||||||
|
const hasLastSyncJobId = await knex.schema.hasColumn(TableName.Integration, "lastSyncJobId");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.Integration, (t) => {
|
||||||
|
if (!hasIsSyncedColumn) {
|
||||||
|
t.boolean("isSynced").nullable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasSyncMessageColumn) {
|
||||||
|
t.text("syncMessage").nullable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasLastSyncJobId) {
|
||||||
|
t.string("lastSyncJobId").nullable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasIsSyncedColumn = await knex.schema.hasColumn(TableName.Integration, "isSynced");
|
||||||
|
const hasSyncMessageColumn = await knex.schema.hasColumn(TableName.Integration, "syncMessage");
|
||||||
|
const hasLastSyncJobId = await knex.schema.hasColumn(TableName.Integration, "lastSyncJobId");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.Integration, (t) => {
|
||||||
|
if (hasIsSyncedColumn) {
|
||||||
|
t.dropColumn("isSynced");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSyncMessageColumn) {
|
||||||
|
t.dropColumn("syncMessage");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasLastSyncJobId) {
|
||||||
|
t.dropColumn("lastSyncJobId");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -28,7 +28,10 @@ export const IntegrationsSchema = z.object({
|
|||||||
secretPath: z.string().default("/"),
|
secretPath: z.string().default("/"),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
lastUsed: z.date().nullable().optional()
|
lastUsed: z.date().nullable().optional(),
|
||||||
|
isSynced: z.boolean().nullable().optional(),
|
||||||
|
syncMessage: z.string().nullable().optional(),
|
||||||
|
lastSyncJobId: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIntegrations = z.infer<typeof IntegrationsSchema>;
|
export type TIntegrations = z.infer<typeof IntegrationsSchema>;
|
||||||
|
|||||||
@@ -463,20 +463,37 @@ export const secretQueueFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await syncIntegrationSecrets({
|
try {
|
||||||
createManySecretsRawFn,
|
await syncIntegrationSecrets({
|
||||||
updateManySecretsRawFn,
|
createManySecretsRawFn,
|
||||||
integrationDAL,
|
updateManySecretsRawFn,
|
||||||
integration,
|
integrationDAL,
|
||||||
integrationAuth,
|
integration,
|
||||||
secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets,
|
integrationAuth,
|
||||||
accessId: accessId as string,
|
secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets,
|
||||||
accessToken,
|
accessId: accessId as string,
|
||||||
appendices: {
|
accessToken,
|
||||||
prefix: metadata?.secretPrefix || "",
|
appendices: {
|
||||||
suffix: metadata?.secretSuffix || ""
|
prefix: metadata?.secretPrefix || "",
|
||||||
}
|
suffix: metadata?.secretSuffix || ""
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await integrationDAL.updateById(integration.id, {
|
||||||
|
lastSyncJobId: job.id,
|
||||||
|
lastUsed: new Date(),
|
||||||
|
syncMessage: "",
|
||||||
|
isSynced: true
|
||||||
|
});
|
||||||
|
} catch (err: unknown) {
|
||||||
|
logger.info("Secret integration sync error:", err);
|
||||||
|
await integrationDAL.updateById(integration.id, {
|
||||||
|
lastSyncJobId: job.id,
|
||||||
|
lastUsed: new Date(),
|
||||||
|
syncMessage: (err as Error)?.message,
|
||||||
|
isSynced: false
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Secret integration sync ended: %s", job.id);
|
logger.info("Secret integration sync ended: %s", job.id);
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ export type TIntegration = {
|
|||||||
secretPath: string;
|
secretPath: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
lastUsed?: string;
|
||||||
|
isSynced?: boolean;
|
||||||
|
syncMessage?: string;
|
||||||
__v: number;
|
__v: number;
|
||||||
metadata?: {
|
metadata?: {
|
||||||
secretSuffix?: string;
|
secretSuffix?: string;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { faArrowRight, faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faArrowRight, faCheck, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { format } from "date-fns";
|
||||||
import { integrationSlugNameMapping } from "public/data/frequentConstants";
|
import { integrationSlugNameMapping } from "public/data/frequentConstants";
|
||||||
|
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
@@ -74,7 +75,7 @@ export const IntegrationsSection = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isLoading && isBotActive && (
|
{!isLoading && isBotActive && (
|
||||||
<div className="flex flex-col min-w-max space-y-4 p-6 pt-0">
|
<div className="flex min-w-max flex-col space-y-4 p-6 pt-0">
|
||||||
{integrations?.map((integration) => (
|
{integrations?.map((integration) => (
|
||||||
<div
|
<div
|
||||||
className="max-w-8xl flex justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-3"
|
className="max-w-8xl flex justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-3"
|
||||||
@@ -137,11 +138,12 @@ export const IntegrationsSection = ({
|
|||||||
"App"
|
"App"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<div className="min-w-[8rem] max-w-[12rem] overflow-scroll no-scrollbar no-scrollbar::-webkit-scrollbar whitespace-nowrap rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
<div className="no-scrollbar::-webkit-scrollbar min-w-[8rem] max-w-[12rem] overflow-scroll whitespace-nowrap rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200 no-scrollbar">
|
||||||
{(integration.integration === "hashicorp-vault" &&
|
{(integration.integration === "hashicorp-vault" &&
|
||||||
`${integration.app} - path: ${integration.path}`) ||
|
`${integration.app} - path: ${integration.path}`) ||
|
||||||
(integration.scope === "github-org" && `${integration.owner}`) ||
|
(integration.scope === "github-org" && `${integration.owner}`) ||
|
||||||
(integration.integration === "aws-parameter-store" && `${integration.path}`) ||
|
(integration.integration === "aws-parameter-store" &&
|
||||||
|
`${integration.path}`) ||
|
||||||
(integration.scope?.startsWith("github-") &&
|
(integration.scope?.startsWith("github-") &&
|
||||||
`${integration.owner}/${integration.app}`) ||
|
`${integration.owner}/${integration.app}`) ||
|
||||||
integration.app}
|
integration.app}
|
||||||
@@ -188,6 +190,28 @@ export const IntegrationsSection = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex cursor-default items-center">
|
<div className="flex cursor-default items-center">
|
||||||
|
{!!integration.isSynced && !!integration.lastUsed && (
|
||||||
|
<div className="mr-5 flex items-center justify-end text-sm">
|
||||||
|
<div>
|
||||||
|
Last sync: {format(new Date(integration.lastUsed), "yyyy-MM-dd, hh:mm aaa")}
|
||||||
|
</div>
|
||||||
|
<Tooltip
|
||||||
|
center
|
||||||
|
content={
|
||||||
|
integration.isSynced
|
||||||
|
? "Secrets are in sync"
|
||||||
|
: `Failed to sync secrets: ${integration.syncMessage || "Generic error"}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={integration.isSynced ? faCheck : faXmark}
|
||||||
|
className={`ml-3 ${
|
||||||
|
integration.isSynced ? "text-green-600" : "text-red-600"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Delete}
|
I={ProjectPermissionActions.Delete}
|
||||||
a={ProjectPermissionSub.Integrations}
|
a={ProjectPermissionSub.Integrations}
|
||||||
@@ -217,7 +241,9 @@ export const IntegrationsSection = ({
|
|||||||
isOpen={popUp.deleteConfirmation.isOpen}
|
isOpen={popUp.deleteConfirmation.isOpen}
|
||||||
title={`Are you sure want to remove ${
|
title={`Are you sure want to remove ${
|
||||||
(popUp?.deleteConfirmation.data as TIntegration)?.integration || " "
|
(popUp?.deleteConfirmation.data as TIntegration)?.integration || " "
|
||||||
} integration for ${(popUp?.deleteConfirmation.data as TIntegration)?.app || "this project"}?`}
|
} integration for ${
|
||||||
|
(popUp?.deleteConfirmation.data as TIntegration)?.app || "this project"
|
||||||
|
}?`}
|
||||||
onChange={(isOpen) => handlePopUpToggle("deleteConfirmation", isOpen)}
|
onChange={(isOpen) => handlePopUpToggle("deleteConfirmation", isOpen)}
|
||||||
deleteKey={
|
deleteKey={
|
||||||
(popUp?.deleteConfirmation?.data as TIntegration)?.app ||
|
(popUp?.deleteConfirmation?.data as TIntegration)?.app ||
|
||||||
|
|||||||
Reference in New Issue
Block a user