mirror of
https://github.com/Infisical/infisical.git
synced 2026-01-09 15:38:03 -05:00
Merge pull request #423 from Infisical/check-integrations
Patch create integration page on no integration projects and add support for groups in GitLab integration
This commit is contained in:
@@ -2,13 +2,16 @@ 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,
|
IntegrationAuth,
|
||||||
Bot
|
Bot
|
||||||
} from '../../models';
|
} from '../../models';
|
||||||
import { INTEGRATION_SET, INTEGRATION_OPTIONS } from '../../variables';
|
import { INTEGRATION_SET, INTEGRATION_OPTIONS } from '../../variables';
|
||||||
import { IntegrationService } from '../../services';
|
import { IntegrationService } from '../../services';
|
||||||
import { getApps, revokeAccess } from '../../integrations';
|
import {
|
||||||
|
getApps,
|
||||||
|
getTeams,
|
||||||
|
revokeAccess
|
||||||
|
} from '../../integrations';
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Return integration authorization with id [integrationAuthId]
|
* Return integration authorization with id [integrationAuthId]
|
||||||
@@ -154,25 +157,54 @@ 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({
|
const teamId = req.query.teamId as string;
|
||||||
integrationAuth: req.integrationAuth,
|
|
||||||
accessToken: req.accessToken,
|
apps = await getApps({
|
||||||
});
|
integrationAuth: req.integrationAuth,
|
||||||
} catch (err) {
|
accessToken: req.accessToken,
|
||||||
Sentry.setUser({ email: req.user.email });
|
...teamId && { teamId }
|
||||||
Sentry.captureException(err);
|
});
|
||||||
return res.status(400).send({
|
} catch (err) {
|
||||||
message: "Failed to get integration authorization applications",
|
Sentry.setUser({ email: req.user.email });
|
||||||
});
|
Sentry.captureException(err);
|
||||||
}
|
return res.status(400).send({
|
||||||
|
message: "Failed to get integration authorization applications",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
apps,
|
apps
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of teams allowed for integration with integration authorization id [integrationAuthId]
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
|
||||||
|
let teams;
|
||||||
|
try {
|
||||||
|
teams = await getTeams({
|
||||||
|
integrationAuth: req.integrationAuth,
|
||||||
|
accessToken: req.accessToken
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser({ email: req.user.email });
|
||||||
|
Sentry.captureException(err);
|
||||||
|
return res.status(400).send({
|
||||||
|
message: "Failed to get integration authorization teams"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
teams
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete integration authorization with id [integrationAuthId]
|
* Delete integration authorization with id [integrationAuthId]
|
||||||
* @param req
|
* @param req
|
||||||
|
|||||||
@@ -2,10 +2,7 @@ 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,
|
Integration
|
||||||
Workspace,
|
|
||||||
Bot,
|
|
||||||
BotKey
|
|
||||||
} from '../../models';
|
} from '../../models';
|
||||||
import { EventService } from '../../services';
|
import { EventService } from '../../services';
|
||||||
import { eventPushSecrets } from '../../events';
|
import { eventPushSecrets } from '../../events';
|
||||||
@@ -18,6 +15,7 @@ import { eventPushSecrets } from '../../events';
|
|||||||
*/
|
*/
|
||||||
export const createIntegration = async (req: Request, res: Response) => {
|
export const createIntegration = async (req: Request, res: Response) => {
|
||||||
let integration;
|
let integration;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
integrationAuthId,
|
integrationAuthId,
|
||||||
@@ -34,19 +32,19 @@ export const createIntegration = async (req: Request, res: Response) => {
|
|||||||
// TODO: validate [sourceEnvironment] and [targetEnvironment]
|
// TODO: validate [sourceEnvironment] and [targetEnvironment]
|
||||||
|
|
||||||
// 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,
|
||||||
environment: sourceEnvironment,
|
environment: sourceEnvironment,
|
||||||
isActive,
|
isActive,
|
||||||
app,
|
app,
|
||||||
appId,
|
appId,
|
||||||
targetEnvironment,
|
targetEnvironment,
|
||||||
owner,
|
owner,
|
||||||
path,
|
path,
|
||||||
region,
|
region,
|
||||||
integration: req.integrationAuth.integration,
|
integration: req.integrationAuth.integration,
|
||||||
integrationAuth: new Types.ObjectId(integrationAuthId)
|
integrationAuth: new Types.ObjectId(integrationAuthId)
|
||||||
}).save();
|
}).save();
|
||||||
|
|
||||||
if (integration) {
|
if (integration) {
|
||||||
// trigger event - push secrets
|
// trigger event - push secrets
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrati
|
|||||||
// access token is expired
|
// access token is expired
|
||||||
const refreshToken = await getIntegrationAuthRefreshHelper({ integrationAuthId });
|
const refreshToken = await getIntegrationAuthRefreshHelper({ integrationAuthId });
|
||||||
accessToken = await exchangeRefresh({
|
accessToken = await exchangeRefresh({
|
||||||
integration: integrationAuth.integration,
|
integrationAuth,
|
||||||
refreshToken
|
refreshToken
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,26 +25,30 @@ import {
|
|||||||
INTEGRATION_TRAVISCI_API_URL,
|
INTEGRATION_TRAVISCI_API_URL,
|
||||||
} from "../variables";
|
} from "../variables";
|
||||||
|
|
||||||
|
interface App {
|
||||||
|
name: string;
|
||||||
|
appId?: string;
|
||||||
|
owner?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return list of names of apps for integration named [integration]
|
* Return list of names of apps for integration named [integration]
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {String} obj.integration - name of integration
|
* @param {String} obj.integration - name of integration
|
||||||
* @param {String} obj.accessToken - access token for integration
|
* @param {String} obj.accessToken - access token for integration
|
||||||
|
* @param {String} obj.teamId - (optional) id of team for getting integration apps (used for integrations like GitLab)
|
||||||
* @returns {Object[]} apps - names of integration apps
|
* @returns {Object[]} apps - names of integration apps
|
||||||
* @returns {String} apps.name - name of integration app
|
* @returns {String} apps.name - name of integration app
|
||||||
*/
|
*/
|
||||||
const getApps = async ({
|
const getApps = async ({
|
||||||
integrationAuth,
|
integrationAuth,
|
||||||
accessToken,
|
accessToken,
|
||||||
|
teamId
|
||||||
}: {
|
}: {
|
||||||
integrationAuth: IIntegrationAuth;
|
integrationAuth: IIntegrationAuth;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
|
teamId?: string;
|
||||||
}) => {
|
}) => {
|
||||||
interface App {
|
|
||||||
name: string;
|
|
||||||
appId?: string;
|
|
||||||
owner?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
let apps: App[] = [];
|
let apps: App[] = [];
|
||||||
try {
|
try {
|
||||||
@@ -82,6 +86,7 @@ const getApps = async ({
|
|||||||
case INTEGRATION_GITLAB:
|
case INTEGRATION_GITLAB:
|
||||||
apps = await getAppsGitlab({
|
apps = await getAppsGitlab({
|
||||||
accessToken,
|
accessToken,
|
||||||
|
teamId
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_RENDER:
|
case INTEGRATION_RENDER:
|
||||||
@@ -434,44 +439,107 @@ const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
* @returns {Object[]} apps - names of GitLab sites
|
* @returns {Object[]} apps - names of GitLab sites
|
||||||
* @returns {String} apps.name - name of GitLab site
|
* @returns {String} apps.name - name of GitLab site
|
||||||
*/
|
*/
|
||||||
const getAppsGitlab = async ({ accessToken }: {accessToken: string}) => {
|
const getAppsGitlab = async ({
|
||||||
let apps;
|
accessToken,
|
||||||
|
teamId
|
||||||
|
}: {
|
||||||
|
accessToken: string;
|
||||||
|
teamId?: string;
|
||||||
|
}) => {
|
||||||
|
const apps: App[] = [];
|
||||||
|
|
||||||
|
let page = 1;
|
||||||
|
const perPage = 10;
|
||||||
|
let hasMorePages = true;
|
||||||
try {
|
try {
|
||||||
const { id } = (
|
|
||||||
await request.get(
|
|
||||||
`${INTEGRATION_GITLAB_API_URL}/v4/user`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"Authorization": `Bearer ${accessToken}`,
|
|
||||||
"Accept-Encoding": "application/json",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
const res = (
|
if (teamId) {
|
||||||
await request.get(
|
// case: fetch projects for group with id [teamId] in GitLab
|
||||||
`${INTEGRATION_GITLAB_API_URL}/v4/users/${id}/projects`,
|
|
||||||
{
|
while (hasMorePages) {
|
||||||
headers: {
|
const params = new URLSearchParams({
|
||||||
"Authorization": `Bearer ${accessToken}`,
|
page: String(page),
|
||||||
"Accept-Encoding": "application/json",
|
per_page: String(perPage)
|
||||||
},
|
});
|
||||||
}
|
|
||||||
)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
apps = res?.map((a: any) => {
|
const { data } = (
|
||||||
return {
|
await request.get(
|
||||||
name: a?.name,
|
`${INTEGRATION_GITLAB_API_URL}/v4/groups/${teamId}/projects`,
|
||||||
appId: `${a?.id}`,
|
{
|
||||||
|
params,
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Bearer ${accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
data.map((a: any) => {
|
||||||
|
apps.push({
|
||||||
|
name: a.name,
|
||||||
|
appId: a.id
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.length < perPage) {
|
||||||
|
hasMorePages = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
page++;
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
}catch (err) {
|
// case: fetch projects for individual in GitLab
|
||||||
|
|
||||||
|
const { id } = (
|
||||||
|
await request.get(
|
||||||
|
`${INTEGRATION_GITLAB_API_URL}/v4/user`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Bearer ${accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).data;
|
||||||
|
|
||||||
|
while (hasMorePages) {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
page: String(page),
|
||||||
|
per_page: String(perPage)
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = (
|
||||||
|
await request.get(
|
||||||
|
`${INTEGRATION_GITLAB_API_URL}/v4/users/${id}/projects`,
|
||||||
|
{
|
||||||
|
params,
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Bearer ${accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
data.map((a: any) => {
|
||||||
|
apps.push({
|
||||||
|
name: a.name,
|
||||||
|
appId: a.id
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.length < perPage) {
|
||||||
|
hasMorePages = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
throw new Error("Failed to get GitLab repos");
|
throw new Error("Failed to get GitLab projects");
|
||||||
}
|
}
|
||||||
|
|
||||||
return apps;
|
return apps;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
INTEGRATION_VERCEL_TOKEN_URL,
|
INTEGRATION_VERCEL_TOKEN_URL,
|
||||||
INTEGRATION_NETLIFY_TOKEN_URL,
|
INTEGRATION_NETLIFY_TOKEN_URL,
|
||||||
INTEGRATION_GITHUB_TOKEN_URL,
|
INTEGRATION_GITHUB_TOKEN_URL,
|
||||||
INTEGRATION_GITLAB_TOKEN_URL,
|
INTEGRATION_GITLAB_TOKEN_URL
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
import {
|
import {
|
||||||
SITE_URL,
|
SITE_URL,
|
||||||
@@ -73,7 +73,7 @@ interface ExchangeCodeGithubResponse {
|
|||||||
interface ExchangeCodeGitlabResponse {
|
interface ExchangeCodeGitlabResponse {
|
||||||
access_token: string;
|
access_token: string;
|
||||||
token_type: string;
|
token_type: string;
|
||||||
expires_in: string;
|
expires_in: number;
|
||||||
refresh_token: string;
|
refresh_token: string;
|
||||||
scope: string;
|
scope: string;
|
||||||
created_at: number;
|
created_at: number;
|
||||||
@@ -168,7 +168,7 @@ const exchangeCodeAzure = async ({
|
|||||||
accessExpiresAt.setSeconds(
|
accessExpiresAt.setSeconds(
|
||||||
accessExpiresAt.getSeconds() + res.expires_in
|
accessExpiresAt.getSeconds() + res.expires_in
|
||||||
);
|
);
|
||||||
} catch (err: any) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed OAuth2 code-token exchange with Azure');
|
throw new Error('Failed OAuth2 code-token exchange with Azure');
|
||||||
@@ -370,6 +370,7 @@ const exchangeCodeGithub = async ({ code }: { code: string }) => {
|
|||||||
*/
|
*/
|
||||||
const exchangeCodeGitlab = async ({ code }: { code: string }) => {
|
const exchangeCodeGitlab = async ({ code }: { code: string }) => {
|
||||||
let res: ExchangeCodeGitlabResponse;
|
let res: ExchangeCodeGitlabResponse;
|
||||||
|
const accessExpiresAt = new Date();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
res = (
|
res = (
|
||||||
@@ -389,7 +390,11 @@ const exchangeCodeGitlab = async ({ code }: { code: string }) => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
).data;
|
).data;
|
||||||
}catch (err) {
|
|
||||||
|
accessExpiresAt.setSeconds(
|
||||||
|
accessExpiresAt.getSeconds() + res.expires_in
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed OAuth2 code-token exchange with Gitlab');
|
throw new Error('Failed OAuth2 code-token exchange with Gitlab');
|
||||||
@@ -397,8 +402,8 @@ const exchangeCodeGitlab = async ({ code }: { code: string }) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
accessToken: res.access_token,
|
accessToken: res.access_token,
|
||||||
refreshToken: null,
|
refreshToken: res.refresh_token,
|
||||||
accessExpiresAt: null
|
accessExpiresAt
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { exchangeCode } from './exchange';
|
import { exchangeCode } from './exchange';
|
||||||
import { exchangeRefresh } from './refresh';
|
import { exchangeRefresh } from './refresh';
|
||||||
import { getApps } from './apps';
|
import { getApps } from './apps';
|
||||||
|
import { getTeams } from './teams';
|
||||||
import { syncSecrets } from './sync';
|
import { syncSecrets } from './sync';
|
||||||
import { revokeAccess } from './revoke';
|
import { revokeAccess } from './revoke';
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ export {
|
|||||||
exchangeCode,
|
exchangeCode,
|
||||||
exchangeRefresh,
|
exchangeRefresh,
|
||||||
getApps,
|
getApps,
|
||||||
|
getTeams,
|
||||||
syncSecrets,
|
syncSecrets,
|
||||||
revokeAccess
|
revokeAccess
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,29 @@
|
|||||||
import request from '../config/request';
|
import request from '../config/request';
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from '@sentry/node';
|
||||||
import { INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_HEROKU } from '../variables';
|
import {
|
||||||
|
IIntegrationAuth
|
||||||
|
} from '../models';
|
||||||
|
import {
|
||||||
|
INTEGRATION_AZURE_KEY_VAULT,
|
||||||
|
INTEGRATION_HEROKU,
|
||||||
|
INTEGRATION_GITLAB,
|
||||||
|
} from '../variables';
|
||||||
import {
|
import {
|
||||||
SITE_URL,
|
SITE_URL,
|
||||||
CLIENT_ID_AZURE,
|
CLIENT_ID_AZURE,
|
||||||
|
CLIENT_ID_GITLAB,
|
||||||
CLIENT_SECRET_AZURE,
|
CLIENT_SECRET_AZURE,
|
||||||
CLIENT_SECRET_HEROKU
|
CLIENT_SECRET_HEROKU,
|
||||||
|
CLIENT_SECRET_GITLAB
|
||||||
} from '../config';
|
} from '../config';
|
||||||
import {
|
import {
|
||||||
INTEGRATION_AZURE_TOKEN_URL,
|
INTEGRATION_AZURE_TOKEN_URL,
|
||||||
INTEGRATION_HEROKU_TOKEN_URL
|
INTEGRATION_HEROKU_TOKEN_URL,
|
||||||
|
INTEGRATION_GITLAB_TOKEN_URL
|
||||||
} from '../variables';
|
} from '../variables';
|
||||||
|
import {
|
||||||
|
IntegrationService
|
||||||
|
} from '../services';
|
||||||
|
|
||||||
interface RefreshTokenAzureResponse {
|
interface RefreshTokenAzureResponse {
|
||||||
token_type: string;
|
token_type: string;
|
||||||
@@ -21,6 +34,23 @@ interface RefreshTokenAzureResponse {
|
|||||||
refresh_token: string;
|
refresh_token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RefreshTokenHerokuResponse {
|
||||||
|
access_token: string;
|
||||||
|
expires_in: number;
|
||||||
|
refresh_token: string;
|
||||||
|
token_type: string;
|
||||||
|
user_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RefreshTokenGitLabResponse {
|
||||||
|
token_type: string;
|
||||||
|
scope: string;
|
||||||
|
expires_in: number;
|
||||||
|
access_token: string;
|
||||||
|
refresh_token: string;
|
||||||
|
created_at: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return new access token by exchanging refresh token [refreshToken] for integration
|
* Return new access token by exchanging refresh token [refreshToken] for integration
|
||||||
* named [integration]
|
* named [integration]
|
||||||
@@ -29,33 +59,61 @@ interface RefreshTokenAzureResponse {
|
|||||||
* @param {String} obj.refreshToken - refresh token to use to get new access token for Heroku
|
* @param {String} obj.refreshToken - refresh token to use to get new access token for Heroku
|
||||||
*/
|
*/
|
||||||
const exchangeRefresh = async ({
|
const exchangeRefresh = async ({
|
||||||
integration,
|
integrationAuth,
|
||||||
refreshToken
|
refreshToken
|
||||||
}: {
|
}: {
|
||||||
integration: string;
|
integrationAuth: IIntegrationAuth;
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
let accessToken;
|
|
||||||
|
interface TokenDetails {
|
||||||
|
accessToken: string;
|
||||||
|
refreshToken: string;
|
||||||
|
accessExpiresAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tokenDetails: TokenDetails;
|
||||||
try {
|
try {
|
||||||
switch (integration) {
|
switch (integrationAuth.integration) {
|
||||||
case INTEGRATION_AZURE_KEY_VAULT:
|
case INTEGRATION_AZURE_KEY_VAULT:
|
||||||
accessToken = await exchangeRefreshAzure({
|
tokenDetails = await exchangeRefreshAzure({
|
||||||
refreshToken
|
refreshToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case INTEGRATION_HEROKU:
|
case INTEGRATION_HEROKU:
|
||||||
accessToken = await exchangeRefreshHeroku({
|
tokenDetails = await exchangeRefreshHeroku({
|
||||||
refreshToken
|
refreshToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case INTEGRATION_GITLAB:
|
||||||
|
tokenDetails = await exchangeRefreshGitLab({
|
||||||
|
refreshToken
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('Failed to exchange token for incompatible integration');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tokenDetails?.accessToken && tokenDetails?.refreshToken && tokenDetails?.accessExpiresAt) {
|
||||||
|
await IntegrationService.setIntegrationAuthAccess({
|
||||||
|
integrationAuthId: integrationAuth._id.toString(),
|
||||||
|
accessId: null,
|
||||||
|
accessToken: tokenDetails.accessToken,
|
||||||
|
accessExpiresAt: tokenDetails.accessExpiresAt
|
||||||
|
});
|
||||||
|
|
||||||
|
await IntegrationService.setIntegrationAuthRefresh({
|
||||||
|
integrationAuthId: integrationAuth._id.toString(),
|
||||||
|
refreshToken: tokenDetails.refreshToken
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenDetails.accessToken;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed to get new OAuth2 access token');
|
throw new Error('Failed to get new OAuth2 access token');
|
||||||
}
|
}
|
||||||
|
|
||||||
return accessToken;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,7 +129,8 @@ const exchangeRefreshAzure = async ({
|
|||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
const res: RefreshTokenAzureResponse = (await request.post(
|
const accessExpiresAt = new Date();
|
||||||
|
const { data }: { data: RefreshTokenAzureResponse } = await request.post(
|
||||||
INTEGRATION_AZURE_TOKEN_URL,
|
INTEGRATION_AZURE_TOKEN_URL,
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
client_id: CLIENT_ID_AZURE,
|
client_id: CLIENT_ID_AZURE,
|
||||||
@@ -80,9 +139,17 @@ const exchangeRefreshAzure = async ({
|
|||||||
grant_type: 'refresh_token',
|
grant_type: 'refresh_token',
|
||||||
client_secret: CLIENT_SECRET_AZURE
|
client_secret: CLIENT_SECRET_AZURE
|
||||||
} as any)
|
} as any)
|
||||||
)).data;
|
);
|
||||||
|
|
||||||
return res.access_token;
|
accessExpiresAt.setSeconds(
|
||||||
|
accessExpiresAt.getSeconds() + data.expires_in
|
||||||
|
);
|
||||||
|
|
||||||
|
return ({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
refreshToken: data.refresh_token,
|
||||||
|
accessExpiresAt
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
@@ -102,10 +169,13 @@ const exchangeRefreshHeroku = async ({
|
|||||||
}: {
|
}: {
|
||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
let accessToken;
|
|
||||||
try {
|
try {
|
||||||
const res = await request.post(
|
const accessExpiresAt = new Date();
|
||||||
|
const {
|
||||||
|
data
|
||||||
|
}: {
|
||||||
|
data: RefreshTokenHerokuResponse
|
||||||
|
} = await request.post(
|
||||||
INTEGRATION_HEROKU_TOKEN_URL,
|
INTEGRATION_HEROKU_TOKEN_URL,
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
grant_type: 'refresh_token',
|
grant_type: 'refresh_token',
|
||||||
@@ -114,14 +184,69 @@ const exchangeRefreshHeroku = async ({
|
|||||||
} as any)
|
} as any)
|
||||||
);
|
);
|
||||||
|
|
||||||
accessToken = res.data.access_token;
|
accessExpiresAt.setSeconds(
|
||||||
|
accessExpiresAt.getSeconds() + data.expires_in
|
||||||
|
);
|
||||||
|
|
||||||
|
return ({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
refreshToken: data.refresh_token,
|
||||||
|
accessExpiresAt
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Sentry.setUser(null);
|
Sentry.setUser(null);
|
||||||
Sentry.captureException(err);
|
Sentry.captureException(err);
|
||||||
throw new Error('Failed to refresh OAuth2 access token for Heroku');
|
throw new Error('Failed to refresh OAuth2 access token for Heroku');
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return accessToken;
|
/**
|
||||||
|
* Return new access token by exchanging refresh token [refreshToken] for the
|
||||||
|
* GitLab integration
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.refreshToken - refresh token to use to get new access token for GitLab
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const exchangeRefreshGitLab = async ({
|
||||||
|
refreshToken
|
||||||
|
}: {
|
||||||
|
refreshToken: string;
|
||||||
|
}) => {
|
||||||
|
try {
|
||||||
|
const accessExpiresAt = new Date();
|
||||||
|
const {
|
||||||
|
data
|
||||||
|
}: {
|
||||||
|
data: RefreshTokenGitLabResponse
|
||||||
|
} = await request.post(
|
||||||
|
INTEGRATION_GITLAB_TOKEN_URL,
|
||||||
|
new URLSearchParams({
|
||||||
|
grant_type: 'refresh_token',
|
||||||
|
refresh_token: refreshToken,
|
||||||
|
client_id: CLIENT_ID_GITLAB,
|
||||||
|
client_secret: CLIENT_SECRET_GITLAB,
|
||||||
|
redirect_uri: `${SITE_URL}/integrations/gitlab/oauth2/callback`
|
||||||
|
} as any),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Accept-Encoding": "application/json",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
accessExpiresAt.setSeconds(
|
||||||
|
accessExpiresAt.getSeconds() + data.expires_in
|
||||||
|
);
|
||||||
|
|
||||||
|
return ({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
refreshToken: data.refresh_token,
|
||||||
|
accessExpiresAt
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to refresh OAuth2 access token for GitLab');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export { exchangeRefresh };
|
export { exchangeRefresh };
|
||||||
|
|||||||
@@ -172,7 +172,6 @@ const syncSecretsAzureKeyVault = async ({
|
|||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
interface GetAzureKeyVaultSecret {
|
interface GetAzureKeyVaultSecret {
|
||||||
id: string; // secret URI
|
id: string; // secret URI
|
||||||
attributes: {
|
attributes: {
|
||||||
@@ -1512,7 +1511,7 @@ const syncSecretsGitLab = async ({
|
|||||||
)
|
)
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
for (const key of Object.keys(secrets)) {
|
for await (const key of Object.keys(secrets)) {
|
||||||
const existingSecret = getSecretsRes.find((s: any) => s.key == key);
|
const existingSecret = getSecretsRes.find((s: any) => s.key == key);
|
||||||
if (!existingSecret) {
|
if (!existingSecret) {
|
||||||
await request.post(
|
await request.post(
|
||||||
@@ -1533,7 +1532,7 @@ const syncSecretsGitLab = async ({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}else {
|
} else {
|
||||||
// udpate secret
|
// udpate secret
|
||||||
await request.put(
|
await request.put(
|
||||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${existingSecret.key}`,
|
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${existingSecret.key}`,
|
||||||
@@ -1553,7 +1552,7 @@ const syncSecretsGitLab = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// delete secrets
|
// delete secrets
|
||||||
for (const sec of getSecretsRes) {
|
for await (const sec of getSecretsRes) {
|
||||||
if (!(sec.key in secrets)) {
|
if (!(sec.key in secrets)) {
|
||||||
await request.delete(
|
await request.delete(
|
||||||
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${sec.key}`,
|
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${sec.key}`,
|
||||||
|
|||||||
92
backend/src/integrations/teams.ts
Normal file
92
backend/src/integrations/teams.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import * as Sentry from "@sentry/node";
|
||||||
|
import {
|
||||||
|
IIntegrationAuth
|
||||||
|
} from '../models';
|
||||||
|
import {
|
||||||
|
INTEGRATION_GITLAB,
|
||||||
|
INTEGRATION_GITLAB_API_URL
|
||||||
|
} from '../variables';
|
||||||
|
import request from '../config/request';
|
||||||
|
|
||||||
|
interface Team {
|
||||||
|
name: string;
|
||||||
|
teamId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of teams for integration authorization [integrationAuth]
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.integrationAuth - integration authorization to get teams for
|
||||||
|
* @param {String} obj.accessToken - access token for integration authorization
|
||||||
|
* @returns {Object[]} teams - teams of integration authorization
|
||||||
|
* @returns {String} teams.name - name of team
|
||||||
|
* @returns {String} teams.teamId - id of team
|
||||||
|
*/
|
||||||
|
const getTeams = async ({
|
||||||
|
integrationAuth,
|
||||||
|
accessToken
|
||||||
|
}: {
|
||||||
|
integrationAuth: IIntegrationAuth;
|
||||||
|
accessToken: string;
|
||||||
|
}) => {
|
||||||
|
|
||||||
|
let teams: Team[] = [];
|
||||||
|
try {
|
||||||
|
switch (integrationAuth.integration) {
|
||||||
|
case INTEGRATION_GITLAB:
|
||||||
|
teams = await getTeamsGitLab({
|
||||||
|
accessToken
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error('Failed to get integration teams');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return teams;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of teams for GitLab integration
|
||||||
|
* @param {Object} obj
|
||||||
|
* @param {String} obj.accessToken - access token for GitLab API
|
||||||
|
* @returns {Object[]} teams - teams that user is part of in GitLab
|
||||||
|
* @returns {String} teams.name - name of team
|
||||||
|
* @returns {String} teams.teamId - id of team
|
||||||
|
*/
|
||||||
|
const getTeamsGitLab = async ({
|
||||||
|
accessToken
|
||||||
|
}: {
|
||||||
|
accessToken: string;
|
||||||
|
}) => {
|
||||||
|
let teams: Team[] = [];
|
||||||
|
try {
|
||||||
|
const res = (await request.get(
|
||||||
|
`${INTEGRATION_GITLAB_API_URL}/v4/groups`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)).data;
|
||||||
|
|
||||||
|
teams = res.map((t: any) => ({
|
||||||
|
name: t.name,
|
||||||
|
teamId: t.id
|
||||||
|
}));
|
||||||
|
} catch (err) {
|
||||||
|
Sentry.setUser(null);
|
||||||
|
Sentry.captureException(err);
|
||||||
|
throw new Error("Failed to get GitLab integration teams");
|
||||||
|
}
|
||||||
|
|
||||||
|
return teams;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getTeams
|
||||||
|
}
|
||||||
@@ -62,13 +62,11 @@ const integrationSchema = new Schema<IIntegration>(
|
|||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
appId: {
|
appId: {
|
||||||
// (new)
|
|
||||||
// id of app in provider
|
// id of app in provider
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
targetEnvironment: {
|
targetEnvironment: {
|
||||||
// (new)
|
|
||||||
// target environment
|
// target environment
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ export interface IIntegrationAuth {
|
|||||||
refreshCiphertext?: string;
|
refreshCiphertext?: string;
|
||||||
refreshIV?: string;
|
refreshIV?: string;
|
||||||
refreshTag?: string;
|
refreshTag?: string;
|
||||||
accessIdCiphertext?: string; // new
|
accessIdCiphertext?: string;
|
||||||
accessIdIV?: string; // new
|
accessIdIV?: string;
|
||||||
accessIdTag?: string; // new
|
accessIdTag?: string;
|
||||||
accessCiphertext?: string;
|
accessCiphertext?: string;
|
||||||
accessIV?: string;
|
accessIV?: string;
|
||||||
accessTag?: string;
|
accessTag?: string;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { body, param } from 'express-validator';
|
import { body, param, query } from 'express-validator';
|
||||||
import {
|
import {
|
||||||
requireAuth,
|
requireAuth,
|
||||||
requireWorkspaceAuth,
|
requireWorkspaceAuth,
|
||||||
@@ -73,10 +73,24 @@ router.get(
|
|||||||
acceptedRoles: [ADMIN, MEMBER]
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
}),
|
}),
|
||||||
param('integrationAuthId'),
|
param('integrationAuthId'),
|
||||||
|
query('teamId'),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
integrationAuthController.getIntegrationAuthApps
|
integrationAuthController.getIntegrationAuthApps
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:integrationAuthId/teams',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: ['jwt']
|
||||||
|
}),
|
||||||
|
requireIntegrationAuthorizationAuth({
|
||||||
|
acceptedRoles: [ADMIN, MEMBER]
|
||||||
|
}),
|
||||||
|
param('integrationAuthId'),
|
||||||
|
validateRequest,
|
||||||
|
integrationAuthController.getIntegrationAuthTeams
|
||||||
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
'/:integrationAuthId',
|
'/:integrationAuthId',
|
||||||
requireAuth({
|
requireAuth({
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ import {
|
|||||||
setIntegrationAuthAccessHelper,
|
setIntegrationAuthAccessHelper,
|
||||||
} from '../helpers/integration';
|
} from '../helpers/integration';
|
||||||
|
|
||||||
// should sync stuff be here too? Probably.
|
|
||||||
// TODO: move bot functions to IntegrationService.
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to handle integrations
|
* Class to handle integrations
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ const INTEGRATION_FLYIO_API_URL = "https://api.fly.io/graphql";
|
|||||||
const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api";
|
const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api";
|
||||||
const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com";
|
const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com";
|
||||||
|
|
||||||
|
// TODO: deprecate types?
|
||||||
const INTEGRATION_OPTIONS = [
|
const INTEGRATION_OPTIONS = [
|
||||||
{
|
{
|
||||||
name: 'Heroku',
|
name: 'Heroku',
|
||||||
@@ -156,7 +157,7 @@ const INTEGRATION_OPTIONS = [
|
|||||||
slug: 'gitlab',
|
slug: 'gitlab',
|
||||||
image: 'GitLab.png',
|
image: 'GitLab.png',
|
||||||
isAvailable: true,
|
isAvailable: true,
|
||||||
type: 'oauth',
|
type: 'custom',
|
||||||
clientId: CLIENT_ID_GITLAB,
|
clientId: CLIENT_ID_GITLAB,
|
||||||
docsLink: ''
|
docsLink: ''
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export {
|
export {
|
||||||
useGetIntegrationAuthApps,
|
useGetIntegrationAuthApps,
|
||||||
useGetIntegrationAuthById} from './queries';
|
useGetIntegrationAuthById,
|
||||||
|
useGetIntegrationAuthTeams} from './queries';
|
||||||
@@ -4,11 +4,13 @@ import { apiRequest } from "@app/config/request";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
App,
|
App,
|
||||||
IntegrationAuth} from './types';
|
IntegrationAuth,
|
||||||
|
Team} from './types';
|
||||||
|
|
||||||
const integrationAuthKeys = {
|
const integrationAuthKeys = {
|
||||||
getIntegrationAuthById: (integrationAuthId: string) => [{ integrationAuthId }, 'integrationAuth'] as const,
|
getIntegrationAuthById: (integrationAuthId: string) => [{ integrationAuthId }, 'integrationAuth'] as const,
|
||||||
getIntegrationAuthApps: (integrationAuthId: string) => [{ integrationAuthId }, 'integrationAuthApps'] as const,
|
getIntegrationAuthApps: (integrationAuthId: string, teamId?: string) => [{ integrationAuthId, teamId }, 'integrationAuthApps'] as const,
|
||||||
|
getIntegrationAuthTeams: (integrationAuthId: string) => [{ integrationAuthId }, 'integrationAuthTeams'] as const
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchIntegrationAuthById = async (integrationAuthId: string) => {
|
const fetchIntegrationAuthById = async (integrationAuthId: string) => {
|
||||||
@@ -16,11 +18,26 @@ const fetchIntegrationAuthById = async (integrationAuthId: string) => {
|
|||||||
return data.integrationAuth;
|
return data.integrationAuth;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchIntegrationAuthApps = async (integrationAuthId: string) => {
|
const fetchIntegrationAuthApps = async ({
|
||||||
const { data } = await apiRequest.get<{ apps: App[] }>(`/api/v1/integration-auth/${integrationAuthId}/apps`);
|
integrationAuthId,
|
||||||
|
teamId
|
||||||
|
}: {
|
||||||
|
integrationAuthId: string;
|
||||||
|
teamId?: string;
|
||||||
|
}) => {
|
||||||
|
const searchParams = new URLSearchParams(teamId ? { teamId } : undefined);
|
||||||
|
const { data } = await apiRequest.get<{ apps: App[] }>(
|
||||||
|
`/api/v1/integration-auth/${integrationAuthId}/apps`,
|
||||||
|
{ params: searchParams }
|
||||||
|
);
|
||||||
return data.apps;
|
return data.apps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchIntegrationAuthTeams = async (integrationAuthId: string) => {
|
||||||
|
const { data } = await apiRequest.get<{ teams: Team[] }>(`/api/v1/integration-auth/${integrationAuthId}/teams`);
|
||||||
|
return data.teams;
|
||||||
|
}
|
||||||
|
|
||||||
export const useGetIntegrationAuthById = (integrationAuthId: string) => {
|
export const useGetIntegrationAuthById = (integrationAuthId: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: integrationAuthKeys.getIntegrationAuthById(integrationAuthId),
|
queryKey: integrationAuthKeys.getIntegrationAuthById(integrationAuthId),
|
||||||
@@ -29,10 +46,28 @@ export const useGetIntegrationAuthById = (integrationAuthId: string) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useGetIntegrationAuthApps = (integrationAuthId: string) => {
|
// TODO: fix to teamId
|
||||||
|
export const useGetIntegrationAuthApps = ({
|
||||||
|
integrationAuthId,
|
||||||
|
teamId
|
||||||
|
}: {
|
||||||
|
integrationAuthId: string;
|
||||||
|
teamId?: string;
|
||||||
|
}) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: integrationAuthKeys.getIntegrationAuthApps(integrationAuthId),
|
queryKey: integrationAuthKeys.getIntegrationAuthApps(integrationAuthId, teamId),
|
||||||
queryFn: () => fetchIntegrationAuthApps(integrationAuthId),
|
queryFn: () => fetchIntegrationAuthApps({
|
||||||
|
integrationAuthId,
|
||||||
|
teamId
|
||||||
|
}),
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useGetIntegrationAuthTeams = (integrationAuthId: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: integrationAuthKeys.getIntegrationAuthTeams(integrationAuthId),
|
||||||
|
queryFn: () => fetchIntegrationAuthTeams(integrationAuthId),
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -10,4 +10,9 @@ export type App = {
|
|||||||
name: string;
|
name: string;
|
||||||
appId?: string;
|
appId?: string;
|
||||||
owner?: string;
|
owner?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Team = {
|
||||||
|
name: string;
|
||||||
|
teamId: string;
|
||||||
}
|
}
|
||||||
@@ -196,16 +196,16 @@ export default function Integrations() {
|
|||||||
link = `https://gitlab.com/oauth/authorize?client_id=${integrationOption.clientId}&redirect_uri=${window.location.origin}/integrations/gitlab/oauth2/callback&response_type=code&state=${state}`;
|
link = `https://gitlab.com/oauth/authorize?client_id=${integrationOption.clientId}&redirect_uri=${window.location.origin}/integrations/gitlab/oauth2/callback&response_type=code&state=${state}`;
|
||||||
break;
|
break;
|
||||||
case 'render':
|
case 'render':
|
||||||
link = `${window.location.origin}/integrations/render/authorize`
|
link = `${window.location.origin}/integrations/render/authorize`;
|
||||||
break;
|
break;
|
||||||
case 'flyio':
|
case 'flyio':
|
||||||
link = `${window.location.origin}/integrations/flyio/authorize`
|
link = `${window.location.origin}/integrations/flyio/authorize`;
|
||||||
break;
|
break;
|
||||||
case 'circleci':
|
case 'circleci':
|
||||||
link = `${window.location.origin}/integrations/circleci/authorize`
|
link = `${window.location.origin}/integrations/circleci/authorize`;
|
||||||
break;
|
break;
|
||||||
case 'travisci':
|
case 'travisci':
|
||||||
link = `${window.location.origin}/integrations/travisci/authorize`
|
link = `${window.location.origin}/integrations/travisci/authorize`;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ export default function CircleCICreateIntegrationPage() {
|
|||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||||
|
integrationAuthId: integrationAuthId as string ?? ''
|
||||||
|
});
|
||||||
|
|
||||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||||
const [targetApp, setTargetApp] = useState('');
|
const [targetApp, setTargetApp] = useState('');
|
||||||
@@ -36,9 +38,12 @@ export default function CircleCICreateIntegrationPage() {
|
|||||||
}, [workspace]);
|
}, [workspace]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO: handle case where apps can be empty
|
|
||||||
if (integrationAuthApps) {
|
if (integrationAuthApps) {
|
||||||
setTargetApp(integrationAuthApps[0]?.name);
|
if (integrationAuthApps.length > 0 ) {
|
||||||
|
setTargetApp(integrationAuthApps[0]?.name);
|
||||||
|
} else {
|
||||||
|
setTargetApp('none');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [integrationAuthApps]);
|
}, [integrationAuthApps]);
|
||||||
|
|
||||||
@@ -84,7 +89,7 @@ export default function CircleCICreateIntegrationPage() {
|
|||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
>
|
>
|
||||||
{workspace?.environments.map((sourceEnvironment) => (
|
{workspace?.environments.map((sourceEnvironment) => (
|
||||||
<SelectItem value={sourceEnvironment.slug} key={`azure-key-vault-environment-${sourceEnvironment.slug}`}>
|
<SelectItem value={sourceEnvironment.slug} key={`source-environment-${sourceEnvironment.slug}`}>
|
||||||
{sourceEnvironment.name}
|
{sourceEnvironment.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
@@ -98,19 +103,27 @@ export default function CircleCICreateIntegrationPage() {
|
|||||||
value={targetApp}
|
value={targetApp}
|
||||||
onValueChange={(val) => setTargetApp(val)}
|
onValueChange={(val) => setTargetApp(val)}
|
||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
{integrationAuthApps.map((integrationAuthApp) => (
|
{integrationAuthApps.length > 0 ? (
|
||||||
<SelectItem value={integrationAuthApp.name} key={`render-environment-${integrationAuthApp.name}`}>
|
integrationAuthApps.map((integrationAuthApp) => (
|
||||||
{integrationAuthApp.name}
|
<SelectItem value={integrationAuthApp.name} key={`target-app-${integrationAuthApp.name}`}>
|
||||||
|
{integrationAuthApp.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="none" key="target-app-none">
|
||||||
|
No projects found
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
className='mt-4'
|
className='mt-4'
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ export default function FlyioCreateIntegrationPage() {
|
|||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||||
|
integrationAuthId: integrationAuthId as string ?? ''
|
||||||
|
});
|
||||||
|
|
||||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||||
const [targetApp, setTargetApp] = useState('');
|
const [targetApp, setTargetApp] = useState('');
|
||||||
@@ -31,14 +33,18 @@ export default function FlyioCreateIntegrationPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workspace) {
|
if (workspace) {
|
||||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||||
}
|
}
|
||||||
}, [workspace]);
|
}, [workspace]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO: handle case where apps can be empty
|
// TODO: handle case where apps can be empty
|
||||||
if (integrationAuthApps) {
|
if (integrationAuthApps) {
|
||||||
|
if (integrationAuthApps.length > 0) {
|
||||||
setTargetApp(integrationAuthApps[0].name);
|
setTargetApp(integrationAuthApps[0].name);
|
||||||
|
} else {
|
||||||
|
setTargetApp('none');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [integrationAuthApps]);
|
}, [integrationAuthApps]);
|
||||||
|
|
||||||
@@ -84,7 +90,7 @@ export default function FlyioCreateIntegrationPage() {
|
|||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
>
|
>
|
||||||
{workspace?.environments.map((sourceEnvironment) => (
|
{workspace?.environments.map((sourceEnvironment) => (
|
||||||
<SelectItem value={sourceEnvironment.slug} key={`flyio-environment-${sourceEnvironment.slug}`}>
|
<SelectItem value={sourceEnvironment.slug} key={`source-environment-${sourceEnvironment.slug}`}>
|
||||||
{sourceEnvironment.name}
|
{sourceEnvironment.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
@@ -98,12 +104,19 @@ export default function FlyioCreateIntegrationPage() {
|
|||||||
value={targetApp}
|
value={targetApp}
|
||||||
onValueChange={(val) => setTargetApp(val)}
|
onValueChange={(val) => setTargetApp(val)}
|
||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
{integrationAuthApps.map((integrationAuthApp) => (
|
{integrationAuthApps.length > 0 ? (
|
||||||
<SelectItem value={integrationAuthApp.name} key={`render-environment-${integrationAuthApp.name}`}>
|
integrationAuthApps.map((integrationAuthApp) => (
|
||||||
{integrationAuthApp.name}
|
<SelectItem value={integrationAuthApp.name} key={`target-app-${integrationAuthApp.name}`}>
|
||||||
|
{integrationAuthApp.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="none" key="target-app-none">
|
||||||
|
No apps found
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
@@ -111,6 +124,7 @@ export default function FlyioCreateIntegrationPage() {
|
|||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
className='mt-4'
|
className='mt-4'
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ export default function GitHubCreateIntegrationPage() {
|
|||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||||
|
integrationAuthId: integrationAuthId as string ?? ''
|
||||||
|
});
|
||||||
|
|
||||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||||
const [owner, setOwner] = useState<string | null>(null);
|
const [owner, setOwner] = useState<string | null>(null);
|
||||||
@@ -37,10 +39,13 @@ export default function GitHubCreateIntegrationPage() {
|
|||||||
}, [workspace]);
|
}, [workspace]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO: handle case where apps can be empty
|
|
||||||
if (integrationAuthApps) {
|
if (integrationAuthApps) {
|
||||||
|
if (integrationAuthApps.length > 0) {
|
||||||
setTargetApp(integrationAuthApps[0].name);
|
setTargetApp(integrationAuthApps[0].name);
|
||||||
setOwner(integrationAuthApps[0]?.owner ?? null);
|
setOwner(integrationAuthApps[0]?.owner ?? null);
|
||||||
|
} else {
|
||||||
|
setTargetApp('none');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [integrationAuthApps]);
|
}, [integrationAuthApps]);
|
||||||
|
|
||||||
@@ -99,21 +104,29 @@ export default function GitHubCreateIntegrationPage() {
|
|||||||
value={targetApp}
|
value={targetApp}
|
||||||
onValueChange={(val) => setTargetApp(val)}
|
onValueChange={(val) => setTargetApp(val)}
|
||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
{integrationAuthApps.map((integrationAuthApp) => (
|
{integrationAuthApps.length > 0 ? (
|
||||||
<SelectItem value={integrationAuthApp.name} key={`github-environment-${integrationAuthApp.name}`}>
|
integrationAuthApps.map((integrationAuthApp) => (
|
||||||
{integrationAuthApp.name}
|
<SelectItem value={integrationAuthApp.name} key={`github-environment-${integrationAuthApp.name}`}>
|
||||||
|
{integrationAuthApp.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="none" key="target-app-none">
|
||||||
|
No repositories found
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
className='mt-4'
|
className='mt-4'
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,10 +11,18 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
SelectItem
|
SelectItem
|
||||||
} from '../../../components/v2';
|
} from '../../../components/v2';
|
||||||
import { useGetIntegrationAuthApps,useGetIntegrationAuthById } from '../../../hooks/api/integrationAuth';
|
import {
|
||||||
|
useGetIntegrationAuthApps,
|
||||||
|
useGetIntegrationAuthById,
|
||||||
|
useGetIntegrationAuthTeams} from '../../../hooks/api/integrationAuth';
|
||||||
import { useGetWorkspaceById } from '../../../hooks/api/workspace';
|
import { useGetWorkspaceById } from '../../../hooks/api/workspace';
|
||||||
import createIntegration from "../../api/integrations/createIntegration";
|
import createIntegration from "../../api/integrations/createIntegration";
|
||||||
|
|
||||||
|
const gitLabEntities = [
|
||||||
|
{ name: 'Individual', value: 'individual' },
|
||||||
|
{ name: 'Group', value: 'group' }
|
||||||
|
]
|
||||||
|
|
||||||
export default function GitLabCreateIntegrationPage() {
|
export default function GitLabCreateIntegrationPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -22,28 +30,52 @@ export default function GitLabCreateIntegrationPage() {
|
|||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
|
||||||
|
|
||||||
|
const [targetTeamId, setTargetTeamId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||||
|
integrationAuthId: integrationAuthId as string ?? '',
|
||||||
|
...(targetTeamId ? { teamId: targetTeamId } : {})
|
||||||
|
});
|
||||||
|
const { data: integrationAuthTeams } = useGetIntegrationAuthTeams(integrationAuthId as string ?? '');
|
||||||
|
|
||||||
|
const [targetEntity, setTargetEntity] = useState(gitLabEntities[0].value);
|
||||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||||
const [owner, setOwner] = useState<string | null>(null);
|
const [targetAppId, setTargetAppId] = useState('');
|
||||||
const [targetApp, setTargetApp] = useState('');
|
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workspace) {
|
if (workspace) {
|
||||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [workspace]);
|
}, [workspace]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO: handle case where apps can be empty
|
if (integrationAuthApps) {
|
||||||
if (integrationAuthApps) {
|
if (integrationAuthApps.length > 0) {
|
||||||
setTargetApp(integrationAuthApps[0].name);
|
setTargetAppId(integrationAuthApps[0].appId as string);
|
||||||
setOwner(integrationAuthApps[0]?.owner ?? null);
|
} else {
|
||||||
|
setTargetAppId('none');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, [integrationAuthApps]);
|
}, [integrationAuthApps]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (targetEntity === 'group' && integrationAuthTeams && integrationAuthTeams.length > 0) {
|
||||||
|
if (integrationAuthTeams) {
|
||||||
|
if (integrationAuthTeams.length > 0) {
|
||||||
|
// case: user is part of at least 1 group in GitLab
|
||||||
|
setTargetTeamId(integrationAuthTeams[0].teamId);
|
||||||
|
} else {
|
||||||
|
// case: user is not part of any groups in GitLab
|
||||||
|
setTargetTeamId('none');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (targetEntity === 'individual') {
|
||||||
|
setTargetTeamId(null);
|
||||||
|
}
|
||||||
|
}, [targetEntity, integrationAuthTeams]);
|
||||||
|
|
||||||
const handleButtonClick = async () => {
|
const handleButtonClick = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -53,11 +85,11 @@ export default function GitLabCreateIntegrationPage() {
|
|||||||
await createIntegration({
|
await createIntegration({
|
||||||
integrationAuthId: integrationAuth?._id,
|
integrationAuthId: integrationAuth?._id,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
app: targetApp,
|
app: (integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.appId === targetAppId))?.name ?? null,
|
||||||
appId: (integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.name === targetApp))?.appId ?? null,
|
appId: targetAppId,
|
||||||
sourceEnvironment: selectedSourceEnvironment,
|
sourceEnvironment: selectedSourceEnvironment,
|
||||||
targetEnvironment: null,
|
targetEnvironment: null,
|
||||||
owner,
|
owner: null,
|
||||||
path: null,
|
path: null,
|
||||||
region: null
|
region: null
|
||||||
});
|
});
|
||||||
@@ -71,7 +103,7 @@ export default function GitLabCreateIntegrationPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps && targetApp) ? (
|
return (integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps && integrationAuthTeams && targetAppId) ? (
|
||||||
<div className="h-full w-full flex justify-center items-center">
|
<div className="h-full w-full flex justify-center items-center">
|
||||||
<Card className="max-w-md p-8 rounded-md">
|
<Card className="max-w-md p-8 rounded-md">
|
||||||
<CardTitle className='text-center'>GitLab Integration</CardTitle>
|
<CardTitle className='text-center'>GitLab Integration</CardTitle>
|
||||||
@@ -85,33 +117,83 @@ export default function GitLabCreateIntegrationPage() {
|
|||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
>
|
>
|
||||||
{workspace?.environments.map((sourceEnvironment) => (
|
{workspace?.environments.map((sourceEnvironment) => (
|
||||||
<SelectItem value={sourceEnvironment.slug} key={`azure-key-vault-environment-${sourceEnvironment.slug}`}>
|
<SelectItem value={sourceEnvironment.slug} key={`source-environment-${sourceEnvironment.slug}`}>
|
||||||
{sourceEnvironment.name}
|
{sourceEnvironment.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl
|
<FormControl
|
||||||
label="GitLab Repo"
|
label="GitLab Integration Type"
|
||||||
className='mt-4'
|
className='mt-4'
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
value={targetApp}
|
value={targetEntity}
|
||||||
onValueChange={(val) => setTargetApp(val)}
|
onValueChange={(val) => setTargetEntity(val)}
|
||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
>
|
>
|
||||||
{integrationAuthApps.map((integrationAuthApp) => (
|
{gitLabEntities.map((entity) => {
|
||||||
<SelectItem value={integrationAuthApp.name} key={`gitlab-environment-${integrationAuthApp.name}`}>
|
return (
|
||||||
{integrationAuthApp.name}
|
<SelectItem value={entity.value} key={`target-entity-${entity.value}`}>
|
||||||
|
{entity.name}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
{targetEntity === 'group' && targetTeamId && (
|
||||||
|
<FormControl
|
||||||
|
label="GitLab Group"
|
||||||
|
className='mt-4'
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
value={targetTeamId}
|
||||||
|
onValueChange={(val) => setTargetTeamId(val)}
|
||||||
|
className='w-full border border-mineshaft-500'
|
||||||
|
>
|
||||||
|
{integrationAuthTeams.length > 0 ? (
|
||||||
|
integrationAuthTeams.map((integrationAuthTeam) => (
|
||||||
|
<SelectItem value={integrationAuthTeam.teamId} key={`target-team-${integrationAuthTeam.teamId}`}>
|
||||||
|
{integrationAuthTeam.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="none" key="target-team-none">
|
||||||
|
No groups found
|
||||||
|
</SelectItem>
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
<FormControl
|
||||||
|
label="GitLab Project"
|
||||||
|
className='mt-4'
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
value={targetAppId}
|
||||||
|
onValueChange={(val) => setTargetAppId(val)}
|
||||||
|
className='w-full border border-mineshaft-500'
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
|
>
|
||||||
|
{integrationAuthApps.length > 0 ? (
|
||||||
|
integrationAuthApps.map((integrationAuthApp) => (
|
||||||
|
<SelectItem value={integrationAuthApp.appId as string} key={`target-app-${integrationAuthApp.appId}`}>
|
||||||
|
{integrationAuthApp.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="none" key="target-app-none">
|
||||||
|
No projects found
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
className='mt-4'
|
className='mt-4'
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ export default function HerokuCreateIntegrationPage() {
|
|||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||||
|
integrationAuthId: integrationAuthId as string ?? ''
|
||||||
|
});
|
||||||
|
|
||||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||||
const [targetApp, setTargetApp] = useState('');
|
const [targetApp, setTargetApp] = useState('');
|
||||||
@@ -36,9 +38,12 @@ export default function HerokuCreateIntegrationPage() {
|
|||||||
}, [workspace]);
|
}, [workspace]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO: handle case where apps can be empty
|
|
||||||
if (integrationAuthApps) {
|
if (integrationAuthApps) {
|
||||||
|
if (integrationAuthApps.length > 0) {
|
||||||
setTargetApp(integrationAuthApps[0].name);
|
setTargetApp(integrationAuthApps[0].name);
|
||||||
|
} else {
|
||||||
|
setTargetApp('none');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [integrationAuthApps]);
|
}, [integrationAuthApps]);
|
||||||
|
|
||||||
@@ -83,7 +88,7 @@ export default function HerokuCreateIntegrationPage() {
|
|||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
>
|
>
|
||||||
{workspace?.environments.map((sourceEnvironment) => (
|
{workspace?.environments.map((sourceEnvironment) => (
|
||||||
<SelectItem value={sourceEnvironment.slug} key={`azure-key-vault-environment-${sourceEnvironment.slug}`}>
|
<SelectItem value={sourceEnvironment.slug} key={`source-environment-${sourceEnvironment.slug}`}>
|
||||||
{sourceEnvironment.name}
|
{sourceEnvironment.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
@@ -97,19 +102,27 @@ export default function HerokuCreateIntegrationPage() {
|
|||||||
value={targetApp}
|
value={targetApp}
|
||||||
onValueChange={(val) => setTargetApp(val)}
|
onValueChange={(val) => setTargetApp(val)}
|
||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
{integrationAuthApps.map((integrationAuthApp) => (
|
{integrationAuthApps.length > 0 ? (
|
||||||
<SelectItem value={integrationAuthApp.name} key={`heroku-environment-${integrationAuthApp.name}`}>
|
integrationAuthApps.map((integrationAuthApp) => (
|
||||||
{integrationAuthApp.name}
|
<SelectItem value={integrationAuthApp.name} key={`target-app-${integrationAuthApp.name}`}>
|
||||||
|
{integrationAuthApp.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="none" key="target-app-none">
|
||||||
|
No apps found
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
className='mt-4'
|
className='mt-4'
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ export default function NetlifyCreateIntegrationPage() {
|
|||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||||
|
integrationAuthId: integrationAuthId as string ?? ''
|
||||||
|
});
|
||||||
|
|
||||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||||
const [targetApp, setTargetApp] = useState('');
|
const [targetApp, setTargetApp] = useState('');
|
||||||
@@ -45,10 +47,13 @@ export default function NetlifyCreateIntegrationPage() {
|
|||||||
}, [workspace]);
|
}, [workspace]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO: handle case where apps can be empty
|
if (integrationAuthApps) {
|
||||||
if (integrationAuthApps) {
|
if (integrationAuthApps.length > 0) {
|
||||||
setTargetApp(integrationAuthApps[0].name);
|
setTargetApp(integrationAuthApps[0].name);
|
||||||
|
} else {
|
||||||
|
setTargetApp('none');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, [integrationAuthApps]);
|
}, [integrationAuthApps]);
|
||||||
|
|
||||||
const handleButtonClick = async () => {
|
const handleButtonClick = async () => {
|
||||||
@@ -91,7 +96,7 @@ export default function NetlifyCreateIntegrationPage() {
|
|||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
>
|
>
|
||||||
{workspace?.environments.map((sourceEnvironment) => (
|
{workspace?.environments.map((sourceEnvironment) => (
|
||||||
<SelectItem value={sourceEnvironment.slug} key={`azure-key-vault-environment-${sourceEnvironment.slug}`}>
|
<SelectItem value={sourceEnvironment.slug} key={`source-environment-${sourceEnvironment.slug}`}>
|
||||||
{sourceEnvironment.name}
|
{sourceEnvironment.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
@@ -104,12 +109,19 @@ export default function NetlifyCreateIntegrationPage() {
|
|||||||
value={targetApp}
|
value={targetApp}
|
||||||
onValueChange={(val) => setTargetApp(val)}
|
onValueChange={(val) => setTargetApp(val)}
|
||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
{integrationAuthApps.map((integrationAuthApp) => (
|
{integrationAuthApps.length > 0 ? (
|
||||||
<SelectItem value={integrationAuthApp.name} key={`heroku-environment-${integrationAuthApp.name}`}>
|
integrationAuthApps.map((integrationAuthApp) => (
|
||||||
{integrationAuthApp.name}
|
<SelectItem value={integrationAuthApp.name} key={`target-app-${integrationAuthApp.name}`}>
|
||||||
|
{integrationAuthApp.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="none" key="target-app-none">
|
||||||
|
No sites found
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl
|
<FormControl
|
||||||
@@ -121,17 +133,18 @@ export default function NetlifyCreateIntegrationPage() {
|
|||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
>
|
>
|
||||||
{netlifyEnvironments.map((netlifyEnvironment) => (
|
{netlifyEnvironments.map((netlifyEnvironment) => (
|
||||||
<SelectItem value={netlifyEnvironment.slug} key={`netlify-environment-${netlifyEnvironment.slug}`}>
|
<SelectItem value={netlifyEnvironment.slug} key={`target-environment-${netlifyEnvironment.slug}`}>
|
||||||
{netlifyEnvironment.name}
|
{netlifyEnvironment.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
className='mt-4'
|
className='mt-4'
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ export default function RenderCreateIntegrationPage() {
|
|||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||||
|
integrationAuthId: integrationAuthId as string ?? ''
|
||||||
|
});
|
||||||
|
|
||||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||||
const [targetApp, setTargetApp] = useState('');
|
const [targetApp, setTargetApp] = useState('');
|
||||||
@@ -38,7 +40,11 @@ export default function RenderCreateIntegrationPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO: handle case where apps can be empty
|
// TODO: handle case where apps can be empty
|
||||||
if (integrationAuthApps) {
|
if (integrationAuthApps) {
|
||||||
setTargetApp(integrationAuthApps[0].name);
|
if (integrationAuthApps.length > 0) {
|
||||||
|
setTargetApp(integrationAuthApps[0].name);
|
||||||
|
} else {
|
||||||
|
setTargetApp('none');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [integrationAuthApps]);
|
}, [integrationAuthApps]);
|
||||||
|
|
||||||
@@ -84,7 +90,7 @@ export default function RenderCreateIntegrationPage() {
|
|||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
>
|
>
|
||||||
{workspace?.environments.map((sourceEnvironment) => (
|
{workspace?.environments.map((sourceEnvironment) => (
|
||||||
<SelectItem value={sourceEnvironment.slug} key={`azure-key-vault-environment-${sourceEnvironment.slug}`}>
|
<SelectItem value={sourceEnvironment.slug} key={`source-environment-${sourceEnvironment.slug}`}>
|
||||||
{sourceEnvironment.name}
|
{sourceEnvironment.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
@@ -98,19 +104,27 @@ export default function RenderCreateIntegrationPage() {
|
|||||||
value={targetApp}
|
value={targetApp}
|
||||||
onValueChange={(val) => setTargetApp(val)}
|
onValueChange={(val) => setTargetApp(val)}
|
||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
{integrationAuthApps.map((integrationAuthApp) => (
|
{integrationAuthApps.length > 0 ? (
|
||||||
<SelectItem value={integrationAuthApp.name} key={`render-environment-${integrationAuthApp.name}`}>
|
integrationAuthApps.map((integrationAuthApp) => (
|
||||||
{integrationAuthApp.name}
|
<SelectItem value={integrationAuthApp.name} key={`target-app-${integrationAuthApp.name}`}>
|
||||||
|
{integrationAuthApp.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="none" key="target-app-none">
|
||||||
|
No services found
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
className='mt-4'
|
className='mt-4'
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ export default function TravisCICreateIntegrationPage() {
|
|||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||||
|
integrationAuthId: integrationAuthId as string ?? ''
|
||||||
|
});
|
||||||
|
|
||||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||||
const [targetApp, setTargetApp] = useState('');
|
const [targetApp, setTargetApp] = useState('');
|
||||||
@@ -36,10 +38,13 @@ export default function TravisCICreateIntegrationPage() {
|
|||||||
}, [workspace]);
|
}, [workspace]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO: handle case where apps can be empty
|
if (integrationAuthApps) {
|
||||||
if (integrationAuthApps) {
|
if (integrationAuthApps.length > 0) {
|
||||||
setTargetApp(integrationAuthApps[0]?.name);
|
setTargetApp(integrationAuthApps[0].name);
|
||||||
|
} else {
|
||||||
|
setTargetApp('none');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}, [integrationAuthApps]);
|
}, [integrationAuthApps]);
|
||||||
|
|
||||||
const handleButtonClick = async () => {
|
const handleButtonClick = async () => {
|
||||||
@@ -84,7 +89,7 @@ export default function TravisCICreateIntegrationPage() {
|
|||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
>
|
>
|
||||||
{workspace?.environments.map((sourceEnvironment) => (
|
{workspace?.environments.map((sourceEnvironment) => (
|
||||||
<SelectItem value={sourceEnvironment.slug} key={`azure-key-vault-environment-${sourceEnvironment.slug}`}>
|
<SelectItem value={sourceEnvironment.slug} key={`source-environment-${sourceEnvironment.slug}`}>
|
||||||
{sourceEnvironment.name}
|
{sourceEnvironment.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
@@ -98,19 +103,27 @@ export default function TravisCICreateIntegrationPage() {
|
|||||||
value={targetApp}
|
value={targetApp}
|
||||||
onValueChange={(val) => setTargetApp(val)}
|
onValueChange={(val) => setTargetApp(val)}
|
||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
{integrationAuthApps.map((integrationAuthApp) => (
|
{integrationAuthApps.length > 0 ? (
|
||||||
<SelectItem value={integrationAuthApp.name} key={`render-environment-${integrationAuthApp.name}`}>
|
integrationAuthApps.map((integrationAuthApp) => (
|
||||||
{integrationAuthApp.name}
|
<SelectItem value={integrationAuthApp.name} key={`target-environment-${integrationAuthApp.name}`}>
|
||||||
|
{integrationAuthApp.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="none" key="target-app-none">
|
||||||
|
No projects found
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
className='mt-4'
|
className='mt-4'
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -28,11 +28,13 @@ export default function VercelCreateIntegrationPage() {
|
|||||||
|
|
||||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? '');
|
||||||
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId as string ?? '');
|
||||||
const { data: integrationAuthApps } = useGetIntegrationAuthApps(integrationAuthId as string ?? '');
|
const { data: integrationAuthApps } = useGetIntegrationAuthApps({
|
||||||
|
integrationAuthId: integrationAuthId as string ?? ''
|
||||||
|
});
|
||||||
|
|
||||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState('');
|
||||||
const [targetApp, setTargetApp] = useState('');
|
const [targetApp, setTargetApp] = useState('');
|
||||||
const [targetEnvironment, setTargetEnvironemnt] = useState('');
|
const [targetEnvironment, setTargetEnvironment] = useState('');
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
@@ -43,10 +45,14 @@ export default function VercelCreateIntegrationPage() {
|
|||||||
}, [workspace]);
|
}, [workspace]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO: handle case where apps can be empty
|
|
||||||
if (integrationAuthApps) {
|
if (integrationAuthApps) {
|
||||||
|
if (integrationAuthApps.length > 0) {
|
||||||
setTargetApp(integrationAuthApps[0].name);
|
setTargetApp(integrationAuthApps[0].name);
|
||||||
setTargetEnvironemnt(vercelEnvironments[0].slug);
|
setTargetEnvironment(vercelEnvironments[0].slug);
|
||||||
|
} else {
|
||||||
|
setTargetApp('none');
|
||||||
|
setTargetEnvironment(vercelEnvironments[0].slug);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [integrationAuthApps]);
|
}, [integrationAuthApps]);
|
||||||
|
|
||||||
@@ -103,12 +109,19 @@ export default function VercelCreateIntegrationPage() {
|
|||||||
value={targetApp}
|
value={targetApp}
|
||||||
onValueChange={(val) => setTargetApp(val)}
|
onValueChange={(val) => setTargetApp(val)}
|
||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
{integrationAuthApps.map((integrationAuthApp) => (
|
{integrationAuthApps.length > 0 ? (
|
||||||
<SelectItem value={integrationAuthApp.name} key={`heroku-environment-${integrationAuthApp.name}`}>
|
integrationAuthApps.map((integrationAuthApp) => (
|
||||||
{integrationAuthApp.name}
|
<SelectItem value={integrationAuthApp.name} key={`target-app-${integrationAuthApp.name}`}>
|
||||||
|
{integrationAuthApp.name}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<SelectItem value="none" key="target-app-none">
|
||||||
|
No projects found
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl
|
<FormControl
|
||||||
@@ -116,21 +129,22 @@ export default function VercelCreateIntegrationPage() {
|
|||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
value={targetEnvironment}
|
value={targetEnvironment}
|
||||||
onValueChange={(val) => setTargetEnvironemnt(val)}
|
onValueChange={(val) => setTargetEnvironment(val)}
|
||||||
className='w-full border border-mineshaft-500'
|
className='w-full border border-mineshaft-500'
|
||||||
>
|
>
|
||||||
{vercelEnvironments.map((vercelEnvironment) => (
|
{vercelEnvironments.map((vercelEnvironment) => (
|
||||||
<SelectItem value={vercelEnvironment.slug} key={`vercel-environment-${vercelEnvironment.slug}`}>
|
<SelectItem value={vercelEnvironment.slug} key={`target-environment-${vercelEnvironment.slug}`}>
|
||||||
{vercelEnvironment.name}
|
{vercelEnvironment.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
className='mt-4'
|
className='mt-4'
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
Create Integration
|
Create Integration
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user