mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 13:27:46 +00:00
circle-ci integration on progress
This commit is contained in:
@@ -1,23 +1,16 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from "express";
|
||||||
import { Types } from 'mongoose';
|
import { Types } from "mongoose";
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from "@sentry/node";
|
||||||
import {
|
import { Integration, IntegrationAuth, Bot } from "../../models";
|
||||||
Integration,
|
import { INTEGRATION_SET, INTEGRATION_OPTIONS } from "../../variables";
|
||||||
IntegrationAuth,
|
import { IntegrationService } from "../../services";
|
||||||
Bot
|
import { getApps, revokeAccess } from "../../integrations";
|
||||||
} from '../../models';
|
|
||||||
import { INTEGRATION_SET, INTEGRATION_OPTIONS } from '../../variables';
|
|
||||||
import { IntegrationService } from '../../services';
|
|
||||||
import { getApps, revokeAccess } from '../../integrations';
|
|
||||||
|
|
||||||
export const getIntegrationOptions = async (
|
export const getIntegrationOptions = async (req: Request, res: Response) => {
|
||||||
req: Request,
|
return res.status(200).send({
|
||||||
res: Response
|
integrationOptions: INTEGRATION_OPTIONS,
|
||||||
) => {
|
});
|
||||||
return res.status(200).send({
|
};
|
||||||
integrationOptions: INTEGRATION_OPTIONS
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform OAuth2 code-token exchange as part of integration [integration] for workspace with id [workspaceId]
|
* Perform OAuth2 code-token exchange as part of integration [integration] for workspace with id [workspaceId]
|
||||||
@@ -25,38 +18,35 @@ export const getIntegrationOptions = async (
|
|||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const oAuthExchange = async (
|
export const oAuthExchange = async (req: Request, res: Response) => {
|
||||||
req: Request,
|
try {
|
||||||
res: Response
|
const { workspaceId, code, integration } = req.body;
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const { workspaceId, code, integration } = req.body;
|
|
||||||
|
|
||||||
if (!INTEGRATION_SET.has(integration))
|
if (!INTEGRATION_SET.has(integration))
|
||||||
throw new Error('Failed to validate integration');
|
throw new Error("Failed to validate integration");
|
||||||
|
|
||||||
const environments = req.membership.workspace?.environments || [];
|
const environments = req.membership.workspace?.environments || [];
|
||||||
if(environments.length === 0){
|
if (environments.length === 0) {
|
||||||
throw new Error("Failed to get environments")
|
throw new Error("Failed to get environments");
|
||||||
}
|
}
|
||||||
|
|
||||||
await IntegrationService.handleOAuthExchange({
|
await IntegrationService.handleOAuthExchange({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
integration,
|
integration,
|
||||||
code,
|
code,
|
||||||
environment: environments[0].slug,
|
environment: environments[0].slug,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser({ email: req.user.email });
|
Sentry.setUser({ email: req.user.email });
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
message: 'Failed to get OAuth2 code-token exchange'
|
message: "Failed to get OAuth2 code-token exchange",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: 'Successfully enabled integration authorization'
|
message: "Successfully enabled integration authorization",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,60 +55,66 @@ export const oAuthExchange = async (
|
|||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const saveIntegrationAccessToken = async (
|
export const saveIntegrationAccessToken = async (
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
// TODO: refactor
|
// TODO: refactor
|
||||||
let integrationAuth;
|
let integrationAuth;
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
accessToken,
|
accessToken,
|
||||||
integration
|
integration,
|
||||||
}: {
|
}: {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
integration: string;
|
integration: string;
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
integrationAuth = await IntegrationAuth.findOneAndUpdate({
|
integrationAuth = await IntegrationAuth.findOneAndUpdate(
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
{
|
||||||
integration
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
}, {
|
integration,
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
},
|
||||||
integration
|
{
|
||||||
}, {
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
new: true,
|
integration,
|
||||||
upsert: true
|
},
|
||||||
});
|
{
|
||||||
|
new: true,
|
||||||
|
upsert: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const bot = await Bot.findOne({
|
const bot = await Bot.findOne({
|
||||||
workspace: new Types.ObjectId(workspaceId),
|
workspace: new Types.ObjectId(workspaceId),
|
||||||
isActive: true
|
isActive: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!bot) throw new Error('Bot must be enabled to save integration access token');
|
if (!bot)
|
||||||
|
throw new Error("Bot must be enabled to save integration access token");
|
||||||
|
|
||||||
// encrypt and save integration access token
|
// encrypt and save integration access token
|
||||||
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
|
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
|
||||||
integrationAuthId: integrationAuth._id.toString(),
|
integrationAuthId: integrationAuth._id.toString(),
|
||||||
accessToken,
|
accessToken,
|
||||||
accessExpiresAt: undefined
|
accessExpiresAt: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!integrationAuth) throw new Error('Failed to save integration access token');
|
if (!integrationAuth)
|
||||||
} catch (err) {
|
throw new Error("Failed to save integration access token");
|
||||||
Sentry.setUser({ email: req.user.email });
|
} catch (err) {
|
||||||
Sentry.captureException(err);
|
Sentry.setUser({ email: req.user.email });
|
||||||
return res.status(400).send({
|
Sentry.captureException(err);
|
||||||
message: 'Failed to save access token for integration'
|
return res.status(400).send({
|
||||||
});
|
message: "Failed to save access token for integration",
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
integrationAuth
|
integrationAuth,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of applications allowed for integration with integration authorization id [integrationAuthId]
|
* Return list of applications allowed for integration with integration authorization id [integrationAuthId]
|
||||||
@@ -127,23 +123,24 @@ export const saveIntegrationAccessToken = async (
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getIntegrationAuthApps = async (req: Request, res: Response) => {
|
export const getIntegrationAuthApps = async (req: Request, res: Response) => {
|
||||||
let apps;
|
let apps;
|
||||||
try {
|
try {
|
||||||
apps = await getApps({
|
apps = await getApps({
|
||||||
integrationAuth: req.integrationAuth,
|
integrationAuth: req.integrationAuth,
|
||||||
accessToken: req.accessToken
|
accessToken: req.accessToken,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser({ email: req.user.email });
|
console.log(err); // testing
|
||||||
Sentry.captureException(err);
|
Sentry.setUser({ email: req.user.email });
|
||||||
return res.status(400).send({
|
Sentry.captureException(err);
|
||||||
message: 'Failed to get integration authorization applications'
|
return res.status(400).send({
|
||||||
});
|
message: "Failed to get integration authorization applications",
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
apps
|
apps,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,21 +150,21 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteIntegrationAuth = async (req: Request, res: Response) => {
|
export const deleteIntegrationAuth = async (req: Request, res: Response) => {
|
||||||
let integrationAuth;
|
let integrationAuth;
|
||||||
try {
|
try {
|
||||||
integrationAuth = await revokeAccess({
|
integrationAuth = await revokeAccess({
|
||||||
integrationAuth: req.integrationAuth,
|
integrationAuth: req.integrationAuth,
|
||||||
accessToken: req.accessToken
|
accessToken: req.accessToken,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser({ email: req.user.email });
|
Sentry.setUser({ email: req.user.email });
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
message: 'Failed to delete integration authorization'
|
message: "Failed to delete integration authorization",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
integrationAuth
|
integrationAuth,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from "express";
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from "@sentry/node";
|
||||||
import {
|
import { Integration, Workspace, Bot, BotKey } from "../../models";
|
||||||
Integration,
|
import { EventService } from "../../services";
|
||||||
Workspace,
|
import { eventPushSecrets } from "../../events";
|
||||||
Bot,
|
|
||||||
BotKey
|
|
||||||
} from '../../models';
|
|
||||||
import { EventService } from '../../services';
|
|
||||||
import { eventPushSecrets } from '../../events';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create/initialize an (empty) integration for integration authorization
|
* Create/initialize an (empty) integration for integration authorization
|
||||||
@@ -16,29 +11,30 @@ import { eventPushSecrets } from '../../events';
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const createIntegration = async (req: Request, res: Response) => {
|
export const createIntegration = async (req: Request, res: Response) => {
|
||||||
let integration;
|
let integration;
|
||||||
try {
|
try {
|
||||||
// initialize new integration after saving integration access token
|
// initialize new integration after saving integration access token
|
||||||
integration = await new Integration({
|
integration = await new Integration({
|
||||||
workspace: req.integrationAuth.workspace._id,
|
workspace: req.integrationAuth.workspace._id,
|
||||||
isActive: false,
|
isActive: false,
|
||||||
app: null,
|
app: null,
|
||||||
environment: req.integrationAuth.workspace?.environments[0].slug,
|
environment: req.integrationAuth.workspace?.environments[0].slug,
|
||||||
integration: req.integrationAuth.integration,
|
integration: req.integrationAuth.integration,
|
||||||
integrationAuth: req.integrationAuth._id
|
integrationAuth: req.integrationAuth._id,
|
||||||
}).save();
|
}).save();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser({ email: req.user.email });
|
console.log(err);
|
||||||
Sentry.captureException(err);
|
Sentry.setUser({ email: req.user.email });
|
||||||
return res.status(400).send({
|
Sentry.captureException(err);
|
||||||
message: 'Failed to create integration'
|
return res.status(400).send({
|
||||||
});
|
message: "Failed to create integration",
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
integration
|
integration,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change environment or name of integration with id [integrationId]
|
* Change environment or name of integration with id [integrationId]
|
||||||
@@ -47,57 +43,57 @@ export const createIntegration = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const updateIntegration = async (req: Request, res: Response) => {
|
export const updateIntegration = async (req: Request, res: Response) => {
|
||||||
let integration;
|
let integration;
|
||||||
|
|
||||||
// TODO: add integration-specific validation to ensure that each
|
// TODO: add integration-specific validation to ensure that each
|
||||||
// integration has the correct fields populated in [Integration]
|
// integration has the correct fields populated in [Integration]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
environment,
|
environment,
|
||||||
isActive,
|
isActive,
|
||||||
app,
|
app,
|
||||||
appId,
|
appId,
|
||||||
targetEnvironment,
|
targetEnvironment,
|
||||||
owner, // github-specific integration param
|
owner, // github-specific integration param
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
integration = await Integration.findOneAndUpdate(
|
integration = await Integration.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
_id: req.integration._id
|
_id: req.integration._id,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
environment,
|
environment,
|
||||||
isActive,
|
isActive,
|
||||||
app,
|
app,
|
||||||
appId,
|
appId,
|
||||||
targetEnvironment,
|
targetEnvironment,
|
||||||
owner
|
owner,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true
|
new: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (integration) {
|
if (integration) {
|
||||||
// trigger event - push secrets
|
// trigger event - push secrets
|
||||||
EventService.handleEvent({
|
EventService.handleEvent({
|
||||||
event: eventPushSecrets({
|
event: eventPushSecrets({
|
||||||
workspaceId: integration.workspace.toString()
|
workspaceId: integration.workspace.toString(),
|
||||||
})
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser({ email: req.user.email });
|
Sentry.setUser({ email: req.user.email });
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
message: 'Failed to update integration'
|
message: "Failed to update integration",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
integration
|
integration,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -108,24 +104,24 @@ export const updateIntegration = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteIntegration = async (req: Request, res: Response) => {
|
export const deleteIntegration = async (req: Request, res: Response) => {
|
||||||
let integration;
|
let integration;
|
||||||
try {
|
try {
|
||||||
const { integrationId } = req.params;
|
const { integrationId } = req.params;
|
||||||
|
|
||||||
integration = await Integration.findOneAndDelete({
|
integration = await Integration.findOneAndDelete({
|
||||||
_id: integrationId
|
_id: integrationId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!integration) throw new Error('Failed to find integration');
|
if (!integration) throw new Error("Failed to find integration");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser({ email: req.user.email });
|
Sentry.setUser({ email: req.user.email });
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
message: 'Failed to delete integration'
|
message: "Failed to delete integration",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
integration
|
integration,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -321,9 +321,10 @@ const getAppsCircleci = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
await axios.get(`${INTEGRATION_CIRCLECI_API_URL}/v2/me/collaborations`, {
|
await axios.get(`${INTEGRATION_CIRCLECI_API_URL}/v2/me/collaborations`, {
|
||||||
headers: {
|
headers: {
|
||||||
"Circle-Token": accessToken,
|
"Circle-Token": accessToken,
|
||||||
|
"Accept-Encoding": "application/json",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
).data;
|
).data[0];
|
||||||
|
|
||||||
const { slug } = circleciOrganizationDetail;
|
const { slug } = circleciOrganizationDetail;
|
||||||
|
|
||||||
@@ -333,15 +334,17 @@ const getAppsCircleci = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Circle-Token": accessToken,
|
"Circle-Token": accessToken,
|
||||||
|
"Accept-Encoding": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
).data.items;
|
).data?.items;
|
||||||
|
|
||||||
apps = res.map((a: any) => ({
|
apps = res.map((a: any) => ({
|
||||||
name: a?.project_slug?.split("/")[2],
|
name: a?.project_slug?.split("/")[2],
|
||||||
}));
|
}));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
throw new Error("Failed to get Render services");
|
throw new Error("Failed to get Render services");
|
||||||
|
|||||||
@@ -87,12 +87,12 @@ const syncSecrets = async ({
|
|||||||
accessToken,
|
accessToken,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
// case INTEGRATION_CIRCLECI:
|
case INTEGRATION_CIRCLECI:
|
||||||
// await syncSecretsCircleci({
|
await syncSecretsCircleci({
|
||||||
// integration,
|
integration,
|
||||||
// secrets,
|
secrets,
|
||||||
// accessToken,
|
accessToken,
|
||||||
// });
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
@@ -831,14 +831,67 @@ const syncSecretsFlyio = async ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// const syncSecretsCircleci = async ({
|
const syncSecretsCircleci = async ({
|
||||||
// integration,
|
integration,
|
||||||
// secrets,
|
secrets,
|
||||||
// accessToken,
|
accessToken,
|
||||||
// }: {
|
}: {
|
||||||
// integration: IIntegration;
|
integration: IIntegration;
|
||||||
// secrets: any;
|
secrets: any;
|
||||||
// accessToken: string;
|
accessToken: string;
|
||||||
// }) => {};
|
}) => {
|
||||||
|
try {
|
||||||
|
const circleciOrganizationDetail = (
|
||||||
|
await axios.get(`${INTEGRATION_CIRCLECI_API_URL}/v2/me/collaborations`, {
|
||||||
|
headers: {
|
||||||
|
"Circle-Token": accessToken,
|
||||||
|
"Accept-Encoding": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).data[0];
|
||||||
|
|
||||||
|
const { slug } = circleciOrganizationDetail;
|
||||||
|
|
||||||
|
// get secrets from CircleCI
|
||||||
|
const getSecretsRes = (
|
||||||
|
await axios.get(
|
||||||
|
`${INTEGRATION_CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Circle-Token": accessToken,
|
||||||
|
"Accept-Encoding": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).data?.items;
|
||||||
|
|
||||||
|
console.log(getSecretsRes);
|
||||||
|
console.log(secrets);
|
||||||
|
|
||||||
|
// inject secrets to CircleCI
|
||||||
|
// note: no relivent api end point was found in CircleCI to do entire secrets at a same time so
|
||||||
|
// it is done one by one
|
||||||
|
Object.keys(secrets).forEach(
|
||||||
|
async (key) =>
|
||||||
|
await axios.post(
|
||||||
|
`${INTEGRATION_CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar`,
|
||||||
|
{
|
||||||
|
name: key,
|
||||||
|
value: secrets[key],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Circle-Token": accessToken,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error("Failed to sync secrets to CircleCI");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export { syncSecrets };
|
export { syncSecrets };
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
INTEGRATION_GITHUB,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_RENDER,
|
INTEGRATION_RENDER,
|
||||||
INTEGRATION_FLYIO,
|
INTEGRATION_FLYIO,
|
||||||
|
INTEGRATION_CIRCLECI,
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
|
|
||||||
export interface IIntegration {
|
export interface IIntegration {
|
||||||
@@ -74,6 +75,7 @@ const integrationSchema = new Schema<IIntegration>(
|
|||||||
INTEGRATION_GITHUB,
|
INTEGRATION_GITHUB,
|
||||||
INTEGRATION_RENDER,
|
INTEGRATION_RENDER,
|
||||||
INTEGRATION_FLYIO,
|
INTEGRATION_FLYIO,
|
||||||
|
INTEGRATION_CIRCLECI,
|
||||||
],
|
],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY,
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_GITHUB,
|
INTEGRATION_GITHUB,
|
||||||
|
INTEGRATION_CIRCLECI,
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
|
|
||||||
export interface IIntegrationAuth {
|
export interface IIntegrationAuth {
|
||||||
@@ -42,6 +43,7 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
|
|||||||
INTEGRATION_VERCEL,
|
INTEGRATION_VERCEL,
|
||||||
INTEGRATION_NETLIFY,
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_GITHUB,
|
INTEGRATION_GITHUB,
|
||||||
|
INTEGRATION_CIRCLECI,
|
||||||
],
|
],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -138,8 +138,8 @@ const INTEGRATION_OPTIONS = [
|
|||||||
name: "Circle CI",
|
name: "Circle CI",
|
||||||
slug: "circleci",
|
slug: "circleci",
|
||||||
image: "Circle CI.png",
|
image: "Circle CI.png",
|
||||||
isAvailable: false,
|
isAvailable: true,
|
||||||
type: "",
|
type: "pat",
|
||||||
clientId: "",
|
clientId: "",
|
||||||
docsLink: "",
|
docsLink: "",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,24 +1,16 @@
|
|||||||
// membership roles
|
// membership roles
|
||||||
const OWNER = 'owner';
|
const OWNER = "owner";
|
||||||
const ADMIN = 'admin';
|
const ADMIN = "admin";
|
||||||
const MEMBER = 'member';
|
const MEMBER = "member";
|
||||||
|
|
||||||
// membership statuses
|
// membership statuses
|
||||||
const INVITED = 'invited';
|
const INVITED = "invited";
|
||||||
|
|
||||||
// membership permissions ability
|
// membership permissions ability
|
||||||
const ABILITY_READ = 'read';
|
const ABILITY_READ = "read";
|
||||||
const ABILITY_WRITE = 'write';
|
const ABILITY_WRITE = "write";
|
||||||
|
|
||||||
// -- organization
|
// -- organization
|
||||||
const ACCEPTED = 'accepted';
|
const ACCEPTED = "accepted";
|
||||||
|
|
||||||
export {
|
export { OWNER, ADMIN, MEMBER, INVITED, ACCEPTED, ABILITY_READ, ABILITY_WRITE };
|
||||||
OWNER,
|
|
||||||
ADMIN,
|
|
||||||
MEMBER,
|
|
||||||
INVITED,
|
|
||||||
ACCEPTED,
|
|
||||||
ABILITY_READ,
|
|
||||||
ABILITY_WRITE
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ const CloudIntegration = ({
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className={`relative ${
|
className={`relative ${
|
||||||
cloudIntegrationOption.isAvailable
|
cloudIntegrationOption.isAvailable
|
||||||
? 'hover:bg-white/10 duration-200 cursor-pointer'
|
? 'cursor-pointer duration-200 hover:bg-white/10'
|
||||||
: 'opacity-50'
|
: 'opacity-50'
|
||||||
} flex flex-row bg-white/5 h-32 rounded-md p-4 items-center`}
|
} flex h-32 flex-row items-center rounded-md bg-white/5 p-4`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!cloudIntegrationOption.isAvailable) return;
|
if (!cloudIntegrationOption.isAvailable) return;
|
||||||
setSelectedIntegrationOption(cloudIntegrationOption);
|
setSelectedIntegrationOption(cloudIntegrationOption);
|
||||||
@@ -61,22 +61,22 @@ const CloudIntegration = ({
|
|||||||
alt="integration logo"
|
alt="integration logo"
|
||||||
/>
|
/>
|
||||||
{cloudIntegrationOption.name.split(' ').length > 2 ? (
|
{cloudIntegrationOption.name.split(' ').length > 2 ? (
|
||||||
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-4 max-w-xs">
|
<div className="ml-4 max-w-xs text-3xl font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
|
||||||
<div>{cloudIntegrationOption.name.split(' ')[0]}</div>
|
<div>{cloudIntegrationOption.name.split(' ')[0]}</div>
|
||||||
<div className="text-base">
|
<div className="text-base">
|
||||||
{cloudIntegrationOption.name.split(' ')[1]} {cloudIntegrationOption.name.split(' ')[2]}
|
{cloudIntegrationOption.name.split(' ')[1]} {cloudIntegrationOption.name.split(' ')[2]}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-xl ml-4 max-w-xs">
|
<div className="ml-4 max-w-xs text-xl font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
|
||||||
{cloudIntegrationOption.name}
|
{cloudIntegrationOption.name}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{cloudIntegrationOption.isAvailable &&
|
{cloudIntegrationOption.isAvailable &&
|
||||||
integrationAuths
|
integrationAuths
|
||||||
.map((authorization) => authorization.integration)
|
.map((authorization) => authorization?.integration)
|
||||||
.includes(cloudIntegrationOption.slug) && (
|
.includes(cloudIntegrationOption.slug) && (
|
||||||
<div className="absolute group z-40 top-0 right-0 flex flex-row">
|
<div className="group absolute top-0 right-0 z-40 flex flex-row">
|
||||||
<div
|
<div
|
||||||
onKeyDown={() => null}
|
onKeyDown={() => null}
|
||||||
role="button"
|
role="button"
|
||||||
@@ -86,8 +86,7 @@ const CloudIntegration = ({
|
|||||||
const deletedIntegrationAuth = await deleteIntegrationAuth({
|
const deletedIntegrationAuth = await deleteIntegrationAuth({
|
||||||
integrationAuthId: integrationAuths
|
integrationAuthId: integrationAuths
|
||||||
.filter(
|
.filter(
|
||||||
(authorization) =>
|
(authorization) => authorization.integration === cloudIntegrationOption.slug
|
||||||
authorization.integration === cloudIntegrationOption.slug
|
|
||||||
)
|
)
|
||||||
.map((authorization) => authorization._id)[0]
|
.map((authorization) => authorization._id)[0]
|
||||||
});
|
});
|
||||||
@@ -96,20 +95,20 @@ const CloudIntegration = ({
|
|||||||
integrationAuth: deletedIntegrationAuth
|
integrationAuth: deletedIntegrationAuth
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200"
|
className="flex w-max cursor-pointer flex-row items-center rounded-b-md bg-red py-0.5 px-2 text-xs opacity-0 duration-200 group-hover:opacity-100"
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faX} className="text-xs mr-2 py-px" />
|
<FontAwesomeIcon icon={faX} className="mr-2 py-px text-xs" />
|
||||||
Revoke
|
Revoke
|
||||||
</div>
|
</div>
|
||||||
<div className="w-max bg-primary py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90 group-hover:opacity-100 duration-200">
|
<div className="flex w-max flex-row items-center rounded-bl-md rounded-tr-md bg-primary py-0.5 px-2 text-xs text-black opacity-90 duration-200 group-hover:opacity-100">
|
||||||
<FontAwesomeIcon icon={faCheck} className="text-xs mr-2" />
|
<FontAwesomeIcon icon={faCheck} className="mr-2 text-xs" />
|
||||||
Authorized
|
Authorized
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!cloudIntegrationOption.isAvailable && (
|
{!cloudIntegrationOption.isAvailable && (
|
||||||
<div className="absolute group z-50 top-0 right-0 flex flex-row">
|
<div className="group absolute top-0 right-0 z-50 flex flex-row">
|
||||||
<div className="w-max bg-yellow py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90">
|
<div className="flex w-max flex-row items-center rounded-bl-md rounded-tr-md bg-yellow py-0.5 px-2 text-xs text-black opacity-90">
|
||||||
Coming Soon
|
Coming Soon
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const IntegrationTile = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
// set initial environment. This find will only execute when component is mounting
|
// set initial environment. This find will only execute when component is mounting
|
||||||
const [integrationEnvironment, setIntegrationEnvironment] = useState<Props['environments'][0]>(
|
const [integrationEnvironment, setIntegrationEnvironment] = useState<Props['environments'][0]>(
|
||||||
environments.find(({ slug }) => slug === integration.environment) || {
|
environments.find(({ slug }) => slug === integration?.environment) || {
|
||||||
name: '',
|
name: '',
|
||||||
slug: ''
|
slug: ''
|
||||||
}
|
}
|
||||||
@@ -66,27 +66,27 @@ const IntegrationTile = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadIntegration = async () => {
|
const loadIntegration = async () => {
|
||||||
|
|
||||||
const tempApps: [IntegrationApp] = await getIntegrationApps({
|
const tempApps: [IntegrationApp] = await getIntegrationApps({
|
||||||
integrationAuthId: integration.integrationAuth
|
integrationAuthId: integration?.integrationAuth
|
||||||
});
|
});
|
||||||
|
|
||||||
setApps(tempApps);
|
setApps(tempApps);
|
||||||
setIntegrationApp(integration.app ? integration.app : tempApps[0].name);
|
setIntegrationApp(integration?.app ? integration.app : tempApps[0].name);
|
||||||
|
|
||||||
switch (integration.integration) {
|
switch (integration.integration) {
|
||||||
case 'vercel':
|
case 'vercel':
|
||||||
setIntegrationTargetEnvironment(
|
setIntegrationTargetEnvironment(
|
||||||
integration?.targetEnvironment
|
integration?.targetEnvironment
|
||||||
? integration.targetEnvironment.charAt(0).toUpperCase() + integration.targetEnvironment.substring(1)
|
? integration.targetEnvironment.charAt(0).toUpperCase() +
|
||||||
: 'Development'
|
integration.targetEnvironment.substring(1)
|
||||||
|
: 'Development'
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'netlify':
|
case 'netlify':
|
||||||
setIntegrationTargetEnvironment(
|
setIntegrationTargetEnvironment(
|
||||||
integration?.targetEnvironment
|
integration?.targetEnvironment
|
||||||
? contextNetlifyMapping[integration.targetEnvironment]
|
? contextNetlifyMapping[integration.targetEnvironment]
|
||||||
: 'Local development'
|
: 'Local development'
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -107,7 +107,7 @@ const IntegrationTile = ({
|
|||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const siteApp = apps.find((app) => app.name === integrationApp); // obj or undefined
|
const siteApp = apps.find((app) => app.name === integrationApp); // obj or undefined
|
||||||
@@ -126,12 +126,12 @@ const IntegrationTile = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
setIntegrations(
|
setIntegrations(
|
||||||
integrations.map((i) => i._id === updatedIntegration._id ? updatedIntegration : i)
|
integrations.map((i) => (i._id === updatedIntegration._id ? updatedIntegration : i))
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
const renderIntegrationSpecificParams = (integration: Integration) => {
|
const renderIntegrationSpecificParams = (integration: Integration) => {
|
||||||
@@ -140,7 +140,7 @@ const IntegrationTile = ({
|
|||||||
case 'vercel':
|
case 'vercel':
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="text-gray-400 text-xs font-semibold mb-2 w-60">ENVIRONMENT</div>
|
<div className="mb-2 w-60 text-xs font-semibold text-gray-400">ENVIRONMENT</div>
|
||||||
<ListBox
|
<ListBox
|
||||||
data={!integration.isActive ? ['Development', 'Preview', 'Production'] : null}
|
data={!integration.isActive ? ['Development', 'Preview', 'Production'] : null}
|
||||||
isSelected={integrationTargetEnvironment}
|
isSelected={integrationTargetEnvironment}
|
||||||
@@ -152,7 +152,7 @@ const IntegrationTile = ({
|
|||||||
case 'netlify':
|
case 'netlify':
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="text-gray-400 text-xs font-semibold mb-2">CONTEXT</div>
|
<div className="mb-2 text-xs font-semibold text-gray-400">CONTEXT</div>
|
||||||
<ListBox
|
<ListBox
|
||||||
data={
|
data={
|
||||||
!integration.isActive
|
!integration.isActive
|
||||||
@@ -177,10 +177,10 @@ const IntegrationTile = ({
|
|||||||
if (!integrationApp || apps.length === 0) return <div />;
|
if (!integrationApp || apps.length === 0) return <div />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-5xl p-6 mx-6 mb-8 rounded-md bg-white/5 flex justify-between">
|
<div className="mx-6 mb-8 flex max-w-5xl justify-between rounded-md bg-white/5 p-6">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-gray-400 text-xs font-semibold mb-2">ENVIRONMENT</p>
|
<p className="mb-2 text-xs font-semibold text-gray-400">ENVIRONMENT</p>
|
||||||
<ListBox
|
<ListBox
|
||||||
data={!integration.isActive ? environments.map(({ name }) => name) : null}
|
data={!integration.isActive ? environments.map(({ name }) => name) : null}
|
||||||
isSelected={integrationEnvironment.name}
|
isSelected={integrationEnvironment.name}
|
||||||
@@ -196,16 +196,16 @@ const IntegrationTile = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
<FontAwesomeIcon icon={faArrowRight} className="mx-4 text-gray-400 mt-8" />
|
<FontAwesomeIcon icon={faArrowRight} className="mx-4 mt-8 text-gray-400" />
|
||||||
</div>
|
</div>
|
||||||
<div className="mr-2">
|
<div className="mr-2">
|
||||||
<p className="text-gray-400 text-xs font-semibold mb-2">INTEGRATION</p>
|
<p className="mb-2 text-xs font-semibold text-gray-400">INTEGRATION</p>
|
||||||
<div className="py-2.5 bg-white/[.07] rounded-md pl-4 pr-10 text-sm font-semibold text-gray-300">
|
<div className="rounded-md bg-white/[.07] py-2.5 pl-4 pr-10 text-sm font-semibold text-gray-300">
|
||||||
{integration.integration.charAt(0).toUpperCase() + integration.integration.slice(1)}
|
{integration.integration.charAt(0).toUpperCase() + integration.integration.slice(1)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mr-2">
|
<div className="mr-2">
|
||||||
<div className="text-gray-400 text-xs font-semibold mb-2">APP</div>
|
<div className="mb-2 text-xs font-semibold text-gray-400">APP</div>
|
||||||
<ListBox
|
<ListBox
|
||||||
data={!integration.isActive ? apps.map((app) => app.name) : null}
|
data={!integration.isActive ? apps.map((app) => app.name) : null}
|
||||||
isSelected={integrationApp}
|
isSelected={integrationApp}
|
||||||
@@ -218,9 +218,9 @@ const IntegrationTile = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-end">
|
<div className="flex items-end">
|
||||||
{integration.isActive ? (
|
{integration.isActive ? (
|
||||||
<div className="max-w-5xl flex flex-row items-center bg-white/5 p-2 rounded-md px-4">
|
<div className="flex max-w-5xl flex-row items-center rounded-md bg-white/5 p-2 px-4">
|
||||||
<FontAwesomeIcon icon={faRotate} className="text-lg mr-2.5 text-primary animate-spin" />
|
<FontAwesomeIcon icon={faRotate} className="mr-2.5 animate-spin text-lg text-primary" />
|
||||||
<div className="text-gray-300 font-semibold">In Sync</div>
|
<div className="font-semibold text-gray-300">In Sync</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
@@ -230,11 +230,13 @@ const IntegrationTile = ({
|
|||||||
size="md"
|
size="md"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="opacity-50 hover:opacity-100 duration-200 ml-2">
|
<div className="ml-2 opacity-50 duration-200 hover:opacity-100">
|
||||||
<Button
|
<Button
|
||||||
onButtonPressed={() => handleDeleteIntegration({
|
onButtonPressed={() =>
|
||||||
integration
|
handleDeleteIntegration({
|
||||||
})}
|
integration
|
||||||
|
})
|
||||||
|
}
|
||||||
color="red"
|
color="red"
|
||||||
size="icon-md"
|
size="icon-md"
|
||||||
icon={faX}
|
icon={faX}
|
||||||
|
|||||||
@@ -33,16 +33,14 @@ const ProjectIntegrationSection = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
return integrations.length > 0 ? (
|
return integrations.length > 0 ? (
|
||||||
<div className="mb-12">
|
<div className="mb-12">
|
||||||
<div className="flex flex-col justify-between items-start mx-4 mb-4 mt-6 text-xl max-w-5xl px-2">
|
<div className="mx-4 mb-4 mt-6 flex max-w-5xl flex-col items-start justify-between px-2 text-xl">
|
||||||
<h1 className="font-semibold text-3xl">Current Integrations</h1>
|
<h1 className="text-3xl font-semibold">Current Integrations</h1>
|
||||||
<p className="text-base text-gray-400">
|
<p className="text-base text-gray-400">Manage integrations with third-party services.</p>
|
||||||
Manage integrations with third-party services.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
{integrations.map((integration: Integration) => {
|
{integrations.map((integration: Integration) => {
|
||||||
return (
|
return (
|
||||||
<IntegrationTile
|
<IntegrationTile
|
||||||
key={`integration-${integration._id.toString()}`}
|
key={`integration-${integration?._id.toString()}`}
|
||||||
integration={integration}
|
integration={integration}
|
||||||
integrations={integrations}
|
integrations={integrations}
|
||||||
bot={bot}
|
bot={bot}
|
||||||
@@ -57,6 +55,6 @@ const ProjectIntegrationSection = ({
|
|||||||
) : (
|
) : (
|
||||||
<div />
|
<div />
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default ProjectIntegrationSection;
|
export default ProjectIntegrationSection;
|
||||||
Reference in New Issue
Block a user