Compare commits
56 Commits
infisical-
...
infisical/
Author | SHA1 | Date | |
---|---|---|---|
f51e9ba8ff | |||
a255af6ad8 | |||
30da2e50b1 | |||
7f9bd93382 | |||
e81ea314e1 | |||
f19aca2904 | |||
763bdabd60 | |||
7ec708b71d | |||
3c6c1891a8 | |||
01d3d84b40 | |||
32bec03adf | |||
5b6c2e05f2 | |||
c623f572b7 | |||
48f7bd146f | |||
da6fa6d8ce | |||
cf8e597c7d | |||
43c31332e4 | |||
88fbf6f88e | |||
119730ac1a | |||
1d66dbbce3 | |||
b0991c33b0 | |||
d863dece79 | |||
96fbc6c5a0 | |||
a93631d41c | |||
2c7aac37a2 | |||
6b8d4c2fea | |||
f84235eea3 | |||
63e8ecce5b | |||
ef7bf09398 | |||
3be3867579 | |||
7f753b23f8 | |||
81827e2deb | |||
f02ea8d9b8 | |||
1609bd4652 | |||
a620f1c924 | |||
0a3e7731d9 | |||
0ca8425965 | |||
14a260b785 | |||
b6219e14f0 | |||
663c4869b9 | |||
3103075c3f | |||
e3ef826f52 | |||
215ef0bb29 | |||
9cc220e51f | |||
8fa90d94ac | |||
609204f7f6 | |||
d501130e64 | |||
45734d78c0 | |||
dd9a2dd345 | |||
80bec24219 | |||
4765dd0696 | |||
0b59a92dfb | |||
0f02ef701e | |||
1c5e80e68a | |||
c30381edbc | |||
2554ad2b3c |
@ -17,9 +17,9 @@ jobs:
|
||||
- name: 📦 Install dependencies to test all dependencies
|
||||
run: npm ci --only-production
|
||||
working-directory: backend
|
||||
- name: 🧪 Run tests
|
||||
run: npm run test:ci
|
||||
working-directory: backend
|
||||
# - name: 🧪 Run tests
|
||||
# run: npm run test:ci
|
||||
# working-directory: backend
|
||||
- name: Save commit hashes for tag
|
||||
id: commit
|
||||
uses: pr-mpt/actions-commit-hash@v2
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
INTEGRATION_BITBUCKET_API_URL,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_NORTHFLANK_API_URL,
|
||||
INTEGRATION_QOVERY_API_URL,
|
||||
INTEGRATION_RAILWAY_API_URL,
|
||||
INTEGRATION_SET,
|
||||
INTEGRATION_VERCEL_API_URL,
|
||||
@ -344,6 +345,362 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of Qovery Orgs for a specific user
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getIntegrationAuthQoveryOrgs = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { integrationAuthId }
|
||||
} = await validateRequest(reqValidator.GetIntegrationAuthQoveryOrgsV1, req);
|
||||
|
||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: new ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
);
|
||||
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_QOVERY_API_URL}/organization`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
interface QoveryOrg {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const orgs = data.results.map((a: QoveryOrg) => {
|
||||
return {
|
||||
name: a.name,
|
||||
orgId: a.id,
|
||||
};
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
orgs
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of Qovery Projects for a specific orgId
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getIntegrationAuthQoveryProjects = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { integrationAuthId },
|
||||
query: { orgId }
|
||||
} = await validateRequest(reqValidator.GetIntegrationAuthQoveryProjectsV1, req);
|
||||
|
||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: new ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
);
|
||||
|
||||
interface Project {
|
||||
name: string;
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
interface QoveryProject {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
let projects: Project[] = [];
|
||||
|
||||
if (orgId && orgId !== "") {
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_QOVERY_API_URL}/organization/${orgId}/project`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
projects = data.results.map((a: QoveryProject) => {
|
||||
return {
|
||||
name: a.name,
|
||||
projectId: a.id,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
projects
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of Qovery environments for project with id [projectId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getIntegrationAuthQoveryEnvironments = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { integrationAuthId },
|
||||
query: { projectId }
|
||||
} = await validateRequest(reqValidator.GetIntegrationAuthQoveryEnvironmentsV1, req);
|
||||
|
||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: new ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
);
|
||||
|
||||
interface Environment {
|
||||
name: string;
|
||||
environmentId: string;
|
||||
}
|
||||
|
||||
interface QoveryEnvironment {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
let environments: Environment[] = [];
|
||||
|
||||
if (projectId && projectId !== "" && projectId !== "none") { // TODO: fix
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_QOVERY_API_URL}/project/${projectId}/environment`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
environments = data.results.map((a: QoveryEnvironment) => {
|
||||
return {
|
||||
name: a.name,
|
||||
environmentId: a.id,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
environments
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of Qovery apps for environment with id [environmentId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getIntegrationAuthQoveryApps = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { integrationAuthId },
|
||||
query: { environmentId }
|
||||
} = await validateRequest(reqValidator.GetIntegrationAuthQoveryScopesV1, req);
|
||||
|
||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: new ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
);
|
||||
|
||||
interface App {
|
||||
name: string;
|
||||
appId: string;
|
||||
}
|
||||
|
||||
interface QoveryApp {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
let apps: App[] = [];
|
||||
|
||||
if (environmentId && environmentId !== "") {
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_QOVERY_API_URL}/environment/${environmentId}/application`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
apps = data.results.map((a: QoveryApp) => {
|
||||
return {
|
||||
name: a.name,
|
||||
appId: a.id,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
apps
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of Qovery containers for environment with id [environmentId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getIntegrationAuthQoveryContainers = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { integrationAuthId },
|
||||
query: { environmentId }
|
||||
} = await validateRequest(reqValidator.GetIntegrationAuthQoveryScopesV1, req);
|
||||
|
||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: new ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
);
|
||||
|
||||
interface Container {
|
||||
name: string;
|
||||
appId: string;
|
||||
}
|
||||
|
||||
interface QoveryContainer {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
let containers: Container[] = [];
|
||||
|
||||
if (environmentId && environmentId !== "") {
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_QOVERY_API_URL}/environment/${environmentId}/container`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
containers = data.results.map((a: QoveryContainer) => {
|
||||
return {
|
||||
name: a.name,
|
||||
appId: a.id,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
containers
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of Qovery jobs for environment with id [environmentId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getIntegrationAuthQoveryJobs = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { integrationAuthId },
|
||||
query: { environmentId }
|
||||
} = await validateRequest(reqValidator.GetIntegrationAuthQoveryScopesV1, req);
|
||||
|
||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: new ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
);
|
||||
|
||||
interface Job {
|
||||
name: string;
|
||||
appId: string;
|
||||
}
|
||||
|
||||
interface QoveryJob {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
let jobs: Job[] = [];
|
||||
|
||||
if (environmentId && environmentId !== "") {
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_QOVERY_API_URL}/environment/${environmentId}/job`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
jobs = data.results.map((a: QoveryJob) => {
|
||||
return {
|
||||
name: a.name,
|
||||
appId: a.id,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
jobs
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of Railway environments for Railway project with
|
||||
* id [appId]
|
||||
@ -713,11 +1070,12 @@ export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res
|
||||
*/
|
||||
export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { integrationAuthId, appId }
|
||||
params: { integrationAuthId },
|
||||
query: { appId }
|
||||
} = await validateRequest(reqValidator.GetIntegrationAuthTeamCityBuildConfigsV1, req);
|
||||
|
||||
|
||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||
const { integrationAuth } = await getIntegrationAuthAccessHelper({
|
||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: new ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
@ -749,13 +1107,13 @@ export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res:
|
||||
const {
|
||||
data: { buildType }
|
||||
} = await standardRequest.get<GetTeamCityBuildConfigsRes>(
|
||||
`${req.integrationAuth.url}/app/rest/buildTypes`,
|
||||
`${integrationAuth.url}/app/rest/buildTypes`,
|
||||
{
|
||||
params: {
|
||||
locator: `project:${appId}`
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer ${req.accessToken}`,
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
appId,
|
||||
owner,
|
||||
region,
|
||||
scope,
|
||||
targetService,
|
||||
targetServiceId,
|
||||
integrationAuthId,
|
||||
@ -42,7 +43,7 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
metadata
|
||||
}
|
||||
} = await validateRequest(reqValidator.CreateIntegrationV1, req);
|
||||
|
||||
|
||||
const integrationAuth = await IntegrationAuth.findById(integrationAuthId)
|
||||
.populate<{ workspace: IWorkspace }>("workspace")
|
||||
.select(
|
||||
@ -90,6 +91,7 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
owner,
|
||||
path,
|
||||
region,
|
||||
scope,
|
||||
secretPath,
|
||||
integration: integrationAuth.integration,
|
||||
integrationAuth: new Types.ObjectId(integrationAuthId),
|
||||
|
@ -15,6 +15,7 @@ import { IntegrationAuthMetadata } from "../models/integrationAuth/types";
|
||||
interface Update {
|
||||
workspace: string;
|
||||
integration: string;
|
||||
url?: string;
|
||||
teamId?: string;
|
||||
accountId?: string;
|
||||
metadata?: IntegrationAuthMetadata
|
||||
@ -63,6 +64,10 @@ export const handleOAuthExchangeHelper = async ({
|
||||
workspace: workspaceId,
|
||||
integration
|
||||
};
|
||||
|
||||
if (res.url) {
|
||||
update.url = res.url;
|
||||
}
|
||||
|
||||
switch (integration) {
|
||||
case INTEGRATION_VERCEL:
|
||||
@ -160,7 +165,7 @@ export const getIntegrationAuthAccessHelper = async ({
|
||||
let accessId;
|
||||
let accessToken;
|
||||
const integrationAuth = await IntegrationAuth.findById(integrationAuthId).select(
|
||||
"workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt +refreshCiphertext +refreshIV +refreshTag +accessIdCiphertext +accessIdIV +accessIdTag metadata"
|
||||
"workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt +refreshCiphertext +refreshIV +refreshTag +accessIdCiphertext +accessIdIV +accessIdTag metadata teamId url"
|
||||
);
|
||||
|
||||
if (!integrationAuth)
|
||||
|
@ -423,6 +423,7 @@ const exchangeCodeGitlab = async ({
|
||||
accessToken: res.access_token,
|
||||
refreshToken: res.refresh_token,
|
||||
accessExpiresAt,
|
||||
url
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -40,6 +40,8 @@ import {
|
||||
INTEGRATION_NETLIFY_API_URL,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_NORTHFLANK_API_URL,
|
||||
INTEGRATION_QOVERY,
|
||||
INTEGRATION_QOVERY_API_URL,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_RAILWAY_API_URL,
|
||||
INTEGRATION_RENDER,
|
||||
@ -219,6 +221,13 @@ const syncSecrets = async ({
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_QOVERY:
|
||||
await syncSecretsQovery({
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_TERRAFORM_CLOUD:
|
||||
await syncSecretsTerraformCloud({
|
||||
integration,
|
||||
@ -941,7 +950,11 @@ const syncSecretsVercel = async ({
|
||||
? {
|
||||
teamId: integrationAuth.teamId
|
||||
}
|
||||
: {})
|
||||
: {}),
|
||||
...(integration?.path
|
||||
? {
|
||||
gitBranch: integration?.path
|
||||
} : {})
|
||||
};
|
||||
|
||||
const vercelSecrets: VercelSecret[] = (
|
||||
@ -960,7 +973,7 @@ const syncSecretsVercel = async ({
|
||||
|
||||
if (
|
||||
integration.targetEnvironment === "preview" &&
|
||||
integration.path &&
|
||||
secret.gitBranch &&
|
||||
integration.path !== secret.gitBranch
|
||||
) {
|
||||
// case: secret on preview environment does not have same target git branch
|
||||
@ -969,7 +982,7 @@ const syncSecretsVercel = async ({
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
const res: { [key: string]: VercelSecret } = {};
|
||||
|
||||
for await (const vercelSecret of vercelSecrets) {
|
||||
@ -2126,6 +2139,97 @@ const syncSecretsCheckly = async ({
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync/push [secrets] to Qovery app
|
||||
* @param {Object} obj
|
||||
* @param {IIntegration} obj.integration - integration details
|
||||
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
|
||||
* @param {String} obj.accessToken - access token for Qovery integration
|
||||
*/
|
||||
const syncSecretsQovery = async ({
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
}: {
|
||||
integration: IIntegration;
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
|
||||
const getSecretsRes = (
|
||||
await standardRequest.get(`${INTEGRATION_QOVERY_API_URL}/${integration.scope}/${integration.appId}/environmentVariable`, {
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
})
|
||||
).data.results.reduce(
|
||||
(obj: any, secret: any) => ({
|
||||
...obj,
|
||||
[secret.key]: {"id": secret.id, "value": secret.value}
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
// add secrets
|
||||
for await (const key of Object.keys(secrets)) {
|
||||
if (!(key in getSecretsRes)) {
|
||||
// case: secret does not exist in qovery
|
||||
// -> add secret
|
||||
await standardRequest.post(
|
||||
`${INTEGRATION_QOVERY_API_URL}/${integration.scope}/${integration.appId}/environmentVariable`,
|
||||
{
|
||||
key,
|
||||
value: secrets[key].value
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// case: secret exists in qovery
|
||||
// -> update/set secret
|
||||
|
||||
if (secrets[key].value !== getSecretsRes[key].value) {
|
||||
await standardRequest.put(
|
||||
`${INTEGRATION_QOVERY_API_URL}/${integration.scope}/${integration.appId}/environmentVariable/${getSecretsRes[key].id}`,
|
||||
{
|
||||
key,
|
||||
value: secrets[key].value
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This one is dangerous because there might be a lot of qovery-specific secrets
|
||||
|
||||
// for await (const key of Object.keys(getSecretsRes)) {
|
||||
// if (!(key in secrets)) {
|
||||
// console.log(3)
|
||||
// // delete secret
|
||||
// await standardRequest.delete(`${INTEGRATION_QOVERY_API_URL}/application/${integration.appId}/environmentVariable/${getSecretsRes[key].id}`, {
|
||||
// headers: {
|
||||
// Authorization: `Token ${accessToken}`,
|
||||
// Accept: "application/json",
|
||||
// "X-Qovery-Account": integration.appId
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync/push [secrets] to Terraform Cloud project with id [integration.appId]
|
||||
* @param {Object} obj
|
||||
|
@ -18,6 +18,7 @@ import {
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_QOVERY,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_SUPABASE,
|
||||
@ -45,6 +46,7 @@ export interface IIntegration {
|
||||
targetServiceId: string;
|
||||
path: string;
|
||||
region: string;
|
||||
scope: string;
|
||||
secretPath: string;
|
||||
integration:
|
||||
| "azure-key-vault"
|
||||
@ -63,6 +65,7 @@ export interface IIntegration {
|
||||
| "travisci"
|
||||
| "supabase"
|
||||
| "checkly"
|
||||
| "qovery"
|
||||
| "terraform-cloud"
|
||||
| "teamcity"
|
||||
| "hashicorp-vault"
|
||||
@ -119,11 +122,13 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
},
|
||||
targetService: {
|
||||
// railway-specific service
|
||||
// qovery-specific project
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
targetServiceId: {
|
||||
// railway-specific service
|
||||
// qovery specific project
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
@ -143,6 +148,11 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
scope: {
|
||||
// qovery-specific scope
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
integration: {
|
||||
type: String,
|
||||
enum: [
|
||||
@ -162,6 +172,7 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_QOVERY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
|
@ -1,6 +1,3 @@
|
||||
|
||||
// TODO: in the future separate metadata
|
||||
// into distinct types by integration
|
||||
export type Metadata = {
|
||||
secretPrefix?: string;
|
||||
secretSuffix?: string;
|
||||
|
@ -52,6 +52,7 @@ import {
|
||||
| "aws-parameter-store"
|
||||
| "aws-secret-manager"
|
||||
| "checkly"
|
||||
| "qovery"
|
||||
| "cloudflare-pages"
|
||||
| "codefresh"
|
||||
| "digital-ocean-app-platform"
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Document, Schema, Types, model } from "mongoose";
|
||||
import {
|
||||
import {
|
||||
ALGORITHM_AES_256_GCM,
|
||||
ENCODING_SCHEME_BASE64,
|
||||
ENCODING_SCHEME_UTF8,
|
||||
@ -53,4 +53,6 @@ const secretBlindIndexDataSchema = new Schema<ISecretBlindIndexData>(
|
||||
}
|
||||
);
|
||||
|
||||
secretBlindIndexDataSchema.index({ workspace: 1 });
|
||||
|
||||
export const SecretBlindIndexData = model<ISecretBlindIndexData>("SecretBlindIndexData", secretBlindIndexDataSchema);
|
@ -60,6 +60,54 @@ router.get(
|
||||
integrationAuthController.getIntegrationAuthVercelBranches
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:integrationAuthId/qovery/orgs",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
integrationAuthController.getIntegrationAuthQoveryOrgs
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:integrationAuthId/qovery/projects",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
integrationAuthController.getIntegrationAuthQoveryProjects
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:integrationAuthId/qovery/environments",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
integrationAuthController.getIntegrationAuthQoveryEnvironments
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:integrationAuthId/qovery/apps",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
integrationAuthController.getIntegrationAuthQoveryApps
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:integrationAuthId/qovery/containers",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
integrationAuthController.getIntegrationAuthQoveryContainers
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:integrationAuthId/qovery/jobs",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
integrationAuthController.getIntegrationAuthQoveryJobs
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:integrationAuthId/railway/environments",
|
||||
requireAuth({
|
||||
|
@ -21,8 +21,9 @@ import {
|
||||
} from "../config";
|
||||
import { getSSOConfigHelper } from "../ee/helpers/organizations";
|
||||
import { InternalServerError, OrganizationNotFoundError } from "./errors";
|
||||
import { ACCEPTED, INVITED, MEMBER } from "../variables";
|
||||
import { ACCEPTED, INTEGRATION_GITHUB_API_URL, INVITED, MEMBER } from "../variables";
|
||||
import { getSiteURL } from "../config";
|
||||
import { standardRequest } from "../config/request";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const GoogleStrategy = require("passport-google-oauth20").Strategy;
|
||||
@ -143,10 +144,28 @@ const initializePassport = async () => {
|
||||
passReqToCallback: true,
|
||||
clientID: clientIdGitHubLogin,
|
||||
clientSecret: clientSecretGitHubLogin,
|
||||
callbackURL: "/api/v1/sso/github"
|
||||
callbackURL: "/api/v1/sso/github",
|
||||
scope: ["user:email"]
|
||||
},
|
||||
async (req : express.Request, accessToken : any, refreshToken : any, profile : any, done : any) => {
|
||||
const email = profile.emails[0].value;
|
||||
interface GitHubEmail {
|
||||
email: string;
|
||||
primary: boolean;
|
||||
verified: boolean;
|
||||
visibility: null | string;
|
||||
}
|
||||
|
||||
const { data }: { data: GitHubEmail[] } = await standardRequest.get(
|
||||
`${INTEGRATION_GITHUB_API_URL}/user/emails`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const primaryEmail = data.filter((gitHubEmail: GitHubEmail) => gitHubEmail.primary)[0];
|
||||
const email = primaryEmail.email;
|
||||
|
||||
let user = await User.findOne({
|
||||
email
|
||||
|
@ -819,3 +819,18 @@ export const backfillPermission = async () => {
|
||||
console.info("Could not acquire lock for script [backfillPermission], skipping");
|
||||
}
|
||||
};
|
||||
|
||||
export const migrateRoleFromOwnerToAdmin = async () => {
|
||||
await MembershipOrg.updateMany(
|
||||
{
|
||||
role: OWNER
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
role: ADMIN
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
console.info("Backfill: Finished converting owner role to member");
|
||||
}
|
@ -18,7 +18,8 @@ import {
|
||||
backfillServiceToken,
|
||||
backfillServiceTokenMultiScope,
|
||||
backfillTrustedIps,
|
||||
backfillUserAuthMethods
|
||||
backfillUserAuthMethods,
|
||||
migrateRoleFromOwnerToAdmin
|
||||
} from "./backfillData";
|
||||
import {
|
||||
reencryptBotOrgKeys,
|
||||
@ -84,7 +85,8 @@ export const setup = async () => {
|
||||
await backfillServiceTokenMultiScope();
|
||||
await backfillTrustedIps();
|
||||
await backfillUserAuthMethods();
|
||||
await backfillPermission();
|
||||
// await backfillPermission();
|
||||
await migrateRoleFromOwnerToAdmin()
|
||||
|
||||
// re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY
|
||||
// to base64 256-bit ROOT_ENCRYPTION_KEY
|
||||
|
@ -76,13 +76,14 @@ export const CreateIntegrationV1 = z.object({
|
||||
owner: z.string().trim().optional(),
|
||||
path: z.string().trim().optional(),
|
||||
region: z.string().trim().optional(),
|
||||
scope: z.string().trim().optional(),
|
||||
metadata: z.object({
|
||||
secretPrefix: z.string().optional(),
|
||||
secretSuffix: z.string().optional(),
|
||||
secretGCPLabel: z.object({
|
||||
labelName: z.string(),
|
||||
labelValue: z.string()
|
||||
}).optional()
|
||||
}).optional(),
|
||||
}).optional()
|
||||
})
|
||||
});
|
||||
|
@ -113,6 +113,39 @@ export const GetIntegrationAuthVercelBranchesV1 = z.object({
|
||||
})
|
||||
});
|
||||
|
||||
export const GetIntegrationAuthQoveryOrgsV1 = z.object({
|
||||
params: z.object({
|
||||
integrationAuthId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetIntegrationAuthQoveryProjectsV1 = z.object({
|
||||
params: z.object({
|
||||
integrationAuthId: z.string().trim()
|
||||
}),
|
||||
query: z.object({
|
||||
orgId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetIntegrationAuthQoveryEnvironmentsV1 = z.object({
|
||||
params: z.object({
|
||||
integrationAuthId: z.string().trim()
|
||||
}),
|
||||
query: z.object({
|
||||
projectId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetIntegrationAuthQoveryScopesV1 = z.object({
|
||||
params: z.object({
|
||||
integrationAuthId: z.string().trim()
|
||||
}),
|
||||
query: z.object({
|
||||
environmentId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetIntegrationAuthRailwayEnvironmentsV1 = z.object({
|
||||
params: z.object({
|
||||
integrationAuthId: z.string().trim()
|
||||
@ -153,9 +186,11 @@ export const DeleteIntegrationAuthV1 = z.object({
|
||||
});
|
||||
|
||||
export const GetIntegrationAuthTeamCityBuildConfigsV1 = z.object({
|
||||
params:z.object({
|
||||
appId:z.string().trim(),
|
||||
params: z.object({
|
||||
integrationAuthId:z.string().trim()
|
||||
}),
|
||||
query: z.object({
|
||||
appId:z.string().trim()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -270,7 +270,7 @@ export const ToggleAutoCapitalizationV2 = z.object({
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
autoCapitalization: z.string().trim()
|
||||
autoCapitalization: z.boolean()
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -28,6 +28,7 @@ export const INTEGRATION_TRAVISCI = "travisci";
|
||||
export const INTEGRATION_TEAMCITY = "teamcity";
|
||||
export const INTEGRATION_SUPABASE = "supabase";
|
||||
export const INTEGRATION_CHECKLY = "checkly";
|
||||
export const INTEGRATION_QOVERY = "qovery";
|
||||
export const INTEGRATION_TERRAFORM_CLOUD = "terraform-cloud";
|
||||
export const INTEGRATION_HASHICORP_VAULT = "hashicorp-vault";
|
||||
export const INTEGRATION_CLOUDFLARE_PAGES = "cloudflare-pages";
|
||||
@ -53,6 +54,7 @@ export const INTEGRATION_SET = new Set([
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_QOVERY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
@ -83,6 +85,7 @@ export const INTEGRATION_BITBUCKET_TOKEN_URL = "https://bitbucket.org/site/oauth
|
||||
export const INTEGRATION_GCP_API_URL = "https://cloudresourcemanager.googleapis.com";
|
||||
export const INTEGRATION_HEROKU_API_URL = "https://api.heroku.com";
|
||||
export const INTEGRATION_GITLAB_API_URL = "https://gitlab.com/api";
|
||||
export const INTEGRATION_GITHUB_API_URL = "https://api.github.com";
|
||||
export const INTEGRATION_VERCEL_API_URL = "https://api.vercel.com";
|
||||
export const INTEGRATION_NETLIFY_API_URL = "https://api.netlify.com";
|
||||
export const INTEGRATION_RENDER_API_URL = "https://api.render.com";
|
||||
@ -93,6 +96,7 @@ export const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com";
|
||||
export const INTEGRATION_SUPABASE_API_URL = "https://api.supabase.com";
|
||||
export const INTEGRATION_LARAVELFORGE_API_URL = "https://forge.laravel.com";
|
||||
export const INTEGRATION_CHECKLY_API_URL = "https://api.checklyhq.com";
|
||||
export const INTEGRATION_QOVERY_API_URL = "https://api.qovery.com";
|
||||
export const INTEGRATION_TERRAFORM_CLOUD_API_URL = "https://app.terraform.io";
|
||||
export const INTEGRATION_CLOUDFLARE_PAGES_API_URL = "https://api.cloudflare.com";
|
||||
export const INTEGRATION_BITBUCKET_API_URL = "https://api.bitbucket.org";
|
||||
@ -272,6 +276,15 @@ export const getIntegrationOptions = async () => {
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Qovery",
|
||||
slug: "qovery",
|
||||
image: "Qovery.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "HashiCorp Vault",
|
||||
slug: "hashicorp-vault",
|
||||
|
@ -1,6 +1,6 @@
|
||||
module github.com/Infisical/infisical-merge
|
||||
|
||||
go 1.19
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/lipgloss v0.5.0
|
||||
|
@ -198,7 +198,7 @@ var secretsSetCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
// Key and value from argument
|
||||
key := strings.ToUpper(splitKeyValueFromArg[0])
|
||||
key := splitKeyValueFromArg[0]
|
||||
value := splitKeyValueFromArg[1]
|
||||
|
||||
hashedKey := fmt.Sprintf("%x", sha256.Sum256([]byte(key)))
|
||||
@ -417,7 +417,7 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
|
||||
secretsMap := getSecretsByKeys(secrets)
|
||||
|
||||
for _, secretKeyFromArg := range args {
|
||||
if value, ok := secretsMap[strings.ToUpper(secretKeyFromArg)]; ok {
|
||||
if value, ok := secretsMap[secretKeyFromArg]; ok {
|
||||
requestedSecrets = append(requestedSecrets, value)
|
||||
} else {
|
||||
requestedSecrets = append(requestedSecrets, models.SingleEnvironmentVariable{
|
||||
@ -625,7 +625,7 @@ func generateExampleEnv(cmd *cobra.Command, args []string) {
|
||||
func CenterString(s string, numStars int) string {
|
||||
stars := strings.Repeat("*", numStars)
|
||||
padding := (numStars - len(s)) / 2
|
||||
cenetredTextWithStar := stars[:padding] + " " + strings.ToUpper(s) + " " + stars[padding:]
|
||||
cenetredTextWithStar := stars[:padding] + " " + s + " " + stars[padding:]
|
||||
|
||||
hashes := strings.Repeat("#", len(cenetredTextWithStar)+2)
|
||||
return fmt.Sprintf("%s \n# %s \n%s", hashes, cenetredTextWithStar, hashes)
|
||||
|
@ -4,6 +4,12 @@ title: "Changelog"
|
||||
|
||||
The changelog below reflects new product developments and updates on a monthly basis.
|
||||
|
||||
## September
|
||||
|
||||
- Released an update to access controls; every user role now clearly defines and enforces a certain set of conditions across Infisical.
|
||||
- Updated UI/UX for integrations.
|
||||
- Added a native integration with [Qovery](https://infisical.com/docs/integrations/cloud/qovery).
|
||||
|
||||
## August 2023
|
||||
|
||||
- Release Audit Logs V2.
|
||||
|
@ -1,10 +1,9 @@
|
||||
---
|
||||
title: "Reference/Import Secrets"
|
||||
title: "Reference and Import Secrets"
|
||||
description: "How to use reference secrets in Infisical"
|
||||
---
|
||||
|
||||
Secret referencing is a powerful feature that allows you to create a secret whose value is linked to one or more other secrets.
|
||||
This is useful when you need to use a single secret's value across multiple other secrets.
|
||||
Secret referencing is a powerful feature that allows you to values of other secrets. This way, you just need to update the secret value once for it to be propagated to all the references.
|
||||
|
||||
Consider a scenario where you have a database password. In order to utilize this password, you may need to incorporate it into a database connection string.
|
||||
With secret referencing, you can easily construct these more intricate secrets by directly referencing the base secret.
|
||||
@ -34,7 +33,7 @@ For instance, to access a secret 'A' composed of secrets 'B' and 'C' from differ
|
||||
When using [service tokens](./token) to fetch referenced secrets, ensure the service token has read access to all referenced environments and folders.
|
||||
Without proper permissions, the final secret value may be incomplete.
|
||||
|
||||
## Import entire folders
|
||||
## Import entire folders/environments
|
||||
|
||||
While secret referencing effectively minimizes duplication, there might be instances where you need to import or replicate an entire folder's secrets into another. This can be achieved using the 'Import' feature.
|
||||
|
||||
@ -50,3 +49,5 @@ Additionally, any secrets you define directly in your environment will override
|
||||
You can modify the order of folders to control overrides using the `Change Order` drag handle.
|
||||
|
||||

|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/o11bMU0pXRs?si=dCprt3xLWPrSOJxy" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
|
@ -3,6 +3,13 @@ title: "Azure SAML"
|
||||
description: "Configure Azure SAML for Infisical SSO"
|
||||
---
|
||||
|
||||
<Info>
|
||||
Azure SAML SSO feature is a paid feature.
|
||||
|
||||
If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical,
|
||||
then you should contact team@infisical.com to purchase an enterprise license to use it.
|
||||
</Info>
|
||||
|
||||
1. In Infisical, head over to your organization Settings > Authentication > SAML SSO Configuration and select **Set up SAML SSO**.
|
||||
Next, copy the **Reply URL (Assertion Consumer Service URL)** and **Identifier (Entity ID)** to use when configuring the Azure SAML application.
|
||||
|
||||
|
37
docs/documentation/platform/sso/github.mdx
Normal file
@ -0,0 +1,37 @@
|
||||
---
|
||||
title: "GitHub SSO"
|
||||
description: "Configure GitHub SSO for Infisical"
|
||||
---
|
||||
|
||||
Using GitHub SSO on a self-hosted instance of Infisical requires configuring an OAuth2 application in GitHub and registering your instance with it.
|
||||
|
||||
## Create an OAuth application in GitHub
|
||||
|
||||
Navigate to your user Settings > Developer settings > OAuth Apps to create a new GitHub OAuth application.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
Create the OAuth application. As part of the form, set the **Homepage URL** to your self-hosted domain `https://your-domain.com`
|
||||
and the **Authorization callback URL** to `https://your-domain.com/api/v1/sso/github`.
|
||||
|
||||

|
||||
|
||||
<Note>
|
||||
If you have a GitHub organization, you can create an OAuth application under it
|
||||
in your organization Settings > Developer settings > OAuth Apps > New Org OAuth App.
|
||||
</Note>
|
||||
|
||||
## Add your OAuth application credentials to Infisical
|
||||
|
||||
Obtain the **Client ID** and generate a new **Client Secret** for your GitHub OAuth application.
|
||||
|
||||

|
||||
|
||||
Back in your Infisical instance, add two new environment variables for the credentials of your GitHub OAuth application:
|
||||
|
||||
- `CLIENT_ID_GITHUB_LOGIN`: The **Client ID** of your GitHub OAuth application.
|
||||
- `CLIENT_SECRET_GITHUB_LOGIN`: The **Client Secret** of your GitHub OAuth application.
|
||||
|
||||
Once added, restart your Infisical instance and log in with GitHub.
|
30
docs/documentation/platform/sso/google.mdx
Normal file
@ -0,0 +1,30 @@
|
||||
---
|
||||
title: "Google SSO"
|
||||
description: "Configure Google SSO for Infisical"
|
||||
---
|
||||
|
||||
Using Google SSO on a self-hosted instance of Infisical requires configuring an OAuth2 application in GCP and registering your instance with it.
|
||||
|
||||
## Create an OAuth2 application in GCP
|
||||
|
||||
Navigate to your project API & Services > Credentials to create a new OAuth2 application.
|
||||
|
||||

|
||||

|
||||
|
||||
Create the application. As part of the form, add to **Authorized redirect URIs**: `https://your-domain.com/api/v1/sso/google`.
|
||||
|
||||

|
||||
|
||||
## Add your OAuth2 application credentials to Infisical
|
||||
|
||||
Obtain the **Client ID** and **Client Secret** for your GCP OAuth2 application.
|
||||
|
||||

|
||||
|
||||
Back in your Infisical instance, add two new environment variables for the credentials of your GCP OAuth2 application:
|
||||
|
||||
- `CLIENT_ID_GOOGLE_LOGIN`: The **Client ID** of your GCP OAuth2 application.
|
||||
- `CLIENT_SECRET_GOOGLE_LOGIN`: The **Client Secret** of your GCP OAuth2 application.
|
||||
|
||||
Once added, restart your Infisical instance and log in with Google
|
@ -3,6 +3,13 @@ title: "JumpCloud SAML"
|
||||
description: "Configure JumpCloud SAML for Infisical SSO"
|
||||
---
|
||||
|
||||
<Info>
|
||||
JumpCloud SAML SSO feature is a paid feature.
|
||||
|
||||
If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical,
|
||||
then you should contact team@infisical.com to purchase an enterprise license to use it.
|
||||
</Info>
|
||||
|
||||
1. In Infisical, head over to your organization Settings > Authentication > SAML SSO Configuration and select **Set up SAML SSO**.
|
||||
Next, copy the **ACS URL** and **SP Entity ID** to use when configuring the JumpCloud SAML application.
|
||||
|
||||
|
@ -3,6 +3,13 @@ title: "Okta SAML"
|
||||
description: "Configure Okta SAML 2.0 for Infisical SSO"
|
||||
---
|
||||
|
||||
<Info>
|
||||
Okta SAML SSO feature is a paid feature.
|
||||
|
||||
If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical,
|
||||
then you should contact team@infisical.com to purchase an enterprise license to use it.
|
||||
</Info>
|
||||
|
||||
1. In Infisical, head over to your organization Settings > Authentication > SAML SSO Configuration and select **Set up SAML SSO**.
|
||||
Next, copy the **Single sign-on URL** and **Audience URI (SP Entity ID)** to use when configuring the Okta SAML 2.0 application.
|
||||
|
||||
|
@ -4,9 +4,11 @@ description: "Log in to Infisical via SSO protocols"
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Infisical currently has confirmed support for SAML SSO authentication with
|
||||
Okta, Azure AD, and JumpCloud. We're expanding support for other IdPs in the
|
||||
coming months, so stay tuned and feel free to request a IdP at this
|
||||
Infisical offers Google SSO and GitHub SSO for free across both Infisical Cloud and Infisical Self-hosted.
|
||||
|
||||
Infisical also offers SAML SSO authentication but as paid features that can be unlocked on Infisical Cloud's **Pro** tier
|
||||
or via enterprise license on self-hosted instances of Infisical. On this front, we currently support Okta, Azure AD, and JumpCloud and
|
||||
are expanding support for other IdPs in the coming months; stay tuned and feel free to request a IdP at this
|
||||
[issue](https://github.com/Infisical/infisical/issues/442).
|
||||
</Warning>
|
||||
|
||||
@ -15,6 +17,8 @@ You can configure your organization in Infisical to have members authenticate wi
|
||||
To note, configuring SSO retains the end-to-end encrypted architecture of Infisical because we decouple the **authentication** and **decryption** steps. In all login with SSO implementations,
|
||||
your IdP cannot and will not have access to the decryption key needed to decrypt your secrets.
|
||||
|
||||
- [Google SSO](/documentation/platform/sso/google)
|
||||
- [GitHub SSO](/documentation/platform/sso/github)
|
||||
- [Okta SAML](/documentation/platform/sso/okta)
|
||||
- [Azure SAML](/documentation/platform/sso/azure)
|
||||
- [JumpCloud SAML](/documentation/platform/sso/jumpcloud)
|
||||
- [JumpCloud SAML](/documentation/platform/sso/jumpcloud)
|
BIN
docs/images/integrations/qovery/integrations-qovery-auth.png
Normal file
After Width: | Height: | Size: 464 KiB |
BIN
docs/images/integrations/qovery/integrations-qovery-create-1.png
Normal file
After Width: | Height: | Size: 2.7 MiB |
BIN
docs/images/integrations/qovery/integrations-qovery-create-2.png
Normal file
After Width: | Height: | Size: 2.8 MiB |
BIN
docs/images/integrations/qovery/integrations-qovery-token.png
Normal file
After Width: | Height: | Size: 971 KiB |
BIN
docs/images/integrations/qovery/integrations-qovery.png
Normal file
After Width: | Height: | Size: 1.6 MiB |
BIN
docs/images/sso/github/credentials.png
Normal file
After Width: | Height: | Size: 740 KiB |
BIN
docs/images/sso/github/dev-settings.png
Normal file
After Width: | Height: | Size: 856 KiB |
BIN
docs/images/sso/github/new-app-form.png
Normal file
After Width: | Height: | Size: 772 KiB |
BIN
docs/images/sso/github/new-app.png
Normal file
After Width: | Height: | Size: 602 KiB |
BIN
docs/images/sso/github/settings.png
Normal file
After Width: | Height: | Size: 1.5 MiB |
BIN
docs/images/sso/google/api-services.png
Normal file
After Width: | Height: | Size: 370 KiB |
BIN
docs/images/sso/google/credentials.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
docs/images/sso/google/new-app-form.png
Normal file
After Width: | Height: | Size: 1.3 MiB |
BIN
docs/images/sso/google/new-app.png
Normal file
After Width: | Height: | Size: 940 KiB |
43
docs/integrations/cloud/qovery.mdx
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
title: "Qovery"
|
||||
description: "How to sync secrets from Infisical to Qovery"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
|
||||
## Navigate to your project's integrations tab
|
||||
|
||||

|
||||
|
||||
## Enter your Qovery API Token
|
||||
|
||||
Obtain a Qovery API Token in Settings > API Token.
|
||||
|
||||

|
||||
|
||||
Press on the Qovery tile and input your Qovery API Token to grant Infisical access to your Qovery account.
|
||||
|
||||

|
||||
|
||||
<Info>
|
||||
If this is your project's first cloud integration, then you'll have to grant
|
||||
Infisical access to your project's environment variables. Although this step
|
||||
breaks E2EE, it is necessary for Infisical to sync the environment variables to
|
||||
the cloud platform.
|
||||
</Info>
|
||||
|
||||
## Start integration
|
||||
|
||||
Select which Infisical environment secrets you want to sync to Qovery and press create integration to start syncing secrets.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
<Note>
|
||||
Infisical supports syncing secrets to various Qovery scopes including applications, jobs, or containers.
|
||||
</Note>
|
||||
|
||||

|
34
docs/integrations/frameworks/bun.mdx
Normal file
@ -0,0 +1,34 @@
|
||||
---
|
||||
title: "Bun"
|
||||
description: "How to use Infisical to inject environment variables and secrets into a Bun app."
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- [Install the CLI](/cli/overview)
|
||||
|
||||
## Initialize Infisical for your [Bun](https://bun.sh)
|
||||
|
||||
```bash
|
||||
# navigate to the root of your of your project
|
||||
cd /path/to/project
|
||||
|
||||
# then initialize infisical
|
||||
infisical init
|
||||
```
|
||||
|
||||
## Start your application as usual but with Infisical
|
||||
|
||||
```bash
|
||||
infisical run -- <your application start command>
|
||||
|
||||
# Example
|
||||
infisical run -- bun run dev
|
||||
```
|
||||
|
||||
<Info>
|
||||
Bun environment variables can be called as either `Bun.env.SECRET` or `process.env.SECRET`. We also recommend you check this more in-depth [guide to environment variables in Bun](https://infisical.com/blog/bun-environment-variables).
|
||||
</Info>
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/m1NNVuiP07M?si=wJtdQdhQ2ylGeRI8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
@ -27,6 +27,7 @@ Missing an integration? [Throw in a request](https://github.com/Infisical/infisi
|
||||
| [Northflank](/integrations/cloud/northflank) | Cloud | Available |
|
||||
| [Cloudflare Pages](/integrations/cloud/cloudflare-pages) | Cloud | Available |
|
||||
| [Checkly](/integrations/cloud/checkly) | Cloud | Available |
|
||||
| [Qovery](/integrations/cloud/qovery) | Cloud | Available |
|
||||
| [HashiCorp Vault](/integrations/cloud/hashicorp-vault) | Cloud | Available |
|
||||
| [AWS Parameter Store](/integrations/cloud/aws-parameter-store) | Cloud | Available |
|
||||
| [AWS Secrets Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
|
||||
|
@ -126,6 +126,8 @@
|
||||
"group": "SSO",
|
||||
"pages": [
|
||||
"documentation/platform/sso/overview",
|
||||
"documentation/platform/sso/google",
|
||||
"documentation/platform/sso/github",
|
||||
"documentation/platform/sso/okta",
|
||||
"documentation/platform/sso/azure",
|
||||
"documentation/platform/sso/jumpcloud"
|
||||
@ -150,6 +152,7 @@
|
||||
"self-hosting/configuration/envars",
|
||||
"self-hosting/configuration/email",
|
||||
"self-hosting/configuration/redis",
|
||||
"self-hosting/configuration/sso",
|
||||
"self-hosting/faq"
|
||||
]
|
||||
},
|
||||
@ -235,6 +238,7 @@
|
||||
"integrations/cloud/teamcity",
|
||||
"integrations/cloud/cloudflare-pages",
|
||||
"integrations/cloud/checkly",
|
||||
"integrations/cloud/qovery",
|
||||
"integrations/cloud/hashicorp-vault",
|
||||
"integrations/cloud/azure-key-vault",
|
||||
"integrations/cloud/gcp-secret-manager",
|
||||
|
20
docs/self-hosting/configuration/sso.mdx
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
title: "Configure SSO"
|
||||
description: "How to configure SSO when self-hosting Infisical."
|
||||
---
|
||||
|
||||
<Warning>
|
||||
Infisical offers Google SSO and GitHub SSO for free.
|
||||
|
||||
Infisical also offers SAML SSO authentication but as paid features that can be unlocked via enterprise license; if this is of interest, please contact team@infisical.com.
|
||||
On this front, we currently support Okta, Azure AD, and JumpCloud and are expanding support for other IdPs in the coming months; stay tuned and feel free to request a IdP at this
|
||||
[issue](https://github.com/Infisical/infisical/issues/442).
|
||||
</Warning>
|
||||
|
||||
You can view specific documentation for how to set up each SSO authentication method below:
|
||||
|
||||
- [Google SSO](/documentation/platform/sso/google)
|
||||
- [GitHub SSO](/documentation/platform/sso/github)
|
||||
- [Okta SAML](/documentation/platform/sso/okta)
|
||||
- [Azure SAML](/documentation/platform/sso/azure)
|
||||
- [JumpCloud SAML](/documentation/platform/sso/jumpcloud)
|
28
frontend/package-lock.json
generated
@ -72,7 +72,6 @@
|
||||
"react": "^17.0.2",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-code-input": "^3.10.1",
|
||||
"react-contenteditable": "^3.3.7",
|
||||
"react-day-picker": "^8.8.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
@ -13340,7 +13339,8 @@
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-diff": {
|
||||
"version": "1.3.0",
|
||||
@ -19594,18 +19594,6 @@
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-contenteditable": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/react-contenteditable/-/react-contenteditable-3.3.7.tgz",
|
||||
"integrity": "sha512-GA9NbC0DkDdpN3iGvib/OMHWTJzDX2cfkgy5Tt98JJAbA3kLnyrNbBIpsSpPpq7T8d3scD39DHP+j8mAM7BIfQ==",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"prop-types": "^15.7.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-day-picker": {
|
||||
"version": "8.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.8.0.tgz",
|
||||
@ -33272,7 +33260,8 @@
|
||||
"fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
},
|
||||
"fast-diff": {
|
||||
"version": "1.3.0",
|
||||
@ -37807,15 +37796,6 @@
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"react-contenteditable": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/react-contenteditable/-/react-contenteditable-3.3.7.tgz",
|
||||
"integrity": "sha512-GA9NbC0DkDdpN3iGvib/OMHWTJzDX2cfkgy5Tt98JJAbA3kLnyrNbBIpsSpPpq7T8d3scD39DHP+j8mAM7BIfQ==",
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"prop-types": "^15.7.1"
|
||||
}
|
||||
},
|
||||
"react-day-picker": {
|
||||
"version": "8.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.8.0.tgz",
|
||||
|
@ -80,7 +80,6 @@
|
||||
"react": "^17.0.2",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-code-input": "^3.10.1",
|
||||
"react-contenteditable": "^3.3.7",
|
||||
"react-day-picker": "^8.8.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
|
@ -19,6 +19,7 @@ const integrationSlugNameMapping: Mapping = {
|
||||
"travisci": "TravisCI",
|
||||
"supabase": "Supabase",
|
||||
"checkly": "Checkly",
|
||||
"qovery": "Qovery",
|
||||
"terraform-cloud": "Terraform Cloud",
|
||||
"teamcity": "TeamCity",
|
||||
"hashicorp-vault": "Vault",
|
||||
|
BIN
frontend/public/images/integrations/Qovery.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
46
frontend/src/components/basic/popups/GlobPatternExamples.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import { useState } from "react";
|
||||
import { faInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { Tooltip } from "@app/components/v2/Tooltip";
|
||||
|
||||
const GlobPatternExamples = () => {
|
||||
const [showTip, setShowTip] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
isOpen={showTip}
|
||||
onOpenChange={setShowTip}
|
||||
content={
|
||||
<div>
|
||||
<h4 className="mb-2">Here are some examples of glob patterns:</h4>
|
||||
<div className="ol-listStyleType">
|
||||
<li>
|
||||
<code className="text-primary">/</code> - Matches all files and directories in the
|
||||
current directory
|
||||
</li>
|
||||
<li>
|
||||
<code className="text-primary">**/*</code> - Matches all files and directories in the
|
||||
current directory and its subdirectories
|
||||
</li>
|
||||
<li>
|
||||
<code className="text-primary">{"/{dir1,dir2}"}</code> - Matches all files and
|
||||
directories in dir1 and dir2
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
position="right"
|
||||
className="text-xs"
|
||||
>
|
||||
<div
|
||||
className="flex h-3.5 w-3.5 items-center justify-center rounded-full border border-[1px] border-mineshaft-300"
|
||||
onMouseEnter={() => setShowTip(true)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faInfo} className="h-2 w-2" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobPatternExamples;
|
30
frontend/src/components/v2/Accordion/Accordion.stories.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "./Accordion";
|
||||
|
||||
const meta: Meta<typeof Accordion> = {
|
||||
title: "Components/Accordion",
|
||||
component: Accordion,
|
||||
tags: ["v2"],
|
||||
argTypes: {}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Accordion>;
|
||||
|
||||
export const Basic: Story = {
|
||||
render: (args) => (
|
||||
<div className="flex justify-center w-full">
|
||||
<Accordion {...args}>
|
||||
<AccordionItem value="section-1">
|
||||
<AccordionTrigger>Section 1</AccordionTrigger>
|
||||
<AccordionContent>Description of Section 1</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="section-2">
|
||||
<AccordionTrigger>Section 2</AccordionTrigger>
|
||||
<AccordionContent>Description of Section 2</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
)
|
||||
};
|
@ -8,7 +8,7 @@ export const AccordionItem = forwardRef<HTMLDivElement, AccordionPrimitive.Accor
|
||||
({ children, className, ...props }, forwardedRef) => (
|
||||
<AccordionPrimitive.Item
|
||||
className={twMerge(
|
||||
"focus-within:shadow-mauve12 mt-px overflow-hidden first:mt-0 first:rounded-t last:rounded-b focus-within:relative focus-within:z-10 focus-within:shadow-[0_0_0_2px]",
|
||||
"mt-px overflow-hidden first:mt-0 data-[state=open]:border-l data-[state=open]:border-primary transition-all border-transparent",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@ -27,7 +27,7 @@ export const AccordionTrigger = forwardRef<
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
className={twMerge(
|
||||
"text-violet11 shadow-mauve6 hover:bg-mauve2 group flex h-[45px] flex-1 cursor-default items-center justify-between bg-white px-5 text-[15px] leading-none shadow-[0_1px_0] outline-none",
|
||||
"py-2 px-4 group data-[state=open]:text-primary h-11 hover:text-primary flex flex-1 outline-none items-center justify-between ",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@ -36,7 +36,7 @@ export const AccordionTrigger = forwardRef<
|
||||
{children}
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronDown}
|
||||
className="text-violet10 ease-[cubic-bezier(0.87,_0,_0.13,_1)] transition-transform duration-300 group-data-[state=open]:rotate-180"
|
||||
className="ease-[cubic-bezier(0.87,_0,_0.13,_1)] transition-transform duration-300 group-data-[state=open]:rotate-180 text-sm"
|
||||
aria-hidden
|
||||
/>
|
||||
</AccordionPrimitive.Trigger>
|
||||
@ -51,13 +51,13 @@ export const AccordionContent = forwardRef<
|
||||
>(({ children, className, ...props }, forwardedRef) => (
|
||||
<AccordionPrimitive.Content
|
||||
className={twMerge(
|
||||
"text-mauve11 bg-mauve2 data-[state=open]:animate-slideDown data-[state=closed]:animate-slideUp overflow-hidden text-[15px]",
|
||||
"data-[state=open]:animate-slideDown data-[state=closed]:animate-slideUp overflow-hidden",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={forwardedRef}
|
||||
>
|
||||
<div className="py-[15px] px-5">{children}</div>
|
||||
<div className="text-sm py-2 px-4">{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
));
|
||||
|
||||
@ -68,10 +68,7 @@ export const Accordion = ({
|
||||
children,
|
||||
...props
|
||||
}: AccordionPrimitive.AccordionSingleProps | AccordionPrimitive.AccordionMultipleProps) => (
|
||||
<AccordionPrimitive.Root
|
||||
className="bg-mauve6 w-[300px] rounded-md shadow-[0_2px_10px] shadow-black/5"
|
||||
{...props}
|
||||
>
|
||||
<AccordionPrimitive.Root {...props} className={twMerge("w-80 text-bunker-300", props.className)}>
|
||||
{children}
|
||||
</AccordionPrimitive.Root>
|
||||
);
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* eslint-disable react/no-danger */
|
||||
import { forwardRef, HTMLAttributes } from "react";
|
||||
import ContentEditable from "react-contenteditable";
|
||||
import sanitizeHtml, { DisallowedTagsModes } from "sanitize-html";
|
||||
|
||||
import { useToggle } from "@app/hooks";
|
||||
@ -35,58 +34,52 @@ const syntaxHighlight = (content?: string | null, isVisible?: boolean) => {
|
||||
`<span class="ph-no-capture text-yellow">${<span class="ph-no-capture text-yello-200/80">${b}</span>}</span>`
|
||||
);
|
||||
|
||||
return newContent;
|
||||
// akhilmhdh: Dont remove this br. I am still clueless how this works but weirdly enough
|
||||
// when break is added a line break works properly
|
||||
return `${newContent}<br/>`;
|
||||
};
|
||||
|
||||
type Props = Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "onBlur"> & {
|
||||
type Props = HTMLAttributes<HTMLTextAreaElement> & {
|
||||
value?: string | null;
|
||||
isVisible?: boolean;
|
||||
isDisabled?: boolean;
|
||||
onChange?: (val: string) => void;
|
||||
onBlur?: () => void;
|
||||
};
|
||||
|
||||
export const SecretInput = forwardRef<HTMLDivElement, Props>(
|
||||
({ value, isVisible, onChange, onBlur, isDisabled, ...props }, ref) => {
|
||||
const commonClassName = "font-mono text-sm caret-white border-none outline-none w-full break-all";
|
||||
|
||||
export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
({ value, isVisible, onBlur, isDisabled, onFocus, ...props }, ref) => {
|
||||
const [isSecretFocused, setIsSecretFocused] = useToggle();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="thin-scrollbar relative overflow-y-auto overflow-x-hidden"
|
||||
style={{ maxHeight: `${21 * 7}px` }}
|
||||
>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: syntaxHighlight(value, isVisible || isSecretFocused)
|
||||
}}
|
||||
className={`absolute top-0 left-0 z-0 h-full w-full inline-block text-ellipsis whitespace-pre-wrap break-all ${
|
||||
!value && value !== "" && "italic text-red-600/70"
|
||||
}`}
|
||||
ref={ref}
|
||||
/>
|
||||
<ContentEditable
|
||||
className="relative z-10 h-full w-full text-ellipsis inline-block whitespace-pre-wrap break-all text-transparent caret-white outline-none"
|
||||
role="textbox"
|
||||
onChange={(evt) => {
|
||||
if (onChange) onChange(evt.currentTarget.innerText.trim());
|
||||
}}
|
||||
onFocus={() => setIsSecretFocused.on()}
|
||||
disabled={isDisabled}
|
||||
spellCheck={false}
|
||||
onBlur={() => {
|
||||
if (onBlur) onBlur();
|
||||
setIsSecretFocused.off();
|
||||
}}
|
||||
html={
|
||||
isVisible || isSecretFocused
|
||||
? sanitizeHtml(
|
||||
value?.replaceAll("<", "<").replaceAll(">", ">") || "",
|
||||
sanitizeConf
|
||||
)
|
||||
: syntaxHighlight(value, false)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
<div className="overflow-auto w-full" style={{ maxHeight: `${21 * 7}px` }}>
|
||||
<div className="relative overflow-hidden">
|
||||
<pre aria-hidden className="m-0 ">
|
||||
<code className={`inline-block w-full ${commonClassName}`}>
|
||||
<span
|
||||
style={{ whiteSpace: "break-spaces" }}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: syntaxHighlight(value, isVisible || isSecretFocused) ?? ""
|
||||
}}
|
||||
/>
|
||||
</code>
|
||||
</pre>
|
||||
<textarea
|
||||
style={{ whiteSpace: "break-spaces" }}
|
||||
aria-label="secret value"
|
||||
ref={ref}
|
||||
className={`absolute inset-0 block h-full resize-none overflow-hidden bg-transparent text-transparent no-scrollbar focus:border-0 ${commonClassName}`}
|
||||
onFocus={() => setIsSecretFocused.on()}
|
||||
disabled={isDisabled}
|
||||
spellCheck={false}
|
||||
onBlur={(evt) => {
|
||||
onBlur?.(evt);
|
||||
setIsSecretFocused.off();
|
||||
}}
|
||||
value={value || ""}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ export const Tr = ({
|
||||
}: TrProps): JSX.Element => (
|
||||
<tr
|
||||
className={twMerge(
|
||||
"cursor-default border-b last:border-b-0 border-solid border-mineshaft-600",
|
||||
"cursor-default border-b border-solid border-mineshaft-600 last:border-b-0",
|
||||
isHoverable && "hover:bg-mineshaft-600",
|
||||
isSelectable && "cursor-pointer",
|
||||
className
|
||||
|
@ -45,8 +45,7 @@ export const withPermission = <T extends {}, J extends TOrgPermission>(
|
||||
<div>
|
||||
<div className="text-4xl font-medium mb-2">Access Restricted</div>
|
||||
<div className="text-sm">
|
||||
Your role has limited permissions, please <br /> You do not have permission to this
|
||||
page.
|
||||
Your role has limited permissions, please <br /> contact your admin to gain access
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,6 +9,8 @@ import {
|
||||
Environment,
|
||||
IntegrationAuth,
|
||||
NorthflankSecretGroup,
|
||||
Org,
|
||||
Project,
|
||||
Service,
|
||||
Team,
|
||||
TeamCityBuildConfig} from "./types";
|
||||
@ -27,6 +29,31 @@ const integrationAuthKeys = {
|
||||
integrationAuthId: string;
|
||||
appId: string;
|
||||
}) => [{ integrationAuthId, appId }, "integrationAuthVercelBranches"] as const,
|
||||
getIntegrationAuthQoveryOrgs: (integrationAuthId: string) =>
|
||||
[{ integrationAuthId }, "integrationAuthQoveryOrgs"] as const,
|
||||
getIntegrationAuthQoveryProjects: ({
|
||||
integrationAuthId,
|
||||
orgId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
orgId: string;
|
||||
}) => [{ integrationAuthId, orgId }, "integrationAuthQoveryProjects"] as const,
|
||||
getIntegrationAuthQoveryEnvironments: ({
|
||||
integrationAuthId,
|
||||
projectId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
projectId: string;
|
||||
}) => [{ integrationAuthId, projectId }, "integrationAuthQoveryEnvironments"] as const,
|
||||
getIntegrationAuthQoveryScopes: ({
|
||||
integrationAuthId,
|
||||
environmentId,
|
||||
scope
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
environmentId: string;
|
||||
scope: "job" | "application" | "container";
|
||||
}) => [{ integrationAuthId, environmentId, scope }, "integrationAuthQoveryScopes"] as const,
|
||||
getIntegrationAuthRailwayEnvironments: ({
|
||||
integrationAuthId,
|
||||
appId
|
||||
@ -120,6 +147,121 @@ const fetchIntegrationAuthVercelBranches = async ({
|
||||
return branches;
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthQoveryOrgs = async (integrationAuthId: string) => {
|
||||
const {
|
||||
data: { orgs }
|
||||
} = await apiRequest.get<{ orgs: Org[] }>(
|
||||
`/api/v1/integration-auth/${integrationAuthId}/qovery/orgs`
|
||||
);
|
||||
|
||||
return orgs;
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthQoveryProjects = async ({
|
||||
integrationAuthId,
|
||||
orgId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
orgId: string;
|
||||
}) => {
|
||||
if (orgId === "none") return [];
|
||||
|
||||
const {
|
||||
data: { projects }
|
||||
} = await apiRequest.get<{ projects: Project[] }>(
|
||||
`/api/v1/integration-auth/${integrationAuthId}/qovery/projects`,
|
||||
{
|
||||
params: {
|
||||
orgId
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return projects;
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthQoveryEnvironments = async ({
|
||||
integrationAuthId,
|
||||
projectId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
projectId: string;
|
||||
}) => {
|
||||
if (projectId === "none") return [];
|
||||
|
||||
const {
|
||||
data: { environments }
|
||||
} = await apiRequest.get<{ environments: Environment[] }>(
|
||||
`/api/v1/integration-auth/${integrationAuthId}/qovery/environments`,
|
||||
{
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return environments;
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthQoveryScopes = async ({
|
||||
integrationAuthId,
|
||||
environmentId,
|
||||
scope
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
environmentId: string;
|
||||
scope: "job" | "application" | "container";
|
||||
}) => {
|
||||
if (environmentId === "none") return [];
|
||||
|
||||
if (scope === "application") {
|
||||
const {
|
||||
data: { apps }
|
||||
} = await apiRequest.get<{ apps: App[] }>(
|
||||
`/api/v1/integration-auth/${integrationAuthId}/qovery/apps`,
|
||||
{
|
||||
params: {
|
||||
environmentId
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return apps;
|
||||
}
|
||||
|
||||
if (scope === "container") {
|
||||
const {
|
||||
data: { containers }
|
||||
} = await apiRequest.get<{ containers: App[] }>(
|
||||
`/api/v1/integration-auth/${integrationAuthId}/qovery/containers`,
|
||||
{
|
||||
params: {
|
||||
environmentId
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return containers;
|
||||
}
|
||||
|
||||
if (scope === "job") {
|
||||
const {
|
||||
data: { jobs }
|
||||
} = await apiRequest.get<{ jobs: App[] }>(
|
||||
`/api/v1/integration-auth/${integrationAuthId}/qovery/jobs`,
|
||||
{
|
||||
params: {
|
||||
environmentId
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthRailwayEnvironments = async ({
|
||||
integrationAuthId,
|
||||
appId
|
||||
@ -197,6 +339,8 @@ const fetchIntegrationAuthTeamCityBuildConfigs = async ({
|
||||
integrationAuthId: string;
|
||||
appId: string;
|
||||
}) => {
|
||||
if (appId === "") return [];
|
||||
|
||||
const {
|
||||
data: { buildConfigs }
|
||||
} = await apiRequest.get<{ buildConfigs: TeamCityBuildConfig[] }>(
|
||||
@ -269,6 +413,82 @@ export const useGetIntegrationAuthVercelBranches = ({
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIntegrationAuthQoveryOrgs = (integrationAuthId: string) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthQoveryOrgs(integrationAuthId),
|
||||
queryFn: () =>
|
||||
fetchIntegrationAuthQoveryOrgs(integrationAuthId),
|
||||
enabled: true
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIntegrationAuthQoveryProjects = ({
|
||||
integrationAuthId,
|
||||
orgId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
orgId: string;
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthQoveryProjects({
|
||||
integrationAuthId,
|
||||
orgId
|
||||
}),
|
||||
queryFn: () =>
|
||||
fetchIntegrationAuthQoveryProjects({
|
||||
integrationAuthId,
|
||||
orgId
|
||||
}),
|
||||
enabled: true
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIntegrationAuthQoveryEnvironments = ({
|
||||
integrationAuthId,
|
||||
projectId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
projectId: string;
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthQoveryEnvironments({
|
||||
integrationAuthId,
|
||||
projectId
|
||||
}),
|
||||
queryFn: () =>
|
||||
fetchIntegrationAuthQoveryEnvironments({
|
||||
integrationAuthId,
|
||||
projectId
|
||||
}),
|
||||
enabled: true
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIntegrationAuthQoveryScopes = ({
|
||||
integrationAuthId,
|
||||
environmentId,
|
||||
scope
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
environmentId: string;
|
||||
scope: "job" | "application" | "container";
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthQoveryScopes({
|
||||
integrationAuthId,
|
||||
environmentId,
|
||||
scope
|
||||
}),
|
||||
queryFn: () =>
|
||||
fetchIntegrationAuthQoveryScopes({
|
||||
integrationAuthId,
|
||||
environmentId,
|
||||
scope
|
||||
}),
|
||||
enabled: true
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIntegrationAuthRailwayEnvironments = ({
|
||||
integrationAuthId,
|
||||
appId
|
||||
|
@ -26,6 +26,21 @@ export type Environment = {
|
||||
environmentId: string;
|
||||
};
|
||||
|
||||
export type Container = {
|
||||
name: string;
|
||||
containerId: string;
|
||||
};
|
||||
|
||||
export type Org = {
|
||||
name: string;
|
||||
orgId: string;
|
||||
};
|
||||
|
||||
export type Project = {
|
||||
name: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type Service = {
|
||||
name: string;
|
||||
serviceId: string;
|
||||
|
@ -40,6 +40,7 @@ export const useCreateIntegration = () => {
|
||||
owner,
|
||||
path,
|
||||
region,
|
||||
scope,
|
||||
secretPath,
|
||||
metadata
|
||||
}: {
|
||||
@ -56,6 +57,7 @@ export const useCreateIntegration = () => {
|
||||
owner?: string;
|
||||
path?: string;
|
||||
region?: string;
|
||||
scope?: string;
|
||||
metadata?: {
|
||||
secretPrefix?: string;
|
||||
secretSuffix?: string;
|
||||
@ -73,6 +75,7 @@ export const useCreateIntegration = () => {
|
||||
targetServiceId,
|
||||
owner,
|
||||
path,
|
||||
scope,
|
||||
region,
|
||||
secretPath,
|
||||
metadata
|
||||
|
@ -32,5 +32,9 @@ export type TIntegration = {
|
||||
__v: number;
|
||||
metadata?: {
|
||||
secretSuffix?: string;
|
||||
scope: string;
|
||||
org: string;
|
||||
project: string;
|
||||
environment: string;
|
||||
}
|
||||
};
|
||||
|
@ -487,7 +487,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
{/* <Link href={`/project/${currentWorkspace?._id}/audit-logs`} passHref>
|
||||
<Link href={`/project/${currentWorkspace?._id}/audit-logs`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={
|
||||
@ -498,7 +498,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
Audit Logs
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link> */}
|
||||
</Link>
|
||||
<Link href={`/project/${currentWorkspace?._id}/settings`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
|
@ -84,6 +84,7 @@ export default function GitLabCreateIntegrationPage() {
|
||||
const selectedSourceEnvironment = watch("selectedSourceEnvironment");
|
||||
const targetEntity = watch("targetEntity");
|
||||
const targetTeamId = watch("targetTeamId");
|
||||
const targetAppIdValue = watch("targetAppId");
|
||||
|
||||
const { mutateAsync } = useCreateIntegration();
|
||||
|
||||
@ -440,6 +441,7 @@ export default function GitLabCreateIntegrationPage() {
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isLoading}
|
||||
isDisabled={targetAppIdValue === "none"}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
|
106
frontend/src/pages/integrations/qovery/authorize.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
import { useState } from "react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import {
|
||||
useSaveIntegrationAccessToken
|
||||
} from "@app/hooks/api";
|
||||
|
||||
import { Button, Card, CardTitle, FormControl, Input } from "../../../components/v2";
|
||||
|
||||
export default function QoveryCreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
const { mutateAsync } = useSaveIntegrationAccessToken();
|
||||
|
||||
const [accessToken, setAccessToken] = useState("");
|
||||
const [accessTokenErrorText, setAccessTokenErrorText] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
setAccessTokenErrorText("");
|
||||
if (accessToken.length === 0) {
|
||||
setAccessTokenErrorText("Access token cannot be blank");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const integrationAuth = await mutateAsync({
|
||||
workspaceId: localStorage.getItem("projectData.id"),
|
||||
integration: "qovery",
|
||||
accessToken
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
router.push(`/integrations/qovery/create?integrationAuthId=${integrationAuth._id}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Head>
|
||||
<title>Authorize Qovery Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<Card className="max-w-lg rounded-md border border-mineshaft-600 mb-12">
|
||||
<CardTitle
|
||||
className="text-left px-6 text-xl"
|
||||
subTitle="After adding your API key, you will be prompted to set up an integration for a particular Infisical project and environment."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center pb-0.5">
|
||||
<Image
|
||||
src="/images/integrations/Qovery.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="Qovery logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">Qovery Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/qovery" passHref>
|
||||
<a target="_blank" rel="noopener noreferrer">
|
||||
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||
Docs
|
||||
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</CardTitle>
|
||||
<FormControl
|
||||
label="Qovery API token"
|
||||
errorText={accessTokenErrorText}
|
||||
isError={accessTokenErrorText !== "" ?? false}
|
||||
className="mx-6"
|
||||
>
|
||||
<Input
|
||||
placeholder=""
|
||||
value={accessToken}
|
||||
onChange={(e) => setAccessToken(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||
isFullWidth={false}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Connect to Qovery
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
QoveryCreateIntegrationPage.requireAuth = true;
|
409
frontend/src/pages/integrations/qovery/create.tsx
Normal file
@ -0,0 +1,409 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { faArrowUpRightFromSquare, faBookOpen, faBugs } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { motion } from "framer-motion";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem,
|
||||
Tab,
|
||||
TabList,
|
||||
TabPanel,
|
||||
Tabs
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
useCreateIntegration
|
||||
} from "@app/hooks/api";
|
||||
import {
|
||||
useGetIntegrationAuthQoveryEnvironments,
|
||||
useGetIntegrationAuthQoveryOrgs,
|
||||
useGetIntegrationAuthQoveryProjects,
|
||||
useGetIntegrationAuthQoveryScopes
|
||||
} from "@app/hooks/api/integrationAuth/queries";
|
||||
|
||||
import { useGetIntegrationAuthById } from "../../../hooks/api/integrationAuth";
|
||||
import { useGetWorkspaceById } from "../../../hooks/api/workspace";
|
||||
|
||||
const qoveryScopes = [
|
||||
{ label: "Application", value: "application" },
|
||||
{ label: "Container", value: "container" },
|
||||
{ label: "Job", value: "job" }
|
||||
];
|
||||
|
||||
enum TabSections {
|
||||
InfisicalSettings = "infisicalSettings",
|
||||
QoverySettings = "qoverySettings"
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow the logic:
|
||||
* - Access token
|
||||
* -> Get organizations (orgId) - select an organization
|
||||
* -> Get projects belonging to the organization - select project
|
||||
* -> Get environments belonging to the project - select environmnet
|
||||
* -> Get qovery scopes / application (this is the application to sync to)
|
||||
*
|
||||
* Do we even need to get organizations?
|
||||
*/
|
||||
|
||||
export default function QoveryCreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
const { mutateAsync } = useCreateIntegration();
|
||||
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
|
||||
const [scope, setScope] = useState("application");
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||
const [secretPath, setSecretPath] = useState("/");
|
||||
|
||||
const { data: integrationAuthOrgs } = useGetIntegrationAuthQoveryOrgs((integrationAuthId as string) ?? "");
|
||||
const [targetOrgId, setTargetOrgId] = useState("");
|
||||
|
||||
const { data: integrationAuthProjects } = useGetIntegrationAuthQoveryProjects({
|
||||
integrationAuthId: (integrationAuthId as string) ?? "",
|
||||
orgId: targetOrgId
|
||||
});
|
||||
const [targetProjectId, setTargetProjectId] = useState("");
|
||||
|
||||
const { data: integrationAuthEnvironments } = useGetIntegrationAuthQoveryEnvironments({
|
||||
integrationAuthId: (integrationAuthId as string) ?? "",
|
||||
projectId: targetProjectId
|
||||
});
|
||||
const [targetEnvironmentId, setTargetEnvironmentId] = useState("");
|
||||
|
||||
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthQoveryScopes({
|
||||
integrationAuthId: (integrationAuthId as string) ?? "",
|
||||
environmentId: targetEnvironmentId,
|
||||
scope: (scope as ("job" | "application" | "container"))
|
||||
});
|
||||
const [targetAppId, setTargetAppId] = useState("");
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetAppId(String(integrationAuthApps[0].appId));
|
||||
} else {
|
||||
setTargetAppId("none");
|
||||
}
|
||||
}
|
||||
}, [integrationAuthApps]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthOrgs) {
|
||||
if (integrationAuthOrgs.length > 0) {
|
||||
setTargetOrgId(String(integrationAuthOrgs[0].orgId));
|
||||
} else {
|
||||
setTargetOrgId("none");
|
||||
}
|
||||
}
|
||||
}, [integrationAuthOrgs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthProjects) {
|
||||
if (integrationAuthProjects.length > 0) {
|
||||
setTargetProjectId(String(integrationAuthProjects[0].projectId));
|
||||
} else {
|
||||
setTargetProjectId("none");
|
||||
}
|
||||
}
|
||||
}, [integrationAuthProjects]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthEnvironments) {
|
||||
if (integrationAuthEnvironments.length > 0) {
|
||||
setTargetEnvironmentId(String(integrationAuthEnvironments[0].environmentId));
|
||||
} else {
|
||||
setTargetEnvironmentId("none");
|
||||
}
|
||||
}
|
||||
}, [integrationAuthEnvironments]);
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
if (!integrationAuth?._id) return;
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const targetOrg = integrationAuthOrgs?.find((integrationAuthOrg) => integrationAuthOrg.orgId === targetOrgId)?.name;
|
||||
const targetProject = integrationAuthProjects?.find((integrationAuthProject) => integrationAuthProject.projectId === targetProjectId)?.name;
|
||||
const targetEnvironment = integrationAuthEnvironments?.find((integrationAuthEnvironment) => integrationAuthEnvironment.environmentId === targetEnvironmentId)?.name;
|
||||
const targetApp = integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.appId === targetAppId)?.name;
|
||||
|
||||
await mutateAsync({
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: targetApp,
|
||||
appId: targetAppId,
|
||||
scope,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment,
|
||||
targetEnvironmentId,
|
||||
targetService: targetProject,
|
||||
targetServiceId: targetProjectId,
|
||||
owner: targetOrg,
|
||||
secretPath
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return integrationAuth &&
|
||||
workspace &&
|
||||
selectedSourceEnvironment ? (
|
||||
<div className="flex flex-col h-full w-full items-center justify-center bg-gradient-to-tr from-mineshaft-900 to-bunker-900">
|
||||
<Head>
|
||||
<title>Set Up Qovery Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<Card className="max-w-lg rounded-md border border-mineshaft-600 p-0">
|
||||
<CardTitle
|
||||
className="text-left px-6 text-xl"
|
||||
subTitle="Choose which environment in Infisical you want to sync to Checkly environment variables."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center pb-0.5">
|
||||
<Image
|
||||
src="/images/integrations/Qovery.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="Qovery logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">Qovery Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/qovery" passHref>
|
||||
<a target="_blank" rel="noopener noreferrer">
|
||||
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||
Docs
|
||||
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</CardTitle>
|
||||
<Tabs defaultValue={TabSections.InfisicalSettings} className="px-6">
|
||||
<TabList>
|
||||
<div className="flex flex-row border-b border-mineshaft-600 w-full">
|
||||
<Tab value={TabSections.InfisicalSettings}>Infisical Settings</Tab>
|
||||
<Tab value={TabSections.QoverySettings}>Qovery Settings</Tab>
|
||||
</div>
|
||||
</TabList>
|
||||
<TabPanel value={TabSections.InfisicalSettings}>
|
||||
<motion.div
|
||||
key="panel-1"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<FormControl label="Infisical Project Environment">
|
||||
<Select
|
||||
value={selectedSourceEnvironment}
|
||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem
|
||||
value={sourceEnvironment.slug}
|
||||
key={`source-environment-${sourceEnvironment.slug}`}
|
||||
>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path" className="pb-[14.68rem]">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
<TabPanel value={TabSections.QoverySettings}>
|
||||
<motion.div
|
||||
key="panel-1"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: -30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<FormControl label="Qovery Scope">
|
||||
<Select
|
||||
value={scope}
|
||||
onValueChange={(val) => setScope(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
>
|
||||
{qoveryScopes.map((qoveryScope) => (
|
||||
<SelectItem
|
||||
value={qoveryScope.value}
|
||||
key={`target-app-${qoveryScope.value}`}
|
||||
>
|
||||
{qoveryScope.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
{integrationAuthOrgs && <FormControl label="Qovery Organization">
|
||||
<Select
|
||||
value={targetOrgId}
|
||||
onValueChange={(val) => setTargetOrgId(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={integrationAuthOrgs.length === 0}
|
||||
>
|
||||
{integrationAuthOrgs.length > 0 ? (
|
||||
integrationAuthOrgs.map((integrationAuthOrg) => (
|
||||
<SelectItem
|
||||
value={integrationAuthOrg.orgId}
|
||||
key={`target-app-${integrationAuthOrg.orgId}`}
|
||||
>
|
||||
{integrationAuthOrg.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No organizaztions found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>}
|
||||
{integrationAuthProjects && <FormControl label="Qovery Project">
|
||||
<Select
|
||||
value={targetProjectId}
|
||||
onValueChange={(val) => setTargetProjectId(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={integrationAuthProjects.length === 0}
|
||||
>
|
||||
{integrationAuthProjects.length > 0 ? (
|
||||
integrationAuthProjects.map((integrationAuthProject) => (
|
||||
<SelectItem
|
||||
value={integrationAuthProject.projectId}
|
||||
key={`target-app-${integrationAuthProject.projectId}`}
|
||||
>
|
||||
{integrationAuthProject.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No projects found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>}
|
||||
{integrationAuthEnvironments && <FormControl label="Qovery Environment">
|
||||
<Select
|
||||
value={targetEnvironmentId}
|
||||
onValueChange={(val) => setTargetEnvironmentId(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={integrationAuthEnvironments.length === 0}
|
||||
>
|
||||
{integrationAuthEnvironments.length > 0 ? (
|
||||
integrationAuthEnvironments.map((integrationAuthEnvironment) => (
|
||||
<SelectItem
|
||||
value={integrationAuthEnvironment.environmentId}
|
||||
key={`target-app-${integrationAuthEnvironment.environmentId}`}
|
||||
>
|
||||
{integrationAuthEnvironment.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No environments found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>}
|
||||
{(scope && integrationAuthApps) && <FormControl label={`Qovery ${scope.charAt(0).toUpperCase() + scope.slice(1)}`}>
|
||||
<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 {scope}s found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>}
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
variant="outline_bg"
|
||||
className="mb-6 ml-auto mr-6"
|
||||
isFullWidth={false}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
</Card>
|
||||
{/* <div className="border-t border-mineshaft-800 w-full max-w-md mt-6"/>
|
||||
<div className="flex flex-col bg-mineshaft-800 border border-mineshaft-600 w-full p-4 max-w-lg mt-6 rounded-md">
|
||||
<div className="flex flex-row items-center"><FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-200 text-xl"/> <span className="ml-3 text-md text-mineshaft-100">Pro Tips</span></div>
|
||||
<span className="text-mineshaft-300 text-sm mt-4">After creating an integration, your secrets will start syncing immediately. This might cause an unexpected override of current secrets in Qovery with secrets from Infisical.</span>
|
||||
</div> */}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center items-center w-full h-full">
|
||||
<Head>
|
||||
<title>Set Up Qovery Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
{isIntegrationAuthAppsLoading ? <img src="/images/loading/loading.gif" height={70} width={120} alt="infisical loading indicator" /> : <div className="max-w-md h-max p-6 border border-mineshaft-600 rounded-md bg-mineshaft-800 text-mineshaft-200 flex flex-col text-center">
|
||||
<FontAwesomeIcon icon={faBugs} className="text-6xl my-2 inlineli"/>
|
||||
<p>
|
||||
Something went wrong. Please contact <a
|
||||
className="inline underline underline-offset-4 decoration-primary-500 opacity-80 hover:opacity-100 text-mineshaft-100 duration-200 cursor-pointer"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="mailto:support@infisical.com"
|
||||
>
|
||||
support@infisical.com
|
||||
</a> if the issue persists.
|
||||
</p>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
QoveryCreateIntegrationPage.requireAuth = true;
|
@ -49,7 +49,7 @@ export default function TeamCityCreateIntegrationPage() {
|
||||
integrationAuthId: (integrationAuthId as string) ?? "",
|
||||
appId: targetAppId
|
||||
});
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
|
@ -15,6 +15,7 @@ import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import * as yup from "yup";
|
||||
|
||||
import GlobPatternExamples from "@app/components/basic/popups/GlobPatternExamples";
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
// TODO:(akhilmhdh) convert all the util functions like this into a lib folder grouped by functionality
|
||||
@ -222,7 +223,7 @@ export const SecretDropzone = ({
|
||||
onDragOver={handleDrag}
|
||||
onDrop={handleDrop}
|
||||
className={twMerge(
|
||||
"relative mx-0.5 mb-4 mt-4 flex cursor-pointer items-center justify-center rounded-md bg-mineshaft-900 py-4 text-sm px-2 text-mineshaft-200 opacity-60 outline-dashed outline-2 outline-chicago-600 duration-200 hover:opacity-100",
|
||||
"relative mx-0.5 mb-4 mt-4 flex cursor-pointer items-center justify-center rounded-md bg-mineshaft-900 py-4 px-2 text-sm text-mineshaft-200 opacity-60 outline-dashed outline-2 outline-chicago-600 duration-200 hover:opacity-100",
|
||||
isDragActive && "opacity-100",
|
||||
!isSmaller && "w-full max-w-3xl flex-col space-y-4 py-20",
|
||||
isLoading && "bg-bunker-800"
|
||||
@ -234,7 +235,7 @@ export const SecretDropzone = ({
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||
<div className="flex items-center justify-cente flex-col space-y-2">
|
||||
<div className="justify-cente flex flex-col items-center space-y-2">
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faUpload} size={isSmaller ? "2x" : "5x"} />
|
||||
</div>
|
||||
@ -324,7 +325,12 @@ export const SecretDropzone = ({
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<FormControl label="Secret Path" className="flex-grow" isRequired>
|
||||
<FormControl
|
||||
label="Secret Path"
|
||||
className="flex-grow"
|
||||
isRequired
|
||||
icon={<GlobPatternExamples />}
|
||||
>
|
||||
<Input
|
||||
{...register("secretPath")}
|
||||
placeholder="Provide a path, default is /"
|
||||
@ -334,7 +340,7 @@ export const SecretDropzone = ({
|
||||
<div className="border-t border-mineshaft-600 pt-4">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<div>Secrets</div>
|
||||
<div className="w-1/2 flex items-center space-x-2">
|
||||
<div className="flex w-1/2 items-center space-x-2">
|
||||
<Input
|
||||
placeholder="Search for secret"
|
||||
value={searchFilter}
|
||||
@ -367,7 +373,7 @@ export const SecretDropzone = ({
|
||||
{!isSecretsLoading && !secrets?.secrets?.length && (
|
||||
<EmptyState title="No secrets found" icon={faKey} />
|
||||
)}
|
||||
<div className="grid grid-cols-2 gap-4 max-h-64 overflow-auto thin-scrollbar ">
|
||||
<div className="thin-scrollbar grid max-h-64 grid-cols-2 gap-4 overflow-auto ">
|
||||
{isSecretsLoading &&
|
||||
Array.apply(0, Array(2)).map((_x, i) => (
|
||||
<Skeleton
|
||||
|
@ -270,7 +270,7 @@ export const SecretInputRow = memo(
|
||||
className="flex w-full flex-grow flex-row border-r border-none border-red"
|
||||
style={{ padding: "0.5rem 0 0.5rem 1rem" }}
|
||||
>
|
||||
<div className="w-full">
|
||||
<div className="w-full flex items-center">
|
||||
{isOverridden ? (
|
||||
<Controller
|
||||
control={control}
|
||||
|
@ -87,6 +87,9 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) =>
|
||||
case "checkly":
|
||||
link = `${window.location.origin}/integrations/checkly/authorize`;
|
||||
break;
|
||||
case "qovery":
|
||||
link = `${window.location.origin}/integrations/qovery/authorize`;
|
||||
break;
|
||||
case "railway":
|
||||
link = `${window.location.origin}/integrations/railway/authorize`;
|
||||
break;
|
||||
|
@ -96,8 +96,30 @@ export const IntegrationsSection = ({
|
||||
{integrationSlugNameMapping[integration.integration]}
|
||||
</div>
|
||||
</div>
|
||||
{(integration.integration === "qovery") && (
|
||||
<div className="flex flex-row">
|
||||
<div className="ml-2 flex flex-col">
|
||||
<FormLabel label="Org" />
|
||||
<div className="rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{integration?.owner || "-"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-2 flex flex-col">
|
||||
<FormLabel label="Project" />
|
||||
<div className="rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{integration?.targetService || "-"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-2 flex flex-col">
|
||||
<FormLabel label="Env" />
|
||||
<div className="rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{integration?.targetEnvironment || "-"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="ml-2 flex flex-col">
|
||||
<FormLabel label="App" />
|
||||
<FormLabel label={integration?.metadata?.scope || "App"} />
|
||||
<div className="min-w-[8rem] rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{integration.integration === "hashicorp-vault"
|
||||
? `${integration.app} - path: ${integration.path}`
|
||||
@ -131,7 +153,7 @@ export const IntegrationsSection = ({
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={ProjectPermissionSub.Integrations}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
{(isAllowed: boolean) => (
|
||||
<div className="ml-2 opacity-80 duration-200 hover:opacity-100">
|
||||
<Tooltip content="Remove Integration">
|
||||
<IconButton
|
||||
|
@ -5,6 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { motion } from "framer-motion";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import GlobPatternExamples from "@app/components/basic/popups/GlobPatternExamples";
|
||||
import {
|
||||
Checkbox,
|
||||
FormControl,
|
||||
@ -98,7 +99,7 @@ export const MultiEnvProjectPermission = ({
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"px-10 py-6 bg-mineshaft-800 rounded-md",
|
||||
"rounded-md bg-mineshaft-800 px-10 py-6",
|
||||
(selectedPermissionCategory !== Permission.NoAccess || isCustom) &&
|
||||
"border-l-2 border-primary-600"
|
||||
)}
|
||||
@ -107,8 +108,8 @@ export const MultiEnvProjectPermission = ({
|
||||
<div>
|
||||
<FontAwesomeIcon icon={icon} className="text-4xl" />
|
||||
</div>
|
||||
<div className="flex-grow flex flex-col">
|
||||
<div className="font-medium mb-1 text-lg">{title}</div>
|
||||
<div className="flex flex-grow flex-col">
|
||||
<div className="mb-1 text-lg font-medium">{title}</div>
|
||||
<div className="text-xs font-light">{subtitle}</div>
|
||||
</div>
|
||||
<div>
|
||||
@ -130,12 +131,19 @@ export const MultiEnvProjectPermission = ({
|
||||
animate={{ height: isCustom ? "auto" : 0 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<TableContainer className="border-mineshaft-500 mt-6">
|
||||
<TableContainer className="mt-6 border-mineshaft-500">
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th />
|
||||
<Th className="min-w-[8rem]">Secret Path</Th>
|
||||
<Th className="min-w-[8rem]">
|
||||
<div className="flex items-center gap-2">
|
||||
Secret Path
|
||||
<span className="text-xs normal-case">
|
||||
<GlobPatternExamples />
|
||||
</span>
|
||||
</div>
|
||||
</Th>
|
||||
<Th className="text-center">View</Th>
|
||||
<Th className="text-center">Create</Th>
|
||||
<Th className="text-center">Modify</Th>
|
||||
|
@ -3,6 +3,7 @@ import { faElementor } from "@fortawesome/free-brands-svg-icons";
|
||||
import {
|
||||
faAnchorLock,
|
||||
faArrowLeft,
|
||||
faBook,
|
||||
faCog,
|
||||
faKey,
|
||||
faLock,
|
||||
@ -82,6 +83,12 @@ const SINGLE_PERMISSION_LIST = [
|
||||
icon: faTags,
|
||||
formName: "tags"
|
||||
},
|
||||
{
|
||||
title: "Audit Logs",
|
||||
subtitle: "Audit log management control",
|
||||
icon: faBook,
|
||||
formName: "audit-logs"
|
||||
},
|
||||
{
|
||||
title: "IP Allowlist",
|
||||
subtitle: "IP allowlist management control",
|
||||
@ -169,7 +176,7 @@ export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => {
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||
<div className="flex justify-between mb-2 items-center">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<h1 className="text-xl font-semibold text-mineshaft-100">
|
||||
{isNewRole ? "New" : "Edit"} Role
|
||||
</h1>
|
||||
@ -211,7 +218,7 @@ export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => {
|
||||
>
|
||||
<Input {...register("description")} isReadOnly={isNonEditable} />
|
||||
</FormControl>
|
||||
<div className="flex justify-between items-center pt-6 border-t border-t-mineshaft-800">
|
||||
<div className="flex items-center justify-between border-t border-t-mineshaft-800 pt-6">
|
||||
<div>
|
||||
<h2 className="text-xl font-medium">Add Permission</h2>
|
||||
</div>
|
||||
@ -255,7 +262,7 @@ export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4 mt-12">
|
||||
<div className="mt-12 flex items-center space-x-4">
|
||||
<Button
|
||||
type="submit"
|
||||
isDisabled={isSubmitting || isNonEditable || !isDirty}
|
||||
|
@ -23,8 +23,8 @@ enum Permission {
|
||||
}
|
||||
|
||||
const PERMISSIONS = [
|
||||
{ action: "edit", label: "Update workspace details" },
|
||||
{ action: "delete", label: "Delete workspace" }
|
||||
{ action: "edit", label: "Update project details" },
|
||||
{ action: "delete", label: "Delete projects" }
|
||||
] as const;
|
||||
|
||||
export const WsProjectPermission = ({ isNonEditable, setValue, control }: Props) => {
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { faInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
|
||||
import GlobPatternExamples from "@app/components/basic/popups/GlobPatternExamples";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
@ -13,8 +12,7 @@ import {
|
||||
ModalClose,
|
||||
ModalContent,
|
||||
Select,
|
||||
SelectItem,
|
||||
Tooltip
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
|
||||
const formSchema = yup.object({
|
||||
@ -48,7 +46,6 @@ export const AddWebhookForm = ({
|
||||
} = useForm<TFormSchema>({
|
||||
resolver: yupResolver(formSchema)
|
||||
});
|
||||
const [showTip, setShowTip] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
@ -89,40 +86,7 @@ export const AddWebhookForm = ({
|
||||
/>
|
||||
<FormControl
|
||||
label="Secret Path"
|
||||
icon={
|
||||
<Tooltip
|
||||
isOpen={showTip}
|
||||
onOpenChange={setShowTip}
|
||||
content={
|
||||
<div>
|
||||
<h4 className="mb-2">Here are some examples of glob patterns:</h4>
|
||||
<div className="ol-listStyleType">
|
||||
<li>
|
||||
<code className="text-primary">/</code> - Matches all files and
|
||||
directories in the current directory
|
||||
</li>
|
||||
<li>
|
||||
<code className="text-primary">**/*</code> - Matches all files and
|
||||
directories in the current directory and its subdirectories
|
||||
</li>
|
||||
<li>
|
||||
<code className="text-primary">{"/{dir1,dir2}"}</code> - Matches all files
|
||||
and directories in dir1 and dir2
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
position="right"
|
||||
className="text-xs"
|
||||
>
|
||||
<div
|
||||
className="flex h-3.5 w-3.5 items-center justify-center rounded-full border border-[1px] border-mineshaft-300"
|
||||
onMouseEnter={() => setShowTip(true)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faInfo} className="h-2 w-2" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
icon={<GlobPatternExamples />}
|
||||
isRequired
|
||||
isError={Boolean(errors?.secretPath)}
|
||||
errorText={errors?.secretPath?.message}
|
||||
|
@ -7,7 +7,7 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.3.3
|
||||
version: 0.3.4
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
|
@ -50,7 +50,7 @@ frontend:
|
||||
limits:
|
||||
memory: 100Mi
|
||||
requests:
|
||||
cpu: 10m
|
||||
cpu: 100m
|
||||
## @param frontend.kubeSecretRef Backend secret resource reference name (containing required [frontend configuration variables](https://infisical.com/docs/self-hosting/configuration/envars))
|
||||
##
|
||||
kubeSecretRef: ""
|
||||
|